@@ -47,9 +47,11 @@
"connect": "^3.7.0",
"date-fns": "^4.1.0",
"dhtmlx-gantt": "^8.0.6",
+ "driver.js": "^0.9.8",
"echarts": "^4.9.0",
"element-ui": "^2.14.1",
"i": "^0.3.7",
+ "intro.js": "^7.2.0",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.2.2",
"moment": "^2.29.4",
@@ -66,9 +68,12 @@
"swiper": "^5.4.5",
"tiptap": "^1.32.2",
"uglify-js": "^3.19.3",
+ "v-viewer": "^1.7.4",
+ "viewerjs": "^1.11.7",
"vue": "^2.7.15",
"vue-baidu-map": "^0.21.20",
"vue-calendar-component": "^2.8.2",
+ "vue-clickaway": "^2.2.2",
"vue-count-to": "^1.0.13",
"vue-json-excel": "^0.3.0",
"vue-photo-preview": "^1.0.9",
@@ -79,6 +84,7 @@
"vue-tree-chart": "^1.2.9",
"vue-tree-color": "^2.3.3",
"vue2-org-tree": "^1.3.6",
+ "vuedraggable": "^2.24.3",
"vuex": "^3.6.2",
"ws": "^8.18.1",
"xlsx": "^0.17.5",
@@ -107,7 +113,7 @@
"eslint-friendly-formatter": "4.0.1",
"eslint-loader": "2.0.0",
"eslint-plugin-html": "4.0.5",
- "file-loader": "1.1.11",
+ "file-loader": "^1.1.11",
"friendly-errors-webpack-plugin": "1.7.0",
"hash-sum": "^1.0.2",
"html-webpack-plugin": "4.0.0-alpha",
@@ -75,16 +75,20 @@ export default {
// 方法
methods: {
set_info() {
- let info = this.$getEmployeeMapItem(this.id) || { name: '', img_url: '', id: 0 };
- if (this.img_url != '') {
- info.img_url = this.img_url;
+ if (this.id) {
+ let info = this.id && this.$getEmployeeMapItem(this.id) || { name: '', img_url: '', id: 0 };
+ if (this.img_url != '') {
+ info.img_url = this.img_url;
+ }
+ this.info = info;
}
- this.info = info;
+
},
// 加载
name_no() {
- if(this.id){
+ console.log(this.id)
+ let info = this.id && this.$getEmployeeMapItem(Number(this.id)) || { name: '', img_url: '', id: 0 };
if(info.id){
let pattern = new RegExp('^[\u4E00-\u9FA5]+');
this.imgUrl=info.img_url;
@@ -120,7 +124,7 @@ export default {
// 组件挂载完成
mounted() {
- this.name_no();
+ // this.name_no();
};
</script>
@@ -6,17 +6,17 @@
<div style="background-color: #fff;padding:20px;margin-bottom: 10px;" class="br-5 ">
- <div class="flex-box-ce" v-if="user_info">
+ <div class="flex-box-ce" v-if="user_info && user_info.id">
<div style="margin-right: 10px;">
- <userImage :id="user_info.id" :img_url="user_info.img_url" :user_name="user_info.name" width="50px" height="50px"></userImage>
+ <userImage v-if="user_info && user_info.id" :id="user_info.id" :img_url="user_info.img_url" :user_name="user_info.name" width="50px" height="50px"></userImage>
</div>
<div class="flex-1">
<div class="flex-box-ce" style="margin-bottom: 6px;font-size: 14px;color: #666;font-weight: 700;">
- <div style="font-size: 16px;font-weight: 700;padding-right:15px;" class="black">你好,{{ user_info.name }}</div>
+ <div style="font-size: 16px;font-weight: 700;padding-right:15px;" class="black">你好,{{ user_info.name }} </div>
<div class="fontColorC font-flex-word" style="max-width: 180px;">{{returnDeptName()}}</div>
- <template v-if="user_info.post_info.length>0">
+ <template v-if="user_info.post_info.length > 0">
<div class="flex-box-v flex-center-center" v-for="(item,index) in user_info.post_info" :key="index" style="border-radius: 3px;margin-left: 10px;padding: 10px;background-color: #f0f4fa;">
<i style="font-size: 18px;" class="el-icon-s-custom fontColorC"></i>
<span class="fontColorB">{{item.name}}</span>
@@ -21,7 +21,7 @@
<div style="margin-left: 20px;">
<el-popover v-if="isAdministrator" placement="bottom" width="364" trigger="hover">
<SystemMessage></SystemMessage>
- <div slot="reference" class="count_max">{{ site_info.user_count_max }}用户</div>
+ <div slot="reference" class="count_max">{{ site_info.user_count_max }}用户 </div>
</el-popover>
<div class="flex-1"></div>
@@ -96,7 +96,7 @@ Vue.prototype.$onFilePreView = onFilePreView
Vue.prototype.$action = 'https://integralsys.oss-cn-shenzhen.aliyuncs.com'
Vue.prototype.$acceptImg = 'image/jpeg,image/png'
Vue.prototype.$acceptFile = 'application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-Vue.prototype.$acceptImgFile='image/jpeg,image/png,application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+Vue.prototype.$acceptImgFile ='image/jpeg,image/png,application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
Vue.prototype.$xtoken={ 'X-Token': getToken() };
Vue.directive('dragscroll', function (el) {
el.onmousedown = function (ev) {
@@ -136,6 +136,13 @@ Object.keys(filters).forEach(key => {
})
+Vue.directive('focus', {
+ inserted: function (el) {
+ el.querySelector('input').focus();
+});
import tableMove from "@/utils/tableMove";
Vue.use(tableMove)
@@ -154,6 +161,18 @@ Vue.prototype.$bus = bus
Vue.config.productionTip = false
+import Viewer from 'v-viewer';
+import 'viewerjs/dist/viewer.css';
+Vue.use(Viewer, {
+ defaultOptions: {
+ zIndex: 9999, // 设置图片预览组件的层级,确保能在其他组件之上
+ },
new Vue({
el: '#app',
router,
@@ -0,0 +1,591 @@
+<template>
+ <div class="contrast-container">
+ <el-button class="export-btn" type="primary" @click="exportToExcel('mytable', '考核对比')">导出报表</el-button>
+ <el-tabs v-model="activeName" style="width: 100%; height: 100%; ">
+ <el-tab-pane label="月度" name="4" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+ <div class="block">
+ <el-date-picker v-model="params.year" type="year" placeholder="选择年" value-format="yyyy">
+ </el-date-picker>
+ <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+ <div class="dept_inp" @click="show_dept_selector = true">
+ <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+ <span v-else style="color: #b9b9b9;">选择部门</span>
+ </div>
+ <i class="el-icon-arrow-down"></i>
+ <div class="dept_wdiv flex-box-ce" style="margin: 0 10px;">
+ <div class="dept_inp" @click="showEmployeeSearch = true">
+ <span v-if="employeeVisibleName != ''">{{ employeeVisibleName }}</span>
+ <span v-else style="color: #b9b9b9;">选择员工</span>
+ <el-select style="margin-right: 10px;" v-model="filters.level" placeholder="选择等级"
+ @change="changeLevel">
+ <el-option v-for="item in gradeLevels" :key="item.name" :label="item.name" :value="item.name">
+ </el-option>
+ </el-select>
+ <el-button @click="reset">重置</el-button>
+ <div class="table-box">
+ <el-table id="mytable" :data="filteredData" style="width: 100%; height: auto;" border stripe
+ :header-cell-style="{ background: '#f5f7fa' }" v-loading="loading" size="small">
+ <el-table-column prop="employeeName" label="姓名" align="center">
+ </el-table-column>
+ <el-table-column prop="departments" label="部门" align="center" width="200">
+ <template slot-scope="scope">
+ <el-tooltip class="item" effect="dark" placement="top">
+ <div slot="content" style="max-width: 300px; ">
+ {{ scope.row.departments | formatDeptName }}
+ <div class="oneLine">
+ </el-tooltip>
+ </template>
+ <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item" align="center">
+ <div>{{ scope.row.scores[index] }}</div>
+ <div>{{ scope.row.levelNames[index] }}</div>
+ </el-table>
+ </el-tab-pane>
+ <el-tab-pane label="季度" name="3" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+ <el-table id="mytable" :data="filteredData" style="width: 100%; " border stripe
+ <el-tab-pane label="半年度" name="2" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+ <el-table-column prop="departments" label="部门" align="center">
+ <el-tab-pane label="年度" name="1" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+ <el-table-column prop="departments" label="部门" align="center" width="300">
+ </el-tabs>
+ <div style="height: 50px;"></div>
+ <!-- 部门选择 -->
+ <EmployeeSelector :title="'选择部门'" :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true"
+ :dept_children="false" :selected="dept_selected" :visible.sync="show_dept_selector"
+ @confirm="dept_confirm" />
+ <EmployeeSelector :multi="true" :is_filtration_creator="false" :selected="employeeSelectedObj"
+ :isChecKedAll="false" :visible.sync="showEmployeeSearch" @confirm="onEmployeeConfirm" />
+</template>
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import cloneDeep from 'lodash.clonedeep';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 部门选择
+import FileSaver from "file-saver";
+import XLSX from "xlsx";
+export default {
+ components: {
+ EmployeeSelector
+ data() {
+ return {
+ employees: this.$getEmployeeMap(), // 员工列表
+ activeName: "4", // activeName 和 周期类型对应 周期种类 1-年度 2-半年度 3-季度 4-月度
+ loading: false,
+ treeData: {},
+ deptList: [], // 部门列表 - 树形结构
+ dept_list: [], // 部门列表
+ tableHeader: [],
+ tableData: [],
+ filteredData: [],
+ userList: [],
+ // 筛选条件
+ filters: {
+ dept_ids: [], // 选择的部门列表
+ employee_ids: [], // 选择的员工列表
+ level: ''
+ params: {
+ cycleType: "",
+ year: "2025",
+ gradeLevels: [],
+ show_dept_selector: false,
+ deptVisibleName: '',
+ dept_selected: { dept: [], employee: [] },
+ showEmployeeSearch: false,
+ employeeVisibleName: '',
+ employeeSelectedObj: {
+ employee: [],
+ dept: []
+ watch: {
+ activeName(val) {
+ this.params.cycleType = val
+ this.params.year = '2025'
+ this.deptVisibleName = '';
+ this.dept_selected = { dept: [], employee: [] };
+ this.employeeSelectedObj = { dept: [], employee: [] };
+ this.employeeVisibleName = '';
+ this.filters = {
+ this.tableData = [];
+ this.filteredData = [];
+ this.getRecords();
+ 'params.year'(val) {
+ formatDate(val) {
+ if (val) return moment(val).format('YYYY-MM-DD')
+ else return "--"
+ formatDeptName(val) {
+ let str = '';
+ if (val && val.length > 0) {
+ val.forEach(dept => {
+ str += dept.name +","
+ })
+ str = str.substr(0, str.length - 1)
+ } else {
+ str = "--"
+ return str
+ computed: {
+ ...mapGetters(['user_info']),
+ created() {
+ this.getAllSet();
+ methods: {
+ exportToExcel(tableId, fileName) {
+ // exportToExcel(tableId, fileName)
+ this.downloadLoading = true
+ const xlsxParam = { raw: true };
+ const wb = XLSX.utils.table_to_book(document.getElementById(tableId), xlsxParam);
+ // 遍历工作表中的所有单元格,处理换行
+ const ws = wb.Sheets[wb.SheetNames[0]]; // 工作簿1
+ for (const cell in ws) {
+ if (ws[cell].v && typeof ws[cell].v === 'string') {
+ ws[cell].v = ws[cell].v.replace(/\n/g, '\n');
+ ws[cell].s = {
+ alignment: {
+ wrapText: true // 启用自动换行
+ };
+ const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' });
+ try {
+ FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName + '.xlsx');
+ } catch (e) {
+ console.error(e, wbout);
+ this.downloadLoading = false;
+ // 获取全局等级设置
+ async getAllSet() {
+ let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
+ let data = res.data.data;
+ let levels = data.level_scope.levels;
+ let gradeLevels = [];
+ let max = 0;//最大值
+ if (levels && levels.length > 0) {
+ levels.forEach((item, index) => {
+ var obj;
+ if (index == 0) {
+ obj = { name: item.name, max: Number(item.value), min: 0 };
+ obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+ max = item.value;
+ gradeLevels.push(obj);
+ this.gradeLevels = gradeLevels;
+ reset() {
+ this.params.cycleType = this.activeName
+ this.applyFilters();
+ // 按参数获取对比数据
+ getRecords() {
+ this.loading = true;
+ let params = {
+ year: this.params.year
+ let url = `/performance/statistics/cycle/employees/${this.user_info.site_id}/${this.activeName}`
+ this.$axiosUser("get", url, params).then(res => {
+ this.loading = false;
+ let { data: { data: { titles , users}, code } } = res;
+ this.tableHeader = titles;
+ this.tableData = users;
+ this.filteredData = users;
+ // 应用筛选条件
+ applyFilters() {
+ this.filteredData = this.tableData.filter((item) => {
+ const levelMatch = this.filters.level.length === 0 || item.levelNames.some(level => this.filters.level === level);
+ const employeeMatch = this.filters.employee_ids.length === 0 || this.filters.employee_ids.some(employeeId => employeeId == item.employeeId);
+ const departmentMatch = this.filters.dept_ids.length === 0 || item.departments.some(dept => this.filters.dept_ids.includes(dept.id));
+ return levelMatch && employeeMatch && departmentMatch;
+ });
+ //部门选择
+ dept_confirm(data) {
+ let deptList_id = [];
+ if (data.dept !== null && data.dept.length != 0) {
+ this.dept_selected = data;
+ data.dept.forEach((element, index) => {
+ deptList_id.push(element.dept_id);
+ this.deptVisibleName += element.dept_name;
+ if (data.dept.length - index > 1) {
+ this.deptVisibleName += ',';
+ this.filters.dept_ids = deptList_id
+ this.applyFilters()
+ // 员工选择
+ onEmployeeConfirm(data) {
+ let employee_ids = [];
+ if (data.employee !== null && data.employee.length != 0) {
+ this.employeeSelectedObj = data;
+ data.employee.forEach((element, index) => {
+ employee_ids.push(element.id);
+ this.employeeVisibleName += element.name;
+ if (data.employee.length - index > 1) {
+ this.employeeVisibleName += ',';
+ this.filters.employee_ids = employee_ids
+ // 等级选择
+ changeLevel(v) {
+ this.filters.level = v;
+}
+</script>
+<style scoped="scoped" lang="scss">
+.el-tabs__content, .el-tab-pane {
+ width: 100% !important;
+ height: 100% !important;
+.export-btn {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ z-index: 99;
+ &:hover {
+ cursor: hand !important;
+.contrast-container {
+ width: 100%;
+ height: 100%;
+ padding: 10px;
+ background-color: #fff;
+ position: relative;
+ overflow: hidden;
+ box-sizing: border-box;
+ .el-tabs {
+ display: flex;
+ flex-direction: column;
+ .block {
+ margin: 10px 0;
+ .dept_wdiv {
+ width: 240px;
+ .dept_inp {
+ z-index: 9;
+ height: 36px;
+ line-height: 36px;
+ border: 1px solid #e0e0e0;
+ border-radius: 3px;
+ font-size: 12px;
+ padding: 0 10px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ cursor: pointer;
+ color: #676767;
+ i {
+ top: 0;
+ right: 0;
+ top: 12px;
+ font-size: 14px;
+ color: #b5b5b5;
+ .table-box {
+ flex: 1;
+ overflow-y: auto;
+ /* 设置滚动条的宽度和背景色 */
+ &::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+ background-color: #f9f9f9;
+ /* 设置滚动条滑块的样式 */
+ &::-webkit-scrollbar-thumb {
+ border-radius: 6px;
+ background-color: #c1c1c1;
+ /* 设置滚动条滑块hover样式 */
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: #a8a8a8;
+ /* 设置滚动条轨道的样式 */
+ &::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+ background: #ededed;
+</style>
@@ -1,9 +1,9 @@
<template>
<div class="contrast-container scroll-bar" style="padding-top: 10px;">
- <div class="flex-box-ce title-box" style="" v-if="selectExamineList && selectExamineList.length > 0">
+ <!-- <div class="flex-box-ce title-box" style="" v-if="selectExamineList && selectExamineList.length > 0">
<div class="title">已选择的考核列表:</div>
- <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+ <el-button size="mini" @click="openChooseExamineItem()">查看更多</el-button>
@@ -15,22 +15,16 @@
{{ item.title }}
</el-tag>
</template>
- </div>
-
- <div v-else class="flex-box-ce" style="padding: 0 20px;">
- <el-button size="mini" @click="dialogVisible = true" style="margin-left: auto;">查看更多</el-button>
+ </div> -->
- <el-tabs v-model="activeName" @tab-click="handleClick"
- style="width: 100%; padding: 0 20px; box-sizing: border-box;">
- <el-tab-pane label="表格" name="0">
- <el-table v-if="userList && userList.length > 0" ref="tableRef" custom-class="openAnimAbcd" id="mytable"
+ <div class="flex-box-ce" style="padding: 0 20px;">
+ <el-button size="mini" @click="openChooseExamineItem()" style="margin-left: auto;">查看更多</el-button>
:data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
- v-loading="loading">
+ v-loading="loading" :height="500">
<el-table-column prop="employeeName" label="姓名" align="center">
</el-table-column>
<el-table-column :label="item.title" v-for="(item, index) in selectExamineList"
- :key="item.reviewPackageId" align="center">
+ :key="item.reviewPackageId" align="center" :render-header="(h, obj) => renderHeader(h, obj)">
<template slot-scope="scope">
<div v-for="user in item.users">
<span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
@@ -41,13 +35,13 @@
</el-tab-pane>
<el-tab-pane label="地图" name="1" style="width: 100%;">
<div v-if="treeData && JSON.stringify(treeData) !== '{}'" class="flex-box-ce scroll-bar"
- style="width: 100%; justify-content: center; overflow-x: auto;">
- <vue2-org-tree :data="treeData" style="width: 100%;" />
+ style="width: 100%; align-items: center; justify-content: center; overflow-x: auto;">
+ <vue2-org-tree :data="treeData" style="margin: 0 auto !important;" />
</el-tabs>
- <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="700px"
+ <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="600px"
:before-close="dialogBeforeClose">
<div>
<div class="search-box">
@@ -73,38 +67,30 @@
<el-button plain round size="mini" style="margin-left: 10px;" @click="reset">重 置</el-button>
<div class="package-list">
- <el-transfer v-model="transferValue" :data="transferData" style="margin: 20px auto;"></el-transfer>
- <!-- <div class="template-list scroll-bar">
- <template v-if="examineList && examineList.length > 0">
- <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll"
- @change="handleCheckAllChange" style="margin-bottom: 10px;">全选</el-checkbox>
- <el-checkbox-group v-model="selectExamineIds" size="small" @change="changeSelectExamineIds">
- <template v-for="item in examineList">
- <el-checkbox :key="item.reviewPackageId" :label="item.reviewPackageId" border>
- {{ item.title }}
- </el-checkbox>
- </template>
- </el-checkbox-group>
- <template v-else>
- <div class="flex-box-ce" style="justify-content: center;">
- <noData content="暂无数据" imgW="120px" imgH="80px"></noData>
+ <div class="template-list-box">
+ <el-checkbox label="全选" v-model="isAllCheck" style="margin-bottom: 10px;"></el-checkbox>
+ <div class="template-list scroll-bar">
+ <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in examineList"
+ :key="item.reviewPackageId" @click="chooseTemplate(item)">
+ {{ item.title }}
- <div class="choose-template scroll-bar">
+ <div class="choose-template-box">
<el-button plain round size="mini" @click="clear">清 空</el-button>
- <div class="line"></div>
- <template v-for="(item, index) in chooseExamineList">
- <div class="flex-box-ce choose-template-item" style="justify-content: space-between;">
- <div>{{ item.title }}</div>
- <i class="el-icon-close" @click="deleteItem(index)"></i>
- <div ref="placeholder" style="height: 50px;"></div>
- </div> -->
+ <div class="choose-template scroll-bar">
+ <template v-for="(item, index) in chooseExamineList">
+ <div v-if="item.isCheck" class="flex-box-ce choose-template-item"
+ style="justify-content: space-between;">
+ {{ item.title }}<i class="el-icon-close" @click="deleteItem(item, index)"></i>
+ <div ref="placeholder" style="height: 50px;"></div>
<div slot="footer">
<el-button @click="dialogVisible = false">取 消</el-button>
@@ -119,6 +105,7 @@
<script>
import { mapGetters } from 'vuex';
import moment from 'moment';
export default {
data() {
@@ -155,8 +142,6 @@ export default {
endDate: '',
status: -1, // 不传默认返回全部 0-未完成 1-已完成
- transferData: [],
- transferValue: [],
pickerOptions: {
shortcuts: [{
text: '最近一周',
@@ -189,16 +174,22 @@ export default {
tableHeader: [],
tableData: [],
userList: [],
- selectExamineIds: [],
- selectExamineList: [],
- chooseExamineList: []
+ chooseExamineList: [],
+ selectExamineList: [], // 显示选择的数据
+ isAllCheck: false
+ isAllCheck(v) {
+ if (v) {
+ this.examineList.forEach(item => item.isCheck = true)
+ this.chooseExamineList = this.examineList
+ this.examineList.forEach(item => item.isCheck = false)
+ this.chooseExamineList = []
- // watch: {
- // chooseExamineList(v) {
- // this.selectExamineList = this.chooseExamineList
- // }
- // },
filters: {
formatDate(val) {
if (val) return moment(val).format('YYYY-MM-DD')
@@ -207,136 +198,135 @@ export default {
computed: {
...mapGetters(['user_info']),
- // selectExamineList() {
- // return this.examineList.filter(item => this.selectExamineIds.includes(item.reviewPackageId))
created() {
this.getRecords();
this.dialogVisible = true;
- deleteItem(index) {
- let chooseIndex = this.selectExamineIds.findIndex(select => select == this.chooseExamineList[index].reviewPackageId)
- this.selectExamineIds.splice(chooseIndex, 1)
- this.chooseExamineList.splice(index, 1)
- },
- changeSelectExamineIds(v) {
- console.log(this.selectExamineIds)
- if (v && v.length > 0) {
- this.examineList.forEach(examine => {
- this.selectExamineIds.forEach(val => {
- if (val == examine.reviewPackageId) this.chooseExamineList.push(examine)
- })
- this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
- } else {
- // this.selectExamineIds = []
- }
+ // 自定义表头
+ renderHeader(h, { column, $index }) {
+ let that = this;
+ return h(
+ 'div', {
+ style: { display: "flex", alignItems: "center", justifyContent: "center" }
+ }, [
+ h('el-button', {
+ props: {
+ icon: 'el-icon-delete',
+ size: 'mini'
+ style: 'padding: 5px',
+ on: {
+ click: function () {
+ that.clickButton({ column, $index });
+ }, column.label)
+ ])
+ clickButton(obj) {
+ let { column, $index } = obj;
+ this.handleTagDelete($index - 1);
- handleCheckAllChange(v) {
- if (v) {
- this.selectExamineIds.push(examine.reviewPackageId)
- this.isIndeterminate = true;
- this.checkAll = true;
- this.$nextTick(() => {
- if (this.$refs.placeholder) this.$refs.placeholder.scrollIntoView(false);
- this.selectExamineIds = [];
- this.isIndeterminate = false;
- this.checkAll = false;
- if (this.examineList && this.examineList.length > 0) {
- let examineIds = this.examineList.map(item => item.reviewPackageId)
- this.chooseExamineList = this.chooseExamineList.filter(choose => !examineIds.includes(choose.reviewPackageId))
+ openChooseExamineItem() {
+ this.chooseExamineList = this.selectExamineList;
+ this.examineList.forEach(item => {
+ if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+ let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+ if (flag) {
+ item.isCheck = true
+ item.isCheck = false
+ this.dialogVisible = true
- chooseExamine(item) {
- if (this.selectExamineIds.includes(item.reviewPackageId)) {
- let index = this.selectExamineIds.findIndex(item => item.reviewPackageId);
- this.selectExamineIds.splice(index, 1)
- this.selectExamineIds.push(item.reviewPackageId);
- if (this.selectExamineIds.length === this.examineList.length) {
+ chooseTemplate(item) {
+ item.isCheck = !item.isCheck
+ if (item.isCheck) {
+ if (!flag) this.chooseExamineList.push(item)
} else {
+ let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+ this.chooseExamineList.splice(index, 1)
- if (this.activeName == '0') this.initTableData()
- if (this.activeName == '1') this.initTreeData()
+ this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
+ deleteItem(item, index) {
+ this.examineList.forEach(examine => {
+ if (examine.reviewPackageId == item.reviewPackageId) {
+ examine.isCheck = false
handleTagDelete(index) {
// 最后一个不能删除
if (this.selectExamineList && this.selectExamineList.length > 1)
this.selectExamineList.splice(index, 1)
getRecords() {
this.loading = true;
if (this.status === '-1') this.params.status = '' // 不传默认返回全部
else this.params.status = this.status
- if (this.cycleType == '-1') this.params = { ...this.params }
- else this.params = { ...this.params, cycleType: this.cycleType }
+ if (this.cycleType == '-1') {
+ if (this.params.hasOwnProperty('cycleType')) {
+ delete this.params.cycleType
+ this.params = { ...this.params, cycleType: this.cycleType }
this.$axiosUser("get", `/performance/statistics/packages/${this.user_info.site_id}`, this.params).then(res => {
this.loading = false;
let { data: { data: { list, total }, code } } = res;
this.examineList = list;
- this.transferData = this.examineList.map(item => ({
- key: item.reviewPackageId,
- label: item.title
- }))
- this.examineList.map(item => {
- this.chooseExamineList.forEach(choose => {
- if (item.reviewPackageId == choose.reviewPackageId)
- this.selectExamineIds.push(item.reviewPackageId)
this.treeData = {
id: 0,
label: "考核对比",
children: []
- // this.selectExamineIds = [];
initTableData() {
let userList = [];
- let selectExamineList = [];
- this.examineList && this.examineList.forEach(item => {
- this.transferValue.forEach(selectExamineId => {
- if (item.reviewPackageId == selectExamineId) {
- selectExamineList.push(item)
+ let selectExamineList = this.chooseExamineList;
selectExamineList.forEach(item => {
let { reviewPackageId, title, users } = item;
if (users && users.length > 0) {
users.forEach(user => {
- let { employeeName, employeeId } = user
- userList.push({ employeeName, employeeId })
+ let { employeeName, employeeId, score } = user
+ userList.push({ employeeName, employeeId, score: score ? score : 0 })
@@ -365,10 +355,20 @@ export default {
children: this.selectExamineList
handleClick(tab, event) {
+ if (this.activeName == '0') {
+ if (this.selectExamineList && this.selectExamineList.length > 0) {
+ this.initTableData()
+ if (this.activeName == '1') {
+ this.initTreeData()
// 日期选择时间
changeDate(v) {
if (v && v.length > 0) {
@@ -376,11 +376,10 @@ export default {
this.params.endDate = v[1] || ''
//选择周期
changeCircle(v) {
- console.log(v);
+ this.cycleType = v;
// 选择状态
@@ -402,14 +401,23 @@ export default {
// 清空选择的考核列表
clear() {
+ this.chooseExamineList.forEach(chooseExamine => {
+ if (chooseExamine.reviewPackageId == examine.reviewPackageId) {
+ this.isAllCheck = false;
this.chooseExamineList = [];
dialogBeforeClose() {
this.dialogVisible = false;
confirm() {
- this.selectExamineList = this.examineList.filter(item => this.transferValue.indexOf(item.reviewPackageId) >= 0);
+ this.selectExamineList = cloneDeep(this.chooseExamineList);
if (this.activeName == '0') this.initTableData()
if (this.activeName == '1') this.initTreeData()
@@ -423,6 +431,11 @@ export default {
<style scoped="scoped" lang="scss">
+.org-tree {
+ margin: 0 auto !important;
.contrast-container {
width: 100%;
height: 100%;
@@ -457,57 +470,67 @@ export default {
.package-list {
height: 400px;
margin-top: 10px;
border: 1px solid #f7f7f7;
- .template-list {
+ .template-list-box {
width: 50%;
- height: 400px;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- border-right: 1px solid #f7f7f7;
padding: 10px;
box-sizing: border-box;
- .el-checkbox-group {
- width: 100% !important;
- height: 100% !important;
- .el-checkbox {
- margin: 0;
- width: 200px;
- margin-bottom: 10px;
+ border-right: 1px solid #f7f7f7;
+ .template-list {
+ height: 350px;
+ .template-item {
+ height: 30px;
+ line-height: 30px;
+ border: 1px solid #f1f1f1;
+ margin-bottom: 5px;
+ padding-left: 10px;
+ .active {
+ border: 1px solid #409eff;
+ color: #409eff;
- .choose-template {
+ .choose-template-box {
- .line {
- width: 90%;
- height: 1px;
- background-color: #f7f7f7;
- margin: 10px auto;
- &-item {
- height: 30px;
- line-height: 30px;
- padding-left: 10px;
- border-bottom: 1px solid #f7f7f7;
- margin: 0 auto 10px auto;
- box-sizing: border-box;
+ .choose-template {
+ .line {
+ width: 90%;
+ height: 1px;
+ background-color: #f7f7f7;
+ margin: 10px auto;
+ &-item {
+ border-bottom: 1px solid #f7f7f7;
+ margin: 0 auto 10px auto;
@@ -1,91 +1,295 @@
<div class="record-container">
- <!-- 左边 考核列表组件 -->
- <LeftExamineRecord @selectRow="handleSeleRow" />
- <!-- 右边 考核明细组件 -->
- <RightExamineRecord v-if="JSON.stringify(detailInfo) !== '{}'" :detailInfo="detailInfo" :cateList="cateList" />
+ <ResultAnalysis />
+ <!-- 水平线 -->
+ <!-- <div v-if="detailInfo" class="line"></div>
+ <div v-if="detailInfo" v-loading="loading" class="main-content">
+ <ResultAnalysis v-if="reviewPackageId" :detailInfo="detailInfo" />
+ <div v-else class="flex-box-v flex-center-center no-data-box" style="">
+ <img src="static/images/invite_new_company.png" style="" />
+ <div class="fontColorB">暂无考核数据</div>
+let that;
-import RightExamineRecord from "./ExamineRecord/RightExamineRecord" // 考核列表组件
-import LeftExamineRecord from "./ExamineRecord/LeftExamineRecord" // 考核列表组件
+import ResultAnalysis from "./ResultAnalysis" // 考核详情组件
components: {
- RightExamineRecord,
- LeftExamineRecord
+ ResultAnalysis
return {
- detailInfo: {},
- cateList: [], // 考核分类
+ reviewPackageId: 0,
+ detailInfo: null,
+ year: '2025',
+ headValue: [],
+ options: [{ value: '0', label: '未定义', leaf: false, children: [] },
+ { value: '1', label: '年度', leaf: false, children: [] },
+ { value: '2', label: '半年度', leaf: false, children: [] },
+ { value: '3', label: '季度', leaf: false, children: [] },
+ { value: '4', label: '月度', leaf: false, children: [] },
+ ], //
+ value: 'value',
+ label: 'label',
+ children: 'children',
+ lazy: true,
+ lazyLoad(node, resolve) {
+ that.getAssessTree(node, resolve);
+ total: 0,
+ selected_dept_ids: [], // 选择的部门列表
+ placeholder: "",
+ chooseChildren: [],
+ noDataText: '暂无数据'
+ year(val) {
+ this.getRecords()
+ headValue(val) {
+ if (this.chooseChildren && this.chooseChildren.length > 0) {
+ let obj = this.chooseChildren.find(child => child.value == this.headValue[1])
+ if (obj) {
+ let value = ''
+ if (this.headValue[0] && this.headValue[0] == '0') value = "未定义 / "
+ if (this.headValue[0] && this.headValue[0] == '1') value = "年度 / "
+ if (this.headValue[0] && this.headValue[0] == '2') value = "半年度 / "
+ if (this.headValue[0] && this.headValue[0] == '3') value = "季度 / "
+ if (this.headValue[0] && this.headValue[0] == '4') value = "月度 / "
+ this.placeholder = obj.label || ''
+ this.getResultAnalyze()
- this.getCateList()
+ // that = this;
+ // this.getRecords('4'); // 优先获取当月最新考核数据 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
+ filterProgress(list) {
+ if (list && list.length > 0) {
+ let sum = 0;
+ list.forEach(item => {
+ if (item.status == 1) sum += 1 // 完成的个数
+ return Number(sum / list.length) * 100
- handleSeleRow(reviewPackageId) {
- // 获取考核详情
- let url = `/performance/statistics/package/info/${this.user_info.site_id}/${reviewPackageId}`
+ getAssessTree(node, resolve) {
+ if (this.options.length == 0) {
+ return
+ const { value } = node;
+ this.chooseChildren = node.children // 用来回显选择的文本
+ node.children = []
+ let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${value}`
this.$axiosUser("get", url, {}).then(res => {
- let { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code } } = res
- this.detailInfo = { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code }, reviewPackageId }
+ let { data: { data: { items, cycleType } } } = res
+ if (items && items.length > 0) {
+ items.forEach(item => {
+ item.leaf = true;
+ item.label = item.remark
+ resolve(items)
+ resolve([])
+ }).finally(() => {
+ // this.tableDataLoad = false;
- // 考核分类列表
- getCateList() {
- let url = `/performance/cate/list/${this.user_info.site_id}`;
- // this.loading = true
- this.$axiosUser('get', url).then(res => {
- let { data: { code, data: { list, total } } } = res
- if (code == 1) {
- this.cateList = list;
+ getRecords(cycle) {
+ if (cycle < 0) {
+ cycle = 0
+ this.loading = true
+ // 周期种类 0-未定义 1-年度 2-半年度 3-季度 4-月度
+ let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${cycle}`
+ this.$axiosUser("get", url, {}).then(res => {
+ item.leaf = true
+ this.options[parseInt(cycle)].children = items
+ this.headValue = [cycle + '', this.options[parseInt(cycle)].children[[0]].value]
+ // if (this.headValue[1]) this.placeholder = this.options[parseInt(cycle)].children[[0]].label || ''
+ this.getRecords(parseInt(cycle) - 1) // 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
+ getResultAnalyze() {
+ this.reviewPackageId = 0
+ let url = `/performance/statistics/cycle/${this.user_info.site_id}/${this.headValue[0]}`
+ if (!this.headValue[1]) return
+ this.$axiosUser("get", url, { value: this.headValue[1]}).then(res => {
+ this.detailInfo = res.data.data
+ this.reviewPackageId = 1
+<style lang="scss">
+.record-container {
+ .el-cascader .el-input__inner::placeholder {
+ color: #606266;
+ /* 设置为你想要的颜色 */
+// .el-cascader .el-input{
+// cursor: pointer;
+// // #606266
+// }
.record-container {
- background-color: #f0f4fa;
+ border-radius: 5px;
display: flex;
- justify-content: space-between;
+ .title-container {
+ align-items: center;
+ .searchBox {
+ width: 300px;
+ .search-title {
+ border-bottom: 1px solid #f1f1f1;
+ font-size: 16px;
+ font-weight: 700;
+ padding-bottom: 10px;
+ .title {
- /* 设置滚动条的宽度和背景色 */
- ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
- width: 10px;
- height: 10px;
- background-color: #f9f9f9;
+ .search-box {
+ justify-content: space-between;
+ height: 50px;
+ .btn-box {
+ padding: 7px 5px;
+ border: 1px solid #DCDFE6;
+ border-radius: 4px;
- /* 设置滚动条滑块的样式 */
- ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
- border-radius: 6px;
- background-color: #c1c1c1;
+ background: #f1f1f1;
+ margin-bottom: 10px;
- /* 设置滚动条滑块hover样式 */
- ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
- background-color: #a8a8a8;
+ .main-content {
- /* 设置滚动条轨道的样式 */
- ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
- box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
- background: #ededed;
+ .no-data-box {
+ padding: 26px 35px;
+ img {
+ width: 200px;
+ height: 200px;
</style>
@@ -43,7 +43,6 @@
<div class="gd"><i class="el-icon-finished" style="margin-right: 5px; color: #999;"></i>筛选</div>
<!-- 水平线 -->
<div class="line"></div>
@@ -86,9 +85,6 @@
@@ -97,9 +93,11 @@
import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
- EmployeeSelector
+ EmployeeSelector,
+ RightExamineRecord
@@ -345,7 +343,7 @@ export default {
align-items: center;
justify-content: space-between;
- height: 40px;
.btn-box {
padding: 7px 5px;
border: 1px solid #DCDFE6;
@@ -360,7 +358,7 @@ export default {
height: 1px;
background: #f1f1f1;
- margin: 20px 0;
::-webkit-scrollbar {
width: 10px;
@@ -0,0 +1,142 @@
+ <!-- 按考核状态分布 -->
+ <div ref="chart1" :style="myChartStyle"></div>
+import echarts from 'echarts';
+ tableData: {
+ type: Array,
+ default: () => []
+ myChart: null,
+ myChartStyle: { width: "100%", height: "400px" },
+ colorList: [
+ { c1: ' #40d3ff', c2: '#409EFF' }, { c1: ' #ede737', c2: '#FF9600' }, { c1: '#89e398', c2: '#67c23a' },
+ { c1: ' #85E9C7', c2: '#00C4CB' }, { c1: ' #f393a9', c2: '#f56c6c' }, { c1: '#e69cf3', c2: '#de43f9' },
+ ],
+ title() {
+ let users = []
+ this.tableData.forEach(item => {
+ if (item.users && item.users.length > 0) {
+ item.users.forEach(user => {
+ users.push(user);
+ return "考核总人数" + users.length
+ // 考核表状态
+ examineStatus() {
+ let examineStatus = [
+ { name: "进行中", value: 0 },
+ { name: "已结束", value: 0 },
+ ]
+ examineStatus[0].value = users.filter(item => item.status == 0).length;
+ examineStatus[1].value = users.filter(item => item.status == 1).length;
+ return examineStatus
+ watch: { //此处为关键,监听tableData值的变化,进行echarts渲染
+ tableData(v){
+ this.initEcharts1(); //值发生改变则渲染一次echarts
+ mounted() {
+ this.initEcharts1();
+ beforeDestroy() {
+ if (this.myChart) {
+ this.myChart.clear();//清空当前实例,会移除实例中所有的组件和图表
+ this.myChart.dispose();//销毁实例,实例销毁后无法再被使用
+ initEcharts1() {
+ const colorList = this.colorList;
+ const option = {
+ title: {
+ show: true,
+ text: this.title,
+ itemGap: 8,
+ left: '48%',
+ top: '0',
+ textStyle: {
+ color: '#000',
+ fontSize: 16,
+ x: 'center',
+ y: 'center',
+ textAlign: 'center'
+ tooltip: {//悬浮提示组件
+ trigger: 'item',
+ legend: {
+ icon: "circle",
+ itemWidth: 10, // 设置宽度
+ itemHeight: 10, // 设置高度
+ y: 'bottom',
+ itemGap: 30
+ series: [
+ {
+ type: "pie",
+ radius: ['40%', '65%'],//小的是内径,大的是外径
+ data: this.examineStatus,
+ itemStyle: {
+ emphasis: {
+ shadowBlur: 9,
+ shadowOffsetX: 0,
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
+ normal: {
+ label: {
+ formatter: '{b} : {c}'
+ labelLine: { show: true },
+ color: function (params) {//颜色渐变函数 前四个参数分别表示四个位置依次为左、下、右、上
+ return new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 0, color: colorList[params.dataIndex].c1 }, { offset: 1, color: colorList[params.dataIndex].c2 }])
+ this.myChart = this.$echarts.init(this.$refs.chart1);
+ this.myChart.setOption(option);
+ window.addEventListener("resize", () => {
+ this.myChart.resize();
@@ -1,6 +1,6 @@
<!-- 正态规则 -->
- <el-popover ref="popoverRef" placement="right" trigger="click" width="300">
+ <el-popover ref="popoverRef" v-model="isPopoverVisible" placement="right" trigger="click" width="300" @show="handleShow" @hide="handleHide">
<div class="setting">
<div class="setting-title-box flex-box-ce" style="justify-content: space-between;">
<div class="setting-title">
@@ -8,34 +8,38 @@
<el-button class="plus-button" icon="el-icon-plus" circle size="mini" @click="addData()"></el-button>
+ <div class="table-box scroll-bar" style="width: 100%; height: 160px; overflow-y: auto;" >
+ <table style="width: 100%;">
+ <tr style="width: 100%;" v-for="(item, index) in editScoreList" :key="index">
+ <td style="width: 40%;">
+ <el-input v-model="item.name" size="mini"></el-input>
+ </td>
+ <td style="width: 50%;">
+ <el-input v-model.number="item.scale" size="mini" maxlength="3">
+ <template slot="append">%</template>
+ </el-input>
+ <td style="width: 10%;" align="center">
+ <i class="el-icon-delete" @click="deleteData(index)"></i>
+ </tr>
+ </table>
- <table style="width: 100%;">
- <tr style="width: 100%;" v-for="(item, index) in scoreList" :key="index">
- <td style="width: 40%;">
- <el-input v-model="item.name" size="mini"></el-input>
- </td>
- <el-input v-model="item.scale" size="mini">
- <template slot="append">%</template>
- </el-input>
- <td style="width: 20%;" align="center">
- <i class="el-icon-delete" @click="deleteData(index)"></i>
- </tr>
- </table>
<div class="flex-box-ce" style="margin-top: 20px; justify-content: center;">
<el-button size="mini" plain round @click="cancelChangeScore">取消</el-button>
<el-button type="primary" plain round size="mini" @click="confirmChangeScore">确定</el-button>
- <el-button slot="reference" size="small" style="margin-left: auto;">正态分布</el-button>
+ <el-button slot="reference" style="margin-left: auto;">正态分布</el-button>
name: "EditScoreList",
props: {
@@ -48,36 +52,69 @@ export default {
default: () => []
+ isPopoverVisible: false,
+ distributionId: "",
+ editScoreList: []
+ handleShow() {
+ if (this.scoreList && this.scoreList.length > 0) {
+ this.editScoreList = []
+ this.editScoreList = cloneDeep(this.scoreList)
+ this.distributionId = this.editScoreList[0].id
+ handleHide() {
addData() {
- this.scoreList.push({ id: this.distributionId, name: "", scale: 0 })
+ this.editScoreList.push({ id: this.distributionId, name: "", scale: 0 })
deleteData(index) {
- this.scoreList.splice(index, 1)
+ this.editScoreList.splice(index, 1)
cancelChangeScore() {
this.$refs.popoverRef && this.$refs.popoverRef.doClose();
confirmChangeScore() {
- let url = `/performance/statistics/package/distribution/${this.user_info.site_id}`
+ let url = `/performance/statistics/distribution/${this.user_info.site_id}`
let reviewPackageId = this.reviewPackageId
- let items = this.scoreList;
+ let items = this.editScoreList;
let sum = 0;
- for (let i = 0; i < items.length - 1; i++) {
+ for (let i = 0; i < items.length; i++) {
if (!items[i].name) return this.$message.error('请输入正太规则名称')
sum += Number(items[i].scale)
if (sum > 100) return this.$message.error('总分布比例不能超过100')
+ if (sum !== 100) return this.$message.error('总分布比例要等于100')
let data = {
- reviewPackageId,
items
this.$http.post(url, data).then(res => {
- this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+ if (res.code == 1) {
+ this.$message.success("保存成功");
+ this.$emit("confirm", res.data.items)
+ this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+ this.$message.error(res.message || "保存失败");
+ this.$emit("confirm", [])
@@ -88,11 +125,10 @@ export default {
<style scoped lang="scss">
.setting {
- width: 300px;
- height: 200px;
+ height: 240px;
.setting-title-box {
- padding-right: 20px;
@@ -107,39 +143,7 @@ export default {
- // table {
- // width: 100%;
- // border-collapse: collapse; // everything will be ok
- // /* 合并表格边框 */
- // border: 1px solid #ccc;
- // /* 设置表格边框样式和颜色 */
- // margin: 0 auto 10px auto;
- // /* 设置表格外边距 */
- // background-color: #f8f8f8;
- // /* 设置表格背景颜色 */
- // color: #000;
- // /* 设置表格文字颜色 */
- // text-align: center;
- // /* 设置表格文字居中 */
- // line-height: 40px;
- // border-radius: 6px;
- // /* 设置表格行高 */
- // tr:nth-child(1) {
- // background-color: #f2f2f2;
- // /* 偶数行背景色 */
- // tr:nth-child(even) {
- // background-color: #fff;
- // tr:nth-child(odd) {
- // /* 奇数行背景色 */
@@ -0,0 +1,685 @@
+ <div class="record-right scroll-box" style="overflow-y: auto;" v-loading="loading">
+ <div class="title-container flex-box-ce" style="height: 30px; justify-content: space-between;">
+ <div class="flex-box-ce">
+ <el-popover placement="bottom" trigger="click" ref="searchPopover">
+ <div class="searchBox">
+ <div class="search-title"
+ style="border-bottom: 1px solid #f1f1f1; font-size: 16px; font-weight: 700; padding: 0 10px; padding-bottom: 10px;">
+ 修改考核表
+ <div style="margin: 10px 0;">考核表名称</div>
+ <EditTitle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :title="title"
+ @handleEditTile="changeTitle" />
+ <div style="margin: 10px 0;">考核表时间</div>
+ <EditCircle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :startTime="startTime"
+ :endTime="endTime" @comfirmChanged="changeDate" />
+ <div style="margin: 10px 0;">考核分类</div>
+ <EditCateType v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :cateIds="cateIds"
+ :cateList="cateList" @comfirmChanged="changeCateIds" />
+ <template slot="reference">
+ <div class="gd">
+ <i class="el-icon-edit" style="margin-right: 5px; color: #999;"></i>
+ <strong style="margin-right: 5px;">{{ title }}</strong>
+ {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
+ </el-popover>
+ <el-button v-if="packageOkrs && packageOkrs.length > 0" type="text" style="margin-left: 10px;"
+ @click="openTargetList(packageOkrs)">考核表OKR</el-button>
+ <div>
+ <!-- 正太规则 -->
+ <EditScoreList v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :scoreList="scoreList" />
+ <div class="line"></div>
+ <!-- 考核人员分数列表 -->
+ <div class="score-list">
+ <div class="score-item heartBeat animated">
+ <div class="score-item-title">总人数</div>
+ <div class="score-item-num">{{ userTotal }}</div>
+ <div class="score-item-percent">{{ userComplete }}人已完成</div>
+ <div v-for="item in gradeLevels" class="score-item heartBeat animated">
+ <div class="score-item-title">{{ item.name }}</div>
+ <div class="score-item-num">{{ users.filter(user => user.level === item.name).length }}</div>
+ <div class="score-item-percent">{{ item.min + '~' + item.max }}</div>
+ <!-- 考核人员列表 -->
+ <div class="user-info flex-box-ce">
+ <div class="info-card" v-for="item in users" :key="item.employeeId">
+ <div class="info" style="height: 30px;">{{ item.employeeName }}</div>
+ <div class="info">
+ <span class="info-label fontColorC">考核状态:</span>
+ <el-tag v-if="item.status" type="success">
+ 已完成
+ </el-tag>
+ <el-tag v-else="item.status" type="warning">
+ 进行中
+ <span class="info-label fontColorC">评分:</span>
+ <el-tag v-if="item.level === '未评分'" type="info">
+ 未评分
+ <el-tag v-else>
+ {{ item.level }}
+ <span class="info-label fontColorC">正态分布:</span>
+ <el-tag>
+ {{ item.scoreResult }}
+ <span class="info-label fontColorC">关联okr</span>
+ <el-button v-if="item.okrs && item.okrs.length > 0" type="text"
+ @click="openTargetList(item.okrs)">个人OKR</el-button>
+ <span class="fontColorC" v-else>暂无关联</span>
+ <!-- 指标列表 -->
+ <div class="title-container" style="margin: 10px 0;">
+ <div class="title">指标信息</div>
+ <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
+ <el-tab-pane label="默认" name="1">
+ <el-table ref="fmeaTableRef2" :data="tableData1" stripe style="width: 100%" border
+ v-table-move="['fmeaTableRef2']" :header-cell-style="{ background: '#f5f7fa' }" :height="330">
+ <el-table-column prop="employeeName" label="员工" align="center"></el-table-column>
+ <el-table-column prop="title" label="指标" align="center">
+ <div v-html="scope.row.title" slot="content" style="max-width:300px"></div>
+ <div class="oneLine">{{ scope.row.title }}</div>
+ <el-table-column prop="target" label="目标" align="center" width="100">
+ <span v-if="scope.row.target !== null">
+ {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
+ </span>
+ <span v-else>--</span>
+ <el-table-column prop="result" label="结果" align="center" width="100">
+ {{ scope.row.result || '--' }}
+ <el-table-column prop="different" label="差距" align="center" width="100">
+ <el-tag v-if="scope.row.difference > 0" type="success">
+ {{ scope.row.difference }}
+ <el-tag v-else-if="scope.row.difference < 0" type="danger">
+ <div v-else>
+ --
+ <el-table-column prop="score" label="评分" align="center" width="100">
+ {{ scope.row.score || '--' }}
+ <el-table-column prop="okrs" label="关联OKR" width="100" align="center">
+ <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+ @click="openTargetList(scope.row.okrs)">
+ 指标OKR
+ </el-link>
+ <span v-else class="fontColorC">暂无关联</span>
+ <el-tab-pane label="按指标/目标/单位聚合" name="2">
+ <el-table ref="fmeaTableRef3" :data="tableData2" stripe style="width: 100%" border
+ v-table-move="['fmeaTableRef3']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+ <el-table-column prop="avgResult" label="均值" align="center">
+ <el-table-column prop="standardResultRate" label="超出目标比例" align="center">
+ <el-tag v-if="parseInt(scope.row.standardResultRate) > 0" type="success">
+ {{ scope.row.standardResultRate }}
+ <el-tag v-else-if="parseInt(scope.row.standardResultRate) < 0" type="danger">
+ <el-tag v-else type="info">
+ <el-table-column prop="standardCount" label="达标数" align="center" />
+ <el-table-column prop="standardRate" label="达标率" align="center">
+ <el-tag v-if="parseInt(scope.row.standardRate) > 0" type="success">
+ {{ scope.row.standardRate }}
+ <el-tag v-else-if="parseInt(scope.row.standardRate) < 0" type="danger">
+ <el-table-column prop="avgScore" label="平均分" align="center" />
+ <!-- 关联okr -->
+ <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+ @close="closeTargetList">
+ </TargetListComp>
+import _ from "lodash"
+import EditTitle from './RightEamineComp/EditTitle'; // 修改考核标题组件
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+import EditScoreList from './RightEamineComp/EditScoreList'
+import EditCircle from './RightEamineComp/EditCircle.vue';
+import EditCateType from './RightEamineComp/EditCateType.vue';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+ EditTitle,
+ EditScoreList,
+ EditCircle,
+ EditCateType,
+ TargetListComp
+ detailInfo: {
+ type: Object,
+ default: () => { }
+ cateList: {
+ activeName: "1",
+ reviewPackageId: "",
+ title: "默认标题",
+ startTime: "",
+ endTime: "",
+ scoreList: [],
+ users: [], //考核人员列表
+ tableData1: [], // 考核中的指标列表,
+ tableData2: [], // 按单位/目标/聚合指标列表,
+ level_enable: false,
+ packages: [],
+ userTotal: 0,
+ userComplete: 0,
+ userIncomplete: 0,
+ infos: [],
+ cateIds: [], // 选择的考核分类
+ targetDialogVisible: false,
+ okrs: [],
+ packageOkrs: []
+ detailInfo(v) {
+ this.activeName = '1'
+ let { data: { data: { cateIds, indicators, startTime, endTime, title, okrs, distribution: { items }, users }, code }, reviewPackageId } = v
+ this.packageOkrs = okrs;
+ this.cateIds = cateIds;
+ this.tableData1 = [];
+ this.tableData2 = [];
+ this.reviewPackageId = reviewPackageId;
+ this.title = title;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ this.scoreList = items;
+ this.distributionId = this.scoreList[0].id
+ this.users = users;
+ this.tableData1 = indicators;
+ this.tableData1.forEach(item => {
+ if (item.target && item.result) {
+ item.difference = item.result - item.target
+ item.difference = '--'
+ this.userTotal = 0;
+ this.userComplete = 0;
+ this.userIncomplete = 0;
+ let distribution = [];
+ let userScores = []
+ this.scoreList.forEach(item => {
+ item.level = item.name;
+ item.ratio = item.scale / 100
+ distribution.push(item)
+ this.users.forEach(user => {
+ this.userTotal++;
+ this.userComplete += user.status === 1 ? 1 : 0;
+ user.level = this.findGrade(user.score, this.gradeLevels);
+ userScores.push(user.score)
+ this.infos = [
+ { label: "总人数", num: this.userTotal },
+ { label: "已完成", num: this.userComplete },
+ { label: "未评分", num: this.userIncomplete },
+ let scoreResult = this.assignLevels(userScores, distribution);
+ this.users.forEach(item => {
+ scoreResult.forEach(result => {
+ if (result.scores.includes(item.score)) {
+ item.scoreResult = result.level
+ this.loading = false
+ calcScoreList() {
+ let scoreSet = new Set(this.users.map(item => Number(item.score)))
+ let scores = [...scoreSet].sort((a, b) => b - a);
+ let rate = 1;
+ let scoreCount = scores.length; // 总人数
+ let scoreIndex = 0;
+ let scoreResult = {};
+ let scale = item.scale / 100;
+ let count = Math.round((scoreCount - scoreIndex) * scale / rate);
+ rate -= scale;
+ if (count <= 0) return;
+ for (let i = scoreIndex; i < (scoreIndex + count); i++) {
+ scoreResult[scores[i]] = item.name;
+ scoreIndex += count;
+ return this.users.map(item => {
+ let result = { ...item };
+ result.scoreResult = scoreResult[item.score] || '--'
+ return result;
+ }).sort((a, b) => b.score - a.score);
+ changeCateIds(cateIds) {
+ this.cateIds = cateIds
+ this.$bus.$emit("finishEdit", this.reviewPackageId)
+ changeTitle(title) {
+ changeDate(data) {
+ let { startTime, endTime } = data
+ this.startTime = startTime
+ this.endTime = endTime
+ this.gradeLevels = gradeLevels
+ // 查找分数对应的等级
+ findGrade(score, gradeLevels) {
+ for (let i = 0; i < gradeLevels.length; i++) {
+ if (score && score >= gradeLevels[i].min && score && score <= gradeLevels[i].max) {
+ return gradeLevels[i].name; // 返回对应的等级描述
+ } else if (score && score <= gradeLevels[0].min) {
+ return gradeLevels[0].name; // 返回对应的等级描述
+ } else if (score && score >= gradeLevels[gradeLevels.length - 1].max) {
+ return gradeLevels[gradeLevels.length - 1].name; // 返回对应的等级描述
+ return "未评分"; // 如果分数不在任何范围内
+ assignLevels(scores, levelConfigs) {
+ // 降序排序并去重(假设分数不重复,可省略去重)
+ const sortedScores = [...scores].sort((a, b) => b - a);
+ const total = sortedScores.length;
+ if (total === 0) return [];
+ // 归一化处理比例
+ const totalRatio = levelConfigs.reduce((sum, cfg) => sum + cfg.ratio, 0);
+ const normalized = levelConfigs.map(cfg => cfg.ratio / totalRatio);
+ // 计算每个等级的初始人数
+ let counts = normalized.map(ratio => Math.floor(total * ratio));
+ let remainder = total - counts.reduce((sum, c) => sum + c, 0);
+ // 分配剩余人数,按优先级顺序
+ let idx = 0;
+ while (remainder > 0 && idx < counts.length) {
+ counts[idx]++;
+ remainder--;
+ idx++;
+ // 构建结果:按人数切割数组
+ let start = 0;
+ return counts.map((count, i) => {
+ const end = start + count;
+ const levelScores = sortedScores.slice(start, end);
+ start = end;
+ level: levelConfigs[i].level,
+ scores: levelScores
+ // 选项卡点击事件
+ handleClick(tab, event) {
+ this.tableData2 = []
+ if (this.activeName == 2) {
+ let groups = _.groupBy(this.tableData1, item => `${item.title}(_)${item.target === null || item.target === '' ? 'null' : item.target}(_)${item.unit === null || item.unit === '' ? 'null' : item.unit}`);
+ Object.keys(groups).forEach(key => {
+ let group = {
+ title: '',
+ target: '',
+ unit: '',
+ userCount: 0,
+ scoredCount: 0,
+ standardCount: 0,
+ failCount: 0,
+ standardRate: '--',
+ totalScore: 0,
+ totalResult: 0,
+ avgScore: 0,
+ avgResult: 0,
+ standardResultRate: '--'
+ groups[key].forEach(indicator => {
+ group.title = indicator.title; // 指标名称
+ group.target = indicator.target; // 目标
+ group.unit = indicator.unit; // 单位
+ let standardCount = indicator.difference !== '--' && indicator.difference >= 0 ? 1 : 0; //
+ group.userCount += 1;
+ group.scoredCount += indicator.score !== null ? 1 : 0;
+ group.standardCount += standardCount;
+ group.failCount += standardCount === 1 ? 0 : 1;
+ if (indicator.score !== null) group.totalScore += indicator.score;
+ if (indicator.result !== null) group.totalResult += indicator.result;
+ group.standardCount = group.standardCount * 100;
+ if (group.userCount > 0) {
+ let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
+ let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
+ let avgResult = Math.floor(group.totalResult / group.userCount * 100 * 0.01);
+ group.standardRate = rate > 0 ? `${rate}%` : '--';
+ group.avgScore = avgScore !== 0 ? avgScore : '--';
+ group.avgResult = avgResult !== 0 ? avgResult : '--';
+ if (group.target !== null && group.avgResult !== '--') {
+ let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100) ;
+ group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
+ this.tableData2.push(group);
+ console.log(this.tableData2);
+ closeTargetList() {
+ this.targetDialogVisible = false
+ openTargetList(okrs) {
+ if (okrs && okrs.length > 0) {
+ this.okrs = okrs
+ this.targetDialogVisible = true
+ return this.$message.error("暂无关联okr")
+<style>
+.oneLine {
+.record-right {
+ width: 49.6%;
+ background: #fff;
+ padding: 10px 20px;
+ .score-list {
+ margin-top: 20px;
+ .score-item {
+ flex: 0 0 calc((100% - 80px) / 5);
+ height: 100px;
+ margin: 0 20px 20px 0;
+ justify-content: space-around;
+ color: #999;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+ &-title {
+ font-weight: 600;
+ color: #409EFF;
+ &-num {
+ color: #000;
+ &:nth-child(5n) {
+ /* 去除第5n个的margin-right */
+ margin-right: 0;
+ .user-info {
+ overflow-x: auto;
+ width: 10px;
+ height: 10px;
+ .info-card {
+ width: 220px;
+ margin-right: 20px;
+ padding: 5px 10px;
+ .info {
+ .info-label {
+ width: 100px;
@@ -1,35 +1,19 @@
<div class="record-right scroll-box" style="overflow-y: auto;" v-loading="loading">
- <div class="title-container flex-box-ce" style="justify-content: space-between;">
+ <!-- 考核表详情 -->
+ <div class="title-container" style="margin-bottom: 10px;">
+ <div class="title">考核详情
<div class="flex-box-ce">
- <el-popover placement="bottom" trigger="click" ref="searchPopover">
- <div class="searchBox">
- <div class="search-title"
- style="border-bottom: 1px solid #f1f1f1; font-size: 16px; font-weight: 700; padding: 0 10px; padding-bottom: 10px;">
- 修改考核表
- <div style="margin: 10px 0;">考核表名称</div>
- <EditTitle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :title="title"
- @handleEditTile="changeTitle" />
- <div style="margin: 10px 0;">考核表时间</div>
- <EditCircle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :startTime="startTime"
- :endTime="endTime" @comfirmChanged="changeDate" />
- <div style="margin: 10px 0;">考核分类</div>
- <EditCateType v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :cateIds="cateIds"
- :cateList="cateList" @comfirmChanged="changeCateIds" />
- <template slot="reference">
- <div class="gd">
- <i class="el-icon-edit" style="margin-right: 5px; color: #999;"></i>
- <strong style="margin-right: 5px;">{{ title }}</strong>
- {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
- </el-popover>
+ <!-- <el-button v-if="packageOkrs && packageOkrs.length > 0" type="text" style="margin-left: 10px;"
+ @click="openTargetList(packageOkrs)">考核表OKR</el-button> -->
<!-- 正太规则 -->
@@ -40,8 +24,8 @@
- <!-- 考核人员列表 -->
- <div class="score-list">
+ <!-- <div class="score-list">
<div class="score-item heartBeat animated">
<div class="score-item-title">总人数</div>
<div class="score-item-num">{{ userTotal }}</div>
@@ -52,57 +36,86 @@
<div class="score-item-num">{{ users.filter(user => user.level === item.name).length }}</div>
<div class="score-item-percent">{{ item.min + '~' + item.max }}</div>
+ <div style="width: 100%; display: flex;">
+ <div style="width: 50%; border-right: 1px solid #f1f1f1;">
+ <div ref="echarts2" class="echarts"></div>
+ <div style="width: 50%; padding: 0 20px; box-sizing: border-box;">
+ <el-table :data="gradeLevels" style="width: 100%;" stripe :header-cell-style="{ background: '#f5f7fa' }">
+ <el-table-column prop="name" label="等级">
+ {{ scope.row.name }} ({{ scope.row.min + '~' + scope.row.max }})
+ <el-table-column prop="name" label="人数">
+ {{ users.filter(user => user.level === scope.row.name).length }}
- <table>
- <tr>
- <td v-for="item in users" :key="item.employeeId">{{ item.employeeName }}</td>
+ <div class="title">考核人员列表</div>
- <td v-for="item in users" :key="item.employeeId">{{ item.score || '--' }}</td>
+ <el-table :data="users" style="width: 100%; " :header-cell-style="{ background: '#f5f7fa' }">
+ <el-table-column prop="employeeName" label="员工">
- <td v-for="item in users" :key="item.employeeId">
- <el-tag v-if="item.status" type="success">
+ <el-table-column prop="status" label="考核状态">
+ <el-tag v-if="scope.row.status" type="success">
已完成
- <el-tag v-else="item.status" type="warning">
+ <el-tag v-else="scope.row.status" type="warning">
进行中
- <el-tag v-if="item.level === '未评分'" type="info">
+ <el-table-column prop="level" label="评分">
+ <el-tag v-if="scope.row.level === '未评分'" type="info">
未评分
<el-tag v-else>
{{ item.level }}
+ <el-table-column prop="scoreResult" label="正态分布">
<el-tag>
- {{ item.scoreResult }}
+ {{ scope.row.scoreResult }}
+ <el-table-column label="操作">
+ <el-link type="primary" @click="getDetails()">查看详情</el-link>
<!-- 指标列表 -->
- <div class="title-container" style="margin: 20px 0;">
<div class="title">指标信息</div>
<el-tabs type="card" v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="默认" name="1">
- <el-table ref="fmeaTableRef2" :data="tableData1" stripe style="width: 100%" border
- v-table-move="['fmeaTableRef2']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+ <el-table :data="tableData1" stripe style="width: 100%" border :header-cell-style="{ background: '#f5f7fa' }" :height="500">
<el-table-column prop="employeeName" label="员工" align="center"></el-table-column>
<el-table-column prop="title" label="指标" align="center">
@@ -113,7 +126,7 @@
- <el-table-column prop="target" label="目标" align="center" width="100">
+ <el-table-column prop="target" label="目标" align="center" >
<span v-if="scope.row.target !== null">
{{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
@@ -121,19 +134,19 @@
<span v-else>--</span>
- <el-table-column prop="result" label="结果" align="center" width="100">
+ <el-table-column prop="result" label="结果" align="center">
{{ scope.row.result || '--' }}
- <el-table-column prop="different" label="差距" align="center" width="100">
+ <el-table-column prop="different" label="差距" align="center" >
- <el-tag v-if="scope.row.difference < 0" type="success">
{{ scope.row.difference }}
- <el-tag v-else-if="scope.row.difference > 0" type="danger">
<div v-else>
@@ -141,19 +154,30 @@
- <el-table-column prop="score" label="评分" align="center" width="100">
+ <el-table-column prop="score" label="评分" align="center" >
{{ scope.row.score || '--' }}
+ <el-table-column prop="okrs" label="过程跟踪" align="center">
</el-table>
+ <div style="margin-top: 20px;"></div>
<el-tab-pane label="按指标/目标/单位聚合" name="2">
- <el-table ref="fmeaTableRef3" :data="tableData2" stripe style="width: 100%" border
- v-table-move="['fmeaTableRef3']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+ <el-table :data="tableData2" stripe style="width: 100%" border:header-cell-style="{ background: '#f5f7fa' }" :height="500">
<el-tooltip class="item" effect="dark" placement="top">
@@ -163,7 +187,7 @@
+ <el-table-column prop="target" label="目标" align="center">
@@ -207,24 +231,30 @@
<el-table-column prop="avgScore" label="平均分" align="center" />
import _ from "lodash"
+import ECharts from 'echarts';
import EditTitle from './RightEamineComp/EditTitle'; // 修改考核标题组件
import EditScoreList from './RightEamineComp/EditScoreList'
import EditCircle from './RightEamineComp/EditCircle.vue';
import EditCateType from './RightEamineComp/EditCateType.vue';
@@ -232,7 +262,8 @@ export default {
EditScoreList,
EmployeeSelector,
EditCircle,
- EditCateType
detailInfo: {
@@ -264,15 +295,65 @@ export default {
userIncomplete: 0,
infos: [],
gradeLevels: [],
- cateIds: [] // 选择的考核分类
watch: {
detailInfo(v) {
+ this.initData();
+ if (this.detailInfo && this.detailInfo.reviewPackageId) this.initData();
+ async initData() {
this.loading = true
this.activeName = '1'
- let { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code }, reviewPackageId } = v
- this.getAllSet();
+ let { data: { data: { cateIds, indicators, startTime, endTime, title, okrs, distribution: { items }, users }, code }, reviewPackageId } = this.detailInfo
+ await this.getAllSet();
this.cateIds = cateIds;
this.tableData1 = [];
this.tableData2 = [];
@@ -321,50 +402,66 @@ export default {
+ this.getResult();
this.loading = false
- computed: {
- ...mapGetters(['user_info']),
- calcScoreList() {
- let scoreSet = new Set(this.users.map(item => Number(item.score)))
- let scores = [...scoreSet].sort((a, b) => b - a);
- let rate = 1;
- let scoreCount = scores.length; // 总人数
- let scoreIndex = 0;
- let scoreResult = {};
- this.scoreList.forEach(item => {
- let scale = item.scale / 100;
- let count = Math.round((scoreCount - scoreIndex) * scale / rate);
- rate -= scale;
- if (count <= 0) return;
- for (let i = scoreIndex; i < (scoreIndex + count); i++) {
- scoreResult[scores[i]] = item.name;
- scoreIndex += count;
- });
- return this.users.map(item => {
- let result = { ...item };
- result.scoreResult = scoreResult[item.score] || '--'
- return result;
- }).sort((a, b) => b.score - a.score);
- filters: {
- formatDate(val) {
- if (val) return moment(val).format('YYYY-MM-DD')
- else return "--"
- created() {
- methods: {
+ getResult() {
+ let xData = [], yData = []
+ console.log(this.gradeLevels)
+ console.log(this.users)
+ this.gradeLevels.forEach(item => {
+ xData.push(item.name)
+ yData.push(this.users.filter(user => user.level === item.name).length)
+ console.log(xData);
+ console.log(yData);
+ let option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ // 坐标轴指示器,坐标轴触发有效
+ type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+ xAxis: {
+ type: 'category',
+ data: xData
+ yAxis: {
+ type: 'value',
+ minInterval: 1
+ data: yData,
+ type: 'bar',
+ //这里是重点
+ color: function (params) {
+ //注意,如果颜色太少的话,后面颜色不会自动循环,最好多定义几个颜色
+ var colorList = ['#409EFF', '#4ECB73', '#36CBCB', '#F2637B', '#FBD437', '#749f83', '#ca8622'];
+ var index;
+ //给大于颜色数量的柱体添加循环颜色的判断
+ if (params.dataIndex >= colorList.length) {
+ index = params.dataIndex - colorList.length;
+ return colorList[index];
+ return colorList[params.dataIndex];
+ barMaxWidth: 30
+ var myChart = ECharts.init(this.$refs.echarts2);
+ myChart.setOption(option);
+ getDetails() {
+ this.$emit('changeCurrentId', { currentId: '2', reviewId: this.reviewPackageId })
changeCateIds(cateIds) {
this.cateIds = cateIds
this.$bus.$emit("finishEdit", this.reviewPackageId)
@@ -419,31 +516,7 @@ export default {
return "未评分"; // 如果分数不在任何范围内
- // calcScoreResult(users) {
- // let scoreSet = new Set(users.map(item => Number(item.score)));
- // let scores = [...scoreSet].sort((a, b) => b - a);
- // let rate = 1;
- // let scoreCount = scores.length;
- // let scoreIndex = 0;
- // let scoreResult = {};
- // this.scoreList.forEach(item => {
- // let scale = item.scale / 100;
- // let count = Math.round((scoreCount - scoreIndex) * scale / rate);
- // rate -= scale;
- // if (count <= 0) return;
- // for (let i = scoreIndex; i < (scoreIndex + count); i++) {
- // scoreResult[scores[i]] = item.name;
- // scoreIndex += count;
- // });
- // console.log("-===", scoreResult)
- // return users.map(item => {
- // let result = { ...item };
- // result.scoreResult = scoreResult[item.score] || '--'
- // return result;
- // }).sort((a, b) => b.score - a.score);
assignLevels(scores, levelConfigs) {
// 降序排序并去重(假设分数不重复,可省略去重)
@@ -502,7 +575,6 @@ export default {
standardResultRate: '--'
groups[key].forEach(indicator => {
- console.log(indicator)
group.title = indicator.title; // 指标名称
group.target = indicator.target; // 目标
group.unit = indicator.unit; // 单位
@@ -514,6 +586,7 @@ export default {
if (indicator.score !== null) group.totalScore += indicator.score;
if (indicator.result !== null) group.totalResult += indicator.result;
});
if (group.userCount > 0) {
let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
@@ -523,7 +596,7 @@ export default {
group.avgResult = avgResult !== 0 ? avgResult : '--';
if (group.target !== null && group.avgResult !== '--') {
- let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01);
group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
@@ -531,6 +604,18 @@ export default {
console.log(this.tableData2);
@@ -548,14 +633,41 @@ export default {
.record-right {
- width: 49.6%;
- height: 100%;
border-radius: 5px;
background: #fff;
- padding: 20px;
overflow: hidden;
overflow-y: auto;
+ .echarts {
+ ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+ ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
+ ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
+ ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
.title-container {
@@ -583,14 +695,13 @@ export default {
.score-list {
margin-top: 20px;
.score-item {
flex: 0 0 calc((100% - 80px) / 5);
height: 100px;
@@ -622,39 +733,57 @@ export default {
- table {
- border-collapse: collapse;
- /* 合并表格边框 */
- border: 1px solid #ccc;
- /* 设置表格边框样式和颜色 */
- /* 设置表格外边距 */
- background-color: #f8f8f8;
- /* 设置表格背景颜色 */
- color: #000;
- /* 设置表格文字颜色 */
- text-align: center;
- /* 设置表格文字居中 */
- line-height: 40px;
- /* 设置表格行高 */
- tr:nth-child(1) {
- background-color: #f2f2f2;
- /* 偶数行背景色 */
- tr:nth-child(even) {
- background-color: #fff;
- tr:nth-child(odd) {
- /* 奇数行背景色 */
@@ -0,0 +1,640 @@
+ <div class="all">
+ <div class="create-methods flex-box-ce">
+ <div class="method-item" v-for="(item, index) in methods"
+ :class="currentIndex === index ? 'active' : ''" :key="item.id"
+ @click="changeMethod(item.id, index)">
+ <el-tooltip effect="dark" content="教程指引" placement="top">
+ <div class="icon flex-center-center" @click="initStepData()">
+ <i class="el-icon-document"></i>
+ <div class="setting-content flex-1" v-if="method == 1">
+ <UploadPublish2 ref="UploadPublish2" />
+ <div class="setting-content flex-1" v-if="method == 2">
+ <UploadPublish />
+ <el-dialog :visible.sync="dialogVisible" title="已创建的指标库" center width="600px">
+ <div class="dialog-content">
+ <template v-if="templateList.length > 0">
+ <div class="flex-box-ce template-item" v-for="(item, index) in templateList" :key="index">
+ <div class="flex-box flex-1" @click="chooseTemplate(item)">
+ <div style="width: 20px;"><i class="el-icon-tickets blue"></i></div>
+ <span class="message-content">{{ item.title || '默认标题' }}</span>
+ <el-popconfirm confirm-button-text='确定' cancel-button-text='不用了' icon="el-icon-info"
+ icon-color="red" title="确定删除这个考核模板吗?" @confirm="confirmDelete(item.templateId)"
+ @cancel="cancelDelete()">
+ <el-link slot="reference" type="danger">删除</el-link>
+ </el-popconfirm>
+ <noData v-else content="暂无内容" imgW="120px" imgH="120px"></noData>
+ </el-dialog>
+import TemplateDetails from './IndicatorSetting/TemplateDetails.vue';
+import PublishComp from './IndicatorSetting/PublishComp.vue';
+import TemplateMixedPublish from './IndicatorSetting/TemplateMixedPublish.vue';
+import UploadPublish from './IndicatorSetting/UploadPublish.vue';
+import UploadPublish2 from './IndicatorSetting/UploadPublish2.vue';
+import indicatorSettingStep from "@/newPerformance/utils/indicatorSettingStep"
+import introJs from 'intro.js'
+import 'intro.js/introjs.css'
+ TemplateDetails,
+ PublishComp,
+ TemplateMixedPublish,
+ UploadPublish,
+ UploadPublish2
+ dialogVisible: false,
+ currentIndex: 0,
+ methods: [
+ // { id: 0, title: "方式1:手动创建指标" },
+ { id: 1, title: "方式1:快速创建" },
+ { id: 2, title: "方式2:批量导入指标" },
+ method: 1,
+ templateList: [],
+ step: 0,
+ activeIndex: 1,
+ templateId: "",
+ step(v) {
+ this.activeIndex = v + 1
+ ...mapGetters(['user_info'])
+ this.$nextTick(() => {
+ if (!localStorage.getItem("isLearned")) {
+ this.startGuide()
+ startGuide() {
+ introJs().setOptions({
+ nextLabel: '下一个', // 下一个按钮文字
+ prevLabel: '上一个', // 上一个按钮文字
+ skipLabel: '跳过', // 跳过按钮文字
+ doneLabel: '立即体验',// 完成按钮文字
+ tooltipClass: 'intro-tooltip', /* 引导说明文本框的样式 */
+ highlightClass: 'intro-highlight', /* 说明高亮区域的样式 */
+ exitOnEsc: true, /* 是否使用键盘Esc退出 */
+ exitOnOverlayClick: false, /* 是否允许点击空白处退出 */
+ keyboardNavigation: true, /* 是否允许键盘来操作 */
+ showBullets: false, /* 是否使用点显示进度 */
+ showProgress: false, /* 是否显示进度条 */
+ scrollToElement: true, /* 是否滑动到高亮的区域 */
+ overlayOpacity: 0.5, // 遮罩层的透明度 0-1之间
+ positionPrecedence: ['bottom', 'top', 'right', 'left'], /* 当位置选择自动的时候,位置排列的优先级 */
+ disableInteraction: false, /* 是否禁止与元素的相互关联 */
+ hidePrev: true, /* 是否在第一步隐藏上一步 */
+ hidePrev: false, // 在第一步中是否隐藏上一个按钮
+ hideNext: false, // 在最后一步中是否隐藏下一个按钮
+ exitOnOverlayClick: false, // 点击叠加层时是否退出介绍
+ showStepNumbers: false, // 是否显示红色圆圈的步骤编号
+ disableInteraction: true, // 是否禁用与突出显示的框内的元素的交互,就是禁止点击
+ showBullets: true, // 是否显示面板指示点
+ // 配置内容 steps数组,内部一个对象代表一个步骤
+ steps: indicatorSettingStep
+ }).onbeforechange((e) => {
+ console.log(e)
+ // if (e.className.includes("choose-btn")) {
+ // this.$refs.UploadPublish2.openTemplate()
+ // }
+ }).oncomplete(function () {
+ //点击跳过按钮后执行的事件
+ localStorage.setItem("isLearned", true)
+ }).onexit(function () {
+ //点击结束按钮后, 执行的事件
+ }).start()
+ // 开启教程指引
+ initStepData() {
+ setTimeout(() => {
+ this.startGuide();
+ }, 300)
+ changeMethod(id, index) {
+ this.method = id;
+ this.currentIndex = index;
+ this.templateId = item.templateId
+ this.step = 1;
+ this.dialogVisible = false;
+ getTemplateList() {
+ this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+ this.templateList = res.data.data.list;
+ addTemplate() {
+ this.$axiosUser('post', `/performance/template/create/${this.user_info.site_id}`).then(res => {
+ let { data: { data, code } } = res
+ if (code == 1) {
+ this.templateList = res.data.data.list
+ this.templateId = this.templateList[this.templateList.length - 1].templateId;
+ this.$message.success("创建成功")
+ // 确认删除模板
+ confirmDelete(templateId) {
+ this.$axiosUser('post', `/performance/template/remove/${this.user_info.site_id}/${templateId}`).then(res => {
+ cancelDelete() {
+ console.log("取消删除");
+ prevStep() {
+ if (this.step <= 0) return;
+ this.step--;
+ nextStep() {
+ if (this.step >= 2) return;
+ this.step++;
+ // 发布考核弹框的回调
+ onPubishConfirm(params, selectOkrs) {
+ let templateId = this.templateId;
+ let { title, startDate, endDate, cycleType, employeeIds, okrs, cateId } = params
+ let employees = employeeIds.map(employee => ({
+ employeeId: employee,
+ ids: [],
+ interviewFlow: {
+ nodes: [
+ id: "IT_1894283564934688769",
+ type: "interview",
+ allows: [],
+ enable: false,
+ weight: 0,
+ children: [],
+ assigneeIds: [],
+ leaderLevel: 1,
+ assigneeType: "self",
+ multipleType: "or"
+ }))
+ let requireParams = {
+ title,
+ cycleType,
+ startDate,
+ endDate,
+ okrs,
+ templates: [
+ templateId,
+ employees
+ cateId
+ // let url = `/performance/review/publish/${this.user_info.site_id}`;
+ // console.log(requireParams)
+ let url = `/performance/review/publish/templates/${this.user_info.site_id}`;
+ this.$http.post(url, requireParams).then(res => {
+ console.log(res)
+ let { data, code, message } = res
+ this.$message.success("发布成功");
+ this.$emit("changeCurrentId", {currentId: '1'})
+ this.$router.push("newPerformance")
+ }, 1000);
+ this.$message.error(message || '发布失败');
+ activeStep(index, flag = true) {
+ this.activeIndex = index;
+ if (flag) this.step = index - 1;
+ changeStep(step) {
+ this.activeIndex = step + 1;
+ .introjs-helperLayer {
+ box-shadow: rgba(33, 33, 33, 0.8) 0px 0px 1px 0px, rgba(33, 33, 33, 0.5) 0px 0px 0px 5000px !important;
+ border: 3px dashed #409eff;
+ /* 调整 intro.js 弹出框的大小 */
+ .introjs-tooltip {
+ width: auto;
+ /* 自动调整宽度 */
+ max-width: 1600px;
+ /* 最大宽度 */
+ height: auto;
+ /* 自动调整高度 */
+ /* 防止内容溢出 */
+ .new-tips {
+ line-height: 80px;
+ .introjs-tooltip-title {
+ width: 80%;
+ padding-top: 10px;
+ .warper {
+ line-height: 100px;
+ text-align: center;
+ border: 1px solid saddlebrown;
+ /* 重置引导组件样式(类似element-ui个人使用) */
+ .intro-tooltip {
+ color: #ffff;
+ background: #2c3e50;
+ /* 引导提示框的位置 */
+ .introjs-bottom-left-aligned {
+ left: 45% !important;
+ .introjs-right,
+ .introjs-left {
+ top: 30%;
+ .intro-highlight {
+ background: rgba(255, 255, 255, 0.5);
+ .introjs-arrow.left {
+ border-right-color: #2c3e50;
+ .introjs-arrow.top {
+ border-bottom-color: #2c3e50;
+ .introjs-arrow.right {
+ border-left-color: #2c3e50;
+ .introjs-arrow.bottom {
+ border-top-color: #2c3e50;
+ /* 提示框头部区域 */
+ .introjs-tooltip-header {
+ padding-right: 0 !important;
+ padding-top: 0 !important;
+ .introjs-skipbutton {
+ color: #409eff !important;
+ font-size: 14px !important;
+ font-weight: normal !important;
+ // padding: 8px 10px !important ;
+ .introjs-tooltipbuttons {
+ border: none !important;
+ .introjs-tooltiptext {
+ padding: 15px !important;
+ /* 提示框按钮 */
+ justify-content: center;
+ .introjs-button {
+ width: 50px !important;
+ padding: 4px !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ border-radius: 3px !important;
+ .introjs-button:last-child {
+ margin-left: 10px;
+ .introjs-prevbutton {
+ color: #606266 !important;
+ background: #fff !important;
+ border: 1px solid #dcdfe6 !important;
+ .introjs-nextbutton {
+ color: #fff !important;
+ background-color: #409eff !important;
+ border-color: #409eff !important;
+ .introjs-disabled {
+ color: #9e9e9e !important;
+ border-color: #bdbdbd !important;
+ background-color: #f4f4f4 !important;
+<style scoped lang="scss">
+.dialog-content {
+ height: 500px;
+ padding-bottom: 20px;
+ padding: 5px;
+ transition: all 0.3s;
+ background: #f7f7f7;
+.all {
+ .create-methods {
+ .method-item {
+ padding: 15px;
+ border: 1px solid #ebebeb;
+ color: #222;
+ margin-right: 12px;
+ background-color: #f5f7fa;
+ .icon {
+ width: 40px;
+ height: 40px;
+ border: 1px solid #ccc;
+ border-radius: 50%;
+ font-size: 20px;
+ background: #f5f5f5;
+ .setting-content {
+ .operation-box {
+ border: 1px solid #eee;
+.step-item2 {
+ color: #d6d6d6;
+ height: 60px;
+.step-item2 span {
+ width: 36px;
+ line-height: 28px;
+ border: 4px solid #d6d6d6;
+.step-item2 div {
+ margin-left: 5px;
+.active-step2 span {
+ border: 4px solid #409EFF;
+ color: #fff;
+ background-color: #409EFF;
+.active-step2 div {
+.barder-a::before {
+ content: ' ';
+ height: 4px;
+ width: 120px;
+ top: 28px;
+ background-color: #d6d6d6;
+ right: -60px;
+.step-item {
+ width: 140px;
+ color: #777777;
+.step-item span {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+ margin-right: 10px;
+.active-step {
+ background-color: #1583f2;
+.active-step span {
+ border: 1px solid #1583f2;
+ color: #1583f2;
@@ -0,0 +1,347 @@
+ <el-dialog
+ :visible.sync="innerVisible"
+ @close="handleClose"
+ @open="initData"
+ @closed="dataReset"
+ append-to-body
+ :close-on-click-modal="false"
+ center
+ title="结果录入节点配置"
+ width="600px"
+ >
+ <el-form v-if="currentNode" label-width="200">
+ <el-form-item label="启用">
+ <el-switch
+ v-model="currentNode.enable"
+ />
+ </el-form-item>
+ <el-form-item label="抄送人">
+ <el-radio-group
+ v-model="currentNode.assigneeType"
+ :disabled="!currentNode.enable"
+ @change="onAssigneeTypeChange"
+ <el-radio-button label="leader">组织管理员</el-radio-button>
+ <el-radio-button label="user">指定人员</el-radio-button>
+ <el-radio-button label="self">被考核人</el-radio-button>
+ <el-radio-button label="post">岗位</el-radio-button>
+ <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+ </el-radio-group>
+ <el-form-item label="">
+ <template v-if="currentNode.assigneeType === 'leader'">
+ <el-select
+ v-model="currentNode.leaderLevel"
+ placeholder="请选择管理员"
+ filterable
+ style="width: 300px;"
+ key="leader"
+ @change="onOrgManagerChange"
+ <el-option
+ v-for="item in levelOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ <template v-if="currentNode.assigneeType === 'user'">
+ v-model="userSelected"
+ placeholder="请选择指定人员"
+ multiple
+ key="user"
+ @change="onUserChange"
+ v-for="item in employees"
+ :key="item.id"
+ :label="item.name"
+ :value="item.id"
+ <template v-if="currentNode.assigneeType === 'post'">
+ v-model="postSelected"
+ placeholder="请选择岗位"
+ key="post"
+ @change="onPostChange"
+ v-for="item in postList"
+ <template v-if="currentNode.assigneeType === 'deptLeader'">
+ v-model="deptSelected"
+ placeholder="请选择部门"
+ key="deptLeader"
+ @change="onDeptChange"
+ v-for="item in deptList"
+ </el-form>
+import Template from "@/examine/components/Template.vue";
+ name: "PerCcSelector",
+ components: {Template},
+ showVisible: {
+ type: Boolean,
+ default: false
+ indicator:{
+ default: () =>{
+ return null
+ data(){
+ userInfo: this.$userInfo(),
+ currentNode: null,
+ innerVisible: this.showVisible,
+ loading:false,
+ employees:[],
+ postList:[],
+ deptList:[],
+ userSelected:[],
+ postSelected:[],
+ deptSelected:[],
+ assigneeMap:{
+ leader:'组织管理员',
+ user:'指定人员',
+ self:'被考核人',
+ post:'岗位',
+ deptLeader:'部门负责人',
+ levelOptions: [
+ value: 1,
+ label: '直接管理员'
+ value: 2,
+ label: '二级管理员'
+ value: 3,
+ label: '三级管理员'
+ value: 4,
+ label: '四级管理员'
+ value: 5,
+ label: '五级管理员'
+ value: 6,
+ label: '六级管理员'
+ computed:{
+ employeeMap(){
+ const map = {};
+ this.employees.forEach(e => {
+ if (!map[e.id]) map[e.id] = e.name;
+ return map;
+ postMap(){
+ this.postList.forEach(e => {
+ deptMap(){
+ this.deptList.forEach(e => {
+ watch:{
+ showVisible(val){
+ this.innerVisible = val;
+ matchErrMsg(err){
+ return err.toString().replace(/[(Error: )]/g,'');
+ handleClose(){
+ this.$emit('update:showVisible',false);
+ dataReset(){
+ this.currentNode = null;
+ this.userSelected = [];
+ this.postSelected = [];
+ this.deptSelected = [];
+ initData(){
+ if (this.loading || !this.indicator) return;
+ this.dataReset();
+ let result = true;
+ Promise.all([
+ this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+ this.$axiosUser('get','/org/post'),
+ this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+ .then(([employeeRes,postRes,deptRes]) => {
+ if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+ if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+ if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+ this.employees = employeeRes.data.data.list
+ .filter(e => e.account_id && e.account_id > 0)
+ .map(e => {
+ id:e.id,
+ name:e.name,
+ this.postList = postRes.data.data.map(item => {
+ id: item.id,
+ name: item.name,
+ this.deptList = deptRes.data.data.list.map(item => {
+ this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'cc');
+ switch (this.currentNode.assigneeType){
+ case 'user':
+ this.userSelected = this.currentNode.assigneeIds;
+ break;
+ case 'post':
+ this.postSelected = this.currentNode.assigneeIds;
+ case 'deptLeader':
+ this.deptSelected = this.currentNode.assigneeIds;
+ .catch(err => {
+ this.$message.error(this.matchErrMsg(err));
+ result = false;
+ .finally(() => {
+ if (!result) this.handleClose();
+ generalId(){
+ return Date.now() + Math.floor(Math.random() * 10000);
+ onAssigneeTypeChange(v){
+ this.currentNode.assigneeIds = [];
+ this.currentNode.leaderLevel = 1;
+ onOrgManagerChange(v){
+ this.currentNode.leaderLevel = v;
+ onUserChange(v){
+ this.currentNode.assigneeIds = [...v];
+ this.userSelected = v;
+ onPostChange(v){
+ this.postSelected = v;
+ onDeptChange(v){
+ this.deptSelected = v;
+.handler-list {
+ width: 500px;
+ margin: 0 auto 16px auto;
+ border: 1px solid #d7dae2;
+ padding: 10px 0 0 10px;
+ flex-wrap: wrap;
+ .el-tag {
+ margin: 0 10px 10px 0;
@@ -0,0 +1,362 @@
+ <template v-if="currentNode" #footer>
+ <el-row type="flex" justify="end">
+ <el-col align="end">
+ <el-button
+ type="primary"
+ @click="onConfirm"
+ 确认
+ </el-button>
+ </el-col>
+ </el-row>
+ name: "PerCcSelectorOnly",
+ defaultNode(){
+ const node = JSON.parse("{\"id\":\"CC_1907236768800382984\",\"type\":\"cc\",\"enable\":false,\"assigneeType\":\"user\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[]}");
+ node.id = "CC_" + this.generalId();
+ return node;
+ if (this.loading) return;
+ this.currentNode = this.defaultNode();
+ onConfirm(){
+ this.$emit('confirm',this.currentNode);
+ this.handleClose();
@@ -0,0 +1,316 @@
+ <el-dialog :visible="innerVisible" @close="handleClose" @open="initData" append-to-body width="650px" center
+ title="选择用户">
+ <div class="search-box">
+ <el-input v-model="searchValue" placeholder="请输入员工名称"></el-input>
+ <div class="selected-person-list" style="" v-if="selectedValues && selectedValues.length > 0">
+ <div style="margin: 0 0 10px 0;">表格中已存在的员工</div>
+ <div class="flex-box-ce person-list scorll-bar">
+ <el-tag v-for="item in selectedValues" :key="item.id" style="margin: 0 10px 10px 0;">
+ {{ item.name }}
+ <div class="package-list">
+ <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">员工列表</div>
+ <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in initEmployeeList"
+ @click="chooseTemplate(item)">
+ <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">已选员工</div>
+ <el-button plain round size="mini" @click="clear" style="width: 100px;">清 空</el-button>
+ <div v-if="item.isCheck" class="flex-box-ce choose-template-item" style="justify-content: space-between;">
+ <i class="el-icon-close" @click="deleteItem(item, index)"></i>
+ <div slot="footer">
+ <el-button @click="handleClose()">取 消</el-button>
+ <el-button type="primary" @click="onConfirm()">确 定</el-button>
+ name: 'PerEmployeeSelector',
+ props:{
+ choosePersonList: {
+ selectedEmployees:{
+ employeeStatus:{
+ type: Number,
+ default: null
+ multiple:{
+ default: true
+ employeeList: this.$getEmployeeMap(this.employeeStatus),
+ selectedValues: this.selectedEmployees,
+ searchValue: '',
+ isAllCheck: false,
+ initEmployeeList: [],
+ filterData: []
+ showVisible(val) {
+ searchValue(v) {
+ let filterData
+ if (v) filterData = this.filterData.filter(item => item.name.includes(v))
+ else filterData = this.filterData.filter(item => 1 == 1)
+ this.initEmployeeList = filterData
+ this.initEmployeeList.forEach(item => item.isCheck = true)
+ this.chooseExamineList = this.initEmployeeList
+ this.initEmployeeList.forEach(item => item.isCheck = false)
+ this.$emit('update:showVisible',false)
+ initData() {
+ this.initEmployeeList = []
+ this.selectExamineList = []
+ this.selectedValues = this.selectedEmployees;
+ let selectedEmployeeId = this.selectedValues.map(item => item.id)
+ this.employeeList.forEach(item => {
+ if (!selectedEmployeeId.includes(item.id)) {
+ this.initEmployeeList.push(item)
+ this.chooseExamineList = this.choosePersonList
+ this.filterData = this.initEmployeeList
+ if (this.initEmployeeList && this.initEmployeeList.length > 0) {
+ this.initEmployeeList.forEach(item => {
+ let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.id == item.id)
+ let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.id == item.id)
+ this.initEmployeeList.forEach(examine => {
+ if (examine.id == item.id) {
+ this.initEmployeeList.splice(index, 1)
+ // 清空选择的考核列表
+ clear() {
+ if (chooseExamine.id == examine.id) {
+ this.chooseExamineList = [];
+ onConfirm() {
+ let res = this.selectExamineList.map(item => {
+ imgUrl: item.img_url,
+ this.$emit('confirm',res);
+.search-box {
+.selected-person-list {
+ .person-list {
+ max-height: 60px;
+.package-list {
+ height: 300px;
+ margin-top: 10px;
+ border: 1px solid #f7f7f7;
+ width: 50%;
+ <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+ :close-on-click-modal="false" center title="结果录入节点配置" width="600px">
+ <el-switch v-model="currentNode.enable" />
+ <el-form-item label="录入人">
+ <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable"
+ @change="onAssigneeTypeChange">
+ <div class="flex-center-center" style="margin-left: 100px;">
+ <el-select v-model="currentNode.leaderLevel" placeholder="请选择管理员" :disabled="!currentNode.enable" filterable
+ style="width: 300px;" key="leader" @change="onOrgManagerChange">
+ <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value"
+ style="width: 300px;" />
+ <br>
+ <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+ <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+ style="width: 300px;" key="user" @change="onUserChange">
+ <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id"
+ <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+ <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+ style="width: 300px;" key="post" @change="onPostChange">
+ <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+ <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+ <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+ style="width: 300px;" key="deptLeader" @change="onDeptChange">
+ <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"
+ <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+ <el-form-item label="多人时">
+ <el-radio-group v-model="currentNode.multipleType" :disabled="!currentNode.enable">
+ <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+ <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+ <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+ <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+ ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+ ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+ <!-- <el-form-item label="允许">
+ <el-checkbox-group
+ v-model="currentNode.allows"
+ <el-checkbox-button label="transfer">转交</el-checkbox-button>
+ </el-checkbox-group>
+ </el-form-item> -->
+ name: "PerResultInputSelector",
+ this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'resultInput');
@@ -0,0 +1,327 @@
+ <template #footer>
+ <el-button type="primary" @click="onConfirm">
+ name: "PerResultInputSelectorOnly",
+ idGeneral(){
+ const node = JSON.parse("{\"id\":\"RI_1907236768800382977\",\"type\":\"resultInput\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
+ node.id = "RI_" + this.idGeneral();
@@ -0,0 +1,364 @@
+ :close-on-click-modal="false" center title="审批节点配置" width="600px">
+ <div class="handler-list">
+ <el-tag v-for="(item,index) in currentNode.children" :key="`hl_${index}`"
+ :type="childIndex === index ? '' : 'info'" closable @close="removeChild(index)" @click="childSelect(index)">
+ {{assigneeMap[item.assigneeType] || '未知'}}
+ <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+ @click="addChild" />
+ <el-form-item label="审批人">
+ <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+ <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+ <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+ :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+ @change="onOrgManagerChange">
+ <el-option v-for="item in levelOptions" :key="`leader_${item.value}`" :label="item.label"
+ :value="item.value" style="width: 300px;" />
+ <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+ <el-option v-for="item in employees" :key="`user_${item.id}`" :label="item.name" :value="item.id"
+ <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+ <el-option v-for="item in postList" :key="`post_${item.id}`" :label="item.name" :value="item.id"
+ <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+ <el-option v-for="item in deptList" :key="`deptLeader_${item.id}`" :label="item.name" :value="item.id"
+ <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+ v-model="currentNode.children[childIndex].allows"
+ name: "PerReviewsSelector",
+ childIndex: null,
+ this.childIndex = null;
+ this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'reviews');
+ this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+ if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+ switch (this.currentNode.children[this.childIndex].assigneeType){
+ this.userSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+ this.postSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+ this.deptSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+ addChild(){
+ let id = "R_" + this.generalId();
+ this.currentNode.children.push({
+ id:id,
+ type:'review',
+ enable:true,
+ assigneeType:'leader',
+ leaderLevel:1,
+ assigneeIds:[],
+ multipleType:'or',
+ allows:['transfer'],
+ weight:0,
+ children:[],
+ removeChild(index){
+ if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+ let selectChildId = this.currentNode.children[index].id;
+ this.childIndex = 0;
+ this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+ if (selectChildId){
+ this.currentNode.children.forEach((child,i) => {
+ if (child.id === selectChildId) this.childIndex = i;
+ this.currentNode.children[this.childIndex].assigneeIds = [];
+ this.currentNode.children[this.childIndex].leaderLevel = 1;
+ this.currentNode.children[this.childIndex].leaderLevel = v;
+ this.currentNode.children[this.childIndex].assigneeIds = [...v];
+ childSelect(index){
+ if (!this.currentNode.children[index]) return;
+ this.childIndex = index;
+ this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+ this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+ this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
@@ -0,0 +1,380 @@
+ name: "PerReviewsSelectorOnly",
+ const node = JSON.parse("{\"id\":\"RS_1907236768800382982\",\"type\":\"reviews\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[{\"id\":\"R_1907236768800382983\",\"type\":\"review\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}]}");
+ node.id = "RS_" + this.generalId();
+ node.children.forEach((_,i) => {
+ node.children[i].id = "R_" + this.generalId();
@@ -0,0 +1,93 @@
+ title="互评节点配置"
+ name: "PerScoreSelfOnly",
+ dataReset() { },
+ idGeneral() {
+ defaultNode() {
+ const node = JSON.parse("{\"id\":\"SEO_1907236768800382977\",\"type\":\"scoreEachOther\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
@@ -0,0 +1,95 @@
+ title="自评节点配置"
+ const node = JSON.parse("{\"id\":\"SS_1907236768800382977\",\"type\":\"scoreSelf\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
@@ -0,0 +1,366 @@
+ :close-on-click-modal="false" center title="评分节点配置" width="600px">
+ <el-switch v-model="currentNode.enable" disabled />
+ <el-form-item label="评分人">
+ name: "PerScoresSelector",
+ this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'scores');
+ let id = "S_" + this.generalId();
+ type:'score',
+ weight:100,
@@ -0,0 +1,382 @@
+ name: "PerScoresSelectorOnly",
+ const node = JSON.parse("{\"id\":\"SS_1907236768800382980\",\"type\":\"scores\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[{\"id\":\"S_1907236768800382981\",\"type\":\"score\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":100,\"children\":[]}]}");
+ node.id = "SS_" + this.generalId();
+ node.children[i].id = "S_" + this.generalId();
+ :close-on-click-modal="false" center title="目标确认节点配置" width="600px">
+ <el-tag v-for="(item,index) in currentNode.children" :key="index" :type="childIndex === index ? '' : 'info'"
+ closable @close="removeChild(index)" @click="childSelect(index)">
+ <el-form-item label="确认人">
+ <el-form-item label="允许">
+ <el-checkbox-group v-model="currentNode.children[childIndex].allows" :disabled="!currentNode.enable">
+ <el-checkbox-button label="edit">修改指标</el-checkbox-button>
+ <!-- <el-checkbox-button label="transfer">转交</el-checkbox-button> -->
+ name: "PerTargetConfirmSelector",
+ this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'targetConfirms');
+ this.userSelected = this.currentNode.children[this.childIndex].assigneeIds;
+ this.postSelected = this.currentNode.children[this.childIndex].assigneeIds;
+ this.deptSelected = this.currentNode.children[this.childIndex].assigneeIds;
+ let id = "TC_" + this.generalId();
+ type:'targetConfirm',
+ assigneeType:'self',
+ allows:[],
@@ -0,0 +1,389 @@
+ <!-- <el-alert class="diy-tip" title="可配置多位人员来确定考核的指标" type="success" description>
+ <p>可以是组织管理员,被考核人自己,指定人员,岗位负责人,部门负责人</p>
+ <p>备注:</p>
+ <p>(会签要求所有审批人一致同意)</p>
+ <p>(或签只需任一审批人同意即可)</p>
+ </el-alert> -->
+ name: "PerTargetConfirmSelectorOnly",
+ const node = JSON.parse("{\"id\":\"TCS_1906993155344510977\",\"type\":\"targetConfirms\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[{\"id\":\"TC_1906993155344510978\",\"type\":\"targetConfirm\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[]}]}");
+ node.id = "TCS_" + this.idGeneral();
+ node.children[i].id = "TC_" + this.idGeneral();
@@ -0,0 +1,627 @@
+ <div style="width: 600px; margin: 10px auto; position: relative; ">
+ <!-- <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+ style="width: 100%; margin-top: 10px;"></el-alert> -->
+ <div class="flex-box-ce" style="flex-direction: column; justify-content: center;">
+ <!-- <div class="" style="font-size: 16px; font-weight: 600; text-align: center;">发起考核</div> -->
+ <div style="margin: 0 auto 20px auto; font-size: 16px; font-weight: 600;">填写考核基本信息</div>
+ <el-form :model="form" :rules="rules" ref="ruleForm" label-width="120px" size="small"
+ label-position="right">
+ <!-- <el-form-item label="考核标题" prop="title">
+ <el-input v-model="form.title" style="width: 300px;"></el-input>
+ <el-form-item label="考核人员" prop="employeeIds">
+ <el-select v-model="form.employeeIds" placeholder="请选择指定人员" multiple filterable
+ style="width: 300px;" @change="changeEmployeeIds">
+ <el-option v-for="item in employeeMap" :key="item.id" :label="item.name"
+ :value="item.id"></el-option>
+ <el-form-item label="周期">
+ <el-radio-group v-model="form.cycleType" @change="changeCircle">
+ <el-radio-button label="0">自定义</el-radio-button>
+ <el-radio-button label="1">年度</el-radio-button>
+ <el-radio-button label="2">半年度 </el-radio-button>
+ <el-radio-button label="3">季度</el-radio-button>
+ <el-radio-button label="4">月度</el-radio-button>
+ <!-- <el-form-item label="考核分类">
+ <el-select v-model="form.cateId" @change="changeCateId" placeholder="请选择考核分类" style="width: 300px;">
+ <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
+ <el-form-item v-if="form.cycleType == 0" label="开始日期" prop="startDate">
+ <el-date-picker v-model="form.startDate" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
+ style="width: 300px;">
+ <el-form-item v-if="form.cycleType == 0" label="结束日期" prop="endDate">
+ <el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
+ <el-form-item v-if="form.cycleType == 1" label="选择年份" prop="year">
+ <el-date-picker v-model="form.year" type="year" value-format="yyyy" placeholder="选择年">
+ <div class="selectBox" v-if="form.cycleType == 2 || form.cycleType == 3">
+ <SelectCircle :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm"
+ :id="1">
+ <div class="flex-box-ce cursor">
+ <div>{{ dateParameter.year }}</div>
+ <div style="margin: 0 10px;">{{ dateParameter.name }}</div>
+ <i class="el-icon-caret-bottom fontColorC"></i>
+ </SelectCircle>
+ <el-form-item v-if="form.cycleType == 4" label="选择月份" prop="month">
+ <el-date-picker v-model="form.month" type="month" placeholder="选择月" value-format="yyyy-MM">
+ <!-- <InterviewFlow form-label="主持人" dialog-title="面谈" node-type="interview" @onConfirm="finishHandle" /> -->
+ <el-form-item label="启用面谈">
+ <el-switch v-model="currentNode.enable"></el-switch>
+ <el-form-item label="主持人">
+ <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
+ <el-radio-button label="leader">管理员</el-radio-button>
+ <el-radio-button label="deptLeader">部门</el-radio-button>
+ <el-form-item v-if="currentNode.assigneeType === 'leader'">
+ <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
+ filterable style="width: 300px;" @change="changeManagerIds">
+ <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
+ :value="item.value" style="width: 300px;"></el-option>
+ <el-form-item v-if="currentNode.assigneeType === 'user'">
+ <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+ :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeEmployeeIds">
+ <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+ style="width: 300px;"></el-option>
+ <el-form-item v-if="currentNode.assigneeType === 'post'">
+ <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple :disabled="!currentNode.enable"
+ filterable style="width: 300px;" @change="changePostIds">
+ <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
+ <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
+ :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
+ @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+ <!-- <el-form-item v-if="form.circle == 1" label="结束日期">
+ <el-date-picker v-model="form.endDate" type="date" placeholder="选择结束日期" style="width: 300px;">
+ <!-- <div style="margin: 20px auto;" v-if="selectedData && selectedData.length > 0">
+ <div class="title">已选择的OKR</div>
+ <div class="selected-item" v-for="item in selectedData" @click="changeSelectedOkrs">
+ <el-link v-else type="primary" @click="isShowProject = true">请选择关联OKR</el-link> -->
+ <div style="margin: 20px auto;">
+ <el-button type="primary" @click="submitForm('ruleForm')" size="small">确定</el-button>
+ <el-button @click="resetForm('ruleForm')" size="small">取 消</el-button>
+ <el-drawer title="考核分类" :visible.sync="cateDetailsDialog" direction="rtl" :before-close="handleClose">
+ <CateDetails v-if="cateDetailsDialog"></CateDetails>
+ </el-drawer>
+ <!-- 关联OKR -->
+ <TargetSearch :visible.sync="isShowProject" @confirm="confirmProject" :selectedCheckList="okrs"
+ :showSelectedData="selectedData"></TargetSearch>
+import moment from 'moment'
+// 计算某一季度开始日期和结束日期
+function getQuarterDates(year, quarter) {
+ // 计算季度的开始日期(第一个月的第一天)
+ const startDate = moment().year(year).quarter(quarter).startOf('quarter');
+ // 计算季度的结束日期(最后一个月的最后一天)
+ const endDate = moment().year(year).quarter(quarter).endOf('quarter');
+ start: startDate,
+ end: endDate
+import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle'; //选择周期
+import CateDetails from "@/newPerformance/components/TemplateDetails/CateDetails.vue" // 考核分类明细抽屉
+import InterviewFlow from "@/newPerformance/components/TemplateDetails/InterviewFlow.vue" // 面谈弹框
+import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
+ SelectCircle,
+ CateDetails,
+ InterviewFlow,
+ TargetSearch
+ model: {
+ prop: 'showPublishDialog',
+ event: 'close-dialog'
+ showPublishDialog: {
+ templateIds: {
+ alertTilte: "如果有一个指标标题为空,将会发布不成功,请仔细检查数据!",
+ employeeMap: this.$getEmployeeMap(), // 员工列表
+ cateDetailsDialog: false, // 考核分类弹框
+ form: {
+ title: "",
+ employeeIds: [],
+ startDate: "",
+ endDate: "",
+ cycleType: "0", // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+ year: "",
+ cateId: 0
+ isShowProject: false,
+ rules: {
+ title: [
+ { required: true, message: '请输入考核标题', trigger: 'blur' },
+ employeeIds: [
+ { required: true, message: '请选择考核人员', trigger: 'change' }
+ startDate: [
+ { required: true, message: '请选择开始日期', trigger: 'change' }
+ endDate: [
+ { required: true, message: '请选择结束日期', trigger: 'change' }
+ year: [
+ { required: true, message: '请选择年份', trigger: 'change' }
+ month: [
+ { required: true, message: '请选择月份', trigger: 'change' }
+ dateOptions: [],
+ dateParameter: {
+ year: moment().format('YYYY'),
+ cycle_type: 0,
+ dateId: 1,
+ name: '选择年度',
+ cateList: [], // 考核分类列表,
+ interview: false,
+ params: null,
+ okrs: [], // 关联的OKRS,
+ selectedData: [],
+ cascaderProps: {
+ multiple: true, // 启用多选
+ checkStrictly: true, // 父子节点不联动
+ emitPath: false, // 选中值只返回当前节点的值
+ deptList: JSON.parse(localStorage.getItem("deptList")), // 部门列表 - 树形结构
+ dept_list: JSON.parse(localStorage.getItem("dept_list")), // 部门列表
+ postList: JSON.parse(localStorage.getItem("postList")), // 岗位列表
+ selected_manager_ids: '', // 选择的管理员列表
+ selected_post_ids: [], // 选择的岗位列表
+ selected_employee_ids: [], // 选择的员工列表
+ currentNode: {
+ id: "IT_" + Date.now() + Math.floor(Math.random() * 10000),
+ enable: true,
+ showPublish(v) {
+ // if (v) this.getCateList()
+ // this.getCateList()
+ // 考核分类列表
+ getCateList() {
+ let url = `/performance/cate/list/${this.user_info.site_id}`;
+ // this.loading = true
+ this.$axiosUser('get', url, {}).then(res => {
+ let { data: { code, data: { list, total } } } = res
+ this.cateList = list
+ this.form.cateId = this.cateList[0].cateId || 0
+ // 选择考核分类
+ changeCateId(v) {
+ console.log(v)
+ // 选择员工
+ changeEmployeeIds(v) {
+ if (v && v.length > 0) {
+ v.forEach(item => {
+ this.form.employeeIds.push(item)
+ this.form.employeeIds = Array.from(new Set(this.form.employeeIds)); // 去重
+ changeCircle(v) {
+ if (v == 2) this.dateOptions = [
+ { name: '上半年', id: 1, cycle_type: 3 },
+ { name: '下半年', id: 2, cycle_type: 3 },
+ if (v == 3) this.dateOptions = [
+ { name: '第一季度', id: 1, cycle_type: 2 },
+ { name: '第二季度', id: 2, cycle_type: 2 },
+ { name: '第三季度', id: 3, cycle_type: 2 },
+ { name: '第四季度', id: 4, cycle_type: 2 }
+ dateConfirm(date) {
+ let { year, name } = date;
+ this.dateParameter.year = year;
+ this.dateParameter.name = name;
+ // 上半年度
+ if (name === "上半年") {
+ let startOfYear = moment().year(year).startOf('year'); // 获取年初
+ let endOfFirstHalfYear = moment().year(year).startOf('year').add(6, 'months').subtract(1, 'days'); // 获取上半年结束日(6月30日)
+ this.form.startDate = startOfYear.format('YYYY-MM-DD');
+ this.form.endDate = endOfFirstHalfYear.format('YYYY-MM-DD');
+ // 下半年度
+ if (name === "下半年") {
+ let startOfSecondHalfYear = moment().year(year).startOf('year').add(6, 'months'); // 获取下半年开始日(7月1日)
+ let endOfYear = moment().year(year).endOf('year'); // 获取年末
+ this.form.startDate = startOfSecondHalfYear.format('YYYY-MM-DD');
+ this.form.endDate = endOfYear.format('YYYY-MM-DD');
+ // 第一季度
+ if (name === "第一季度") {
+ const dates = getQuarterDates(year, 1);
+ this.form.startDate = dates.start.format('YYYY-MM-DD');
+ this.form.endDate = dates.end.format('YYYY-MM-DD');
+ if (name === "第二季度") {
+ const dates = getQuarterDates(year, 2);
+ // 第三季度
+ if (name === "第三季度") {
+ const dates = getQuarterDates(year, 3);
+ // 第四季度
+ if (name === "第四季度") {
+ const dates = getQuarterDates(year, 4);
+ handleClose() {
+ this.cateDetailsDialog = false
+ // 关闭弹窗
+ handleCloseDialog() {
+ this.$emit('close-dialog', false);
+ // 选择管理员
+ changeManagerIds(v) {
+ this.currentNode.leaderLevel = v
+ this.currentNode.assigneeIds = v
+ // 选择岗位
+ changePostIds(v) {
+ // 选择部门
+ deptChange(val) {
+ // 获取当前选中的节点
+ const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+ this.selected_dept_ids = checkedNodes.map((node) => node.value);
+ this.currentNode.assigneeIds = this.selected_dept_ids
+ this.currentNode.assigneeIds = Array.from(new Set(this.currentNode.assigneeIds)); // 去重
+ checkInterviewNode() {
+ // 验证表单数据
+ this.currentNode.assigneeIds = []
+ // 回显选中的管理者
+ if (this.currentNode.assigneeType == 'leader') {
+ if (this.selected_manager_ids) this.currentNode.leaderLevel = this.selected_manager_ids
+ else return this.$message.error("请选择管理员")
+ // 回显选中的被考核人
+ if (this.currentNode.assigneeType == 'self') {
+ this.currentNode.assigneeIds.push(this.user_info.id);
+ // 回显选中的岗位
+ if (this.currentNode.assigneeType == 'post') {
+ if (this.selected_post_ids && this.selected_post_ids.length > 0)
+ this.selected_post_ids.forEach(postId => {
+ this.currentNode.assigneeIds.push(postId);
+ else return this.$message.error("请选择岗位")
+ // 回显选中的指定人员
+ if (this.currentNode.assigneeType == 'user') {
+ if (this.selected_employee_ids && this.selected_employee_ids.length > 0)
+ this.selected_employee_ids.forEach(employeeId => {
+ this.currentNode.assigneeIds.push(employeeId);
+ else return this.$message.error("请选择指定人员")
+ // 回显选中的部门列表
+ if (this.currentNode.assigneeType == 'deptLeader') {
+ if (this.selected_dept_ids && this.selected_dept_ids.length > 0)
+ this.selected_dept_ids.forEach(dept_id => {
+ this.currentNode.assigneeIds.push(dept_id);
+ else return this.$message.error("请选择部门")
+ // 去重
+ this.currentNode.assigneeIds = Array.from(new Set(this.currentNode.assigneeIds))
+ // let nodes = [this.currentNode];
+ console.log(this.currentNode)
+ // 确定按钮
+ submitForm(formName) {
+ this.$refs[formName].validate((valid) => {
+ if (valid) {
+ // 周期类型 0 - 未定义 1 - 年度 2 - 半年度 3 - 季度 4 - 月度
+ // 年度
+ if (this.form.cycleType == 1) {
+ let { year } = this.form;
+ // 计算年份的开始时间(即1月1日)
+ const startOfYear = moment().year(year).startOf('year');
+ // 计算年份的结束时间(即12月31日)
+ const endOfYear = moment().year(year).endOf('year');
+ // 年度结束时间
+ // 月
+ if (this.form.cycleType == 4) {
+ let [year, month] = this.form.month.split("-");
+ // 计算开始时间(该月的第1天)
+ const startOfMonth = moment(`${year}-${month}-01`);
+ this.form.startDate = startOfMonth.format('YYYY-MM-DD');
+ // 计算结束时间(该月的最后一天)
+ const endOfMonth = moment(startOfMonth).endOf('month');
+ this.form.endDate = endOfMonth.format('YYYY-MM-DD');
+ this.params = {
+ templateIds: this.templateIds,
+ ...this.form,
+ okrs: this.okrs
+ this.checkInterviewNode()
+ ...this.params,
+ nodes: [...this.currentNode]
+ // this.interview = true
+ this.$emit('onConfirm', this.params);
+ // this.handleCloseDialog();
+ console.log('error submit!!');
+ return false;
+ // 重置表单
+ resetForm(formName) {
+ this.$refs[formName].resetFields();
+ this.handleCloseDialog();
+ finishHandle(nodes) {
+ nodes
+ this.$emit('onConfirm', this.params, this.selectedData);
+ changeSelectedOkrs() {
+ this.isShowProject = true
+ confirmProject(okrs, selectedData) {
+ this.okrs = okrs;
+ this.selectedData = selectedData;
+.selectBox {
+ border-radius: 40px;
+ margin: 0 auto;
+.status-btn-box {
+ z-index: 10;
+ top: 20px;
+ left: 20px;
+ .status-btn {
+ background: transparent;
+ border: 2px dashed;
+ line-height: 40px;
+.title {
+.selected-item {
@@ -0,0 +1,928 @@
+ <div class="box boxMinHeight">
+ <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+ style="width: 100%; margin-bottom: 10px;"></el-alert>
+ <header class="header">
+ <div class="header-content flex-box-ce">
+ <!-- 返回按钮 -->
+ <div class="flex-box-ce header-left">
+ <el-tooltip class="item" effect="dark" :content="templateTitle" placement="bottom">
+ <div>{{ templateTitle }}</div>
+ <el-popover ref="popoverRef" placement="right" width="400" trigger="click">
+ <el-input v-model="title" placeholder="请输入模板名称" size="small"></el-input>
+ <el-button v-if="$getRole(1)" type="primary" size="small" style="margin-left: 10px;"
+ @click="editTitle()">确定</el-button>
+ <i class="el-icon-edit" slot="reference" style="margin-left: 10px; color: #999;"></i>
+ <!-- 发布按钮 -->
+ <div class="header-right">
+ <el-button type="primary" size="small" @click="addData()">添加指标</el-button>
+ <el-button type="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)"
+ @click="confirmDelete()" size="small">批量删除</el-button>
+ <div class="flex-box-ce" style="margin-left: 10px; color: #999;">
+ <el-popover placement="right" trigger="hover" class="popover" @show="showPopover">
+ <div class="flex-box-ce" style="width: 200px; justify-content: space-between;">
+ <el-checkbox v-model="checkAll" @change="checkAllChangeFn">全选</el-checkbox>
+ <el-button type="text" @click="reset(true)">重置</el-button>
+ <div style="height: 1px; background-color: #DCDFE6; margin: 5px 0;"></div>
+ <el-checkbox-group v-model="checkColumns" @change="changeColumns"
+ style="width: 200px; display: flex; flex-direction: column;">
+ <el-checkbox v-for="(item, key) in this.tableColumn" :label="item.label"
+ :key="item.label"></el-checkbox>
+ <div class="flex-box-ce" slot="reference">
+ <el-button class="primaryBtn" icon="el-icon-s-tools" type="primary"
+ size="mini">设置考核流程</el-button>
+ </header>
+ <div style="height: 1px; background-color: #DCDFE6; margin-top: 5px;"></div>
+ <el-table :data="tableData" v-loading="loading" stripe style="width: 100%; margin-top: 10px;" border
+ :header-cell-style="{ background: '#f5f7fa' }" @selection-change="handleSelectChange">
+ <el-table-column type="selection"></el-table-column>
+ <template v-for="item in tableColumn">
+ <el-table-column v-if="item.isShow && item.label === '规则'" :key="item.prop" :prop="item.prop"
+ :label="item.label" align="center" :min-width="item.width">
+ <el-tooltip v-if="scope.row.content" class="item" effect="dark" placement="top">
+ <div v-html="scope.row.content" slot="content"
+ style="max-width: 300px; white-space: pre-line;">
+ <div class="oneLine"
+ @click="editContent(scope.$index, scope.row.content, scope.row.indicatorId)">
+ {{ scope.row.content }}
+ <el-button v-else
+ @click="editContent(scope.$index, scope.row.content, scope.row.indicatorId)">编辑规则</el-button>
+ <el-table-column v-else-if="item.isShow && ['目标', '权重(%)'].includes(item.label)" :key="item.prop"
+ :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+ <el-input v-model="scope.row[item.prop]" :placeholder="item.label" clearable
+ @blur="handleEdit(item.prop, scope.row[item.prop], scope.row.indicatorId)"
+ oninput="value=value.replace(/[^\d.]/g,'')"></el-input>
+ <el-table-column v-else-if="item.isShow && item.label === '计算公式'" prop="formulae" label="计算公式"
+ align="center" min-width="130">
+ <el-button @click="openFormula(scope.row, scope.$index)">
+ {{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
+ ${scope.row.expression.formulas.length} 条` : '公式' }}
+ <el-table-column v-else-if="item.isShow && ['指标', '单位'].includes(item.label)" :key="item.prop"
+ @blur="handleEdit(item.prop, scope.row[item.prop], scope.row.indicatorId)"></el-input>
+ <el-table-column v-else-if="item.isShow && ['过程跟踪'].includes(item.label)" :key="item.prop"
+ <el-table-column v-if="item.isShow && flowColumn.includes(item.label)" :key="item.prop"
+ :prop="item.prop" :label="item.label" :render-header="(h, obj) => renderHeader(h, item, item.prop)"
+ align="center" :min-width="item.width" fixed="right">
+ <template v-if="item.label === '确认目标'">
+ <el-switch v-if="!scope.row.flow.nodes[0].enable" :value="scope.row.flow.nodes[0].enable"
+ @input="handleStatusChange(-1, scope.row, 'targetConfirms')"></el-switch>
+ <ShowDataComp v-else-if="scope.row.flow.nodes[0].enable && isDataShow"
+ :show-data="scope.row.flow.nodes[0]" :select-nodes="scope.row"
+ @btnClick="handleBtnClick" />
+ <template v-if="item.label === '录入结果'">
+ <el-switch v-if="!scope.row.flow.nodes[1].enable" :value="scope.row.flow.nodes[1].enable"
+ @input="handleStatusChange(-1, scope.row, 'resultInput')"></el-switch>
+ <ShowDataComp v-else-if="scope.row.flow.nodes[1].enable && isDataShow"
+ :show-data="scope.row.flow.nodes[1]" :select-nodes="scope.row"
+ <template v-if="item.label === '自评'">
+ <el-switch v-model="scope.row.flow.nodes[2].enable"
+ @change="handleScoreSelf(scope.row, scope.$index)"></el-switch>
+ <template v-if="item.label === '互评'">
+ <el-switch :value="scope.row.flow.nodes[3].enable"
+ @input="handleStatusChange(-1, scope.row, 'scoreEachOther')"></el-switch>
+ <template v-if="item.label === '评分'">
+ <el-switch v-if="!scope.row.flow.nodes[4].enable" :value="scope.row.flow.nodes[4].enable"
+ @input="handleStatusChange(-1, scope.row, 'scores')"></el-switch>
+ <ShowDataComp v-else-if="scope.row.flow.nodes[4].enable && isDataShow"
+ :show-data="scope.row.flow.nodes[4]" :select-nodes="scope.row"
+ <template v-if="item.label === '审批'">
+ <el-switch v-if="!scope.row.flow.nodes[5].enable" :value="scope.row.flow.nodes[5].enable"
+ @input="handleStatusChange(-1, scope.row, 'reviews')"></el-switch>
+ <ShowDataComp v-else-if="scope.row.flow.nodes[5].enable && isDataShow"
+ :show-data="scope.row.flow.nodes[5]" :select-nodes="scope.row"
+ <template v-if="item.label === '抄送'">
+ <el-switch v-if="!scope.row.flow.nodes[6].enable" :value="scope.row.flow.nodes[6].enable"
+ @input="handleStatusChange(-1, scope.row, 'cc')"></el-switch>
+ <ShowDataComp v-else-if="scope.row.flow.nodes[6].enable && isDataShow"
+ :show-data="scope.row.flow.nodes[6]" :select-nodes="scope.row"
+ <!-- <TiptapComp /> -->
+ <!-- 编辑流程节点 -->
+ <!-- <HandleNode v-if="currentIndicator" v-model="dialogVisible" :form-label="formLabel" :dialog-title="dialogTitle"
+ :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+ :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+ @onConfirm="finishHandle" /> -->
+ <!-- 编辑计算公式 -->
+ <FormulaComp v-if="showFormula" v-model="showFormula"
+ :fixed-props="[{ key: 'target', name: '目标' }, { key: 'weight', name: '权重' }, { key: 'result', name: '结果值' }]"
+ :expressions-props="currentIndicator.expression.formulas || []" @onConfirm="onFormulaConfirm" />
+ <!-- 发布考核弹框 -->
+ <PublishComp v-if="showPublish" v-model="showPublish" :template-ids="[templateId]"
+ @onConfirm="onPubishConfirm" />
+ <!-- 编辑规则 -->
+ <EditContentComp v-if="showEditContent" v-model="showEditContent" :content="content"
+ :indicator-id="selectIndicatorId" :template-id="templateId" @finishEdit="finishEditContent" />
+ <!-- 批量修改流程节点 -->
+ <BatchHandleNode v-if="batchHandleDialog" v-model="batchHandleDialog" :template-id="templateId"
+ :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType" :handle-node="handleNode"
+ @onConfirm="finishBatchHandle" />
+ <!-- 编辑确认目标节点 -->
+ <TargetConfirms v-if="targetConfirms" v-model="targetConfirms" :form-label="formLabel"
+ :dialog-title="dialogTitle" :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+ @onConfirm="finishHandle" />
+ <!-- 编辑录入结果节点 -->
+ <ResultInput v-if="resultInput" v-model="resultInput" :form-label="formLabel" :dialog-title="dialogTitle"
+ <!-- 编辑互评节点 -->
+ <ScoreEachOther v-if="scoreEachOther" v-model="scoreEachOther" :form-label="formLabel"
+ <!-- 编辑评分节点 -->
+ <Scores v-if="scores" v-model="scores" :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType"
+ :template-id="templateId" :select-nodes="selectNodes" :indicator-id="selectIndicatorId"
+ @closeDialog="closeDialog" :tag-index="tagIndex" @onConfirm="finishHandle" />
+ <Reviews v-if="reviews" v-model="reviews" :form-label="formLabel" :dialog-title="dialogTitle"
+ <!-- 编辑审批节点 -->
+ <CC v-if="cc" v-model="cc" :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType"
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+import HandleNode from '@/newPerformance/components/TemplateDetails/HandleNode'; //单独设置流程节点弹框
+import PublishComp from '@/newPerformance/components/TemplateDetails/PublishComp'; // 发布考核弹框
+import ShowDataComp from '@/newPerformance/components/PublicComp/ShowData'; // 显示节点数据组件
+import EditContentComp from '@/newPerformance/components/TemplateDetails/EditContent'; // 编辑规则组件
+import BatchHandleNode from '@/newPerformance/components/TemplateDetails/BatchHandleNode'; // 批量设置流程节点
+import TargetConfirms from '@/newPerformance/components/TemplateDetails/TargetConfirms'; // 确认目标流程节点
+import ResultInput from '@/newPerformance/components/TemplateDetails/ResultInput'; // 结果录入流程节点
+import ScoreEachOther from '@/newPerformance/components/TemplateDetails/ScoreEachOther'; // 互评流程节点
+import Scores from '@/newPerformance/components/TemplateDetails/Scores'; // 评分流程节点
+import Reviews from '@/newPerformance/components/TemplateDetails/Reviews'; // 审批流程节点
+import CC from '@/newPerformance/components/TemplateDetails/CC'; // 审批流程节点
+ HandleNode,
+ FormulaComp,
+ ShowDataComp,
+ EditContentComp,
+ BatchHandleNode,
+ TargetConfirms,
+ ResultInput,
+ ScoreEachOther,
+ Scores,
+ Reviews,
+ CC
+ // 模板ID
+ templateId: {
+ type: Number | String,
+ default: ''
+ isDataShow: false,
+ isShow: false,
+ templateTitle: "模板名称",
+ title: "模板名称",
+ alertTilte: "可在表格中直接编辑指标,规则 (规则支持富文本) ,目标,单位,权重,计算公式以及流程节点, 注意: 每个指标的标题不能为空!每个指标的评分节点一定要开启!",
+ tableData: [], // 表格数据
+ showFormula: false, // 编辑计算公式弹框显示
+ showPublish: false, // 发布考核弹框显示
+ dialogVisible: false, // 编辑流程节点弹框显示
+ dialogTitle: "", // 编辑流程节点弹框标题
+ isDisable: false, // 是否禁用
+ nodeType: "", // 节点类型
+ currentIndicator: null, // 操作的指标
+ selectIndicatorId: "", // 选择的指标ID
+ selectNodes: [], // 选中指标所有的节点
+ selectIndex: 0, // 表格行索引
+ formLabel: "",
+ tagIndex: 0,
+ multipleSelection: [],
+ content: "", // 指标规则
+ showEditContent: false, // 编辑指标内容弹框
+ postList: [], // 岗位列表
+ flowColumn: ["确认目标", "录入结果", "自评", "互评", "评分", "审批", "抄送", "过程跟踪"],
+ tableColumn: [
+ { label: "指标", prop: "title", isShow: true, width: 150 },
+ { label: "规则", prop: "content", isShow: true, width: 150 },
+ { label: "目标", prop: "target", isShow: true, width: 80},
+ { label: "单位", prop: "unit", isShow: true, width: 100 },
+ { label: "权重(%)", prop: "weight", isShow: true, width: 100 },
+ { label: "计算公式", prop: "formulae", isShow: true, width: 120 },
+ { label: "过程跟踪", prop: "okrs", isShow: true, width: 120 },
+ { label: "确认目标", prop: "targetConfirms", isShow: false, width: 100 },
+ { label: "录入结果", prop: "resultInput", isShow: false, width: 100 },
+ { label: "自评", prop: "scoreSelf", isShow: false, width: 100 },
+ { label: "互评", prop: "scoreEachOther", isShow: false, width: 100 },
+ { label: "评分", prop: "scores", isShow: false, width: 100 },
+ { label: "审批", prop: "reviews", isShow: false, width: 100 },
+ { label: "抄送", prop: "cc", isShow: false, width: 100 }
+ checkColumns: [],
+ checkAll: false,
+ flowInfo: {},
+ handleNode: {},
+ batchHandleDialog: false,
+ targetConfirms: false,
+ resultInput: false,
+ scoreEachOther: false,
+ scores: false,
+ reviews: false,
+ cc: false
+ // 指标标题为空不能发起考核
+ isTitleNull() {
+ return this.tableData.find(item => item.title == '' || item.title == null) ? true : false
+ // 指标评分为禁用不能发起考核
+ isScoreNull() {
+ return this.tableData.find(item => !item.flow.nodes[4].enable) ? true : false
+ this.get_template_detail();
+ this.get_table_data();
+ this.isDataShow = false
+ // 请求岗位列表,部门列表
+ Promise.all([this.get_dept_list(), this.get_post_list()]).then(([deptRes, postRes]) => {
+ if (deptRes.data.code !== 1) return this.$message.error(deptRes.data.msg || "请求部门列表数据出错")
+ if (postRes.data.code !== 1) return this.$message.error(postRes.data.msg || "请求岗位列表数据出错")
+ this.dept_list = deptRes.data.data.list;
+ this.deptList = this.getTreeData(this.dept_list); // 处理成树状结构
+ this.postList = postRes.data.data.list
+ localStorage.setItem("dept_list", JSON.stringify(this.dept_list))
+ localStorage.setItem("deptList", JSON.stringify(this.deptList))
+ localStorage.setItem("postList", JSON.stringify(this.postList))
+ this.isDataShow = true
+ // 处理部门树状结构数据
+ getTreeData(data) {
+ for (var i = 0; i < data.length; i++) {
+ data[i].checked = false;
+ if (data[i].children.length < 1) {
+ // children若为空数组,则将children设为undefined
+ data[i].children = undefined;
+ // children若不为空数组,则继续 递归调用 本方法
+ this.getTreeData(data[i].children);
+ return data;
+ // 获取部门
+ get_dept_list() {
+ return this.$axiosUser('get', '/api/pro/department/tree', '', 'v2')
+ // 岗位列表
+ get_post_list() {
+ let data = {
+ page: 1,
+ page_size: 999,
+ cate_id: -1,
+ name: ''
+ return this.$axiosUser('get', '/api/pro/post/cate_post_list', data)
+ renderHeader(h, item, type) {
+ let label = ""
+ const labels = {
+ 'targetConfirms': '确认目标',
+ 'resultInput': '录入结果',
+ 'scoreSelf': '自评',
+ 'scoreEachOther': '互评',
+ 'scores': '评分',
+ 'reviews': '审批',
+ 'cc': '抄送'
+ label = labels[type];
+ return h('div', {
+ size: 'small'
+ that.clickButton(type);
+ }, label),
+ clickButton(nodeType) {
+ this.handleNode = this.flowInfo.nodes.find(node => node.type === nodeType)
+ let options = {
+ 'targetConfirms': ['确认目标', '确认人'],
+ 'resultInput': ['录入结果', '录入人'],
+ 'scoreSelf': ['自评', ''],
+ 'scoreEachOther': ['互评', '互评人'],
+ 'scores': ['评分', '评分人'],
+ 'reviews': ['审批', '审批人'],
+ 'cc': ['抄送', '抄送人']
+ this.dialogTitle = options[nodeType][0]
+ this.formLabel = options[nodeType][1]
+ this.nodeType = nodeType; // 节点类型
+ this.batchHandleDialog = true;
+ // 打开编辑规则内容弹框
+ editContent(index, content, indicatorId) {
+ this.selectIndex = index;
+ this.content = content;
+ this.selectIndicatorId = indicatorId;
+ this.showEditContent = true
+ finishEditContent(content) {
+ this.tableData[this.selectIndex].content = content
+ // 获取模板详情
+ get_template_detail() {
+ this.$axiosUser("get", `/performance/template/info/${this.user_info.site_id}/` + this.templateId).then(res => {
+ let { data: { data: { title, flow } } } = res
+ this.templateTitle = title || '模板名称'
+ this.title = title || '模板名称'
+ this.flowInfo = flow || {}
+ // 获取表格数据
+ get_table_data() {
+ this.$axiosUser("get", `/performance/indicator/list/${this.user_info.site_id}/` + this.templateId).then(res => {
+ this.tableData = res.data.data.list
+ editTableData() {
+ // 编辑模板标题
+ editTitle() {
+ let title = this.title
+ if (title !== null && title !== '') {
+ let url = `/performance/template/title/${this.user_info.site_id}`
+ this.$http.post(url, { templateId: this.templateId, title }).then(res => {
+ // 编辑指标名称,规则,权重,目标,单位
+ handleEdit(props, value, id) {
+ let url = '', data = {};
+ url = `/performance/indicator/${props}/${this.user_info.site_id}/${this.templateId}`;
+ data[props] = value;
+ data['indicatorId'] = id;
+ this.$http.post(url, data).then(res => {
+ if(res.code == 0) return this.$message.error(res.msg || '操作失败')
+ handleBtnClick(index, node, row) {
+ this.handleStatusChange(index, row, node.type)
+ // 自评节点单独操作
+ handleScoreSelf(row, index) {
+ let { indicatorId, flow: { nodes } } = row
+ indicatorId, // 指标ID
+ let url = `/performance/indicator/flow/${this.user_info.site_id}/${this.templateId}`
+ let { code, data } = res
+ this.tableData.splice(index, 1, data); //替换元素
+ this.$message.success("操作成功");
+ this.$message.error(res.message || '操作失败');
+ // 操作节点
+ handleStatusChange(index, row, nodeType) {
+ if (index !== -1) this.tagIndex = index // 子节点索引
+ this.currentIndicator = row;
+ let { indicatorId } = row;
+ this.selectIndicatorId = indicatorId; // 指标ID
+ this.selectNodes = row.flow.nodes; // 所有节点
+ if (nodeType == 'targetConfirms') {
+ this.dialogTitle = "确认目标"
+ this.formLabel = "确认人"
+ this.targetConfirms = true;
+ if (nodeType == 'resultInput') {
+ this.dialogTitle = "录入结果"
+ this.formLabel = "录入人"
+ this.resultInput = true;
+ if (nodeType == 'scoreSelf') {
+ this.dialogTitle = "自评"
+ if (nodeType == 'scoreEachOther') {
+ this.dialogTitle = "互评"
+ this.formLabel = "互评人"
+ this.scoreEachOther = true
+ if (nodeType == 'scores') {
+ this.dialogTitle = "评分"
+ this.formLabel = "评分人"
+ this.scores = true
+ if (nodeType == 'reviews') {
+ this.dialogTitle = "审批"
+ this.formLabel = "审批人"
+ this.reviews = true
+ if (nodeType == 'cc') {
+ this.dialogTitle = "抄送"
+ this.formLabel = "抄送人"
+ this.cc = true
+ // this.dialogVisible = true;
+ // 添加指标
+ addData() {
+ let url = `/performance/indicator/create/${this.user_info.site_id}/${this.templateId}`
+ this.$http.post(url, {}).then(res => {
+ let { data, code } = res
+ if (code) this.tableData.push(data)
+ handleSelectChange(val) {
+ this.multipleSelection = val;
+ // 取消删除
+ // 确认删除
+ confirmDelete() {
+ if (this.multipleSelection && this.multipleSelection.length > 0) {
+ this.$confirm('确定删除选中的指标吗?', '提示', {
+ type: 'warning'
+ }).then(() => {
+ // 创建一个包含异步任务的数组,每个任务都是一个 axios 请求
+ const arrays = []
+ this.multipleSelection.forEach(item => {
+ arrays.push(this.deleteIndicator(item))
+ // 当所有请求都成功完成时,responses 是一个包含所有响应的数组
+ Promise.all(arrays).then(responses => {
+ // console.log(responses)
+ }).catch(error => {
+ // 如果任何一个请求失败,将会进入这个 catch 块
+ console.log(error)
+ }).catch(() => { });
+ // 删除指标
+ deleteIndicator(item) {
+ let url = `/performance/indicator/remove/${this.user_info.site_id}/${this.templateId}/${item.indicatorId}`
+ return this.$axiosUser('post', url).then(res => {
+ return res.data
+ // 打开计算公式弹框
+ openFormula(row, index) {
+ this.currentIndicator = null;
+ this.selectIndex = -1;
+ this.showFormula = true;
+ // 关闭编辑计算公式弹框
+ closeDialog() {
+ // 全选复选框事件监听
+ checkAllChangeFn(val) {
+ if (val) {
+ // 全选
+ this.tableColumn.forEach(item => {
+ item.isShow = true
+ // 反全选
+ if (this.flowColumn.includes(item.label)) {
+ item.isShow = false;
+ this.showPopover();
+ // 重置,flag: Boolean,全部重置为flag
+ reset(flag) {
+ this.refreshTable();
+ // 表格列是否显示的方法
+ showColumn(currentColumn) {
+ return this.tableColumn.find(item => item.prop == currentColumn).isShow;
+ /* 选择列 */
+ changeColumns(val) {
+ // columns将val数组存在的值设为true,不存在的设为false
+ val && val.forEach(item => {
+ let current = this.tableColumn.find(i => i.label == item)
+ current.isShow = true;
+ // 判断是否全选
+ this.judgeIsCheckAll();
+ // this.refreshTable();
+ // 重新渲染表格
+ refreshTable() {
+ if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
+ // 气泡框出现
+ showPopover() {
+ this.checkColumns = []
+ if (item.isShow) {
+ this.checkColumns.push(item.label)
+ judgeIsCheckAll() {
+ // 选中的长度 = 表格列的长度 全选按钮就选中
+ if (this.checkColumns.length == this.tableColumn.length)
+ this.checkAll = true
+ else
+ this.checkAll = false
+ let data = nodes
+ let { indicatorId } = this.currentIndicator;
+ let index = this.tableData.findIndex(table => table.indicatorId === indicatorId)
+ this.nodeType = ''
+ this.dialogTitle = ''
+ this.selectNodes = []
+ this.currentIndicator = []
+ this.selectIndicatorId = ''
+ this.tagIndex = -1
+ // 批量操作成功
+ finishBatchHandle(data) {
+ this.flowInfo = data;
+ // 编辑计算公式确定的回调
+ onFormulaConfirm(formulas) {
+ let url = `/performance/indicator/expression/${this.user_info.site_id}/${this.templateId}`;
+ indicatorId,
+ expression: {
+ formulas: formulas.map(item => {
+ condition: item.condition,
+ expression: item.expression
+ this.$http.post(url, params).then(res => {
+ if (code) {
+ this.tableData.splice(this.selectIndex, 1, data); //替换元素
+ // 发布考核
+ publish() {
+ this.showPublish = true;
+ this.$router.go(-1)
+};
+.box {
+ .el-switch__core {
+ width: 30px !important;
+ height: 16px;
+ .el-switch__core::after {
+ width: 14px;
+ height: 14px;
+ margin-top: -1px;
+ .el-switch.is-checked .el-switch__core::after {
+ margin-left: -15px;
+ /*头部样式*/
+ .header-content {
+ .header-left {
+ width: 230px;
+ .item {
+ .el-icon-arrow-left {
+ font-size: 22px;
+ .text {
+ padding-left: 50px;
+ &::before {
+ content: '';
+ width: 1px;
+ background-color: #ebebeb;
+ left: 44px;
+ top: 50%;
+ margin-top: -18px;
+ .header-right {
+ .plus-button {
+ display: block;
@@ -0,0 +1,736 @@
+ <el-carousel v-loading="loading" :autoplay="false" :loop="false" :initial-index="0" height="600px"
+ indicator-position="none" arrow="never" ref="carousel">
+ <el-carousel-item style="overflow-y: auto; ">
+ <el-alert :closable="false" type="info" title="考核基础信息,正确描述考核有助于管理员快速了解考核性质。考核分类可帮助管理员快速找到考核。同一类型的考核更有可比性"
+ style="margin-bottom: 10px;" />
+ <div class="flex-box-ce" style="flex-direction: column; justify-content: center; ">
+ <div style="margin: 20px auto; font-size: 16px; font-weight: 600;">填写考核基本信息</div>
+ <el-form :model="publishData" label-width="100px">
+ <!-- <el-form-item label="考核名" prop="title">
+ <el-input v-model="publishData.title" maxlength="20" style="width: 400px" />
+ <el-form-item label="考核分类">
+ <el-select v-model="publishData.cateId">
+ <el-option v-for="cate in cateList" :key="cate.cateId" :label="cate.name" :value="cate.cateId" />
+ <el-form-item label="周期种类">
+ <el-radio-group v-model="publishData.cycleType" @change="changeCycle">
+ <el-form-item v-if="publishData.cycleType === '0'" label="考核时间" prop="startDate">
+ <el-date-picker v-model="dateRange" type="daterange" unlink-panels range-separator="至"
+ start-placeholder="开始日期" end-placeholder="结束日期" :key="1" />
+ <el-form-item v-if="publishData.cycleType === '1'" label="选择年份" prop="year">
+ <el-date-picker v-model="year" type="year" value-format="yyyy" placeholder="选择年" :key="2" />
+ <el-form-item v-if="publishData.cycleType === '2' || publishData.cycleType === '3'" label="日期区间">
+ <SelectCircle :id="1" :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm">
+ <el-form-item v-if="publishData.cycleType === '4'" label="选择月份" prop="month">
+ <el-date-picker v-model="month" type="month" placeholder="月份" value-format="yyyy-MM" :key="3" />
+ <el-form-item label="考核周期">
+ <el-link v-if="startDate && endDate" type="primary">
+ {{ startDate }} 至 {{ endDate }}
+ <el-link v-else type="warning">请指定周期</el-link>
+ </el-carousel-item>
+ <!-- <el-carousel-item style="overflow-y: auto;">
+ <el-alert :closable="false" type="info" title="关联OKR目标可以帮助考核评审进一步跟踪过程,提高考核的准确性" style="margin-bottom: 10px;" />
+ <el-card style="height: 450px;">
+ <el-row style="height: 50px;" type="flex" justify="space-between" align="middle">
+ <el-col :span="6">
+ <span>关联OKR目标</span>
+ <el-col :span="2">
+ <el-button type="text" icon="el-icon-plus" @click.stop="showTargetSearch = true" />
+ <div style="height: 400px;overflow-y: auto;padding-bottom: 20px;">
+ <el-alert v-for="item in selectedOkrs" :key="item.id" type="success" :title="item.name" :closable="false"
+ </el-card>
+ </el-carousel-item> -->
+ <el-carousel-item style="overflow-y: auto;">
+ <el-alert :closable="false" type="info" style="margin-bottom: 10px;"
+ title="按岗位、职能、角色等方式根据团队实际情况定义指标模板,为不同模板指定人员发起考核更灵活" />
+ <el-row type="flex" justify="start" align="middle" style="height: 30px;">
+ <el-button type="text" icon="el-icon-plus" @click.stop="showTemplateSearch = true">添加考核模板</el-button>
+ <el-table :data="templateList" height="400px">
+ <el-table-column prop="title" label="模板" />
+ <el-table-column prop="employees" label="考核人员" show-overflow-tooltip>
+ <el-link v-for="employee in scope.row.employees" :key="employee.employeeId" style="margin: 5px;"
+ type="primary">
+ {{ employee.name }}
+ <el-table-column prop="others" width="100">
+ <el-button type="primary" size="mini" @click="openEmployeeSelector(scope.row)">绑定人员</el-button>
+ title="面谈流程可以为每个人考核进行复盘跟进,添加跟踪任务等。对个人提升很有帮助" />
+ <div class="flex-box-ce" style="justify-content: center;">
+ <el-form label-width="100px">
+ <el-switch v-model="interviewNode.enable"></el-switch>
+ <el-form-item label="处理人">
+ <el-radio-group v-model="interviewNode.assigneeType" :disabled="!interviewNode.enable" size="mini">
+ <el-form-item v-if="interviewNode.assigneeType === 'leader'">
+ <el-select v-model="interviewNode.leaderLevel" placeholder="请选择管理员" :disabled="!interviewNode.enable"
+ key="leader" filterable>
+ <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value" />
+ <el-form-item v-if="interviewNode.assigneeType === 'user'">
+ <el-select v-model="assigneeUsers" placeholder="请选择指定人员" multiple key="user"
+ :disabled="!interviewNode.enable" filterable style="width: 400px;">
+ <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id" />
+ <el-form-item v-if="interviewNode.assigneeType === 'post'">
+ <el-select v-model="assigneePosts" placeholder="请选择岗位" multiple key="post"
+ <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-form-item v-if="interviewNode.assigneeType === 'deptLeader'">
+ <el-cascader v-model="assigneeDept" size="small" :options="deptList"
+ :props="{ multiple: true, checkStrictly: true, emitPath: false }" placeholder="请选择部门" filterable
+ style="width: 400px;" />
+ </el-carousel>
+ <div class="flex-box-ce" style="justify-content: center; ">
+ <el-button :disabled="!preValidate" @click="prevStep">上一步</el-button>
+ <el-button v-if="step <= 2" type="primary" :disabled="!nextValidate" @click="nextStep">下一步</el-button>
+ <el-button v-if="step >= 3" type="primary" :disabled="!publishValidate"
+ @click.stop="onPublishSubmit">发布</el-button>
+ <TargetSearch :visible.sync="showTargetSearch" :selectedCheckList="okrs" :showSelectedData="selectedOkrs"
+ @confirm="onOkrSelected" />
+ <TemplateSelector :show-visible.sync="showTemplateSearch" :selected-template="templateSelected"
+ @confirm="onTemplateConfirm" />
+ <PerEmployeeSelector :show-visible.sync="showEmployeeSearch" :selected-employees="employeeSelected"
+ :employee-status="1" @confirm="onEmployeeConfirm" />
+import moment from "moment/moment";
+import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle';
+import TargetSearch from "@/performance/views/assessManagement/TargetSearch";
+import TemplateSelector from "@/newPerformance/components/TemplateSelector.vue";
+import PerEmployeeSelector from "@/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue";
+ name: 'TemplateMixedPublish',
+ components:{
+ PerEmployeeSelector,
+ TemplateSelector,
+ TargetSearch,
+ Template,
+ activeIndex:{
+ default: 1
+ innerVisible:this.showVisible,
+ showTargetSearch:false,
+ okrs:[],
+ selectedOkrs:[],
+ showTemplateSearch:false,
+ showEmployeeSearch:false,
+ currentTemplate:null,
+ step:0,
+ dateRange:[],
+ year:'',
+ month:'',
+ publishData:{
+ title:'',
+ cycleType:'0',
+ cateId:0,
+ cateList:[
+ { cateId: 0, name: '无分类' },
+ templateList:[],
+ interviewNode:{
+ id:'',
+ type:'',
+ enable:false,
+ assigneeType:'',
+ assigneeIds:'',
+ multipleType:'',
+ allows:'',
+ weight:'',
+ title: "设置考核表基本信息",
+ employees: this.$getEmployeeMap(),
+ postList: [],
+ deptList: [],
+ assigneeUsers:[],
+ assigneePosts:[],
+ assigneeDept:[],
+ startDate(){
+ switch (this.publishData.cycleType){
+ case '0':
+ return !this.dateRange || this.dateRange.length !== 2 ? '' : moment(this.dateRange[0]).format('YYYY-MM-DD');
+ case '1':
+ return !this.year ? '' : moment(`${this.year}-01-01`).startOf('year').format('YYYY-MM-DD');
+ case '2':
+ return !['上半年','下半年'].includes(this.dateParameter.name) ? '' : (this.dateParameter.name === '上半年' ? moment(this.dateParameter.year).startOf('year').format('YYYY-MM-DD') : moment(this.dateParameter.year).startOf('year').add(6,'months').format('YYYY-MM-DD'));
+ case '3':
+ if (!['第一季度','第二季度','第三季度','第四季度'].includes(this.dateParameter.name)) return '';
+ switch (this.dateParameter.name){
+ case '第一季度':
+ return this.getQuarterDates(this.dateParameter.year,1).start.format('YYYY-MM-DD');
+ case '第二季度':
+ return this.getQuarterDates(this.dateParameter.year,2).start.format('YYYY-MM-DD');
+ case '第三季度':
+ return this.getQuarterDates(this.dateParameter.year,3).start.format('YYYY-MM-DD');
+ case '第四季度':
+ return this.getQuarterDates(this.dateParameter.year,4).start.format('YYYY-MM-DD');
+ default:
+ return '';
+ case '4':
+ if (!this.month) return '';
+ let [year,month] = this.month.split('-');
+ if (!year || !month) return '';
+ return moment(`${year}-${month}-01`).format('YYYY-MM-DD');
+ endDate(){
+ return !this.dateRange || this.dateRange.length !== 2 ? '' : moment(this.dateRange[1]).format('YYYY-MM-DD');
+ return !this.year ? '' : moment(`${this.year}-01-01`).endOf('year').format('YYYY-MM-DD');
+ return !['上半年','下半年'].includes(this.dateParameter.name) ? '' : (this.dateParameter.name === '上半年' ? moment(this.dateParameter.year).startOf('year').add(6,'months').subtract(1,'days').format('YYYY-MM-DD') : moment(this.dateParameter.year).endOf('year').format('YYYY-MM-DD'));
+ return this.getQuarterDates(this.dateParameter.year,1).end.format('YYYY-MM-DD');
+ return this.getQuarterDates(this.dateParameter.year,2).end.format('YYYY-MM-DD');
+ return this.getQuarterDates(this.dateParameter.year,3).end.format('YYYY-MM-DD');
+ return this.getQuarterDates(this.dateParameter.year,4).end.format('YYYY-MM-DD');
+ return moment(`${year}-${month}-01`).endOf('month').format('YYYY-MM-DD');
+ templateMap(){
+ let map = {}
+ this.templateList.forEach(item => {
+ map[item.templateId] = item;
+ templateSelected(){
+ return this.templateList.map(item => item.templateId);
+ employeeSelected(){
+ return !this.currentTemplate ? [] : this.currentTemplate.employees.map(item => item.employeeId);
+ preValidate(){
+ if (this.loading) return false;
+ switch (Number.parseInt(this.step)){
+ case 0:
+ case 1:
+ case 2:
+ return true;
+ nextValidate(){
+ return this.baseValidate();
+ return this.baseValidate() && this.okrValidate();
+ return this.baseValidate() && this.okrValidate() && this.templateValidate();
+ case 3:
+ publishValidate(){
+ return !this.loading && this.baseValidate() && this.okrValidate() && this.templateValidate() && this.interviewValidate() ;
+ interviewFlow(){
+ let assigneeMap = {
+ leader: [],
+ self: [],
+ user: this.assigneeUsers,
+ post: this.assigneePosts,
+ deptLeader: this.assigneeDept
+ let node = {
+ id:this.interviewNode.id,
+ type:this.interviewNode.type,
+ enable:this.interviewNode.enable,
+ assigneeType:this.interviewNode.assigneeType,
+ leaderLevel:this.interviewNode.leaderLevel,
+ assigneeIds:assigneeMap[this.interviewNode.assigneeType] || [],
+ multipleType:this.interviewNode.multipleType,
+ allows:this.interviewNode.allows,
+ weight:this.interviewNode.weight,
+ children:this.interviewNode.children,
+ nodes:[node]
+ this.innerVisible = val
+ activeIndex(v) {
+ this.step = this.activeIndex - 1;
+ this.$refs['carousel'].setActiveItem(this.step);
+ 'interviewNode.assigneeType'(v){
+ if (!v) return;
+ switch (v){
+ case 'self':
+ this.interviewNode.leaderLevel = 1;
+ this.assigneeUsers = [];
+ this.assigneeDept = [];
+ this.assigneePosts = [];
+ case 'leader':
+ if (this.step == 0) this.title = "设置考核表基本信息"
+ if (this.step == 1) this.title = "考核表关联OKR"
+ if (this.step == 2) this.title = "选择考核表及考核人员"
+ if (this.step == 3) this.title = "面谈设置"
+ this.initData()
+ getQuarterDates(year, quarter) {
+ this.step = 0;
+ this.year = moment().format('YYYY');
+ this.publishData = {
+ templates:[],
+ this.okrs = [];
+ this.selectedOkrs = [];
+ this.templateList = [];
+ this.dateRange = [];
+ this.interviewNode = {
+ id:`IT_${Date.now() + Math.floor(Math.random() * 10000)}`,
+ type:'interview',
+ this.title = "设置考核表基本信息"
+ this.$axiosUser('get',`/performance/cate/list/${this.userInfo.site_id}`),
+ this.$axiosUser('get', '/api/pro/department/tree', '', 'v2')
+ .then(([cateRes,postRes,deptRes]) => {
+ if (cateRes.data.code !== 1) throw new Error(cateRes.data.message);
+ if (deptRes.data.code !== 1) throw new Error(deptRes.data.msg);
+ this.cateList = [{cateId:0,name: '无分类'},...cateRes.data.data.list];
+ this.postList = postRes.data.data;
+ this.deptList = this.getTreeData(deptRes.data.data.list);
+ // this.getCateList();
+ prevStep(){
+ this.$emit("changeStep", this.step)
+ this.$refs['carousel'].prev();
+ nextStep(){
+ if (this.step >= 3) return;
+ this.$refs['carousel'].next();
+ changeCycle(v){
+ if (v === '2'){
+ this.dateOptions = [
+ } else if (v === '3'){
+ getCateList(){
+ let url = `/performance/cate/list/${this.userInfo.site_id}`;
+ this.$axiosUser('get', url, {})
+ .then(res => {
+ let { data: { code, data: { list } } } = res
+ if (code === 1) this.cateList = [{cateId:0,name:'无分类'} ,...list]
+ onOkrSelected(okrs,data){
+ this.selectedOkrs = data;
+ onTemplateConfirm(data){
+ if (!data || data.length <= 0) {
+ //清空模板
+ return;
+ let dataMap = {}
+ data.forEach(item => {
+ dataMap[item.templateId] = item;
+ //移除模板
+ this.templateList = this.templateList.filter(item => !!dataMap[item.templateId]);
+ //添加模板
+ if (this.templateMap[item.templateId]) return;
+ this.templateList.push({
+ templateId:item.templateId,
+ title:item.title,
+ employees:[]
+ openEmployeeSelector(row){
+ this.currentTemplate = row;
+ this.showEmployeeSearch = true;
+ onEmployeeConfirm(data){
+ if (!this.currentTemplate) return;
+ //清空用户
+ this.currentTemplate.employees = [];
+ let dataMap = {};
+ dataMap[item.employeeId] = item;
+ //移除用户
+ this.currentTemplate.employees = this.currentTemplate.employees.filter(item => !!dataMap[item.employeeId]);
+ //添加用户
+ let employeeMap = {};
+ this.currentTemplate.employees.forEach(item => {
+ employeeMap[item.employeeId] = item;
+ if (employeeMap[item.employeeId]) return;
+ this.currentTemplate.employees.push({
+ employeeId:item.employeeId,
+ name:item.name
+ this.currentTemplate = null;
+ baseValidate(){
+ // return this.publishData.title && this.publishData.title.length > 0 && this.publishData.title.length <= 20 && this.startDate && this.endDate;
+ return this.startDate && this.endDate;
+ okrValidate(){
+ templateValidate(){
+ return this.templateList && this.templateList.length > 0 && !this.templateList.some(template => {
+ return !template.employees || template.employees.length <= 0
+ interviewValidate(){
+ let node = this.interviewFlow.nodes[0];
+ return node.id && node.id.startsWith("IT_") && node.type === 'interview' && (!node.enable ? true : (node.assigneeType === 'leader' ? node.leaderLevel > 0 : (node.assigneeType === 'self' ? true : (!['post','user','deptLeader'].includes(node.assigneeType) ? false : (node.assigneeIds && node.assigneeIds.length > 0)))));
+ onPublishSubmit(){
+ const params = {
+ title: this.publishData.title,
+ cateId: this.publishData.cateId,
+ cycleType: this.publishData.cycleType,
+ startDate: this.startDate,
+ endDate: this.endDate,
+ okrs: [...this.okrs],
+ templates: this.templateList.map(template => {
+ templateId: template.templateId,
+ employees: template.employees.map(employee => {
+ employeeId: employee.employeeId,
+ interviewFlow:this.interviewFlow
+ let url = `/performance/review/publish/templates/${this.userInfo.site_id}`;
+ this.$http.post(url,params)
+ if (res.code !== 1) {
+ this.$message.error(res.message || '非法请求');
+ throw new Error(res.message || '非法请求');
+ this.$message.success("考核已发布");
+ if (result) this.handleClose();
+.selectBox{
+/deep/ .card-cycle .el-card__body{
+ padding: 20px 0;
@@ -0,0 +1,212 @@
+ <div class="flex-box-ce header">
+ 指标库管理
+ <el-button type="primary" size="small" @click="addTemplate()">添加模板</el-button>
+ <div style="height: 1px; background-color: #f1f1f1; margin: 10px 0;"></div>
+ <el-table v-loading="loading" :data="templateList.slice((page - 1) * pageSize, page * pageSize)"
+ :header-cell-style="{ background: '#f5f7fa' }" style="width: 100%; height: auto;">
+ <el-table-column prop="title" label="模板标题">
+ {{ scope.row.title || "模板名称" }}
+ <el-table-column label="操作" width="200">
+ <el-link type="primary" @click="getDetails(scope.row.templateId)">编辑</el-link>
+ icon-color="red" title="确定删除这个考核模板吗?" @confirm="confirmDelete(scope.row.templateId)"
+ <div class="flex-box-ce" style="width: 100%; justify-content: center; height: 50px;">
+ <el-pagination @current-change="handleCurrentChange" :current-page.sync="page" :page-size="pageSize"
+ :page-sizes="[10, 20, 30, 40]" layout="total, prev, pager, next" :total="total">
+ </el-pagination>
+import Driver from 'driver.js';
+import 'driver.js/dist/driver.min.css';
+ pageSize: 10,
+ alertTilte: "管理每个考核模板,可在考核模板里面添加,修改指标,设置每个指标的考核流程,发布考核时,直接选择套用考核模板",
+ activated() {
+ this.getTemplateList()
+ // mounted() {
+ // this.$nextTick(() => {
+ // this.startGuide()
+ // })
+ // },
+ const steps = [
+ element: '.dispose-left',
+ popover: {
+ title: '第一步',
+ description: '这是第一个引导步骤',
+ position: 'bottom'
+ element: '.template-box',
+ title: '第二步',
+ description: '这是第二个引导步骤',
+ ];
+ this.driver = new Driver({
+ doneBtnText: '完成',
+ animate: true,
+ stageBackground: '#ffffff',
+ nextBtnText: '下一步',
+ prevBtnText: '上一步',
+ closeBtnText: '关闭'
+ this.driver.defineSteps(steps);
+ this.driver.start();
+ this.total = this.templateList.length
+ this.$message.success("添加成功");
+ this.$router.push(`/templateDetails/${this.templateId}`)
+ // 获取详情
+ getDetails(templateId) {
+ this.$router.push(`/templateDetails/${templateId}`)
+ handleSizeChange(val) {
+ this.pageSize = val
+ handleCurrentChange(val) {
+ this.page = val
+ .all {
+ .header {
@@ -0,0 +1,263 @@
+ <div class="flex-box main">
+ <div class="flex-5 right">
+ <div v-show="isActive == 1">
+ <el-form ref="detailForm" :model="ruleForm" :rules="rules" @submit.native.prevent
+ label-width="150px">
+ <div class="title">
+ 总分规则
+ <span class="fontColorB">(量化指标和非量化指标)</span>
+ <el-form-item label="自定义" prop="region">
+ <el-select v-model="ruleForm.region" placeholder="请选择自定义" disabled style="width: 300px;">
+ <el-option label="量化指标和非量化指标合并计算" value="shanghai"></el-option>
+ <el-option label="量化指标和非量化指标分开计算" value="beijing"></el-option>
+ <div class="title">量化指标</div>
+ <el-form-item label="指标类型" prop="name">
+ <el-select v-model="ruleForm.region" disabled style="width: 300px;">
+ <el-option label="数字类型" value="shanghai"></el-option></el-select>
+ <el-form-item label="总分自动加和" prop="tel"><el-switch v-model="ruleForm.delivery"
+ disabled></el-switch></el-form-item>
+ <el-form-item label="录入方式" prop="company_id">
+ <el-select v-model="ruleForm.region" disabled style="width: 300px;"><el-option label="文本框输入"
+ value="shanghai"></el-option></el-select>
+ <div v-show="isActive == 2">
+ <el-form ref="detailForm" @submit.native.prevent label-width="150px">
+ <div class="title">绩效等级配置</div>
+ <!-- <el-form-item label="绩效等级" prop="tel">
+ <template slot="label">
+ <span>绩效等级</span>
+ <el-tooltip effect="dark" content="设置公司的等级名称及对应占比,考核结束后,将按照考核结果的排名,正态强制分布。如:排名前10%的员工绩效等级为3.75" placement="top">
+ <i class="el-icon-warning"></i>
+<el-switch v-model="level_enable" disabled :active-value="1" :inactive-value="0"></el-switch>
+</el-form-item>
+<el-form-item label="录入方式" prop="company_id">
+ <el-select v-model="ruleForm.region" disabled><el-option label="文本框输入" value="shanghai"></el-option></el-select>
+</el-form-item> -->
+ <div style="height: 30px;"></div>
+ <ClassSet ref="ClassSet" :inputs="inputs"
+ :inputsStyle="{ paddingLeft: '150px', width: '700px' }">
+ </ClassSet>
+ <div class="footer"><el-button type="primary" :loading="loading" @click="save()">保存设置</el-button></div>
+import ClassSet from '@/performance/components/public/ClassSet';
+ components: { ClassSet },
+ name: 'BasicsSet',
+ isActive: 2,
+ ruleForm: {
+ region: 'shanghai',
+ delivery: true
+ rules: {},
+ inputs: [],//分值区间
+ level_enable: true,//是否开启绩效等级
+ isSave: true
+ watch: {},
+ mounted() { },
+ // 获取全局设置
+ getAllSet() {
+ this.$axiosUser('get', 'api/pro/per/user/base_config').then(res => {
+ this.level_enable = data.level_enable;
+ var inputs = [];
+ var max = 0;//最大值
+ if (levels) {
+ inputs.push(obj);
+ this.inputs = inputs
+ //保存
+ save() {
+ let is = true, level_scope = [];
+ let isSave = this.$refs.ClassSet.isSave
+ let inputsList = this.$refs.ClassSet.inputsList
+ if (!isSave) {
+ return false
+ inputsList.some(item => {
+ if (!item.name) {
+ this.$message.error('等级的名称不能为空');
+ is = false;
+ if (item.max == 0) {
+ this.$message.error('最大值不能为空或者0');
+ level_scope.push({ name: item.name, value: Number(item.max) });
+ var level_scopes = {//参数的数据结构
+ levels: level_scope
+ if (is) {
+ this.$axiosUser('post', 'api/pro/per/user/set_base_config', { level_scope: JSON.stringify(level_scopes) }).then(res => {
+ this.$message.success('设置成功');
+ // 监听最大值输入
+ InputBiur(e, index) {
+ this.isSave = true
+ var max = this.inputsList[index].max;
+ var min = this.inputsList[index].min;
+ if (max == 0) {
+ if (min >= max) {
+ this.$message.error('不能小于或等于' + min);
+ this.$set(this.inputsList[index], 'max', 0);
+ this.isSave = false
+ if (this.inputsList[index + 1]) {
+ this.$set(this.inputsList[index + 1], 'min', max);
+ addInput() {
+ if (this.inputsList.length == 10) {
+ this.$message.error('最高10个级别');
+ var max = this.inputsList[this.inputsList.length - 1].max;
+ this.inputsList.push({ name: '', min: max, max: 0 });
+ deleteInput(index) {
+ this.$message.error('至少保留一个等级');
+ var min = this.inputsList[index].min; //获取当前元素最小值
+ this.inputsList.splice(index, 1);
+ if (index != this.inputsList.length) {
+ //当删除不是最后一位时
+ this.$set(this.inputsList[index], 'min', min);
+ // // this.inputsList.some((item, i) => {
+ // // if (i == index) {
+ // this.inputsList.splice(i, 1); //在数组的some方法中,如果return true,就会立即终止这个数组的后续循环
+ // // return true;
+ // // }
+ // // });
+ active(index) {
+ this.isActive = index;
+<style scoped="scoped">
+ min-height: calc(100vh - 210px);
+ padding: 20px;
+.main {
+ min-height: calc(100% - 40px);
+.title-f {
+ margin-bottom: 20px;
+.footer {
+ border-top: 1px solid #ebebeb;
+.checkChild {
+ background-color: #fbfdff;
+ margin-left: 20px;
+.left,
+.right {
+ overflow: auto;
+.ul {
+ border-bottom: none;
+.li {
+.li:hover {
+.active-li {
+ color: #409EFF !important;
+.active-li::before {
+ width: 3px;
+ left: 0;
@@ -0,0 +1,226 @@
+ <div v-show="isActive == 2" class="flex-center-center">
+ <ClassSet ref="ClassSet" :inputs="inputs" :inputsStyle="{ paddingLeft: '150px', width: '700px' }" style="margin: 0 auto;">
@@ -1,66 +1,53 @@
<div class="performance">
- <div class="main">
- <div class="main-header flex-box-ce">
- <div class="flex-1 fontColorB item">
- <div>{{ title }}</div>
- <div class="fontColorC">考核表</div>
- <div class="bian"></div>
- <div v-if="cycleType == 0">未定义</div>
- <div v-else-if="cycleType == 1">年度</div>
- <div v-else-if="cycleType == 2">半年度</div>
- <div v-else-if="cycleType == 3">季度</div>
- <div v-else-if="cycleType == 4">月度</div>
- <div v-else="cycleType == -1">未设置</div>
- <div class="fontColorC">周期类型</div>
- <div>{{ startTime | formatDate }} <br>
- {{ endTime | formatDate }}</div>
- <div class="fontColorC">考核周期</div>
+ <div class="perform-info" v-if="detailInfo">
+ <div class="perform-title">{{ title }}</div>
+ <div class="base-info-title flex-box-ce">
+ 基本信息
+ <div class="base-info">
+ <div class="base-info-item">
+ <div class="label">被考核人</div>
+ <div class="value">{{ detailInfo && detailInfo.employeeName || '' }}</div>
- <div>{{ score || '暂无评分' }}</div>
- <div class="fontColorC">考核评分</div>
+ <div class="label">部门</div>
+ <div class="value" v-if="detailInfo && detailInfo.department && detailInfo.department.length > 0">
+ {{ detailInfo.department | formatDeptName }}</div>
- <div v-if="status == 0">考核中</div>
- <div v-if="status == 1">已结束</div>
- <div v-if="status == 2">面谈中</div>
- <div class="fontColorC">考核状态</div>
- <div>{{ create_status }}</div>
- <div class="fontColorC">考核中的指标</div>
+ <div class="label">考核状态</div>
+ <div class="value orange-color" v-if="detailInfo && status == 0">考核中</div>
+ <div class="value green-color" v-if="detailInfo && status == 1">已结束</div>
+ <div class="value blue-color" v-if="detailInfo && status == 2">面谈中</div>
+ <div class="value" v-else></div>
- <div>{{ finish_status }}</div>
- <div class="fontColorC">考核完成的指标</div>
+ <div class="label">考核周期</div>
+ <div class="value">
+ {{ startTime | formatDate }}
+ -
+ {{ endTime | formatDate }}
+ 考核信息
- <div class="btn-box flex-box-ce" style="">
- <div v-if="levelName" class="status-btn-box fadeInDown animated">
- <div class="status-btn">
- {{ levelName }}
+ <div class="btn-box flex-box-ce" v-if="detailInfo && !reviewId">
- <div v-else></div>
- <div class="flex-box-ce" style="margin-right: 10px;">
- <el-switch v-model="isShow" style="margin-right: 5px;" @change="changeShow()"></el-switch>显示流程节点
<div class="flex-box-ce more" @click="dialogVisible = true">
- 查看更多
+ 查看历史考核记录
<i class="el-icon-d-arrow-right"></i>
@@ -68,41 +55,63 @@
<!-- 考核详情 -->
- <div class="perform-list scroll-bar" style="flex: 1;">
- <el-table ref="fmeaTableRef" v-loading="loading" :data="tableData" stripe
- style="width: 100%; margin-bottom: 20px;" :height="600" border
- :header-cell-style="{ background: '#f5f7fa' }" v-table-move="['fmeaTableRef']">
- <el-table-column prop="title" label="指标" align="center" min-width="200" fixed="left">
+ <div class="table-box" v-if="detailInfo">
+ <el-table v-loading="loading" :data="tableData" stripe style="width: 100%; height: auto;" border
+ :header-cell-style="{ background: '#f5f7fa' }">
+ <el-table-column type="index" label="序号" align="center" width="50"></el-table-column>
+ <el-table-column prop="title" label="指标" align="center" min-width="200">
- <el-table-column prop="content" label="规则" align="center" min-width="200">
+ <el-table-column prop="target" label="目标" align="center" min-width="100">
- <el-tooltip class="item" effect="dark" placement="top">
- <div v-html="scope.row.content" slot="content" style="max-width:300px"></div>
- <div class="oneLine">{{ scope.row.content }}</div>
- </el-tooltip>
+ {{ scope.row.target ? scope.row.target + scope.row.unit : '--' }}
- <el-table-column prop="target" label="目标" align="center" min-width="100">
+ <el-table-column prop="result" label="结果值" align="center" min-width="100">
- {{ scope.row.target + scope.row.unit }}
+ <el-tag type="info" v-if="!scope.row.result">--</el-tag>
+ <el-tag type="success"
+ v-if="scope.row.result && Number(scope.row.result - scope.row.target) >= 0">{{
+ scope.row.result }} {{ scope.row.unit || '' }}</el-tag>
+ <el-tag type="danger"
+ v-if="scope.row.result && Number(scope.row.result - scope.row.target) < 0">{{
+ <div v-if="scope.row.resultFiles && scope.row.resultFiles.length > 0">
+ <div v-for="(file, index) in scope.row.resultFiles" :key="file">
+ <el-link type="primary" @click="openFile(file)">
+ {{ "附件" + (index + 1) }}
- <el-table-column prop="result" label="结果" align="center" min-width="100">
+ <el-table-column prop="weight" label="权重(%)" align="center" min-width="80">
- {{ scope.row.result + scope.row.unit }}
+ {{ scope.row.weight || '--' }}
- <el-table-column prop="weight" label="权重" align="center" min-width="100">
+ <el-table-column prop="content" label="规则" align="center" min-width="200">
+ <div v-html="scope.row.content" slot="content" style="max-width:300px"></div>
+ <div class="oneLine">{{ scope.row.content || '--' }}</div>
- <el-table-column prop="formulae" label="计算公式" align="center" min-width="120">
+ <el-table-column prop="formulae" label="计算公式" align="center" min-width="140">
- <el-button v-if="scope.row.expression && scope.row.expression.formulas.length > 0"
+ <el-link type="primary"
+ v-if="scope.row.expression && scope.row.expression.formulas && scope.row.expression.formulas.length > 0"
@click="openFormula(scope.row, scope.$index)">
{{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
${scope.row.expression.formulas.length} 条` : '公式' }}
- </el-button>
<div v-else style="color: #999;">暂无公式</div>
@@ -113,99 +122,131 @@
- <el-table-column prop="businessStatus" label="考核状态" align="center" min-width="120">
+ <el-table-column prop="okrs" label="过程管控" align="center">
- <div v-if="scope.row.businessStatus == 'end'" class="green-color">已结束</div>
- <div v-else class="orange-color">考核中</div>
+ 过程跟踪
- <el-table-column v-if="isShow" prop="confirm_target" label="确认目标" align="center" width="120"
- fixed="right">
- <template slot-scope="scope">
- <el-switch v-if="!scope.row.flow.nodes[0].enable" v-model="scope.row.flow.nodes[0].enable"
- disabled></el-switch>
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[0]" />
- </el-table-column>
- <el-table-column v-if="isShow" prop="input_result" label="录入结果" align="center" width="120"
- <el-switch v-if="!scope.row.flow.nodes[1].enable" v-model="scope.row.flow.nodes[1].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[1]" />
- <el-table-column v-if="isShow" prop="self_assessment" label="自评" align="center" width="120"
- <el-switch v-if="!scope.row.flow.nodes[2].enable" v-model="scope.row.flow.nodes[2].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[2]" />
- <el-table-column v-if="isShow" prop="peer_assessmen" label="互评" align="center" width="120"
- <el-switch v-if="!scope.row.flow.nodes[3].enable" v-model="scope.row.flow.nodes[3].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[3]" />
- <el-table-column v-if="isShow" prop="grade" label="评分" align="center" width="120" fixed="right">
+ <el-table-column prop="businessStatus" label="指标考核状态" align="center" min-width="120">
- <el-switch v-if="!scope.row.flow.nodes[4].enable" v-model="scope.row.flow.nodes[4].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[4]" />
- <el-table-column v-if="isShow" prop="approval" label="审批" align="center" width="120" fixed="right">
- <el-switch v-if="!scope.row.flow.nodes[5].enable" v-model="scope.row.flow.nodes[5].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[5]" />
+ <div v-if="scope.row.businessStatus == 'end'" class="green-color">已结束</div>
+ <div v-else class="orange-color">考核中</div>
- <el-table-column v-if="isShow" prop="carbon_copy" label="抄送" align="center" width="120" fixed="right">
+ <el-table-column label="操作" align="center" min-width="120">
- <el-switch v-if="!scope.row.flow.nodes[6].enable" v-model="scope.row.flow.nodes[6].enable"
- <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[6]" />
+ @click="getProcessTracing({ businessStatus: scope.row.businessStatus, nodes: scope.row.flow.nodes })">指标审批过程</el-link>
+ <div v-if="detailInfo && score" class="status-btn-box fadeInDown animated">
+ <div class="status-btn-green">
+ {{ score }} {{ levelName }}
+ <div v-if="detailInfo && !score" class="status-btn-box fadeInDown animated">
+ <div class="status-btn-orange">
+ 考核中: 未评分
<!-- 编辑计算公式 -->
- <FormulaComp v-if="currentIndicator" v-model="showFormula"
+ <!-- <FormulaComp v-if="currentIndicator" v-model="showFormula"
:fixed-props="[{ key: 'target', name: '目标' }, { key: 'weight', name: '权重' }, { key: 'result', name: '结果值' }]"
:expressions-props="currentIndicator.expression.formulas || []" :is-edit="false"
- @onConfirm="onFormulaConfirm" />
+ @onConfirm="onFormulaConfirm" /> -->
+ <el-dialog :visible.sync="showFormula" append-to-body :close-on-press-escape="true" center width="600px"
+ title="计算公式">
+ <div class="formulas-list-box">
+ <div class="formulas-list" v-for="(item, index) in formulas" :key="index">
+ <div class="formulas-title">公式({{ index + 1 }})</div>
+ <div class="formulas-condition-title">触发条件</div>
+ <div class="value-box">
+ <template v-for="(con, conIndex) in item.condition">
+ <div class="special-value" v-if="con.includes('result')">结果值</div>
+ <div class="special-value" v-else-if="con.includes('weight')">权重</div>
+ <div class="special-value" v-else-if="con.includes('target')">目标值</div>
+ <div class="normal-value" v-else>{{ con }}</div>
+ <div class="formulas-condition-title">计算公式</div>
+ <template v-for="(expr, exprIndex) in item.expression">
+ <div class="special-value" v-if="expr.includes('result')">结果值</div>
+ <div class="special-value" v-else-if="expr.includes('weight')">权重</div>
+ <div class="special-value" v-else-if="expr.includes('target')">目标值</div>
+ <div class="normal-value" v-else>{{ expr }}</div>
- <!-- 选择考核列表弹框 -->
+ <!-- 选择历史考核列表弹框 -->
<SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="choosePerformItem" />
+ <!-- 展示节点处理数据 -->
+ <ShowHandlerComp v-if="showData" v-model="showHandlerDialogVisible" :show-data="showData" />
-import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHanderDialog2'; // 显示节点数据组件
import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
-import { mapGetters } from 'vuex';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue";
name: "MyPerformance",
ShowHandlerComp,
FormulaComp,
- SelectExamineComp
+ SelectExamineComp,
+ reviewId: {
+ sendEmployeeId: {
+ fileUrl: "",
isShow: false,
dialogVisible: false,
loading: false,
value: [],
title: "",
@@ -213,20 +254,22 @@ export default {
endTime: "",
cycleType: "",
score: 0,
- status: 0,
+ status: '',
total: 0,
+ imgUrl: "",
levelName: '', // 考核详情信息
currentIndicator: null,
+ formulas: [], //公式列表
showFormula: false, // 公式弹框
employeeMap: this.$getEmployeeMap(), // 员工列表
employeeId: '',
params: {
keyword: '',
page: 1,
- pageSize: 15,
- cateId: '',
+ pageSize: 1,
startDate: '',
- endDate: ''
+ endDate: '',
+ employeeId: ''
// 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
cycleType: '-1',
@@ -268,19 +311,35 @@ export default {
date: [],
performList: [],
+ showData: null,
+ showHandlerDialogVisible: false
else return "--"
+ str += dept.dept_name + ","
create_status() {
if (this.tableData && this.tableData.length > 0) {
- return this.tableData.filter(data => data.businessStatus !== 'end').length
+ return this.tableData.filter(data => data.businessStatus !== 'end').length
return '--'
@@ -293,27 +352,72 @@ export default {
- this.getTemplateList();
+ if (this.sendEmployeeId) {
+ this.params.employeeId = this.sendEmployeeId
+ this.params.employeeId = this.user_info.id
+ if (this.reviewId) this.getDetails(this.reviewId)
+ else this.getTemplateList();
+ this.$emit('clearReviewId')
+ changePage() {
+ this.$bus.$emit("changeCurrentId", { currentId: '7' })
+ openFile(url) {
+ let file = {
+ url
+ let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
+ let lastIndex = file.url && file.url.lastIndexOf("/") || -1
+ let suffix; //文件后缀名
+ if (lastIndex > 0) {
+ suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
+ if (imgFiles.includes(suffix)) {
+ this.imgUrl = ''
+ this.imgUrl = file.url;
+ this.$viewerApi({
+ images: [this.imgUrl]
+ window.open(file.url, '_blank');
+ // 过程跟踪
+ getProcessTracing(row) {
+ this.showData = row;
+ this.showHandlerDialogVisible = true
handleChange(value) {
- if(value[1]) this.getDetails(value[1]);
+ if (value[1]) this.getDetails(value[1]);
choosePerformItem(item) {
this.getDetails(item.reviewId)
getTemplateList() {
let that = this
- let url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.user_info.id}`
+ let url = `/performance/statistics/reviews/${that.user_info.site_id}`
let requestdata;
if (that.cycleType == '-1') requestdata = { ...that.params }
else {
- requestdata = { ...that.params, cycleType: that.cycleType }
+ requestdata = { ...that.params, cycleType: that.cycleType }
that.$axiosUser('get', url, requestdata).then(res => {
@@ -323,52 +427,94 @@ export default {
let { reviewId } = that.performList[0] || '';
if (reviewId) that.getDetails(reviewId);
that.total = total
that.performList = [];
getDetails(reviewId) {
let url = `/performance/statistics/review/info/${this.user_info.site_id}/${reviewId}`
this.$axiosUser('get', url).then(res => {
let { data: { title, indicators, levelName, startTime, endTime, cycleType, score, status }, code } = res.data
if (code == 1) {
+ this.detailInfo = res.data.data;
this.title = title || ''
this.cycleType = cycleType || ''
this.score = score || ''
- this.status = status || '0'
+ this.status = status || ''
this.startTime = startTime || ''
this.endTime = endTime || ''
this.levelName = levelName || ''
- this.tableData = indicators || [];
+ this.tableData = indicators.reverse() || [];
+ let deptMap = JSON.parse(localStorage.getItem("SET_EMPLOYEE_MAP"));
+ if (Object.keys(deptMap) && Object.keys(deptMap).length > 0) {
+ if (this.detailInfo) {
+ const employeeDetails = deptMap[this.detailInfo.employeeId];
+ if (employeeDetails) {
+ this.detailInfo.department = employeeDetails.employee_detail.dept_list;
+ if (this.tableData && this.tableData.length > 0) {
+ let resultFiles = []
+ if (item.flow.nodes && item.flow.nodes.length > 0) {
+ let resultInputNode = item.flow.nodes[1] // 取到结果值节点
+ if (resultInputNode.tasks && resultInputNode.tasks.length > 0) {
+ resultInputNode.tasks.forEach(task => {
+ if (task.files && task.files.length > 0) {
+ task.files.forEach(file => {
+ resultFiles.push(file)
+ item.resultFiles = resultFiles
// 打开计算公式弹框
openFormula(row, index) {
this.currentIndicator = row;
+ this.formulas = row.expression.formulas
this.showFormula = true;
onFormulaConfirm() { },
changeShow() {
this.$nextTick(() => {
if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
+ else {
@@ -397,7 +543,6 @@ export default {
white-space: nowrap;
text-overflow: ellipsis;
@@ -409,123 +554,262 @@ export default {
.orange-color {
color: #e6a23c;
-.btn-box {
- width: 100%;
- padding: 20px !important;
- color: #999;
- .status-btn-box {
- width: 120px;
- z-index: 10;
+.blue-color {
- .status-btn {
- background: transparent;
- border: 2px dashed #67C23A;
- color: #67C23A;
+.green-status {
+ border: 2px solid #67C23A;
+ color: #67c23a;
+ background: #f0f9eb;
+.orange-status {
+ color: #e6a23c;
+ background: #fdf6ec;
+ border: 2px solid #f5dab1;
+.red-status {
+ border: 2px solid #fbc4c4;
+ color: #f56c6c;
+ background: #fef0f0;
+ width: 160px;
+ margin: 10px 0 10px auto;
+ .status-btn-green {
+ border: 2px dashed #67C23A;
+ color: #67C23A;
+ .status-btn-orange {
+ border: 2px dashed #e6a23c;
+.formulas-list-box {
+ .formulas-list {
+ .formulas-title {
+ font-size: 18px;
+ font-weight: 500;
+ .formulas-condition-title {
font-size: 16px;
+ font-weight: 400;
+ .value-box {
+ .special-value {
+ display: inline-block;
+ padding: 3px;
+ .normal-value {
+ letter-spacing: 2px;
+ margin: 0 3px;
.performance {
background-color: #f0f4fa;
flex-direction: column;
- .more {
- &:hover {
- cursor: pointer;
- .main {
+ .perform-info {
- height: 100px;
- margin: 0 0 10px 0;
background-color: #fff;
- .main-header {
- .bian {
- position: absolute;
- width: 1px;
- background-color: #e8e8e8;
- right: 0;
- top: 50%;
- margin-top: -15px;
+ .back {
+ left: 10px;
- .item {
+ .perform-title {
text-align: center;
- padding: 10px;
- position: relative;
- div:nth-child(1) {
- font-size: 20px;
- font-weight: 600;
- color: #409EFF;
+ font-size: 24px;
+ line-height: 50px;
+ border-bottom: 1px solid #EBEEF5;
+ .base-info-title {
+ width: 4px;
+ height: 18px;
+ background: #409eff;
+ margin: 0 10px;
-}
+ .base-info {
+ border: 1px solid #EBEEF5;
+ .base-info-item {
+ .label {
+ background: #f5f7fa;
+ color: #909399;
-/* 设置滚动条的宽度和背景色 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+ .value {
-/* 设置滚动条滑块的样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
-/* 设置滚动条滑块hover样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
-/* 设置滚动条轨道的样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
+ padding: 10px !important;
+ .more {
-.green-status {
- border: 2px solid #67C23A;
- color: #67c23a;
- background: #f0f9eb;
-.orange-status {
- color: #e6a23c;
- background: #fdf6ec;
- border: 2px solid #f5dab1;
-.red-status {
- border: 2px solid #fbc4c4;
- color: #f56c6c;
- background: #fef0f0;
@@ -1,5 +1,5 @@
- <el-dialog :visible.sync="dialogVisible" width="800px" :before-close="dialogBeforeClose">
+ <el-dialog title="历史考核记录" :visible.sync="dialogVisible" width="800px" :before-close="dialogBeforeClose" append-to-body >
<!-- 搜索框 -->
@@ -20,11 +20,11 @@
size="small">
</el-date-picker>
- <el-select v-model="params.cateId" placeholder="请选择考核分类" style="width: 180px; margin-right: 10px;"
+ <!-- <el-select v-model="params.cateId" placeholder="请选择考核分类" style="width: 180px; margin-right: 10px;"
size="small" @chang="changeCateId">
<el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
</el-option>
- </el-select>
+ </el-select> -->
<el-button type="primary" size="mini" @click="getTemplateList()">查询</el-button>
<el-button size="mini" @click="resetSearch()">重置</el-button>
@@ -92,7 +92,6 @@ export default {
this.params.cateId = cateId + ''
this.getTemplateList()
@@ -106,10 +105,10 @@ export default {
pageSize: 15,
cateId: '',
+ employeeId: '',
- employeeId: '',
- // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+ // 周期类型 -1全部 0-未定义 1-年度 2-半年度 3-季度 4-月度
cycleOptions: [
{ label: "全部", value: '-1' },
@@ -152,6 +151,8 @@ export default {
total: 0
@@ -163,15 +164,14 @@ export default {
if (!_.isEmpty(this.searchOptions)) {
- let { employeeId, cycleType, cateId } = this.searchOptions
- this.employeeId = employeeId
+ let { employeeId, cycleType } = this.searchOptions
+ this.params.employeeId = employeeId
this.params.cycleType = cycleType
this.cycleType = cycleType
- this.params.cateId = cateId
- this.employeeId = this.user_info.id
@@ -189,7 +189,7 @@ export default {
- let url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.employeeId}`
else requestdata = { ...that.params, cycleType: that.cycleType }
@@ -204,8 +204,6 @@ export default {
this.$emit('chooseExamine', item)
this.$emit('close-dialog', false)
@@ -227,18 +225,27 @@ export default {
endDate: ''
- this.cycleType = ''
+ this.cycleType = '-1'
+ this.date = []
+ if (this.searchOptions && this.searchOptions.employeeId) {
+ this.params.employeeId = this.searchOptions.employeeId
this.getTemplateList();
+ this.params.page = 1;
if (this.date[0]) this.params.startDate = this.date[0] || ''
if (this.date[1]) this.params.endDate = this.date[1] || ''
this.params.cycleType = v;
@@ -0,0 +1,194 @@
+ 正态分布设置
+ <div class="flex-box-ce" style="margin: 0 auto 10px auto;">
+ <el-button size="small" @click="addDistribution()">添加</el-button>
+ <el-table :data="distributions" style="width: 600px; height: auto; margin: 0 auto;" border stripe>
+ <el-table-column prop="name" label="名称" align="center">
+ <el-input v-model="scope.row.name" placeholder="请输入名称"></el-input>
+ <el-table-column prop="scale" label="比例(%)" align="center">
+ <el-input v-model.number="scope.row.scale" placeholder="请输入比例(数字)"></el-input>
+ <el-table-column label="操作" align="center">
+ <el-link type="danger" @click="deleteData(scope.$index)">删除</el-link>
+ <div class="footer">
+ <el-button type="primary" @click="confirmChangeScore()">保存设置</el-button>
+ import { mapGetters } from 'vuex';
+ export default {
+ distributions: [],
+ this.getDistributions()
+ console.log("即将离开页面")
+ // if (!this.isSave) {
+ // console.log("未保存")
+ // this.$confirm('未保存数据,是否离开?', '提示', {
+ // confirmButtonText: '保存数据',
+ // cancelButtonText: '取消',
+ // type: 'warning'
+ // }).then(() => {
+ // // this.$message({
+ // // type: 'success',
+ // // message: '删除成功!'
+ // this.confirmChangeScore()
+ // }).catch(() => {
+ // // type: 'info',
+ // // message: '已取消删除'
+ // });
+ async getDistributions() {
+ let res = await this.$axiosUser('get', url)
+ this.distributions = res.data.data.items
+ if (this.distributions && this.distributions.length > 0) {
+ this.distributionId = this.distributions[0].id
+ addDistribution() {
+ this.distributions.push({ id: this.distributionId, name: "", scale: 0 })
+ deleteData(index) {
+ this.distributions.splice(index, 1)
+ confirmChangeScore() {
+ let items = this.distributions;
+ if (!items[i].name) return this.$message.error('请输入正太规则名称')
+ console.log(items[i].scale)
+ sum += Number(items[i].scale)
+ console.log(sum)
+ if (sum > 100) return this.$message.error('总分布比例不能超过100')
+ items
+ this.distributions = res.data.items
+ padding: 15px 0;
+ .footer {
@@ -8,24 +8,14 @@
</el-select>
- <!-- <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple filterable style="width: 300px;"
- @change="changeEmployeeIds" clearable size="small">
- <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
- </el-select> -->
<el-cascader ref="dept" v-model="selected_dept_ids" size="small"
style="width: 300px; margin-left: 10px; " :options="deptList" @change="deptChange"
:props="{ checkStrictly: true, multiple: true }" filterable change-on-select placeholder="请选择部门"
clearable></el-cascader>
- <el-select v-model="cateId" @change="changeCateId" placeholder="请选择考核分类"
- style="width: 300px; margin-left: 10px;" clearable size="small">
- <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
- </el-option>
- <el-button type="primary" @click="choosePerson = true" size="small">添加人员</el-button>
+ <el-button type="primary" @click="openChoosePerson" size="small">添加人员</el-button>
<el-button type="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)"
@click="confirmDelete()" size="small">批量删除</el-button>
<el-button type="warning" size="small" @click="reset()">重置表格</el-button>
@@ -35,84 +25,108 @@
<el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
style="width: 100%; margin-top: 10px;"></el-alert>
+ <el-table v-loading="loading" ref="multipleTable" id="myTable" :data="tableData" style="width: 100%; "
+ :header-cell-style="{ background: '#f5f7fa' }" border stripe @selection-change="handleSelectChange">
+ <el-table-column prop="employeeName" label="姓名" min-width="100" align="center" fixed>
+ <el-link type="primary" @click="chooseExamine(scope.row, scope.$index)">
+ {{ scope.row.employeeName }}
+ <el-table-column prop="deptList" label="部门" min-width="200" align="center">
+ <el-table-column prop="title" label="考核名称" min-width="120" align="center">
+ <el-table-column prop="cycleType" label="周期类型" min-width="120" align="center">
+ <el-tag v-if="scope.row.cycleType" type="primary">{{ scope.row.cycleType | formatCycleType
+ }}</el-tag>
+ <el-tag v-else type="info">{{ scope.row.cycleType | formatCycleType }}</el-tag>
+ <el-table-column prop="startTime" label="开始时间" min-width="120" align="center">
+ {{ scope.row.startTime | formatDate }}
+ <el-table-column prop="endTime" label="结束时间" min-width="120" align="center">
+ {{ scope.row.endTime | formatDate }}
+ <el-table-column prop="levelName" label="等级名称" min-width="100" align="center">
+ <el-table-column prop="lastLevelName" label="上次考核等级名称" min-width="130" align="center">
+ <el-table-column prop="levelChange" label="较上次" min-width="100" align="center">
+ <div v-if="scope.row.levelChange && scope.row.levelChange > 0"
+ style="color: #67c23a !important;">
+ <i class="el-icon-top"></i>{{ scope.row.levelChange }}
+ <div v-else-if="scope.row.levelChange && scope.row.levelChange < 0" class="red"
+ style="color: #f56c6c !important;">
+ <i class="el-icon-bottom"></i>{{ scope.row.levelChange }}
+ <div v-else class="gray" style="color: #999;">
+ <el-table-column prop="score" label="得分" min-width="100" align="center">
+ <!-- <el-table-column prop="packageOkrs" label="关联OKR" min-width="100" align="center">
+ 考核表OKR
+ </el-table-column> -->
+ <el-table-column prop="status" label="考核状态" min-width="100" align="center">
+ <!-- <div>
+ {{ scope.row.status | formatStatus }}
+ <div class="orange-color" v-if="scope.row.status == 0">考核中</div>
+ <div class="green-color" v-if="scope.row.status == 1">已结束</div>
+ <div class="blue-color" v-if="scope.row.status == 2">面谈中</div>
- <el-table ref="multipleTable" id="myTable" :data="tableData" style="width: 100%; margin-top: 10px;"
- :height="500" :header-cell-style="{ background: '#f5f7fa' }" border stripe
- @selection-change="handleSelectChange">
- <el-table-column type="selection"></el-table-column>
- <el-table-column prop="employeeName" label="姓名" min-width="100" align="center">
- <el-link type="primary" @click="chooseExamine(scope.row, scope.$index)">
- {{ scope.row.employeeName }}
- </el-link>
- <el-table-column prop="deptList" label="部门" min-width="200" align="center">
- <el-table-column prop="title" label="考核名称" min-width="120" align="center">
- <el-table-column prop="cycleType" label="周期类型" min-width="120" align="center">
- <el-tag v-if="scope.row.cycleType" type="primary">{{ scope.row.cycleType | formatCycleType
- }}</el-tag>
- <el-tag v-else type="info">{{ scope.row.cycleType | formatCycleType }}</el-tag>
- <el-table-column prop="startTime" label="开始时间" min-width="120" align="center">
- <div>
- {{ scope.row.startTime | formatDate }}
- <el-table-column prop="endTime" label="结束时间" min-width="120" align="center">
- {{ scope.row.endTime | formatDate }}
- <el-table-column prop="levelName" label="等级名称" min-width="100" align="center">
- <el-table-column prop="lastLevelName" label="上次考核等级名称" min-width="150" align="center">
- <el-table-column prop="levelChange" label="较上次" min-width="120" align="center">
- <div v-if="scope.row.levelChange && scope.row.levelChange > 0" style="color: #67c23a !important;">
- <i class="el-icon-top"></i>{{ scope.row.levelChange }}
- <div v-else-if="scope.row.levelChange && scope.row.levelChange < 0" class="red"
- style="color: #f56c6c !important;">
- <i class="el-icon-bottom"></i>{{ scope.row.levelChange }}
- <div v-else class="gray" style="color: #999;">
- --
- <el-table-column prop="score" label="得分" min-width="120" align="center">
- <el-table-column prop="status" label="考核状态" min-width="120" align="center">
- {{ scope.row.startTime | formatStatus }}
- </el-table>
<!-- 选择人员组件 -->
- <EmployeeSelector :visible.sync="choosePerson" :is_filtration_creator="false" :selected="selected"
- @confirm="confirmChoosePerson" />
+ <!-- <EmployeeSelector :visible.sync="choosePerson" :is_filtration_creator="false" :selected="selected"
+ @confirm="confirmChoosePerson" /> -->
+ <PerEmployeeSelector :show-visible.sync="choosePerson" :selected-employees="selectedEmployees"
+ :choosePersonList="[]" :employee-status="1" :multiple="false" @confirm="confirmChoosePerson" />
<!-- 选择考核列表组件 -->
<SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="chooseEaxmineItem"
:search-options="searchOptions" />
@@ -123,15 +137,20 @@ import XLSX from 'xlsx';
import FileSaver from 'file-saver';
import EmployeeSelector from '@/components/EmployeeSelector';
+ TargetListComp,
+ PerEmployeeSelector
+ selectedEmployees: [],
selected_employee_ids: [],
selected_dept_ids: [],
deptList: [], // 部门列表 - 树形结构
@@ -144,7 +163,6 @@ export default {
tableIndex: -1,
- // 添加指标
selected: { employee: [], dept: [] },//已经选择人员
choosePerson: false,
@@ -166,13 +184,16 @@ export default {
multipleSelection: [],
alertTilte: "默认显示每个人的最新考核数据,可选择人员添加考核数据,也可以移除某个人员的考核数据,或点击人员姓名替换某条考核数据",
- dialogVisible: false
+ choosePersonList: []
this.getList();
this.get_dept_list();
- this.get_cate_list();
+ // this.get_cate_list();
...mapGetters(['user_info'])
@@ -223,7 +244,7 @@ export default {
let url = `/performance/statistics/review/last/${this.user_info.site_id}`
let requestdata
- this.cycleType = -1;
+ this.cycleType = '-1';
this.params = {
deptIds: '',
employeeIds: '',
@@ -231,11 +252,9 @@ export default {
pages: 1,
pageSize: 10
- if (this.cycleType == '-1') requestdata = { ...this.params }
- else {
- requestdata = { ...this.params, cycleType: this.cycleType }
- delete requestdata['employeeIds'] // 删除部门字段
+ this.selected_dept_ids = []
+ requestdata = { ...this.params }
+ if ('deptIds' in requestdata) delete requestdata['deptIds'] // 删除部门字段
this.$axiosUser('get', url, requestdata).then(res => {
let { data: { data: { list, total }, code } } = res
@@ -246,10 +265,38 @@ export default {
+ openChoosePerson() {
+ this.selectedEmployees = []
+ Object.keys(this.employeeMap).forEach(key => {
+ // if(key )
+ if (item.employeeId == key) {
+ let obj = {
+ id: this.employeeMap[key].id,
+ name: this.employeeMap[key].name,
+ img_url: this.employeeMap[key].img_url
+ this.selectedEmployees.push(obj) // 表格已存在的人员列表
+ this.choosePerson = true;
+ onEmployeeSelected(data) {
// 表格数据 - 选择人员获取数据
getListByPerson() {
+ this.params.deptIds = ''
+ this.params.employeeIds = this.params.employeeIds && this.params.employeeIds.length > 0 ? this.params.employeeIds.toString() : ''
this.$axiosUser('get', url, this.params).then(res => {
@@ -260,6 +307,9 @@ export default {
new Set(contactList.map(item => JSON.stringify(item)))
).map(item => JSON.parse(item));
this.total = this.tableData.length
+ this.params.cateId = ""
+ this.cateId = ""
@@ -328,7 +378,8 @@ export default {
deptChange(val) {
this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
this.params.deptIds = this.selected_dept_ids.toString()
- this.getList();
+ // 数据去重
+ this.getList();
@@ -341,6 +392,8 @@ export default {
this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
this.params.employeeIds = this.selected_employee_ids.toString()
+ this.params.employeeIds = Array.from(new Set(this.params.employeeIds.map(JSON.stringify))).map(JSON.parse);
@@ -351,13 +404,19 @@ export default {
//选择人员弹框 -- 保存人员
- confirmChoosePerson(e) {
- console.log(e)
- let data = e.employee.length > 0 ? e.employee : []
- let employeeIds = data.map(item => item.id)
- this.params.employeeIds = employeeIds.length > 0 ? employeeIds.toString() : ''
- this.getListByPerson();
- // this.record_ids = e.employee.length > 0 ? e.employee : []
+ confirmChoosePerson(res) {
+ this.choosePersonList = [];
+ this.params.employeeIds = ''
+ this.choosePersonList = res
+ let employeeIds = []
+ if (res && res.length > 0) {
+ res.forEach(item => {
+ employeeIds.push(item.id)
+ this.params.employeeIds = employeeIds
+ this.getListByPerson();
handleSelectChange(val) {
@@ -369,6 +428,9 @@ export default {
this.tableData = this.tableData.filter(item => !this.multipleSelection.includes(item));
this.$message.success("删除成功!");
this.$refs.multipleTable.clearSelection(); // 清空选中状态
chooseExamine(row, index) {
@@ -435,11 +497,35 @@ export default {
this.downloadLoading = false;
+.green-color {
+.orange-color {
.all {
@@ -448,29 +534,40 @@ export default {
@@ -1,7 +1,7 @@
- <el-container style="height: 100%;">
- <el-main v-if="interviewInfo" v-loading="loading">
- <el-card style="height: 10%; margin-bottom: 10px;">
+ <el-container style="height: 100%; ">
+ <el-main v-if="interviewInfo" v-loading="loading" >
+ <el-card style="height: 10%; margin-bottom: 10px; ">
<el-row type="flex" justify="space-between" align="middle">
<el-col>
<el-button
@@ -189,6 +189,7 @@
<el-card
v-for="log in interviewLogs"
:key="log.logId"
+ style="margin-bottom: 10px;"
>
<el-row type="flex" justify="start" align="center">
<el-col :span="3">
@@ -1,64 +1,37 @@
+ <el-container style="min-width: 1440px; height: 100%;">
<el-main v-if="reviewInfo">
<el-card style="height: 10%;margin-bottom: 20px;">
- <el-row type="flex" justify="space-between" align="center">
+ <el-row type="flex" justify="space-between" align="center">
- <WaStstistics
- title="考核表"
- :value="reviewInfo.title"
- />
+ <WaStstistics title="考核表" :value="reviewInfo.title" />
</el-col>
- title="周期种类"
- :value="cycleMap[reviewInfo.cycleType] || '--'"
+ <WaStstistics title="周期种类" :value="cycleMap[reviewInfo.cycleType] || '--'" />
- title="考核时间"
- :value="timeScope"
+ <WaStstistics title="考核时间" :value="timeScope" />
- title="考核状态"
- :value="statusMap[reviewInfo.status] || '--'"
+ <WaStstistics title="考核状态" :value="statusMap[reviewInfo.status] || '--'" />
- title="评分"
- :value="reviewInfo.score === null ? '--' : reviewInfo.score"
+ <WaStstistics title="评分" :value="reviewInfo.score === null ? '--' : reviewInfo.score" />
- title="考核中的指标"
- :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'"
+ <WaStstistics title="考核中的指标"
+ :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'" />
- title="考核完成的指标"
- :value="indicatorEnd"
+ <WaStstistics title="考核完成的指标" :value="indicatorEnd" />
- <el-col
- v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0"
- >
+ <el-col v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0">
<el-dropdown trigger="click">
- <el-button
- type="text"
+ <el-button type="text">
面谈记录
</el-button>
<el-dropdown-menu>
- <el-dropdown-item
- v-for="interview in reviewInfo.interviews"
- :key="interview.interviewId"
+ <el-dropdown-item v-for="interview in reviewInfo.interviews" :key="interview.interviewId">
<span @click="openInterview(interview.interviewId)">
{{ interview.createTime }}
</span>
@@ -68,315 +41,173 @@
</el-row>
</el-card>
- <el-card style="height: calc(90% - 20px); position: relative; " >
+ <el-card style="height: calc(90% - 20px); position: relative; ">
- <Seal
- v-if="reviewInfo.levelName"
- :text="reviewInfo.levelName"
- :size="100"
- font-size="24px"
- :top="10"
- :left="20"
- :rotate="-30"
- :z-index="100"
- <div class="flex-box-end" style="margin-bottom: 10px;">
- <el-switch
- v-model="showNode"
- active-text="显示考核节点"
- inactive-text="隐藏考核节点"
+ <Seal v-if="reviewInfo.levelName" :text="reviewInfo.levelName" :size="100" font-size="24px" :top="10" :left="20"
+ :rotate="-30" :z-index="100" />
+ <div class="flex-box-ce" style="margin-bottom: 10px; justify-content: flex-end;">
+ <el-button type="text" @click="openTargetList(reviewInfo.okrs)">个人OKR</el-button>
+ <el-button type="text" @click="openTargetList(reviewInfo.packageOkrs)"
+ style="margin-right: 10px;">考核表OKR</el-button>
+ <el-switch v-model="showNode" active-text="显示考核节点" inactive-text="隐藏考核节点" />
- <el-table
- :data="indicators"
- <el-table-column
- prop="title"
- label="指标"
- fixed="left"
- show-overflow-tooltip
- align="center"
- prop="content"
- label="规则"
- prop="target"
- label="目标"
+ <el-table :data="indicators">
+ <el-table-column prop="title" label="指标" fixed="left" show-overflow-tooltip align="center" />
+ <el-table-column prop="content" label="规则" show-overflow-tooltip align="center" />
- {{ scope.row.target === null ? '--' : (scope.row.unit ? `${scope.row.target} ${scope.row.unit}` : scope.row.target) }}
+ {{ scope.row.target === null ? '--' : (scope.row.unit ? `${scope.row.target} ${scope.row.unit}` :
+ scope.row.target) }}
- prop="result"
- label="结果"
- {{ scope.row.result === null ? '--' : (scope.row.unit ? `${scope.row.result} ${scope.row.unit}` : scope.row.result) }}
+ {{ scope.row.result === null ? '--' : (scope.row.unit ? `${scope.row.result} ${scope.row.unit}` :
+ scope.row.result) }}
- prop="weight"
- label="权重"
+ <el-table-column prop="weight" label="权重" align="center">
{{ scope.row.weight ? `${scope.row.weight} %` : '--' }}
- prop="businessStatus"
- label="考核状态"
+ <el-table-column prop="businessStatus" label="考核状态" align="center">
- <el-link
- :type="scope.row.businessStatus === 'end' ? 'primary' : 'warning'"
+ <el-link :type="scope.row.businessStatus === 'end' ? 'primary' : 'warning'">
{{ indicatorStatusMap[scope.row.businessStatus] || '--' }}
</el-link>
- prop="score"
- label="得分"
- v-if="showNode"
- prop="targetConfirms"
- label="目标确认"
+ <el-table-column prop="score" label="得分" align="center" />
+ <el-table-column prop="okrs" label="指标OKR" align="center">
- v-if="!scope.row.targetConfirms.enable"
- type="info"
+ <el-table-column v-if="showNode" prop="targetConfirms" label="目标确认" align="center">
+ <el-link v-if="!scope.row.targetConfirms.enable" type="info">
禁用
- v-else-if="scope.row.targetConfirms.tasks.length <= 0"
+ <el-link v-else-if="scope.row.targetConfirms.tasks.length <= 0" type="info">
未开始
- <template v-else >
+ <template v-else>
<div @click="openTasks(scope.row.targetConfirms.tasks,'目标确认任务',scope.row.unit)">
- v-if="scope.row.targetConfirms.finishCount > 0"
- type="primary"
- style="margin-right: 5px;"
- icon="el-icon-check"
+ <el-link v-if="scope.row.targetConfirms.finishCount > 0" type="primary" style="margin-right: 5px;"
+ icon="el-icon-check">
{{ scope.row.targetConfirms.finishCount }}
- v-if="scope.row.targetConfirms.runningCount > 0"
- type="warning"
- icon="el-icon-warning"
+ <el-link v-if="scope.row.targetConfirms.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.targetConfirms.runningCount }}
- prop="resultInput"
- label="结果录入"
+ <el-table-column v-if="showNode" prop="resultInput" label="结果录入">
- v-if="!scope.row.resultInput.enable"
+ <el-link v-if="!scope.row.resultInput.enable" type="info">
- v-else-if="scope.row.resultInput.tasks.length <= 0"
+ <el-link v-else-if="scope.row.resultInput.tasks.length <= 0" type="info">
<div @click="openTasks(scope.row.resultInput.tasks,'结果录入任务',scope.row.unit)">
- v-if="scope.row.resultInput.finishCount > 0"
+ <el-link v-if="scope.row.resultInput.finishCount > 0" type="primary" style="margin-right: 5px;"
{{ scope.row.resultInput.finishCount }}
- v-if="scope.row.resultInput.runningCount > 0"
+ <el-link v-if="scope.row.resultInput.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.resultInput.runningCount }}
- prop="scoreSelf"
- label="自评"
+ <el-table-column v-if="showNode" prop="scoreSelf" label="自评">
- v-if="!scope.row.scoreSelf.enable"
+ <el-link v-if="!scope.row.scoreSelf.enable" type="info">
- v-else-if="scope.row.scoreSelf.tasks.length <= 0"
+ <el-link v-else-if="scope.row.scoreSelf.tasks.length <= 0" type="info">
<div @click="openTasks(scope.row.scoreSelf.tasks,'自评任务',scope.row.unit)">
- v-if="scope.row.scoreSelf.finishCount > 0"
+ <el-link v-if="scope.row.scoreSelf.finishCount > 0" type="primary" style="margin-right: 5px;"
{{ scope.row.scoreSelf.finishCount }}
- v-if="scope.row.scoreSelf.runningCount > 0"
+ <el-link v-if="scope.row.scoreSelf.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.scoreSelf.runningCount }}
- prop="scoreEachOther"
- label="互评"
+ <el-table-column v-if="showNode" prop="scoreEachOther" label="互评">
- v-if="!scope.row.scoreEachOther.enable"
+ <el-link v-if="!scope.row.scoreEachOther.enable" type="info">
- v-else-if="scope.row.scoreEachOther.tasks.length <= 0"
+ <el-link v-else-if="scope.row.scoreEachOther.tasks.length <= 0" type="info">
<div @click="openTasks(scope.row.scoreEachOther.tasks,'互评任务',scope.row.unit)">
- v-if="scope.row.scoreEachOther.finishCount > 0"
+ <el-link v-if="scope.row.scoreEachOther.finishCount > 0" type="primary" style="margin-right: 5px;"
{{ scope.row.scoreEachOther.finishCount }}
- v-if="scope.row.scoreEachOther.runningCount > 0"
+ <el-link v-if="scope.row.scoreEachOther.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.scoreEachOther.runningCount }}
- prop="scores"
- label="评分"
+ <el-table-column v-if="showNode" prop="scores" label="评分">
- v-if="!scope.row.scores.enable"
+ <el-link v-if="!scope.row.scores.enable" type="info">
- v-else-if="scope.row.scores.tasks.length <= 0"
+ <el-link v-else-if="scope.row.scores.tasks.length <= 0" type="info">
<div @click="openTasks(scope.row.scores.tasks,'评分任务',scope.row.unit)">
- v-if="scope.row.scores.finishCount > 0"
+ <el-link v-if="scope.row.scores.finishCount > 0" type="primary" style="margin-right: 5px;"
{{ scope.row.scores.finishCount }}
- v-if="scope.row.scores.runningCount > 0"
+ <el-link v-if="scope.row.scores.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.scores.runningCount }}
- prop="reviews"
- label="审批"
+ <el-table-column v-if="showNode" prop="reviews" label="审批">
- v-if="!scope.row.reviews.enable"
+ <el-link v-if="!scope.row.reviews.enable" type="info">
- v-else-if="scope.row.reviews.tasks.length <= 0"
+ <el-link v-else-if="scope.row.reviews.tasks.length <= 0" type="info">
<div @click="openTasks(scope.row.reviews.tasks,'审批任务',scope.row.unit)">
- v-if="scope.row.reviews.finishCount > 0"
+ <el-link v-if="scope.row.reviews.finishCount > 0" type="primary" style="margin-right: 5px;"
{{ scope.row.reviews.finishCount }}
- v-if="scope.row.reviews.runningCount > 0"
+ <el-link v-if="scope.row.reviews.runningCount > 0" type="warning" icon="el-icon-warning">
{{ scope.row.reviews.runningCount }}
@@ -386,50 +217,41 @@
</el-main>
- <el-dialog
- v-if="tasksInfo.title"
- :visible.sync="showTasks"
- :title="tasksInfo.title"
- center
- append-to-body
+ <el-dialog v-if="tasksInfo.title" :visible.sync="showTasks" :title="tasksInfo.title" center append-to-body>
<div style="max-height: 600px; overflow-y: auto;">
- <el-card
- v-if="tasksInfo.tasks"
- v-for="task in tasksInfo.tasks"
- :key="task.taskId"
- style="margin-bottom: 20px;"
- <el-descriptions
- border
- :column="3"
- :title="task.assigneeName"
- size="mini"
- direction="vertical"
+ <el-card v-if="tasksInfo.tasks" v-for="task in tasksInfo.tasks" :key="task.taskId" style="margin-bottom: 20px;">
+ <el-descriptions border :column="4" :title="task.assigneeName" size="mini" direction="vertical">
<el-descriptions-item label="任务状态">{{ task.state === 'completed' ? '已处理' : '待处理' }}</el-descriptions-item>
- <el-descriptions-item
- v-if="task.score !== null"
+ <el-descriptions-item v-if="task.score !== null" label="评分">
{{ task.score }}
</el-descriptions-item>
- v-if="task.result !== null"
+ <el-descriptions-item v-if="task.result !== null" label="结果">
{{ tasksInfo.unit ? `${task.result} ${tasksInfo.unit}` : task.result }}
- label="评论"
+ <el-descriptions-item label="评论">
{{ task.comment }}
+ <el-descriptions-item v-if="task.files && task.files.length > 0" label="附件列表">
+ <div class="files">
+ <div class="file-list">
+ <div class="file-item" v-for="file in task.files" @click="onFilePreView(file)">
+ {{ file | filterFileName }}
+ </el-descriptions-item>
</el-descriptions>
</el-dialog>
+ <TargetListComp v-if="dialogVisible" :dialogVisible="dialogVisible" :ids="okrs" @close="closeTargetList">
</el-container>
@@ -440,10 +262,11 @@ import moment from "moment";
import Template from "../../examine/components/Template.vue";
import Seal from "./tool/Seal.vue";
import PerInterview from "./PerInterview.vue";
name: "PerReviewDetail",
- components: {PerInterview, Seal, Template, WaStstistics},
+ components: { PerInterview, Seal, Template, WaStstistics, TargetListComp },
reviewId:{
type: Number,
@@ -454,7 +277,8 @@ export default {
userInfo:this.$userInfo(),
loading:false,
- reviewInfo:null,
+ reviewInfo: null,
cycleMap:{
0:'未定义',
1:'年度',
@@ -486,6 +310,17 @@ export default {
unit:'',
tasks:[]
+ okrs: []
+ filterFileName(str) {
+ if (str) {
+ let lastIndex = str.lastIndexOf("/")
+ return str.substr(lastIndex + 1, str.length - 1);
computed:{
@@ -515,7 +350,8 @@ export default {
scoreSelf:null,
scoreEachOther:null,
scores:null,
- reviews:null,
+ reviews: null,
+ okrs: item.okrs,
cc:null,
item.flow.nodes.forEach(node => {
@@ -708,6 +544,24 @@ export default {
if (!interviewId || !this.reviewInfo) return;
window.open(`#/per/interview/${this.reviewId}/${interviewId}`);
+ this.dialogVisible = false
+ onFilePreView(url) {
+ window.open(url, '_blank');
this.initData();
@@ -717,6 +571,26 @@ export default {
+ .files {
+ .file-item {
+ transition: 0.2s;
+ text-decoration: underline;
@@ -0,0 +1,84 @@
+ <div class="tabs">
+ <div class="tab-item" v-for="(item, index) in tabs" :key="item.id"
+ :class="currentIdnex == index ? 'active' : ''" @click="changeTab(index)">
+ <div class="flex-1">
+ <LevelSetting v-if="currentIdnex == 0" />
+ <NormalDistribution v-if="currentIdnex == 1" />
+import LevelSetting from './LevelSetting.vue';
+import NormalDistribution from './NormalDistribution.vue';
+ LevelSetting,
+ NormalDistribution
+ currentIdnex: 0,
+ tabs: [
+ { id: 1, title: "等级设置" },
+ { id: 2, title: "正态分布" },
+ changeTab(index) {
+ this.currentIdnex = index
+ padding: 20px 10px;
+ .tabs {
+ .tab-item {
+ border: 1px solid #409EFF;
+ &:first-child {
+ color: white;
@@ -0,0 +1,225 @@
@@ -0,0 +1,272 @@
+ <div class="coaching-container">
+ 绩效辅导
+ <el-cascader ref="dept" v-model="selected_dept_ids" size="small" style="width: 300px; margin-left: 10px; "
+ :options="deptList" @change="deptChange" :props="{ checkStrictly: true, multiple: true }" filterable
+ change-on-select placeholder="请选择部门" clearable></el-cascader>
+ <el-table v-loading="loading" :data="tableData" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }" border stripe>
+ <el-table-column prop="title" label="面谈标题" align="center">
+ <el-table-column prop="employeeName" label="员工姓名" align="center">
+ <!-- <el-table-column label="部门" align="center">
+ <el-table-column align="center" prop="createTime" label="面谈创建时间">
+ <el-table-column align="center" prop="businessStatus" label="状态">
+ <div v-if="scope.row.businessStatus === 'start'">开始</div>
+ <div v-if="scope.row.businessStatus === 'interview'">面谈中</div>
+ <div v-if="scope.row.businessStatus === 'end'">结束</div>
+ <el-table-column align="center" label="操作">
+ <el-button v-if="scope.row.businessStatus === 'interview'" type="text" @click="getDetails(scope.row)">查看面谈详情</el-button>
+ <el-button v-if="scope.row.businessStatus === 'end'" type="text" @click="getDetails2()">查看归档记录</el-button>
+ <el-dialog :visible.sync="showReviewDetail" title="考核详情" center fullscreen>
+ <div style="height: 85vh;">
+ <PerReviewDetail v-if="currentReviewId" :review-id="currentReviewId" />
+ <el-dialog :visible.sync="dialogVisible" title="归档记录" center width="600px">
+ <div class="dialog-content" style="">
+ <el-descriptions title="" direction="vertical" :column="1" border style="height: 100%;">
+ <el-descriptions-item label="主持人记录">
+ <div class="label">存在问题:</div>
+ <div class="value">暂无发现问题</div>
+ <div class="label">评价及意见:</div>
+ <div class="value">还有很大的提升空间</div>
+ <div class="label">改进方案:</div>
+ <div class="value">1.改进措施1</div>
+ <div class="value">2.改进措施2</div>
+ <div class="value">3.改进措施3</div>
+ <div class="value">4.改进措施4</div>
+ <el-descriptions-item label="被考核人记录">
+ <el-descriptions-item label="其他人发言记录">
+ <div class="label">张三</div>
+ <div class="label">李四</div>
+ <div class="label">王五</div>
+ <div class="label">赵六</div>
+ </el-descriptions>
+import PerReviewDetail from "./PerReviewDetail.vue";
+ PerReviewDetail
+ reviewId: "",
+ keyword: "",
+ employeeId: "",
+ deptIds: "",
+ type: "",
+ pageSize: 10
+ showReviewDetail: false,
+ currentReviewId: '',
+ selected_dept_ids: [],
+ this.getInterviewList();
+ this.get_dept_list();
+ getInterviewList() {
+ let url = `/performance/interview/list/${this.user_info.site_id}`
+ this.$axiosUser("get", url, this.params).then(res => {
+ //待办
+ getNewAgency() {
+ this.params.type = 7
+ this.$axiosUser("get", `/performance/review/job/${this.user_info.site_id}`, this.params).then(res => {
+ let list = res.data.data.list
+ if (item.jobNew) this.tableData.push(item.jobNew)
+ this.total = this.tableData.length || 0;
+ console.log(this.tableData);
+ this.tableData[0].status = 1
+ this.tableData.push({
+ reviewTitle: "2025-04-01 至 2025-04-30 月度考核",
+ employeeName: "尹勇",
+ timeRemark: "2025-04-24 09:00",
+ status: 0
+ // this.editableTabs.forEach(item => {
+ // if (item.name == this.activeName) item.num = res.data.data.total
+ getDetails(row) {
+ this.currentReviewId = row.reviewId;
+ this.showReviewDetail = true;
+ getDetails2() {
+ this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
+ this.dept_list = res.data.data.list; // 用来回显选择的部门数据
+.coaching-container {
+ padding-left: 20px;
+ color: #888;
@@ -138,9 +138,9 @@
<el-table-column prop="title" label="指标名称" width="width">
</el-table> -->
- <el-table ref="fmeaTableRef" :class="isShow ? 'fadeInDown animated' : 'fadeInUp animated'" :data="tableData"
+ <el-table :class="isShow ? 'fadeInDown animated' : 'fadeInUp animated'" :data="tableData"
stripe style="width: 100%; margin-bottom: 20px;" :height="600" border
+ :header-cell-style="{ background: '#f5f7fa' }" >
<el-table-column prop="title" label="指标" align="center" min-width="200" fixed="left">
<el-table-column prop="content" label="规则" align="center" min-width="200">
@@ -726,7 +726,6 @@ export default {
.chat-record {
width: 24%;
@@ -1,92 +1,71 @@
<div class="boxMinHeight">
- <div class="flex-box-ce flex-d-center header">
- <div style="font-size: 18px;font-weight: 700" class="flex-1">个人考核</div>
- <el-select class="select" v-model="cycleType" placeholder="周期类型" @change="changeCircle"
- style="width: 100px; margin-right: 10px;" size="small">
- <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label" :value="item.value">
- <el-select class="select" v-model="selected_employee_ids" placeholder="请选择指定人员" filterable
- style="width: 300px;" @change="changeEmployeeIds" clearable size="small">
- <el-select class="select" v-model="cateId" @change="changeCateId" placeholder="请选择考核分类"
- <el-link style="margin-left: 10px;" type="info" @click="openDetail()">
- 查看明细
- <i class="el-icon-arrow-right"></i>
- <div class="circular_area" style="margin-top: 10px;">
- <div class="flex-box-ce" style="margin-bottom: 10px;">
- <div class="circular_item flex-1" style="margin-right: 10px;">
- <div class="flex-box">
- <div class="circular_title_left flex-1">
- <div>考核表状态分布</div>
+ <!-- <div class="left-container">
+ <el-row :gutter="10" style="height: 100%;">
+ <el-col :span="12"
+ style="height: 100%; display: flex; flex-direction: column; border-bottom: 1px solid #f1f1f1;">
+ <div class="info-title">
+ 部门选择
+ <el-input class="flex-1" placeholder="输入关键字进行过滤" v-model="filterText" size="mini"
+ style="margin-left: 10px;" />
- <PieChart1 key="PieChart1" v-if="tableData && tableData.length > 0" :table-data="tableData" />
- <noData v-else content="暂无数据" imgW="160px" imgH="160px"></noData>
+ <div class="flex-1 dept-list department_box" style="overflow-x: auto;">
+ <el-tree ref="tree" accordion :data="treeData" :props="defaultProps"
+ :filter-node-method="filterNode" :default-checked-keys="checkedKeys"
+ :check-strictly="singleSelection" :default-expanded-keys="checkedKeys"
+ @node-click="handleNodeClick" node-key="id">
+ <div content="tree" class="custom-tree-node" slot-scope="{ node, data }"
+ style="font-size: 14px;color: #606266; width:100%; text-align: left;">
+ <img src="static/images/one.png" />
+ <span class="name">{{ data.name }}</span>
+ </el-tree>
- <div class="circular_item flex-1">
- <div>考核等级分布</div>
- <PieChart2 key="PieChart2" v-if="tableData && tableData.length > 0" :table-data="tableData"
- :gradeLevels="gradeLevels" />
- <div>考核周期类型分布</div>
+ <el-col :span="12" style="height: 100%; display: flex; flex-direction: column;">
+ 员工选择
+ <el-input class="flex-1" placeholder="输入关键字进行过滤" v-model="keyword" size="mini"
- <PieChart3 key="PieChart3" v-if="tableData && tableData.length > 0" :table-data="tableData"
- :cycleOptions="cycleOptions" />
- <div>考核分类分布</div>
- <PieChart4 key="PieChart4" v-if="tableData && tableData.length > 0" :table-data="tableData"
- :cateList="cateList" />
+ <div class="flex-1 user-list scroll-bar" style="min-height: 100%; overflow-y: auto;">
+ <template v-if="userList && userList.length > 0">
+ <div class=" user-item flex-box-ce" v-for="user in userList" :key="user.id"
+ @click="chooseUser(user)">
+ <span class="radio" :class="radioValue == user.id ? 'isCheck' : ''">
+ <span v-show="radioValue == user.id" class="white-dot"></span>
+ <userImage :id="user.id" :img_url="user.img_url" :user_name="user.name" width="24px"
+ height="24px"></userImage>
+ <span class="username">{{ user.name }}</span>
+ <div style="height: 100px;"></div>
+<template v-else>
+ <noData content="暂无内容" imgW="100px" imgH="100px"></noData>
+</div>
+</el-col>
+</el-row>
+</div> -->
+ <!-- <div class="right-container">
+ <ExamineDetails v-if="tableData && tableData.length > 0" :table-data="tableData" :cycle-list="cycleOptions"
+ :cate-list="cateList" :examine-status="examineStatus" :gradeLevels="gradeLevels" :userInfo="userInfo" />
+ <div class="flex-box-ce" style="width: 100%; height: 100%; justify-content: center;">
+ <noData content="暂无内容" imgW="120px" imgH="120px"></noData>
- <ExamineDetails v-if="dialogVisible" v-model="dialogVisible" :table-data="tableData" :cycle-list="cycleOptions"
- :cate-list="cateList" :examine-status="examineStatus"/>
@@ -101,7 +80,7 @@ import PieChart2 from '@/newPerformance/components/PersonalExamine/PieChart2'; /
import PieChart3 from '@/newPerformance/components/PersonalExamine/PieChart3'; // 考核周期类型分布 - 饼图
import PieChart4 from '@/newPerformance/components/PersonalExamine/PieChart4'; // 考核分类分布 - 饼图
import ExamineDetails from '@/newPerformance/components/PersonalExamine/ExamineDetails'; // 考核详情
+import OrganizationSelect from "@/newPerformance/components/PublicComp/OrganizationSelect" // 组织架构
@@ -110,11 +89,24 @@ export default {
PieChart2,
PieChart3,
PieChart4,
- ExamineDetails
+ ExamineDetails,
+ OrganizationSelect
+ filterText: '',
+ treeData: [],
+ singleSelection: true, // 是否单选
+ checkedKeys: [],
+ defaultProps: {
+ label: 'label'
+ dept_select_id: '',
+ keyword: '',
+ radioValue: '',
selected_employee_ids: '',
@@ -146,28 +138,78 @@ export default {
+ organSelectDialog: false,
examineStatus: [
{ label: "考核中", value: "0" },
{ label: "已结束", value: "1" },
{ label: "面谈", value: "2" }
- ]
+ userInfo: null
+ filterText(val) {
+ this.$refs.tree.filter(val);
+ keyword(val) {
+ if (val) this.userList = this.userList.filter(user => user.name.includes(val))
+ radioValue(val) {
+ this.selected_employee_ids = val
+ this.getList(val);
+ this.selected_employee_ids = this.user_info.id
+ this.userInfo = this.user_info
this.getAllSet();
+ this.treeData = res.data.data.list
+ get_user_list() {
+ this.$axiosUser('get', '/api/pro/employee/index',
+ { dept_id: this.dept_select_id, keywords: this.keyword, page: 1, page_size: 2000, child: '1' }, 'v2')
+ this.userList = res.data.data.list;
+ console.log(this.userList)
+ filterNode(value, data) {
+ if (!value) return true;
+ return data.label.indexOf(value) !== -1;
+ handleNodeClick(node) {
+ console.log(node)
+ this.dept_select_id = node.id
+ this.get_user_list();
+ chooseUser(user) {
+ this.radioValue = user.id
+ this.userInfo = user;
// 表格数据 - 最新考核数据
getList(employeeId = '') {
@@ -187,78 +229,6 @@ export default {
- // 处理部门树状结构数据
- getTreeData(data) {
- for (var i = 0; i < data.length; i++) {
- data[i].checked = false;
- if (data[i].children.length < 1) {
- // children若为空数组,则将children设为undefined
- data[i].children = undefined;
- // children若不为空数组,则继续 递归调用 本方法
- this.getTreeData(data[i].children);
- return data;
- // 获取部门
- get_dept_list() {
- this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
- this.dept_list = res.data.data.list; // 用来回显选择的部门数据
- this.deptList = this.getTreeData(this.dept_list); // 处理成树状结构
- get_cate_list() {
- this.$axiosUser('get', url, {}).then(res => {
- this.cateList = list
- changeCircle(v) {
- deptChange(val) {
- this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
- this.params.deptIds = this.selected_dept_ids.toString()
- // 选择员工
- changeEmployeeIds(v) {
- this.selected_employee_ids = v
- this.getList(this.selected_employee_ids);
- // 选择考核分类
- changeCateId(v) {
- this.params.cateId = v;
- //选择人员弹框 -- 保存人员
- openDetail() {
- this.dialogVisible = true
// 获取全局等级设置
async getAllSet() {
let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
@@ -281,75 +251,199 @@ export default {
+ get_cate_list() {
-.boxMinHeight {
+.department_box /deep/ .el-tree-node {
- .header {
- .select ::v-deep .el-input__inner {
- border-radius: 25px;
+.department_box /deep/ .el-tree-node__content {
+ height: auto !important;
+.department_box /deep/ .el-tree-node .el-icon-caret-right {
+ padding: 6px 8px;
+.department_box /deep/ .el-tree-node .el-icon-caret-right.is-leaf {
+ color: transparent;
+ cursor: default;
+.department_box /deep/ .el-tree-node .custom-tree-node {
+.department_box /deep/ .el-tree-node .custom-tree-node img {
+ margin: 0px 5px 0 0;
+ width: 20px;
+.department_box /deep/ .el-tree-node .custom-tree-node span {}
+ padding: 12px 0;
+ border-bottom: 1px #f8f8f8 solid;
- .circular_item {
+.department_box /deep/ .el-tree-node__content:hover {
+ background: #ecf5ff;
+.department_box /deep/ .is-focusable .is-current {
+.department_box /deep/ .is-focusable .is-current .name {
+ font-weight: normal;
+ transition: 0.35s ease-in-out;
+.boxMinHeight {
+ .left-container {
+ width: calc(35% - 10px);
- height: 380px;
- .circular_title_left div:nth-child(1) {
- font-size: 18px;
- font-weight: 700;
+ .info-title {
- .grey_font {
- font-weight: normal;
- font-size: 12px;
- color: grey;
+ .dept-list,
+ .user-list {
+ padding-bottom: 30px;
+ border-top: 1px solid #f1f1f1;
+ border-left: 1px solid #f1f1f1;
+ .user-item {
+ .radio {
+ border-radius: 100%;
+ background-color: #FFF;
+ margin-right: 6px !important;
+ .white-dot {
+ .isCheck {
+ border-color: #409EFF;
+ background: #409EFF;
+ .username {
+ background: #F5F7FA;
+ .right-container {
@@ -1,12 +1,40 @@
<el-dialog :visible.sync="dialogVisible" width="1000px" :before-close="dialogBeforeClose">
+ <div class="flex-box">
+ <el-select v-model="filters.cycleType" placeholder="请选择周期" size="small">
+ <el-option v-for="item in cycleList" multiple :key="item.value" :label="item.label"
+ :value="item.value">
+ <el-select v-model="filters.cateType" placeholder="请选择分类" size="small" style="margin: 0 10px;">
+ <el-option v-for="item in cateList" multiple :key="item.cateId" :label="item.name"
+ :value="item.cateId">
+ <el-select v-model="filters.gradeLevels" placeholder="请选择等级" size="small">
+ <el-option v-for="item in gradeLevels" :key="item.label" :label="item.label"
+ <el-button type="primary" @click="applyFilters" size="small">筛选</el-button>
+ <el-button @click="clearFilters" size="small">清空</el-button>
<!-- 考核模板列表 -->
- <div class="perform-list scroll-bar" style="margin-top: 10px; height: 450px; overflow-y: auto;">
+ <div class="perform-list scroll-bar" style="margin: 10px 0; height: 450px; overflow-y: auto;">
<el-table ref="multipleTable" v-loading="loading"
- :data="tableData.slice(params.page - 1, params.page * params.pageSize)"
+ :data="filteredData.slice(params.page - 1, params.page * params.pageSize)"
style="width: 100%; margin-top: 10px;" :height="450" :header-cell-style="{ background: '#f5f7fa' }"
- border stripe @filter-change="handleFilterChange">
+ border stripe size="small">
<el-table-column prop="title" label="考核名称" min-width="160" align="center">
<el-table-column prop="startTime" label="考核时间" min-width="160" align="center">
@@ -17,16 +45,14 @@
- <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center"
- :filters="cycle_type_list" :filter-method="filterMethods">
+ <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center">
<el-tag type="primary">{{ scope.row.cycleType | formatCycleType }}</el-tag>
- <el-table-column prop="cateId" label="考核分类" min-width="100" align="center" :filters="cate_type_list"
- :filter-method="filterByCateType">
+ <el-table-column prop="cateId" label="考核分类" min-width="100" align="center">
<div v-if="scope.row.cateIds && scope.row.cateIds">
<template v-for="item in cateList">
@@ -36,11 +62,10 @@
- <el-table-column prop="levelName" label="得分" min-width="100" align="center" :filters="gradeLevels"
- :filter-method="filterMethods">
+ <el-table-column prop="levelName" label="得分" min-width="100" align="center">
@@ -48,8 +73,18 @@
- <el-table-column prop="status" label="考核状态" min-width="100" align="center"
- :filters="examine_status_list" :filter-method="filterMethods">
+ <el-table-column prop="packageOkrs" label="关联OKR" min-width="100" align="center">
<div v-if="scope.row.status == 0" class="orange-color">
{{ scope.row.status | formatStatus }}
@@ -71,14 +106,22 @@
</el-pagination>
+ <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs" @close="closeTargetList">
-import TableHeaderRender from "./TableHeaderRender.vue"
model: {
prop: 'dialogVisible',
event: 'close-dialog'
@@ -110,10 +153,24 @@ export default {
+ visible: false,
+ value: '',
+ iconColor: false,
pageSize: 15
+ cycleType: [],
+ cateType: [],
+ // 筛选后的数据
+ targetDialogVisible: false
@@ -139,74 +196,20 @@ export default {
total() {
return this.tableData && this.tableData.length
- // 考核周期过滤数据
- cycle_type_list() {
- let cycle_type_list = []
- if (this.cycleList && this.cycleList.length > 0) {
- cycle_type_list = this.cycleList.filter(item => item.label !== '全部')
- cycle_type_list = cycle_type_list.map(item => ({
- text: item.label,
- value: item.value,
- // 数组去重
- cycle_type_list = Array.from(new Set(cycle_type_list.map(JSON.stringify))).map(JSON.parse);
- return cycle_type_list
- // 考核分类过滤数据
- cate_type_list() {
- let cate_type_list = []
- if (this.cateList && this.cateList.length > 0) {
- cate_type_list = this.cateList.map(item => ({
- text: item.name,
- value: item.cateId,
- cate_type_list = Array.from(new Set(cate_type_list.map(JSON.stringify))).map(JSON.parse);
- return cate_type_list
- // 考核等级过滤数据
- gradeLevels() {
- let gradeLevels = []
- if (this.tableData && this.tableData.length > 0) {
- gradeLevels = this.tableData.map(item => {
- return {
- text: item.levelName || '--',
- value: item.levelName || '--',
- gradeLevels = Array.from(new Set(gradeLevels.map(JSON.stringify))).map(JSON.parse);
- return gradeLevels
- examine_status_list() {
- let examine_status_list = []
- if (this.examineStatus && this.examineStatus.length > 0) {
- examine_status_list = this.examineStatus.map(item => {
- examine_status_list = Array.from(new Set(examine_status_list.map(JSON.stringify))).map(JSON.parse);
- return examine_status_list
+ this.filteredData = this.tableData
+ this.gradeLevels = this.tableData.map(item => {
+ label: item.levelName || '--',
+ value: item.levelName || '--',
+ // 数组去重
+ this.gradeLevels = Array.from(new Set(this.gradeLevels.map(JSON.stringify))).map(JSON.parse);
@@ -217,30 +220,53 @@ export default {
handleSizeChange(v) {
this.params.pageSize = v
- // this.getList();
handleCurrentChange(v) {
this.params.page = v
- filterMethods(value, row, column) {
- console.log(column)
- const property = column["property"];
- return row[property] == value
- filterByCateType(value, row, column) {
- return row['cateIds'].includes(value)
+ // 周期筛选
+ if (this.filters.cycleType && this.filters.cycleType !== '-1' && !this.filters.cycleType.includes(item.cycleType)) {
- handleFilterChange(filters) {
- console.log(filters)
- // 保存筛选条件
- // for (const key in filters) {
- // this.filters[key] = filters[key][0]; // 只取第一个筛选值
+ // 分类筛选
+ if (this.filters.cateType && !item.cateIds.includes(this.filters.cateType)) {
+ if (
+ this.filters.gradeLevels && this.filters.gradeLevels.length > 0 &&
+ !this.filters.gradeLevels.includes(item.levelName)
+ ) {
+ // 清空筛选条件
+ clearFilters() {
+ this.filters.cycleType = [];
+ this.filters.cateType = [];
+ this.filters.gradeLevels = [];
+ this.applyFilters(); // 重新应用筛选条件
+ } else return this.$message.error("暂无关联okr")
@@ -248,7 +274,12 @@ export default {
.perform-list {
padding: 0 20px;
@@ -1,15 +1,67 @@
- <el-dialog :visible.sync="dialogVisible" width="1000px" :before-close="dialogBeforeClose">
- <!-- 考核模板列表 -->
- <el-table ref="multipleTable" v-loading="loading"
- style="width: 100%; margin-top: 10px;" :height="450" :header-cell-style="{ background: '#f5f7fa' }"
- border stripe>
+ <div class="examine-details" v-loading="loading">
+ <div class="info-title flex-box-ce" style="margin-top: 10px;">
+ 考核人员
+ <div class="flex-box-ce fontColorC">
+ <userImage :id="userInfo.id" :img_url="userInfo.img_url" :user_name="userInfo.name" width="36px"
+ height="36px" style="margin:0 10px;"></userImage>
+ {{ userInfo.name }}
+ <div style="border: 1px solid #eee; border-radius: 5px; padding: 10px; box-sizing: border-box; ">
+ 考核统计
+ <div class="flex-box-ce flex-d-wrap" style="justify-content: center;">
+ <div v-for="(item, index) in generalizeList" :key="index" class="generalize-item cursor"
+ @click="composite_state = item.id">
+ <div class="fontColorC">{{ item.name }}</div>
+ <div style="font-size: 36px; font-weight: 600;margin: 5px 0;">{{ item.val }}</div>
+ <div style="width: 60px;height: 2px;" :style="{ backgroundColor: item.color }"></div>
+ <div class="flex-1" style="display: flex; flex-direction: column;">
+ 考核列表
+ <el-table v-loading="loading" :data="filteredData.slice(params.page - 1, params.page * params.pageSize)"
+ style="width: 100%; margin: 10px 0;" :header-cell-style="{ background: '#f5f7fa' }" border stripe
+ size="small" height="490">
- <el-table-column prop="startTime" label="考核时间" min-width="160" align="center">
+ <el-table-column prop="startTime" label="考核时间" min-width="150" align="center">
{{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}
@@ -17,44 +69,27 @@
- <template #header>
- <el-popover placement="bottom" width="200" trigger="manual" v-model="visible"
- @show="showPopover" popper-class="table-header-popover">
- <div class="table-header-popover-template">
- <el-input placeholder="请输入内容" v-model="value" size="small" clearable
- @keyup.enter.native="confirm" ref="sInput"></el-input>
- <span slot="reference" style="margin-left: 5px" @click.stop="popClick"
- v-click-outside="closeOver">
- <i class="filter-icon el-icon-search"
- :style="{ color: iconColor ? '#9a4b9b' : '#909399' }"></i>
- </span>
- <!-- <template #default={ scope }>
- </template> -->
+ <el-table-column prop="cateId" label="考核分类" min-width="120" align="center">
<el-tag :key="item.cateId" v-if="scope.row.cateIds.includes(item.cateId)"
style="margin-bottom: 5px;">
- {{ item.name}}
@@ -62,8 +97,18 @@
@@ -78,21 +123,42 @@
- <div class="flex-box-ce" style="justify-content: center; margin-top: 10px;">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="params.page" :page-sizes="[15, 30, 45, 60]" :page-size="params.pageSize"
layout="total, sizes, prev, pager, next" :total="total">
+ <!-- 考核模板列表 -->
+ <!-- <div class="perform-list scroll-bar" style="margin: 10px 0; overflow-y: auto;">
+ -->
- </el-dialog>
+ <div style="height: 20px;"></div>
@@ -118,6 +184,10 @@ export default {
type: Array,
+ userInfo: {
+ default: () => {}
@@ -130,7 +200,36 @@ export default {
+ generalizeList: [
+ { name: '考核总数', val: 10, color: '#FF9600' },
+ { name: '已结束', val: 10, color: '#409EFF' },
+ { name: '进行中', val: 10, color: '#67c23a' },
+ { name: '面谈中', val: 10, color: '#f56c6c' },
+ { name: '已取消', val: 10, color: '#5F7294' },
+ { name: '未开始', val: 10, color: '#5F7294' },
+ { name: '暂停中', val: 10, color: '#f56c6c' }
+ tableData(v) {
+ },500)
@@ -157,132 +256,84 @@ export default {
+ this.filteredData = this.tableData;
+ console.log(this.gradeLevels);
- mounted() {
- dialogBeforeClose() {
- this.$emit('close-dialog', false)
+ this.generalizeList[0].val = this.tableData.length;
+ this.generalizeList[2].val = this.tableData.filter(item => item.status === 0).length;
+ this.generalizeList[1].val = this.tableData.filter(item => item.status === 1).length;
+ this.generalizeList[3].val = this.tableData.filter(item => item.status === 2).length;
- showPopover() {
- this.$refs.sInput.focus();
- closeOver() {
- this.visible = false;
- popClick(e) {
- this.visible = !this.visible;
- confirm() {
- if (this.value.trim()) {
- this.iconColor = true;
- this.filters = [{ text: this.value, value: this.value }];
- this.filters = [
- { text: '张三', value: '张三' },
- { text: '李四', value: '李四' },
- { text: '王五', value: '王五' },
- ];
- this.iconColor = false;
@@ -292,31 +343,20 @@ export default {
-.perform-list {
+.examine-details {
- padding: 0 20px;
- .green-color {
- .orange-color {
- .blue-color {
- color: #409eff;
/* 设置滚动条的宽度和背景色 */
::v-deep .el-table__body-wrapper::-webkit-scrollbar {
background-color: #f9f9f9;
@@ -337,5 +377,70 @@ export default {
border-radius: 6px;
background: #ededed;
+ .generalize-item {
+ .perform-list {
+ .green-color {
+ .orange-color {
+ .blue-color {
@@ -0,0 +1,855 @@
+ <div class="record-container">
+ <div class="main-content">
+ <el-select style="width: 120px;" v-model="params.cycleType" placeholder="请选择周期种类"
+ @change="changeCycleType">
+ <el-option v-for="item in cycleOptions" :key="item.id" :label="item.name" :value="item.id">
+ <el-date-picker class="cursor" style="width: 150px; margin-left: 10px;" v-model="params.year"
+ type="year" placeholder="选择年" value-format="yyyy" @change="changeYear">
+ <el-date-picker class="cursor" v-if="params.cycleType == '0'"
+ style="width: 300px; margin-left: 10px;" v-model="dateRange" type="daterange"
+ range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" format="yyyy-MM-dd"
+ value-format="yyyy-MM-dd"></el-date-picker>
+ <el-select class="cursor" v-if="params.cycleType == '2'" style="width: 150px; margin-left: 10px;"
+ v-model="cycleValue" placeholder="请选择半年度" @change="changeCycleValue">
+ <el-option label="上半年" value="1">
+ <el-option label="下半年" value="2">
+ <el-select class="cursor" v-if="params.cycleType == '3'" style="width: 150px; margin-left: 10px;"
+ v-model="cycleValue" placeholder="请选择季度" @change="changeCycleValue">
+ <el-option label="第一季度" value="1">
+ <el-option label="第二季度" value="2">
+ <el-option label="第三季度" value="3">
+ <el-option label="第四季度" value="4">
+ <el-select class="cursor" v-if="params.cycleType == '4'" style="width: 150px; margin-left: 10px;"
+ v-model="cycleValue" placeholder="请选择月度" @change="changeCycleValue">
+ <el-option label="一月" value="1">
+ <el-option label="二月" value="2">
+ <el-option label="三月" value="3">
+ <el-option label="四月" value="4">
+ <el-option label="五月" value="5">
+ <el-option label="六月" value="6">
+ <el-option label="七月" value="7">
+ <el-option label="八月" value="8">
+ <el-option label="九月" value="9">
+ <el-option label="十月" value="10">
+ <el-option label="十一月" value="11">
+ <el-option label="十二月" value="12">
+ <el-select class="cursor" style="width: 260px; margin-left: 10px;" v-model="selectEmployeeId"
+ filterable clearable multiple placeholder="员工姓名搜索" @change="changeEmployeeIds">
+ <el-option v-for="item in employees" :key="item.id" :label="item.name"
+ <el-button type="primary" @click="exportToExcel('过程分析', '#myTable')" size="small"
+ :loading="downloadLoading">导出明细</el-button>
+ <div class="flex-box-ce flex-d-wrap"
+ style="justify-content: center; padding: 20px 0; box-sizing: border-box;">
+ <div v-for="(item, index) in generalizeList" :key="index" @click="chooseExamineStatus(item.name)"
+ class="generalize-item">
+ <div class=" fontColorC">{{ item.name }}</div>
+ <div v-for="(item, index) in gradeLevels" @click="chooseExamineLevel(item.name)" :key="item.name"
+ <div style="font-size: 36px; font-weight: 600;margin: 5px 0;">
+ {{tableData.filter(user => user.levelName === item.name).length}}
+ <el-table id="myTable" :data="filteredData" style="width: 100%; height: auto;"
+ :header-cell-style="{ background: '#f5f7fa' }" border stripe>
+ <el-table-column prop="date" label="考核时间" align="center"></el-table-column>
+ <el-table-column prop="employeeName" label="员工姓名" align="center"></el-table-column>
+ <el-table-column prop="department" label="部门" width="300" align="center">
+ {{ scope.row.department | formatDeptName }}
+ <el-table-column prop="status" label="考核状态" align="center">
+ <el-table-column prop="score" label="评分" align="center">
+ <el-tag v-if="!scope.row.score" type="info">
+ {{ scope.row.score }}
+ <el-table-column prop="levelName" label="等级" align="center">
+ <el-tag type="info">
+ {{ scope.row.levelName || '--' }}
+ <el-button type="text" @click="getTemplateDetails(scope.row)">查看明细</el-button>
+ <el-dialog title="员工绩效详情" :visible.sync="detailDialogVisible" @close="handleClose" :close-on-click-modal="false"
+ :close-on-press-escape="true" center fullscreen :show-close="false">
+ <div style=" width: 100%; height: 100%; position: relative;">
+ <el-button round style="position: absolute; top: -65px; left: 0px; z-index: 99;"
+ @click="detailDialogVisible = false">返回</el-button>
+ <!-- 我的考核 -->
+ <MyPerformance v-if="detailDialogVisible" :reviewId="reviewId" :sendEmployeeId='sendEmployeeId' />
+import MyPerformance from './MyPerformance'; // 我的考核
+import XLSX from 'xlsx';
+import FileSaver from 'file-saver';
+ MyPerformance
+ isLeftShow: true,
+ isRightShow: false,
+ employeeName: '',
+ checked: [],
+ status: -1,
+ cycleValue: '1',
+ downloadLoading: false,
+ cycleOptions: [
+ { name: "月度", id: '4' },
+ { name: "季度", id: '3' },
+ { name: "半年度", id: '2' },
+ { name: "年度", id: '1' },
+ { name: "未定义", id: '0' },
+ options: [
+ value: -1,
+ label: '全部'
+ }, {
+ value: '0',
+ label: '未完成'
+ value: '1',
+ label: '已完成'
+ sendEmployeeId: "",
+ selectedReviewPackageId: '',
+ cateIds: [],
+ distributionId: '',
+ { name: '考核人数', val: 0, color: '#FF9600' },
+ { name: '已结束', val: 0, color: '#409EFF' },
+ { name: '进行中', val: 0, color: '#67c23a' },
+ { name: '面谈中', val: 0, color: '#f56c6c' },
+ // { name: '已取消', val: 0, color: '#5F7294' },
+ // { name: '未开始', val: 0, color: '#08EEA1' },
+ // { name: '暂停中', val: 0, color: '#f56c6c' }
+ selectEmployeeId: [],
+ // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+ cycleType: "4",
+ startDate: '',
+ deptIds: '',
+ year: ''
+ level: '',
+ examineStatus: [
+ { label: "考核中", value: "0" },
+ { label: "已结束", value: "1" },
+ { label: "面谈", value: "2" }
+ userInfo: null,
+ isContentLeft: true,
+ isContentRight: true,
+ chooseUserIds: [],
+ isFold: false,
+ dateRange: this.getMonthFirstAndLastDay(), // 设置默认值
+ detailDialogVisible: false,
+ pickerOptions: {
+ shortcuts: [{
+ text: '最近一周',
+ onClick(picker) {
+ const end = new Date();
+ const start = new Date();
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+ picker.$emit('pick', [start, end]);
+ text: '最近一个月',
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+ text: '最近三个月',
+ start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+ }]
+ dateRange(v) {
+ console.log(v);
+ async created() {
+ that = this;
+ this.getDefaultTime();
+ //
+ getMonthFirstAndLastDay() {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = today.getMonth(); // 月份从 0 开始,需要加 1
+ // 获取当月第一天
+ const firstDay = new Date(year, month, 1);
+ const firstDayStr = this.formatDate(firstDay);
+ // 获取当月最后一天
+ const lastDay = new Date(year, month + 1, 0); // 下个月的第 0 天是当前月的最后一天
+ const lastDayStr = this.formatDate(lastDay);
+ // console.log(firstDayStr)
+ // console.log(lastDayStr)
+ return [firstDayStr, lastDayStr];
+ formatDate(date) {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ changeCycleType(v) {
+ if (v !== '0') {
+ this.params.startDate = ''
+ this.params.endDate = ''
+ } else if (v == 1) {
+ // console.log("-----------", 'cycleValue' in this.params)
+ this.params.year = this.currentYear
+ this.cycleValue = ''
+ if ('cycleValue' in this.params) delete this.params.cycleValue
+ } else if (v == 2) {
+ this.params = { ...this.params, cycleValue: this.cycleValue }
+ } else if (v == 3) {
+ } else if (v == 4) {
+ changeDate(v) {
+ this.dateRange[0] = v[0]
+ this.dateRange[1] = v[1]
+ this.params.startDate = v[0]
+ this.params.endDate = v[1]
+ changeYear(v) {
+ this.params.year = v;
+ changeCycleValue(v) {
+ this.params.deptIds = deptList_id.toString()
+ // 员工筛选
+ this.selectEmployeeId = v
+ // 考核状态筛选
+ chooseExamineStatus(name) {
+ this.status = name
+ // 考核等级筛选
+ chooseExamineLevel(name) {
+ this.level = name
+ const employeeMatch = this.selectEmployeeId.length === 0 || this.selectEmployeeId.includes(item.employeeId);
+ // let statusMatch = true;
+ // if (this.status == '考核人数') statusMatch = true
+ // if (this.status == '进行中') statusMatch = item.status == 0
+ // if (this.status == '已结束') statusMatch = item.status == 1
+ // if (this.status == '面谈中') statusMatch = item.status == 2
+ // let levelMatch = !this.level || this.level && item.levelName == this.level
+ // console.log(levelMatch)
+ return employeeMatch
+ // return employeeMatch && statusMatch && levelMatch;
+ // 时间格式化
+ // 隐藏部门菜单
+ toggle() {
+ this.isFold = !this.isFold
+ // 还原部门菜单
+ back() {
+ this.isLeftShow = true;
+ this.isRightShow = false;
+ getTemplateDetails(row) {
+ this.reviewId = row.reviewId
+ this.sendEmployeeId = row.employeeId
+ this.detailDialogVisible = true
+ // this.$emit('changeCurrentId', { currentId: '2', reviewId })
+ // 获取当前时间
+ getDefaultTime() {
+ const date = new Date();
+ this.currentYear = date.getFullYear()
+ this.currentMonth = date.getMonth() + 1; // 注意月份从0开始,所以需要加1
+ const now = new Date(); // 获取当前时间
+ let startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0); // 当前月的第一天
+ let endOfMonth = new Date(startOfMonth.getFullYear(), startOfMonth.getMonth() + 1, 0, 23, 59, 59); // 下个月的第一天的前一天,时间设置为 23:59:59
+ startOfMonth = this.$moment(startOfMonth).format('YYYY-MM-DD')
+ endOfMonth = this.$moment(endOfMonth).format('YYYY-MM-DD')
+ // console.log(startOfMonth); // 输出当前月的结束时间
+ // console.log(endOfMonth); // 输出当前月的结束时间
+ this.params.startDate = startOfMonth
+ this.params.endDate = endOfMonth
+ this.params.year = this.currentYear + ''
+ this.cycleValue = this.currentMonth + ''
+ // console.log("当前月" + this.cycleValue);
+ this.filteredData = []
+ this.tableData = []
+ let url = `/performance/statistics/reviews/${this.user_info.site_id}`
+ this.tableData = res.data.data.list;
+ this.filteredData.forEach(item => {
+ item.date = this.formatDate(item.startTime) + "至" + this.formatDate(item.endTime)
+ const employeeDetails = deptMap[item.employeeId];
+ item.department = employeeDetails.employee_detail.dept_list;
+ this.generalizeList[0].val = this.tableData.length // 总人数
+ this.generalizeList[1].val = this.tableData.filter(item => item.status == 1).length // 已结束人数
+ this.generalizeList[2].val = this.tableData.filter(item => item.status == 0).length // 进行中人数
+ this.generalizeList[3].val = this.tableData.filter(item => item.status == 2).length // 面谈中人数
+ let colorList = ['#5F7294', '#08EEA1', '#f56c6c', '#0887EE', '#92EE08', '#f56c6c']
+ obj.color = colorList[index]
+ // 导出表格
+ exportToExcel(tableName, elementName) {
+ if(!(this.filteredData && this.filteredData.length > 0)) return
+ this.downloadLoading = true;
+ // 如果未传入文件名,则使用当前时间戳
+ if (!tableName) {
+ tableName = new Date().getTime();
+ // 克隆表格 DOM,避免影响原表格
+ const tableDom = document.querySelector(elementName).cloneNode(true);
+ const tableHeader = tableDom.querySelector('.el-table__header-wrapper');
+ const tableBody = tableDom.querySelector('.el-table__body');
+ tableHeader.childNodes[0].append(tableBody.childNodes[1]);
+ // 获取表头 DOM
+ const headerDom = tableHeader.childNodes[0].querySelectorAll('th');
+ // 移除复选框列
+ if (headerDom[0].querySelector('.el-checkbox')) {
+ headerDom[0].remove();
+ // 移除操作列
+ for (let key in headerDom) {
+ if (headerDom[key].innerText === '操作') {
+ headerDom[key].remove();
+ // 清理表格中的复选框和按钮
+ const tableList = tableHeader.childNodes[0].childNodes[2].querySelectorAll('td');
+ for (let key = 0; key < tableList.length; key++) {
+ if (tableList[key].querySelector('.el-checkbox') || tableList[key].querySelector('.el-button')) {
+ tableList[key].remove();
+ // 使用 XLSX 将 DOM 转换为 Excel 文件
+ const webBook = XLSX.utils.table_to_book(tableHeader);
+ const webOut = XLSX.write(webBook, { bookType: 'xlsx', bookSST: true, type: 'array' });
+ FileSaver.saveAs(new Blob([webOut], { type: 'application/octet-stream' }), `${tableName}.xlsx`);
+ console.error(e, webOut);
+ // 打开OKR弹框
+ // 关闭绩效弹框回调事件
+ handleClose() {}
+.cursor {
+ cursor: hand;
+ cursor: hand; /* 鼠标悬停时改为手型指针 */
+/* 默认鼠标样式为箭头指针 */
@@ -0,0 +1,210 @@
+ <el-dialog title="组织筛选-请选择指定人员" :visible.sync="organSelectDialog" width="800px" :before-close="dialogBeforeClose">
+ <el-row :gutter="10">
+ <el-col :span="12">
+ <el-input placeholder="输入关键字进行过滤" v-model="filterText" size="mini" style="margin-bottom: 10px;" />
+ <div class="dept-list">
+ <el-tree ref="tree" class="filter-tree" accordion :data="treeData" :props="defaultProps"
+ <el-input placeholder="输入关键字进行过滤" v-model="keyword" size="mini" style="margin-bottom: 10px;" />
+ <div class="user-list">
+ <div class="user-item flex-box-ce" v-for="user in userList" :key="user.id"
+ @click="radioValue = user.id">
+ <el-button @click="cancel()">取 消</el-button>
+ <el-button type="primary" @click="confirm()">确 定</el-button>
+ prop: 'organSelectDialog',
+ organSelectDialog: {
+ radioValue: ''
+ if(val) this.userList = this.userList.filter(user => user.name.includes(val))
+ this.get_dept_list()
+ dialogBeforeClose() {
+ this.$emit('close-dialog', false)
+ cancel() {
+ this.filterText = '';
+ this.keyword = '';
+ this.radioValue = '';
+ this.dialogBeforeClose();
+ confirm() {
+ this.$emit('confirm', this.radioValue)
+ .dept-list, .user-list {
+ height: 450px;
+ border-right: 1px solid #f1f1f1;
@@ -15,24 +15,38 @@
<template v-if="item.tasks && item.tasks.length > 0">
<div class="item-box" style="width: 100%;">
<div class="value-box" v-for="task in item.tasks" :key="task.taskId">
- <div v-if="['score'].includes(item.type)" style="width: 100%; margin-bottom: 5px;"
- :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
- {{ task.assigneeName }}
- 评分: {{ task.score || '--' }}
- <div v-else style="width: 100%; margin-bottom: 5px;"
+ <div v-if="['score'].includes(item.type)"
+ style="width: 100%; margin-bottom: 5px;"
+ :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+ {{ task.assigneeName }}
+ 评分: {{ task.score || '--' }}
+ <div v-else style="width: 100%; margin-bottom: 5px;"
- <div v-if="task.comment" style="width: 140px; color: #999;">
- <el-tooltip effect="dark" placement="right">
- <div v-html="task.comment" slot="content" style="max-width: 300px">
+ <div v-if="task.comment" style="width: 140px; color: #999;">
+ <el-tooltip effect="dark" placement="right">
+ <div v-html="task.comment" slot="content"
+ style="max-width: 300px">
+ <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+ <div class="files" v-if="task.files && task.files.length > 0">
+ <div class="title">附件列表</div>
+ <div class="file-item" v-for="file in task.files"
+ @click="onFilePreView(file)">
- <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
@@ -50,30 +64,42 @@
<template v-if="showData.tasks && showData.tasks.length > 0">
<div class="value-box" v-for="task in showData.tasks" :key="task.taskId">
- <div v-if="['scoreSelf', 'scoreEachOther'].includes(showData.type)"
- style="width: 100%; margin-bottom: 5px;"
- <div v-else-if="['resultInput'].includes(showData.type)"
- 结果值: {{ task.result || '--' }}
+ <div v-if="['scoreSelf', 'scoreEachOther'].includes(showData.type)"
+ <div v-else-if="['resultInput'].includes(showData.type)"
+ 结果值: {{ task.result || '--' }}
+ <el-tooltip v-if="task.comment" effect="dark" placement="right">
- <el-tooltip v-if="task.comment" effect="dark" placement="right">
- <div v-html="task.comment" slot="content"
- style="max-width: 300px">
@@ -116,14 +142,7 @@ export default {
default: () => { }
- data() {
filterType(v) {
if (v == 'leader') return v = "管理员"
@@ -131,23 +150,43 @@ export default {
if (v == 'post') return v = "岗位"
if (v == 'user') return v = "指定人员"
if (v == 'deptLeader') return v = "部门"
- watch: {
- 'dialogVisible'(v) {
- if (v) this.initData()
- this.initData();
- initData() {
handleCloseDialog() {
@@ -194,10 +233,41 @@ export default {
.value-box {
+<<<<<<< HEAD
+=======
+>>>>>>> c23c758ac5b114f2e4b714b9a5908ff46553bd90
@@ -0,0 +1,623 @@
+ <el-dialog :title="dialogTitle" center :visible.sync="showHandlerDialogVisible" width="600px"
+ :before-close="handleCloseDialog">
+ <ol class="steps scroll-bar" style="width: 100%; height: 500px; overflow-y: auto;">
+ <template v-for="node in showData.nodes">
+ <template v-if="node.type === 'targetConfirms'">
+ <template v-if="node.enable">
+ <li class="title" :class="showData.businessStatus === 'target_confirm' ? 'active' : ''">
+ 1.确认目标{{ showData.businessStatus === 'target_confirm' ? '(进行中)' : '' }}
+ </li>
+ <li class="info-box">
+ <div class="content" v-if="node.children && node.children.length > 0">
+ <div class="content-info" v-for="child in node.children" :key="child.id">
+ <div class="user-type" v-if="child.tasks && child.tasks.length > 0">
+ {{ child.assigneeType | filterType }}
+ <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
+ <div class="info" v-for="task in child.tasks" :key="task.taskId">
+ <div class="info-left">
+ <span
+ :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+ {{ task.state === 'created' ? '进行中' : '已完成' }},
+ <div class="info-right" v-if="task.comment">
+ <div class="content" v-else>
+ 未开始
+ <template v-if="!node.enable">
+ <li class="title">1.确认目标</li>
+ <div class="content">
+ 未启用
+ <template v-if="node.type === 'resultInput'">
+ <li class="title" :class="showData.businessStatus === 'result_input' ? 'active' : ''">
+ 2.录入结果{{ showData.businessStatus === 'result_input' ? '(进行中)' : '' }}
+ <div class="content" v-if="node.tasks && node.tasks.length > 0">
+ <div class="content-info">
+ <div class="user-type">
+ {{ node.assigneeType | filterType }}
+ <div class="task-info" v-for="task in node.tasks" :key="task.taskId">
+ {{ task.assigneeName }},结果值:{{ task.result || '--'}}
+ <div class="file-title">附件列表</div>
+ @click="openFileReview2(file)">
+ <div v-else class="content">
+ <li class="title">2.录入结果</li>
+ <template v-if="node.type === 'scoreSelf'">
+ <li class="title" :class="showData.businessStatus === 'score_self' ? 'active' : ''">
+ 3.自评{{ showData.businessStatus === 'score_self' ? '(进行中)' : '' }}
+ {{ task.assigneeName }},评分:{{ task.score || '--' }}
+ <li class="title">3.自评</li>
+ <template v-if="node.type === 'scoreEachOther'">
+ <li class="title" :class="showData.businessStatus === 'score_each_other' ? 'active' : ''">
+ 4.互评{{ showData.businessStatus === 'score_each_other' ? '(进行中)' : '' }}
+ <li class="title">4.互评</li>
+ <template v-if="node.type === 'scores'">
+ <li class="title" :class="showData.businessStatus === 'score' ? 'active' : ''">
+ 5.评分{{ showData.businessStatus === 'score' ? '(进行中)' : '' }}
+ <template v-for="task in child.tasks">
+ <div class="info" :key="task.taskId">
+ {{ task.assigneeName }},评分:{{ task.score }}
+ <li class="title">5.评分</li>
+ <template v-if="node.type === 'reviews'">
+ <li class="title" :class="showData.businessStatus === 'review' ? 'active' : ''">
+ 6.审批{{ showData.businessStatus === 'review' ? '(进行中)' : '' }}
+ <li class="title">6.审批</li>
+ <template v-if="node.type === 'cc'">
+ <li class="title" :class="showData.businessStatus === 'cc' ? 'active' : ''">
+ 7.抄送{{ showData.businessStatus === 'cc' ? '(进行中)' : '' }}
+ <li class="title">7.抄送</li>
+ </ol>
+ prop: 'showHandlerDialogVisible',
+ showHandlerDialogVisible: {
+ dialogTitle: {
+ type: String,
+ status: {
+ default: 'completed'
+ showData: {
+ type: [],
+ filterType(v) {
+ if (v == 'leader') return v = "管理员"
+ if (v == 'self') return v = "被考核人"
+ if (v == 'post') return v = "岗位"
+ if (v == 'user') return v = "指定人员"
+ if (v == 'deptLeader') return v = "部门"
+ openFileReview2(file) {
+ window.open(file, '_blank');
+.gray-color {
+ color: rgb(144, 147, 153);
+ background: rgb(244, 244, 245);
+ border-color: rgb(144, 147, 153);
+.show-data-box {
+ .item-list {
+ .content {
+ &-left {
+ &-status {}
+.steps {
+ margin: 0;
+ padding: 0;
+ .info-box {
+ margin: 0 20px;
+ border: 1px dashed #ccc;
+ &-info {
+ .user-type {
+ border-radius: 30px;
+ border: 1px solid;
+ border-color: #b3d8ff;
+ .task-info {
+ margin: 5px;
+ .info-right {
+.active {
+.files {
+ .file-title {
- <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="600px"
- :before-close="handleCloseDialog">
+ <el-dialog title="指标审批详情" center :visible.sync="showHandlerDialogVisible" width="600px"
+ :before-close="handleCloseDialog" append-to-body :close-on-press-escape="true" >
<ol class="steps scroll-bar" style="width: 100%; height: 500px; overflow-y: auto;">
<template v-for="node in showData.nodes">
@@ -85,8 +85,20 @@
</el-tooltip>
<div v-else class="content">
@@ -135,6 +147,16 @@
@@ -185,6 +207,17 @@
@@ -205,7 +238,7 @@
- <template v-if="node.type === 'score'">
<template v-if="node.enable">
<li class="title" :class="showData.businessStatus === 'score' ? 'active' : ''">
5.评分{{ showData.businessStatus === 'score' ? '(进行中)' : '' }}
@@ -218,24 +251,35 @@
{{ child.assigneeType | filterType }}
<div class="task-info" v-if="child.tasks && child.tasks.length > 0">
- <div class="info" v-for="task in child.tasks" :key="task.taskId">
- <div class="info-left">
- <span
- :class="task.state === 'created' ? 'orange-color' : 'green-color'">
- {{ task.state === 'created' ? '进行中' : '已完成' }},
- {{ task.assigneeName }},评分:{{ task.score }}
- <div class="info-right" v-if="task.comment">
@@ -272,24 +316,38 @@
@@ -310,7 +368,7 @@
- <template v-if="node.type === 'cc'">
+ <!-- <template v-if="node.type === 'cc'">
<li class="title" :class="showData.businessStatus === 'cc' ? 'active' : ''">
7.抄送{{ showData.businessStatus === 'cc' ? '(进行中)' : '' }}
@@ -359,7 +417,7 @@
+ </template> -->
@@ -368,6 +426,8 @@
</ol>
<div style="height: 50px;"></div>
@@ -375,11 +435,11 @@
- prop: 'dialogVisible',
- dialogVisible: {
type: Boolean,
default: false
@@ -396,14 +456,13 @@ export default {
@@ -411,28 +470,42 @@ export default {
- // targetComfirms() {
- // return this.showData.filters(item => item.type === "targetComfirms")
+ openFileReview2(url) {
@@ -552,4 +625,25 @@ export default {
@@ -0,0 +1,1136 @@
+ <div class="record-right" v-loading="loading">
+ <div class="title-container" style="height: 30px; justify-content: space-between;">
+ <div class="title flex-box-ce">
+ 考核详情
+ <div class="flex-box-ce" style="margin-left: 10px;">
+ <el-cascader ref="cascader" v-model="headValue" :options="options" :props="props"
+ :placeholder="placeholder" :no-data-text="noDataText"></el-cascader>
+ <EditScoreList :scoreList="scoreList" @confirm="onConfrim" />
+ <el-button type="primary" @click="exportToExcel('mytable', '结果分析')">导出明细</el-button>
+ <div class="info-box">
+ <div style="font-size: 16px; font-weight: 600;">等级分布</div>
+ <div style="font-size: 16px; font-weight: 600;">正态分布</div>
+ <div v-for="item in scoreList" class="score-item heartBeat animated">
+ <div class="score-item-num">{{users.filter(user => user.scoreResult === item.name).length}}
+ <el-select v-model="deptIds" clearable multiple placeholder="请选择部门"
+ style="width: 300px; margin-bottom: 10px;" @change="changeDeptName">
+ <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"></el-option>
+ <el-table v-if="filterUsers && filterUsers.length > 0" :data="filterUsers" style="width: 100%; "
+ :header-cell-style="{ background: '#f5f7fa' }" :max-height="600">
+ <el-table-column prop="level" label="评级">
+ 未评级
+ {{ scope.row.level }}
+ @click="getDetails(scope.row.reviewId, scope.row.employeeId)">查看详情</el-link>
+ <el-table id="mytable" :data="filterTableData1" stripe style="width: 100%" border
+ <el-table-column prop="result" label="实际值" align="center">
+ <span v-if="scope.row.result !== null">
+ {{ `${scope.row.result} ${scope.row.unit ? scope.row.unit : ''}` }}
+ <el-table-column prop="different" label="差距" align="center">
+ <el-table :data="filterTableData2" stripe style="width: 100%" border
+ <!-- 员工绩效详情 -->
+import EditScoreList from './ExamineRecord/RightEamineComp/EditScoreList'
+ { value: '0', label: '未定义', leaf: false, children: [] },
+ noDataText: '暂无数据',
+ reviewId: '',
+ deptIds: [],
+ rank: "",
+ rankList: [],
+ filterUsers: [],
+ filterTableData1: [],
+ filterTableData2: [],
+ deptName(v) {},
+ str += dept.name + ","
+ if (this.detailInfo) this.initData();
+ this.getRecords('4'); // 优先获取当月最新考核数据 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
+ let index = parseInt(4 - cycle)
+ this.options[index].children = items
+ this.headValue = [cycle + '', this.options[index].children[[0]].value]
+ this.$axiosUser("get", url, { value: this.headValue[1] }).then(res => {
+ handleClose() { },
+ changeDeptName(v) {
+ this.filterUsers = this.users.filter((item) => {
+ const departmentMatch = this.deptIds.length === 0 || this.deptIds.some((depId) => item.departments.some(dep => dep.id == depId));
+ return departmentMatch;
+ this.filterTableData1 = this.tableData1.filter((item) => {
+ this.filterTableData2 = this.tableData2.filter((item) => {
+ this.deptIds = [];
+ this.deptList = [];
+ this.users = [];
+ this.filterUsers = [];
+ this.isShow = false
+ this.filterTableData1 = [];
+ this.filterTableData2 = [];
+ let { indicators, startTime, endTime, distribution: { items }, users } = this.detailInfo
+ if (user.employeeId == item.employeeId) {
+ item.departments = user.departments
+ this.filterTableData1 = this.tableData1
+ if (this.users && this.users.length > 0) {
+ // this.rankList.push(user.level || '未评分')
+ this.isShow = true
+ this.filterUsers = cloneDeep(this.users)
+ this.initDeptList();
+ initDeptList() {
+ if (user.departments && user.departments.length > 0) {
+ user.departments.forEach(dep => {
+ this.deptList.push({ id: dep.id, name: dep.name })
+ this.deptList = Array.from(new Set(this.deptList.map(JSON.stringify))).map(JSON.parse);
+ window.onresize = myChart.resize;
+ getDetails(reviewId, employeeId) {
+ this.reviewId = reviewId
+ this.sendEmployeeId = employeeId
+ // this.$bus.$emit('changeCurrentId', { currentId: '2', reviewId, employeeId })
+ onConfrim(items) {
+ if (!(items && items.length > 0)) return
+ this.filterUsers = []
+ this.scoreList = items
+ console.log(this.filterUsers);
+ departments: [],
+ group.departments = indicator.departments; // 单位
+ group.standardCount = group.standardCount;
+ let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100);
+ this.filterTableData2 = this.tableData2
+ width: 8px;
+ height: 8px;
+ flex: 0 0 calc((100% - 100px) / 4);
@@ -226,9 +226,33 @@ export default {
min-height: calc(100vh - 210px);
- overflow: auto;
padding: 20px;
.title {
@@ -7,8 +7,7 @@
<el-form ref="form" label-width="80px" size="small">
<el-form-item label="启用">
- <el-switch v-model="currentNode.enable"
- :disabled="dialogTitle == '评分' || dialogTitle == '审批'"></el-switch>
+ <el-switch v-model="currentNode.enable" :disabled="dialogTitle == '评分'"></el-switch>
</el-form-item>
<div v-if="hasChildren" class="handler-list">
@@ -46,71 +45,91 @@
<el-form-item
v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'leader'">
- <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
- :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
+ <el-select :key="idGeneral()" v-model="currentNode.children[childIndex].leaderLevel"
+ placeholder="请选择管理员" :disabled="!currentNode.enable" filterable style="width: 300px;"
+ @change="changeManagerIds">
<el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
:value="item.value" style="width: 300px;"></el-option>
v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'user'">
- <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+ <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
:disabled="!currentNode.enable" filterable style="width: 300px;"
@change="changeEmployeeIds">
<el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
style="width: 300px;"></el-option>
v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'post'">
- <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+ <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
:disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
<el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'deptLeader'">
- <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
- :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
- @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+ <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+ style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+ filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
- <el-form-item
- v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'leader'">
+ <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'leader'">
+ <el-select :key="idGeneral()" v-model="currentNode.leaderLevel" placeholder="请选择管理员"
:disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
<el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'user'">
- <el-select v-model="currentNode.userIds" placeholder="请选择指定人员" multiple
+ <el-select :key="idGeneral()" v-model="currentNode.userIds" placeholder="请选择指定人员" multiple
<el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'post'">
- <el-select v-model="currentNode.postIds" placeholder="请选择岗位" multiple
+ <el-select :key="idGeneral()" v-model="currentNode.postIds" placeholder="请选择岗位" multiple
v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'deptLeader'">
- <el-cascader ref="deptSelectRef" v-model="currentNode.deptIds" size="small"
+ <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="currentNode.deptIds" size="small"
style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
<el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" label="多人时">
@@ -130,6 +149,27 @@
</el-radio-group>
+ <div v-if="['确认目标', '评分', '审批'].includes(dialogTitle)"
+ style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+ <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+ <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+ <div v-if="['录入结果', '抄送'].includes(dialogTitle)"
<el-form-item v-if="['评分'].includes(dialogTitle)" label="权重">
<el-input v-model="currentNode.children[childIndex].weight" :disabled="!currentNode.enable"
style="width: 200px;">
@@ -137,20 +177,20 @@
</el-input>
- <el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" label="允许">
+ <el-form-item v-if="['确认目标'].includes(dialogTitle)" label="允许">
<el-checkbox-group v-model="currentNode.children[childIndex].allows"
:disabled="!currentNode.enable">
- <el-checkbox-button label="edit" v-if="dialogTitle == '确认目标'">修改指标</el-checkbox-button>
- <el-checkbox-button label="transfer"
- v-if="['确认目标', '评分', '审批'].includes(dialogTitle)">转交</el-checkbox-button>
+ <!-- <el-checkbox-button label="transfer"
+ v-if="['确认目标', '评分', '审批'].includes(dialogTitle)">转交</el-checkbox-button> -->
</el-checkbox-group>
- <el-form-item v-if="['录入结果'].includes(dialogTitle)" label="允许">
+ <!-- <el-form-item v-if="['录入结果'].includes(dialogTitle)" label="允许">
<el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
<el-checkbox-button label="transfer">转交</el-checkbox-button>
- </el-form-item>
</el-form>
@@ -278,6 +318,18 @@ export default {
+ var s = [];
+ var hexDigits = "0123456789abcdef";
+ for (var i = 0; i < 36; i++) {
+ s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+ s[14] = "4"; // 代表UUID版本
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+ s[8] = s[13] = s[18] = s[23] = "-";
+ var uuid = s.join("");
+ return uuid;
initData() {
this.selected_manager_ids = ''
this.selected_employee_ids = [];
@@ -347,12 +399,13 @@ export default {
// 选择部门
// 获取当前选中的节点
- const checkedNodes = that.$refs.deptSelectRef.getCheckedNodes();
this.selected_dept_ids = checkedNodes.map((node) => node.value);
if (this.hasChildren) {
- this.currentNode.children[that.childIndex].assigneeIds = []
- this.currentNode.children[that.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+ this.currentNode.children[this.childIndex].assigneeIds = []
+ this.currentNode.children[this.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
this.currentNode.assigneeIds = []
this.currentNode.assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
@@ -412,6 +465,13 @@ export default {
type = "review"
+ let assigneeTypes = ['self', 'leader', 'user', 'post', 'deptLeader'];
+ let childrenAssigneeTypes = this.currentNode.children.map(item => item.assigneeType);
+ // 找出 array1 中有而 array2 中没有的元素
+ const diff1 = assigneeTypes.filter(item => !childrenAssigneeTypes.includes(item));
+ let assigneeType = diff1 && diff1.length > 0 ? diff1[0] : ''
+ if (!assigneeType) return
let obj = {
allows: [],
assigneeIds: [],
@@ -454,7 +514,6 @@ export default {
let data = this.parseNode(this.currentNode)
let url = `/performance/template/node/${this.user_info.site_id}/${this.templateId}`
- console.log(res)
if (res.code == 1) {
this.$message.success("操作成功");
@@ -1,7 +1,6 @@
- <el-dialog :title="dialogTitle" center :visible.sync="reviews" width="600px"
+ <el-dialog :title="dialogTitle" center :visible.sync="reviews" width="600px" :before-close="handleCloseDialog">
<div v-if="currentNode">
@@ -9,8 +8,7 @@
<el-form-item :label="formLabel">
- <el-radio-group v-model="currentNode.assigneeType"
- :disabled="!currentNode.enable">
<el-radio-button label="leader">管理员</el-radio-button>
<el-radio-button label="user">指定人员</el-radio-button>
<el-radio-button label="self">被考核人</el-radio-button>
@@ -21,15 +19,15 @@
<el-form-item v-if="currentNode.assigneeType === 'leader'">
- <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
- filterable style="width: 300px;" @change="changeManagerIds">
+ <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+ :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
<el-form-item v-if="currentNode.assigneeType === 'user'">
@@ -38,7 +36,7 @@
<el-form-item v-if="currentNode.assigneeType === 'post'">
@@ -46,9 +44,9 @@
<el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
@@ -176,6 +174,18 @@ export default {
this.selectNodes.forEach(select => {
// 正在操作的节点
@@ -188,8 +198,6 @@ export default {
this.selected_employee_ids = this.currentNode.assigneeType === 'user' ? this.currentNode.assigneeIds : [];
this.selected_post_ids = this.currentNode.assigneeType === 'post' ? this.currentNode.assigneeIds : [];
this.selected_dept_ids = this.currentNode.assigneeType === 'deptLeader' ? this.currentNode.assigneeIds : [];
- console.log("表单信息");
- console.log(this.currentNode);
@@ -284,7 +292,6 @@ export default {
if (select.type == this.nodeType) {
select = this.currentNode
- console.log(select)
nodes.push(select)
@@ -292,7 +299,6 @@ export default {
indicatorId: this.indicatorId, // 指标ID
nodes
- console.log(data)
this.$emit("onConfirm", data);
this.handleCloseDialog();
@@ -1,7 +1,4 @@
- <!-- <el-dialog title="考核分类明细" center :visible.sync="cateDetailsDialog" width="500px" :before-close="handleCloseDialog">
- </el-dialog> -->
<div style="padding: 0 10px; box-sizing: border-box;">
<el-button type="primary" size="mini" @click="addData()">添加分类</el-button>
@@ -11,19 +8,23 @@
<el-table :data="cateList" style="width: 100%; margin-top: 10px;" stripe border
- @selection-change="handleSelectChange" size="small" :header-cell-style="{ background: '#f5f7fa' }">
- <el-table-column type="selection" align="center"></el-table-column>
- <el-table-column prop="name" label="分类名称" align="center" min-width="150">
- <el-input v-model="scope.row.name" placeholder="分类名称" clearable
- @blur="handleEdit('name', scope.row.name, scope.row.cateId)"></el-input>
+ @selection-change="handleSelectChange" size="small" :header-cell-style="{ background: '#f5f7fa' }"
+ height="450px">
+ <!-- <el-table-column prop="name" label="分类名称" align="center" min-width="150">
<el-table-column prop="description" label="分类描述" align="center" min-width="200">
- <el-input type="textarea" v-model="scope.row.description" placeholder="分类描述" clearable
- @blur="handleEdit('desc', scope.row.description, scope.row.cateId)" cols="4"></el-input>
+ <el-tooltip v-if="scope.row.description" class="item" effect="dark" placement="top">
+ <div v-html="scope.row.description" slot="content"
+ <div class="oneLine" @click="editDescription(scope.row.description, scope.row.cateId)">
+ {{ scope.row.description }}
+ @click="editDescription(scope.row.description, scope.row.cateId)">编辑分类描述</el-button>
@@ -34,12 +35,20 @@
+ <!-- 编辑分类描述组件 -->
+ <EditDescriptionComp v-if="showEditDescription" v-model="showEditDescription" :content="description"
+ :cate-id="cateId" @finishEdit="finishEditContent" />
+import EditDescriptionComp from "./EditDescription" // 编辑分类描述组件
+ EditDescriptionComp
@@ -47,8 +56,11 @@ export default {
+ cateId: "",
cateList: [],
+ showEditDescription: false,
+ description: ""
@@ -56,12 +68,12 @@ export default {
cateDetailsDialog(v) {
- if (v) this.getCateList()
// 考核分类列表
@@ -92,6 +104,11 @@ export default {
url = `/performance/cate/${prop}/${this.user_info.site_id}`;
if (res.code == 0) return this.$message.error(res.msg || '操作失败')
+ let dataIndex = this.cateList.findIndex(cate => cate.cateId == cateId);
+ let replaceData = res.data;
+ this.cateList.splice(dataIndex, 1, replaceData);
// 无ID新增
@@ -110,14 +127,7 @@ export default {
// 创建分类
- // let url = `/performance/cate/create/${this.user_info.site_id}`,
let data = { name: '', description: '' };
- // this.$http.post(url, data).then(res => {
- // if (res.code == 1) {
- // this.getCateList();
- // else return this.$message.error(res.msg || '操作失败')
- // })
this.cateList.push(data)
@@ -149,11 +159,22 @@ export default {
delCateApi(item) {
let url = `/performance/cate/remove/${this.user_info.site_id}/${item.cateId}`
return this.$axiosUser('post', url).then(res => {
- // if (res.data.code == 1) this.get_table_data();
return res.data
+ editDescription(content, cateId) {
+ this.description = content
+ this.cateId = cateId;
+ this.showEditDescription = true
+ finishEditContent(description) {
+ this.handleEdit('desc', description, this.cateId)
this.getCateList();
@@ -169,4 +190,13 @@ export default {
-</script>
@@ -0,0 +1,91 @@
+ <el-dialog title="编辑规则" center :visible.sync="showEditDescription" width="600px" :before-close="handleCloseDialog" append-to-body>
+ <div class="flex-box-ce" style="justify-content: center;" >
+ <el-input type="textarea" v-model="editContent" placeholder="编辑分类描述(按回车可以添加行号)" clearable :rows="10"
+ @keyup.enter.native="addLineNumber()"></el-input>
+ <el-button type="primary" @click="handleEdit()" size="small">确 定</el-button>
+ <el-button @click="cancel" size="small">取 消</el-button>
+ prop: 'showEditDescription',
+ showEditDescription: {
+ indicatorId: {
+ type: String | Number,
+ content: {
+ editContent: "",
+ showEditContent(v) {
+ if (v) this.editContent = this.content
+ this.editContent = this.content
+ addLineNumber() {
+ let content = this.editContent
+ const lines = content.split('\n').length
+ if (lines === 2 && !content.startsWith('1.')) {
+ content = '1.' + content
+ content = content + `${lines}.`
+ this.editContent = content;
+ handleEdit() {
+ this.$emit('finishEdit', this.editContent);
+ submit() {
+ this.handleCloseDialog()
+<style scoped="scoped" lang="scss"></style>
@@ -1,10 +1,10 @@
- <el-dialog v-if="showFormula" title="计算公式" center :visible.sync="showFormula" width="60%" :before-close="handleCloseDialog">
+ <el-dialog v-if="showFormula" title="计算公式" center :visible.sync="showFormula" width="60%" append-to-body :before-close="handleCloseDialog">
<!-- 公式列表 -->
<div v-if="tagList && tagList.length > 0" class="handler-list" style="height: 50px;">
- <el-tag v-for="(item, index) in tagList" :type="currentIndex === index ? '' : 'info'" :key="index" :closable="isEdit"
+ <el-tag class="tags" v-for="(item, index) in tagList" :type="currentIndex === index ? '' : 'info'" :key="index" :closable="isEdit"
@close="handleTagDelete(index)" @click="handleTagClick(index)">
{{ '公式' + (index + 1) }}
@@ -199,7 +199,6 @@ export default {
index.push(2);
this.panels = Object.keys(index);
- console.log(this.expressionsProps);
this.tagList = this.expressionsProps.map(prop => {
id: idGenerator(),
@@ -308,12 +307,18 @@ export default {
.no-bg {
background-color: transparent;
.blue-bg {
background-color: #2196F3;
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
.black-border {
@@ -335,6 +340,9 @@ export default {
.el-tag {
margin: 0 10px 10px 0;
.container {
@@ -411,6 +419,7 @@ export default {
transition: all 0.3s linear;
&:hover {
font-size: 20px;
@@ -250,8 +250,6 @@ export default {
this.form = this.currentNode;
break;
- console.log(this.form);
// 回显选中的管理者
if (this.form.assigneeType === 'leader') {
if (this.form.leaderLevel) {
@@ -610,7 +608,10 @@ export default {
console.log(data)
@@ -1,66 +1,63 @@
- <el-dialog :title="dialogTitle" center :visible.sync="interview" width="600px"
- <div v-if="currentNode" class="flex-box-ce" style="flex-direction: column;">
- <el-form ref="form" label-width="80px" size="small">
- <slot></slot>
- <el-form-item label="启用">
- <el-switch v-model="currentNode.enable"></el-switch>
- <el-form-item :label="formLabel">
- <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
- <el-radio-button label="leader">管理员</el-radio-button>
- <el-radio-button label="user">指定人员</el-radio-button>
- <el-radio-button label="self">被考核人</el-radio-button>
- <el-radio-button label="post">岗位</el-radio-button>
- <el-radio-button label="deptLeader">部门</el-radio-button>
- </el-radio-group>
- <el-form-item v-if="currentNode.assigneeType === 'leader'">
- <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
- :value="item.value" style="width: 300px;"></el-option>
- <el-form-item v-if="currentNode.assigneeType === 'user'">
- :disabled="!currentNode.enable" filterable style="width: 300px;"
- @change="changeEmployeeIds">
- <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
- style="width: 300px;"></el-option>
- <el-form-item v-if="currentNode.assigneeType === 'post'">
- :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
- <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
- <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
- </el-form>
- <div slot="footer">
- <el-button type="primary" @click="submitForm" size="small">确 定</el-button>
- <el-button @click="handleCloseDialog" size="small">取 消</el-button>
+ <div v-if="currentNode" class="flex-box-ce" style="flex-direction: column;">
+ <el-form ref="form" label-width="120px" size="small" label-position="right">
+ <slot></slot>
+ <el-form-item :label="formLabel">
+ :disabled="!currentNode.enable" filterable style="width: 300px;"
+ @change="changeEmployeeIds">
+ <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+ :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
+ <!-- <div slot="footer">
+ <el-button type="primary" @click="submitForm" size="small">发起考核</el-button>
+ <el-button @click="handleCloseDialog" size="small">取 消</el-button>
@@ -172,8 +169,6 @@ export default {
assigneeType: "self",
multipleType: "or"
@@ -43,7 +43,7 @@
<el-form-item v-if="form.cycleType == 0" label="结束日期" prop="endDate">
- <el-date-picker v-model=" form.endDate" type="date" value-format="yyyy-MM-dd"
+ <el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd"
placeholder="选择结束日期" style="width: 300px;">
@@ -125,8 +125,8 @@ function getQuarterDates(year, quarter) {
import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle'; //选择周期
-import CateDetails from "./CateDetails.vue" // 考核分类明细抽屉
-import InterviewFlow from "./InterviewFlow.vue" // 面谈弹框
import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
@@ -205,12 +205,12 @@ export default {
showPublish(v) {
@@ -229,7 +229,7 @@ export default {
// 选择考核分类
changeCateId(v) {
- console.log(v)
+ // console.log(v)
// 选择员工
changeEmployeeIds(v) {
@@ -353,7 +353,6 @@ export default {
// this.$emit('onConfirm', this.params);
// this.handleCloseDialog();
- console.log('error submit!!');
return false;
@@ -374,7 +373,7 @@ export default {
- this.$emit('onConfirm', this.params, this.selectedData);
@@ -409,6 +408,7 @@ export default {
line-height: 40px;
margin-bottom: 5px;
@@ -419,9 +419,9 @@ export default {
border-radius: 4px;
background: #f7f7f7;
cursor: pointer;
@@ -21,34 +21,44 @@
<el-form-item label="多人时">
@@ -59,12 +69,23 @@
+ <div
- <el-form-item label="允许">
- <el-checkbox-button label="transfer" >转交</el-checkbox-button>
@@ -182,11 +203,30 @@ export default {
if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+ console.log("操作节点")
+ this.currentNode.enable = false // 默认禁用
this.selected_manager_ids = 1;
this.selected_post_ids = [];
@@ -194,8 +234,6 @@ export default {
@@ -290,7 +328,6 @@ export default {
@@ -298,7 +335,6 @@ export default {
@@ -4,8 +4,7 @@
<div v-if="isShow">
<div class="handler-list">
@@ -31,34 +30,42 @@
<el-form-item v-if="currentNode.children[childIndex].assigneeType === 'leader'">
<el-form-item v-if="currentNode.children[childIndex].assigneeType === 'user'">
<el-form-item v-if="currentNode.children[childIndex].assigneeType === 'post'">
<el-form-item v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
@@ -70,12 +77,23 @@
@@ -202,6 +220,20 @@ export default {
this.isShow = false
@@ -218,8 +250,7 @@ export default {
this.selected_post_ids = child.assigneeType === 'post' ? child.assigneeIds : [];
this.selected_dept_ids = child.assigneeType === 'deptLeader' ? child.assigneeIds : [];
this.isShow = true
@@ -292,6 +323,13 @@ export default {
@@ -379,7 +417,6 @@ export default {
@@ -9,7 +9,7 @@
+ <!-- <el-form-item :label="formLabel">
<el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
@@ -49,22 +49,15 @@
<el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
:options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
@change="deptChange" :disabled="!currentNode.enable"></el-cascader>
- <el-form-item label="多人时">
- <el-radio-group v-model="currentNode.multipleType" :disabled="!currentNode.enable">
- <el-radio-button label="or">任一人确认(或签)</el-radio-button>
- <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
- <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
- <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
+ <!--
+ <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
+ <el-checkbox-button label="transfer" >转交</el-checkbox-button>
@@ -142,13 +135,13 @@ export default {
@@ -170,7 +163,6 @@ export default {
@@ -31,34 +31,46 @@
@@ -70,6 +82,17 @@
@@ -77,12 +100,12 @@
@@ -208,6 +231,20 @@ export default {
@@ -223,8 +260,6 @@ export default {
@@ -297,6 +332,12 @@ export default {
@@ -384,7 +425,6 @@ export default {
@@ -31,34 +31,42 @@
@@ -70,11 +78,22 @@
<el-form-item label="允许">
<el-checkbox-button label="edit">修改指标</el-checkbox-button>
- <el-checkbox-button label="transfer">转交</el-checkbox-button>
@@ -202,6 +221,20 @@ export default {
@@ -212,13 +245,14 @@ export default {
this.selected_dept_ids = [];
this.childIndex = this.tagIndex;
this.currentNode.children.forEach(child => {
this.selected_employee_ids = child.assigneeType === 'user' ? child.assigneeIds : [];
@@ -291,10 +325,16 @@ export default {
- assigneeType: 'self',
+ assigneeType: assigneeType,
children: [],
enable: true,
id,
@@ -344,6 +384,7 @@ export default {
+ this.$emit('closeDialog')
submitForm() {
@@ -380,7 +421,6 @@ export default {
@@ -6,7 +6,7 @@
:close-on-click-modal="false"
:close-on-press-escape="false"
center
- title="通过模板发布考核"
+ :title="title"
width="600px"
@@ -89,8 +89,7 @@
v-if="publishData.cycleType === '2' || publishData.cycleType === '3'"
label="日期区间"
- <SelectCircle :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm"
- :id="1">
+ <SelectCircle :id="1" :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm" >
<div class="flex-box-ce cursor">
<div>{{ dateParameter.year }}</div>
<div style="margin: 0 10px;">{{ dateParameter.name }}</div>
@@ -165,6 +164,7 @@
type="success"
:title="item.name"
:closable="false"
/>
@@ -182,12 +182,12 @@
align="middle"
style="height: 30px;"
- <el-col :span="2">
+ <el-col :span="5">
type="text"
icon="el-icon-plus"
@click.stop="showTemplateSearch = true"
+ >添加考核表</el-button>
<el-table
@@ -234,7 +234,7 @@
<el-form
label-width="100px"
<el-switch v-model="interviewNode.enable"></el-switch>
<el-form-item label="处理人">
@@ -347,12 +347,13 @@
-import Template from "../../examine/components/Template.vue";
import moment from "moment/moment";
import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle';
import TargetSearch from "@/performance/views/assessManagement/TargetSearch";
import TemplateSelector from "./TemplateSelector.vue";
-import PerEmployeeSelector from "./PerEmployeeSelector.vue";
name: 'TemplateMixedPublish',
@@ -412,6 +413,7 @@ export default {
weight:'',
children:[],
levelOptions: [
{
value: 1,
@@ -609,6 +611,12 @@ export default {
this.interviewNode.leaderLevel = 1;
@@ -674,7 +682,9 @@ export default {
this.assigneePosts = [];
this.assigneeDept = [];
- initData(){
this.dataReset();
this.$refs['carousel'].setActiveItem(this.step);
@@ -1,29 +1,49 @@
- :visible="innerVisible"
- @close="handleClose"
- @open="initData"
- width="650px"
- title="选择模板"
- <el-transfer
- v-model="selectedValues"
- :data="templates"
- :titles="['未选模板', '已选模板']"
- ></el-transfer>
- <el-row type="flex" justify="end">
- <el-col :span="4">
- <el-button type="primary" @click="onConfirm">确定</el-button>
- </el-col>
- </el-row>
+ title="选择指标库">
+ <el-input v-model="searchValue" placeholder="请输入指标库名称"></el-input>
+ <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">指标库列表</div>
+ <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in templateList"
+ :key="item.templateId" @click="chooseTemplate(item)">
+ {{ item.title || '默认标题' }}
+ <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">已选指标库</div>
+ <el-button plain round size="mini" @click="clear">清 空</el-button>
+ {{ item.title || '默认标题' }}<i class="el-icon-close" @click="deleteItem(item, index)"></i>
name: "TemplateSelector",
props:{
@@ -38,46 +58,120 @@ export default {
userInfo: this.$userInfo(),
innerVisible: this.showVisible,
templateList:[],
- selectedValues:this.selectedTemplate,
+ selectedValues: this.selectedTemplate,
+ if (v) filterData = this.filterData.filter(item => item.title.includes(v))
+ this.templateList = filterData
showVisible(val) {
this.innerVisible = val;
- computed:{
- templates(){
- return this.templateList.map(item => {
- key:item.templateId,
- label:item.title
+ this.templateList.forEach(item => item.isCheck = true)
+ this.chooseExamineList = this.templateList
+ this.templateList.forEach(item => item.isCheck = false)
handleClose(){
this.$emit('update:showVisible',false)
initData(){
this.templateList = [];
- this.selectedValues = this.selectedTemplate;
+ this.filterData = []
+ this.selectExamineList = this.selectedTemplate;
this.$axiosUser("get", `/performance/template/list/self/${this.userInfo.site_id}`).then(res => {
this.templateList = res.data.data.list;
+ this.filterData = this.templateList
+ if (this.templateList && this.templateList.length > 0) {
+ let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.templateId == item.templateId)
- onConfirm(){
- let res = this.templateList.filter(item => this.selectedValues.includes(item.templateId)).map(item => {
- templateId:item.templateId,
- title:item.title
+ let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.templateId == item.templateId)
- this.$emit('confirm',res);
+ this.templateList.forEach(examine => {
+ if (examine.templateId == item.templateId) {
+ if (chooseExamine.templateId == examine.templateId) {
+ this.$emit('confirm', this.selectExamineList);
this.handleClose();
@@ -85,5 +179,86 @@ export default {
+ .package-list {
+ height: 400px;
+ height: 320px;
@@ -9,24 +9,38 @@
<div class="dialog-content" v-loading="loading">
<div class="dialog-content-left scroll-bar"
style="padding-bottom: 50px; min-height: 500px; overflow-y: auto;">
<!-- 附加属性 -->
- <div v-if="dialogData.expand && dialogData.expand.length > 0" class="tips">自定义属性(只有通过excel上传发布的指标才有)
+ <div class="append-properties" v-if="dialogData.expand && dialogData.expand.length > 0">
+ <div class="tips">
+ 自定义属性(只有通过excel上传发布的指标才有)
+ <table>
+ <tr>
+ <td v-for="item in dialogData.expand" :key="item.p">
+ <div v-html="item.k" slot="content" style="max-width: 300px;"></div>
+ <div class="oneLine">{{ item.k }}</div>
+ <div v-html="item.v" slot="content" style="max-width: 300px;"></div>
+ <div class="oneLine">{{ item.v }}</div>
- <table v-if="dialogData.expand && dialogData.expand.length > 0">
- <td v-for="item in dialogData.expand" :key="item.p">{{ item.k }}</td>
- <td v-for="item in dialogData.expand" :key="item.p">{{ item.v }}</td>
+ <!-- 考核基本信息 -->
<div class="base-info-box">
<div class="label">
<i class="el-icon-user"></i>
- 考核人
+ 被考核人
<div class="value">
{{ dialogData.task && dialogData.task.assigneeName || '--' }}
@@ -81,7 +95,7 @@
指标
- {{ dialogData.title || '--'}}
+ {{ dialogData.title || '--' }}
@@ -146,59 +160,48 @@
--
<!-- 录入信息 -->
<div class="dialog-content-right">
<div class="data-box" v-if="activeName == 1">
<el-input v-model="formData && formData.title" placeholder="指标标题" style="width: 300px;"
- clearable @blur="handleEdit('title')" :disabled="!isEdit">
+ clearable @focus="handleFocus" @blur="handleEdit('title')" :disabled="!isEdit || loading">
<template slot="prepend">指标</template>
<div class="data-box" v-if="activeName == 1" style="height: 120px;">
<el-input type="textarea" v-model="formData && formData.content" placeholder="规则说明"
- style="width: 300px;" rows="4" cols="4" clearable @blur="handleEdit('content')"
- :disabled="!isEdit">
+ style="width: 300px;" rows="4" cols="4" clearable @focus="handleFocus"
+ @blur="handleEdit('content')" :disabled="!isEdit || loading">
<template slot="prepend">规则</template>
- <el-input placeholder="目标值(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
- v-model="formData && formData.target" style="width: 300px;" :disabled="!isEdit" clearable
+ <el-input placeholder="目标值(数字)" @input="handleInputTarget" v-model="formData && formData.target"
+ style="width: 300px;" :disabled="!isEdit || loading" clearable @focus="handleFocus"
@blur="handleEdit('target')">
<template slot="prepend">目标值</template>
+ <!-- 单位编辑 -->
<el-input placeholder="单位" v-model="formData && formData.unit" style="width: 300px;" clearable
- @blur="handleEdit('unit')" :disabled="!isEdit">
+ @focus="handleFocus" @blur="handleEdit('unit')" :disabled="!isEdit || loading">
<template slot="prepend">单位</template>
- <!-- <div class="data-box" v-if="activeName == 1">
- <div class="label">
- <i class="el-icon-s-check"></i>
- 权重
- <div class="value">
- <el-input placeholder="权重" v-model="formData.weight" style="width: 300px;" clearable
- @blur="handleEdit('weight')"></el-input>
<div class="data-box" v-if="activeName == 2">
<el-input placeholder="结果值" v-model="formData && formData.result" style="width: 300px;"
- clearable @blur="handleEdit('result')">
+ @focus="handleFocus" @blur="handleEdit('result')" clearable :disabled="loading">
<template slot="prepend">结果值</template>
@@ -206,19 +209,18 @@
<!-- 自评 -->
<div class="data-box" v-if="activeName == 3">
- <el-input :controls="false" placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+ <el-input :controls="false" placeholder="评分(数字)" @input="handleInputScore"
v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
- @blur="handleEdit('scoreSelf')">
+ @focus="handleFocus" @blur="handleEdit('scoreSelf')" :disabled="loading">
<template slot="prepend">评分</template>
<!-- 互评 -->
<div class="data-box" v-if="activeName == 4">
- <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+ <el-input placeholder="评分(数字)" @input="handleInputScore"
- @blur="handleEdit('scoreEachOther')">
+ @focus="handleFocus" @blur="handleEdit('scoreEachOther')" :disabled="loading">
@@ -230,11 +232,12 @@
<div v-if="dialogData && dialogData.scoreExpression" class="flex-box-ce"
style="margin-bottom: 10px; ">
<div class="flex-box-ce">系统评分: {{ dialogData.scoreExpression }} </div>
- <el-link type="primary" style="margin-left: 20px;" @click="handleScore()">立即评分</el-link>
+ <el-link type="primary" :disabled="loading" style="margin-left: 20px;"
+ @click="handleScore()">立即评分</el-link>
- @blur="handleEdit('score')">
+ @focus="handleFocus" @blur="handleEdit('score')" :disabled="loading">
@@ -247,19 +250,33 @@
- <div class="data-box">
+ <div class="data-box" style="height: 160px;">
<el-input type="textarea" v-model="comment" placeholder="请输入意见" style="width: 300px;" clearable
- rows="5" cols="3" @blur="confirmCommentDialog(1)"></el-input>
+ rows="5" cols="3" @focus="handleFocus" @blur="confirmCommentDialog(1)"
+ :disabled="loading"></el-input>
+ <uploadOss v-if="activeName > 1 && activeName <= 6" :key="Date.now()" class="avatar-uploader"
+ :headers="$xtoken" :show-file-list="true" :multiple="true" :limit="5" :accept="acceptFile"
+ :file-list="fileList" :action="$action" :on-preview="onFilePreView" :on-success="handleSuccess"
+ :on-remove="handleRemove" :before-upload="beforeFilesUpload">
+ <el-button class="primaryBtn" icon="el-icon-paperclip" plain size="mini">上传附件</el-button>
+ <!-- <div slot="tip" class="el-upload__tip">(支持上传xlsx,xls,doc,docx,pdf,txt,png,jpeg,jpg,gif, 大小不能超过5M)</div> -->
+ </uploadOss>
+ <el-button v-if="activeName > 1 && activeName <= 6" class="primaryBtn" icon="el-icon-check"
+ type="primary" size="mini" @click="comfirmUploadFiles()"
+ style="margin-top: 10px;">提交附件</el-button>
- <el-button v-if="activeName == 6" type="danger" @click="reset">驳 回</el-button>
- <el-button type="primary" @click="confirmCommentDialog(2)">结束任务</el-button>
+ <el-button v-if="activeName == 6" type="danger" @click="reset" :disabled="isInputFocused">驳
+ 回</el-button>
+ <el-button type="primary" @click="confirmCommentDialog(2)" :disabled="isInputFocused">提 交</el-button>
<!-- 输入意见 -->
@@ -273,7 +290,21 @@
+ <!-- 各节点处理详情 -->
<DetailsDialog v-model="detailsDialogVisible" :dialog-data="dialogData" />
+ <!-- 图片查看 -->
+ <el-dialog title="图片查看" :visible.sync="isShowImg" width="50%">
+ <div class="flex-box-ce flex-center-center" style="width: 100%; height: 100%;">
+ <div style="width: 500px;">
+ <img :src="imgUrl" style="width: 100%;" />
+ <span slot="footer" class="dialog-footer">
+ <el-button @click="closePreview">关闭</el-button>
@@ -284,9 +315,12 @@ import moment from 'moment';
import cloneDeep from 'lodash.clonedeep';
import DetailsDialog from "./EditNodeDialog.vue"
+import uploadOss from '@/components/upload';
+import { _debounce } from '@/utils/auth';
- DetailsDialog
+ DetailsDialog,
+ uploadOss
@@ -314,12 +348,13 @@ export default {
dialogData(v) {
if (this.dialogData) {
- this.formData = cloneDeep(this.dialogData)
- this.comment = this.formData.task.comment
+ this.formData = cloneDeep(this.dialogData);
+ this.comment = this.formData.task.comment || '';
formatCycleType(val) {
if (val == 0) return '未定义'
@@ -337,40 +372,49 @@ export default {
+ isInputFocused: false,
commentDialog: false,
+ currentInput: null, // 当前操作的 el-input 的标识
comment: "",
reject: false,
formData: null, // 用于提交的数据,指标,目标,规则,
baseInfo: null, // 用于展示的数据,指标,目标,规则,权重,标题
- detailsDialogVisible: false
+ detailsDialogVisible: false,
+ acceptFile: '.jpg,.jpeg,.png,.gif,.bmp,.pdf,.JPG,.JPEG,.PBG,.GIF,.BMP,.PDF',
+ fileList: [], // 附件列表
+ uploadFileList: [], // 上传成功的fileList
+ isShowImg: false, // 图片预览弹框
+ imgUrl: '', // 预览图片地址
+ isUploadFile: true,
- // 是否可以编辑
+ // 确认目标是否可以编辑
isEdit() {
- let children = [], selectChild = null;
+ let children = [], employeeIds = [];
+ let user_id = this.user_info.id.toString()
const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
- if (nodes && nodes.length > 0) {
- nodes.forEach(node => {
- if (node.type === 'targetConfirms') {
- children = node.children
+ if (nodes && nodes.length > 0)
+ children = nodes.find(node => node.type === 'targetConfirms').children
+ children.forEach(child => {
+ // 是否允许编辑
+ if (child.allows.includes('edit')) {
+ if (['leader', 'deptLeader', 'post', 'user'].includes(child.assigneeType)) {
+ if (child.tasks && child.tasks.length > 0) {
+ child.tasks.forEach(task => {
+ employeeIds.push(task.assignee)
- if (children && children.length > 0) {
- children.forEach(child => {
- if (child.assigneeIds && child.assigneeIds.length > 0) {
- child.assigneeIds.forEach(assigneeId => {
- if (assigneeId == this.user_info.id) {
- selectChild = child
+ employeeIds = [user_id]
- return selectChild && selectChild.allows && selectChild.allows.length > 0 && selectChild.allows.includes('edit')
+ return employeeIds && employeeIds.length > 0 && employeeIds.includes(user_id) ? true : false
// 整个审批的状态 0,进行中 1,已完成
reviewStatus() {
@@ -406,15 +450,59 @@ export default {
this.formData = cloneDeep(this.dialogData);
this.comment = this.formData && this.formData.task && this.formData.task.comment ? this.formData.task.comment : ''
+ // 监听全局 focus 和 blur 事件
+ document.addEventListener('focusin', this.handleGlobalFocus);
+ document.addEventListener('focusout', this.handleGlobalBlur);
+ // 移除全局事件监听器
+ document.removeEventListener('focusin', this.handleGlobalFocus);
+ document.removeEventListener('focusout', this.handleGlobalBlur);
+ handleInputTarget(value) {
+ // 使用正则表达式限制输入
+ const regex = /^([0-9])*$/; // 匹配非负整数
+ // 如果输入值不符合正则表达式,则恢复为之前的值
+ if (!regex.test(value)) {
+ value = ''; // 重置输入框的值
+ this.formData.task.score = 0
+ // 如果输入值符合正则表达式,更新绑定的值
+ this.formData.target = Number(value);
+ handleInputScore(value) {
+ if (this.formData && this.formData.task && this.formData.task.score)
+ this.formData.task.score = Number(value);
// 录入系统评分
handleScore() {
this.formData.task.score = this.dialogData.scoreExpression
+ this.formData = null
+ this.imgUrl = "";
+ this.isShowImg = false;
this.comment = '';
@@ -423,10 +511,32 @@ export default {
this.commentDialog = false;
+ handleFocus() {
+ // 当 el-input 获得焦点时,设置标志变量为 true
+ this.isInputFocused = true;
+ handleBlur() {
+ // 当 el-input 失去焦点时,设置标志变量为 false
+ this.isInputFocused = false;
+ handleGlobalFocus(event) {
+ // 检查触发事件的元素是否是 input
+ if (event.target.tagName.toLowerCase() === 'input') {
+ console.log('全局:有 input 获得焦点');
+ handleGlobalBlur(event) {
+ console.log('全局:input 失去焦点');
// 编辑节点
- handleEdit(type) {
- this.loading = true
+ handleEdit: _debounce(function(type) {
let { reviewIndicatorId } = this.dialogData
let { taskId, nodeType } = this.dialogData.task
let data = { taskId }
@@ -467,11 +577,14 @@ export default {
this.$emit('handleEditSuccess', data)
else this.$message.error(res.message || '操作失败');
- this.loading = false
+ this.handleBlur();
+ }, 100),
confirmCommentDialog(type) {
+ // 阻止事件冒泡,避免触发全局点击事件
+ // event.stopPropagation();
if(this.loading) return
let { reviewIndicatorId } = this.dialogData;
@@ -502,11 +615,21 @@ export default {
default:
taskId,
comment: this.comment,
complete: type == 1 ? false : true // false 暂存,true 提交
+ // 提交时,提醒用户要提交附件
+ if (type == 2) {
+ if (!this.isUploadFile) {
+ return this.$message.warning("上传了附件,记得提交哦")
if (type == 1) {
if (res.code == 1) { }
@@ -520,6 +643,8 @@ export default {
this.comment = "";
this.reject = false; // 驳回标识
+ this.uploadFileList = [];
+ this.fileList = [];
this.$message.error(res.message || '操作失败');
@@ -527,6 +652,109 @@ export default {
+ onFilePreView(file) {
+ this.isShowImg = true;
+ handleSuccess: _debounce(function (response, file, fileList) {
+ this.uploadFileList = fileList.map(item => {
+ return item.url;
+ this.isUploadFile = false;
+ }),
+ handleRemove(file, fileList) {
+ this.fileList = fileList;
+ if (!(this.fileList && this.fileList.length > 0)) {
+ this.isUploadFile = true;
+ }, 500);
+ beforeFilesUpload(file) {
+ const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
+ const isLt2M = file.size / 1024 / 1024 < 5;
+ let len = file.name.split('.').length - 1;
+ const $ext_name = file.name.split('.')[len];
+ let isFile = $ext_list.indexOf($ext_name) != -1;
+ if (!isLt2M) {
+ this.$message.error('文件大小不能超过 5MB!');
+ if (!isFile) {
+ this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
+ return isFile && isLt2M;
+ comfirmUploadFiles() {
+ let { reviewIndicatorId } = this.dialogData
+ let { taskId, nodeType } = this.dialogData.task
+ let url;
+ switch (nodeType) {
+ // 结果值附件录入
+ case 'resultInput':
+ url = `/performance/review/result/Input/result/file/${this.user_info.site_id}/${reviewIndicatorId}`
+ // 自评附件录入
+ case 'scoreSelf':
+ url = `/performance/review/score/self/files/${this.user_info.site_id}/${reviewIndicatorId}`
+ // 互评附件录入
+ case 'scoreEachOther':
+ url = `/performance/review/score/each/other/files/${this.user_info.site_id}/${reviewIndicatorId}`
+ // 评分附件录入
+ case 'score':
+ url = `/performance/review/score/files/${this.user_info.site_id}/${reviewIndicatorId}`
+ // 审批附件录入
+ case 'review':
+ url = `/performance/review/review/files/${this.user_info.site_id}/${reviewIndicatorId}`
+ taskId: taskId,
+ files: this.uploadFileList
+ let { code } = res
+ if (code == 1) return this.$message.success("上传附件成功")
+ else return this.$message.success("上传附件失败")
+ closePreview() {
// 驳回
reset() {
this.reject = true;
@@ -596,14 +824,91 @@ export default {
height: 450px;
- .tips {
- align-items: center;
- justify-content: center;
+ .append-properties {
+ .tips {
+ table {
+ table-layout: fixed;
+ border-collapse: collapse;
+ /* 合并表格边框 */
+ /* 设置表格边框样式和颜色 */
+ /* 设置表格外边距 */
+ background-color: #f8f8f8;
+ /* 设置表格背景颜色 */
+ /* 设置表格文字颜色 */
+ /* 设置表格文字居中 */
+ /* 设置表格行高 */
+ tr:nth-child(1) {
+ background-color: #f2f2f2;
+ /* 偶数行背景色 */
+ tr:nth-child(even) {
+ tr:nth-child(odd) {
+ /* 奇数行背景色 */
+ td {
.dialog-content-left, .dialog-content-right {
@@ -619,39 +924,6 @@ export default {
-table {
.base-info-box {