resultAnalysis.vue 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. <template>
  2. <div style="height: 100%;" :class="{ bg_fff: skeletonLoad }">
  3. <van-nav-bar title="结果分析" left-text="返回" left-arrow @click-left="$route_back" />
  4. <VanSkeleton :skeLoad="skeletonLoad">
  5. <template>
  6. <header>
  7. <!--
  8. <div class="selector" @click="statementTIme">
  9. <span>{{ selectPftiText }}</span>
  10. <van-icon name="arrow-down" />
  11. </div>
  12. -->
  13. <div style="border-bottom: 1px solid #f1f1f1;" class="selector flex-box-ce flex-center-center" @click="openPanel()">
  14. <icon name="YMPicker_item_icon" style="margin-right: 6px;width: 0.3rem;height: 0.3rem;" class="fontColorC"></icon>
  15. <div>{{ dateParameter.year }}年</div>
  16. <div style="margin: 0 6px;">{{ dateParameter.name }}</div>
  17. <van-icon name="arrow-down" />
  18. </div>
  19. </header>
  20. <scroller ref="me_scroller" class="all">
  21. <div style="padding-bottom: 1.5rem;">
  22. <div class="statementFoot">
  23. <van-tabs v-model="active" @change="statementTabs">
  24. <van-tab v-for="(item, index) in statementVanTabs" :key="index" :title="item.name"></van-tab>
  25. </van-tabs>
  26. <!-- 等级分布 -->
  27. <div class="statmentAnalysePie" v-show="active == 0">
  28. <div style="width: 7.5rem; height: 5rem;" id="statmentAnalysePie" ref="statmentAnalysePie"></div>
  29. <div class="PiePropNum">
  30. <div>总人数</div>
  31. <div>{{ userTotal }}人</div>
  32. </div>
  33. </div>
  34. <!-- 正态分布 -->
  35. <div v-show="active == 1">
  36. <div class="statementHnum flex-box-ce flex-d-wrap flex-d-center">
  37. <div class="Hnumvfor">
  38. <span class="smHnLtit">总人数</span>
  39. <br />
  40. <span class="smHnLnum">{{ userTotal }}</span>
  41. <br />
  42. <span{{ userComplete }}人已完成</span>
  43. </div>
  44. <div v-for="(item, index) in scoreList" class="Hnumvfor" :key="index">
  45. <span class="smHnLtit">{{ item.name }}</span>
  46. <br />
  47. <span class="smHnLnum">{{users.filter(user => user.scoreResult === item.name).length}}</span>
  48. <br />
  49. </div>
  50. </div>
  51. </div>
  52. <div>
  53. <div>
  54. <!-- <van-search placeholder="请输入姓名" v-model="keyword" @input="keyVal()" /> -->
  55. <div class="flex-box-ce">
  56. <div class="selectItem flex-2 font-flex-word" @click="showDept = true">
  57. <span>{{ deptName }}</span>
  58. <van-icon name="arrow-down" />
  59. </div>
  60. </div>
  61. <ol v-if="filterUsers && filterUsers.length > 0">
  62. <li @click="openDetail(item)" v-for="(item, index) in filterUsers" :key="index" class="flex-ce-box flex-d-center statmentperson" :style="index != 0 ? 'border-top: 0.02rem solid #e6e6e6;' : ''">
  63. <div class="flex-1 flex-box" style="align-items: center; justify-content: space-between;">
  64. <div class="score-result" >{{ item.scoreResult }}</div>
  65. <div class="flex-box-ce flex-1">
  66. <userImage :id="item.employeeId" :user_name="item.employeeName" fontSize="0.15" width="0.63rem" height="0.63rem" style="margin-top:.08rem;"></userImage>
  67. <div style="padding-left:.2rem;">
  68. <span style="font-size:.3rem;">{{ item.employeeName }}</span>
  69. <br />
  70. <span class="font-flex-word" style="width: 3.8rem;display:inline-block;" v-if="item.dept_list && item.dept_list.length > 0">
  71. <span style="font-size:.25rem;" v-for="(arr, att) in item.dept_list" :key="att">
  72. {{ arr.dept_name }}
  73. <span v-if="item.dept_list.length - att > 1">,</span>
  74. </span>
  75. </span>
  76. </div>
  77. </div>
  78. <div style="display: flex; align-items: center; flex-direction: column;">
  79. <div v-if="item.status == 0" class="review-status">进行中</div>
  80. <div v-if="item.status == 1" class="review-status" style="background-color: #FF9600;">已结束</div>
  81. <div style="display: flex; align-items: center; white-wrap: no-wrap; font-size:.26rem; text-align:center; overflow: hidden;">
  82. <span>{{ item.score == undefined || item.score == null || item.score == '' ? '-' : item.score }}</span>
  83. &nbsp;/&nbsp;
  84. <span style="color:#ffad67;">{{ item.level ? item.level : '--'}}</span>
  85. </div>
  86. </div>
  87. </div>
  88. </li>
  89. </ol>
  90. <noData v-else content="无考核记录"></noData>
  91. </div>
  92. </div>
  93. </div>
  94. <template>
  95. <div class="indicator-list-title">指标分析</div>
  96. <div class="indicator-list">
  97. <div class="indicator-item" v-for="(item, index) in tableData2" :key="index">
  98. <div class="indicator-item-info">
  99. <div class="indicator-info-label">指标名称</div>
  100. <div class="indicator-info-value">{{ item.title }}</div>
  101. </div>
  102. <div class="indicator-item-info">
  103. <div class="indicator-info-label">目标</div>
  104. <div class="indicator-info-value">{{ item.target }}</div>
  105. </div>
  106. <div class="indicator-item-info">
  107. <div class="indicator-info-label">均值</div>
  108. <div class="indicator-info-value">{{ item.avgResult }}</div>
  109. </div>
  110. <div class="indicator-item-info">
  111. <div class="indicator-info-label">超出目标比例</div>
  112. <div v-if="parseInt(item.standardResultRate) > 0" class="indicator-info-value color-green" >{{ item.standardResultRate }}</div>
  113. <div v-else-if="parseInt(item.standardResultRate) < 0" class="indicator-info-value color-red" >{{ item.standardResultRate }}</div>
  114. <div v-else="parseInt(item.standardResultRate) > 0" class="indicator-info-value color-gray" >{{ item.standardResultRate }}</div>
  115. </div>
  116. <div class="indicator-item-info">
  117. <div class="indicator-info-label">达标数</div>
  118. <div class="indicator-info-value">{{ item.standardCount }}</div>
  119. </div>
  120. <div class="indicator-item-info">
  121. <div class="indicator-info-label">达标率</div>
  122. <div v-if="parseInt(item.standardRate) > 0" class="indicator-info-value color-green" >{{ item.standardRate }}</div>
  123. <div v-else-if="parseInt(item.standardRate) < 0" class="indicator-info-value color-red" >{{ item.standardRate }}</div>
  124. <div v-else="parseInt(item.standardRate) > 0" class="indicator-info-value color-gray" >{{ item.standardRate }}</div>
  125. </div>
  126. <div class="indicator-item-info">
  127. <div class="indicator-info-label">平均分</div>
  128. <div class="indicator-info-value">{{ item.avgScore }}</div>
  129. </div>
  130. </div>
  131. </div>
  132. </template>
  133. </div>
  134. </scroller>
  135. </template>
  136. </VanSkeleton>
  137. <!-- 周期选择 -->
  138. <van-action-sheet v-model="pullonThePanel" :closeable="false">
  139. <div class="content">
  140. <van-picker ref="van_picker" show-toolbar :columns="cycleOptions" @cancel="pullonThePanel = false" value-key="name" @confirm="onConfirm" confirm-button-text="完成" />
  141. </div>
  142. </van-action-sheet>
  143. <!-- 部门搜索 -->
  144. <van-dialog v-model="showDept" width="300" confirm-button-text="确定" @confirm="handleConfirm" @cancel="handleCancel" :show-confirm-button="true" closeOnClickOverlay>
  145. <div style="height: 8rem; overflow: auto;" class="scroll-bar">
  146. <div class="dept-list">
  147. <div class="dept-item flex-box-ce" v-for="(item, index) in department_list" :key="index">
  148. <van-checkbox shape="square" v-model="item.select" style="margin-right: 0.16rem;"></van-checkbox>
  149. <div class="dept-item-name">{{ item.dept_name }}</div>
  150. </div>
  151. </div>
  152. </div>
  153. </van-dialog>
  154. </div>
  155. </template>
  156. <script>
  157. import Vue from 'vue';
  158. import VanSkeleton from '@/performance/components/public/VanSkeleton';
  159. import { getBelongType, getColumns, cycleTypeArr} from '@/okr/utils/auth';
  160. import { Picker, ActionSheet, Circle, Tab, Tabs, Skeleton, DropdownMenu, DropdownItem, Progress, Calendar } from 'vant';
  161. import { _debounce } from '@/utils/auth';
  162. import _ from "lodash"
  163. Vue.use(Picker)
  164. .use(ActionSheet)
  165. .use(Circle)
  166. .use(Tab)
  167. .use(Tabs)
  168. .use(Skeleton)
  169. .use(DropdownMenu)
  170. .use(DropdownItem)
  171. .use(Progress)
  172. .use(Calendar);
  173. export default {
  174. data() {
  175. return {
  176. skeletonLoad: true, //
  177. statementtabshow: false,
  178. statementperson: [],
  179. statementResult: [],
  180. allStatementResult: [],
  181. statementVanTabs: [{ name: '等级分布' }, { name: '正态分布' }],
  182. active: 0,
  183. detailInfo: null,
  184. startTime: "",
  185. endTime: "",
  186. deptIds: [],
  187. rank: "",
  188. rankList: [],
  189. deptList: [],
  190. scoreList: [],
  191. users: [], //考核人员列表
  192. filterUsers: [],
  193. tableData1: [], // 考核中的指标列表,
  194. tableData2: [], // 按单位/目标/聚合指标列表,
  195. filterTableData1: [],
  196. filterTableData2: [],
  197. distributionId: '',
  198. userTotal: 0,
  199. userComplete: 0,
  200. userIncomplete: 0,
  201. infos: [],
  202. cycleColumns: getColumns(),
  203. cycleTypeArr:cycleTypeArr(true),
  204. selectPftiTheEcho: [0, 0], // 选项回显
  205. dateParameter: {
  206. year: this.$moment().format('YYYY'),
  207. cycle_type: 0,
  208. dateId: 1,
  209. name: '全部周期'
  210. },
  211. selectPftiText: '', // 展示名字
  212. selectPftiTheEcho: [0, 0], // 选项回显
  213. pullonThePanel: false, // 选项面板
  214. selectPftiData: {}, // 当前选中项
  215. keyword: "",
  216. deptName: '全部部门',
  217. department_list: [],
  218. showDept: false,
  219. recordList: [],
  220. userInfo: this.$userInfo(),
  221. gradeLevels: [], // 全局等级配置
  222. cycleOptions: [
  223. { name: "月度", value: "4", id: "4", text: "月度", children: [] },
  224. { name: "季度", value: "3", id: "3", text: "季度", children: [] },
  225. { name: "半年度", value: "2", id: "2", text: "半年度", children: [] },
  226. { name: "年度", value: "1", id: "1", text: "年度", children: [] },
  227. { name: "自定义", value: "0", id: "0", text: "自定义", children: [] },
  228. ],
  229. isYou: true,
  230. params: {
  231. cycleType: 1,
  232. startDate: '',
  233. endDate: '',
  234. deptIds: '',
  235. year: 2025
  236. }
  237. };
  238. },
  239. components: { VanSkeleton },
  240. watch: {
  241. detailInfo(v) {
  242. console.log("-------------结果分析,初始化数据-------------")
  243. this.$nextTick(() => {
  244. this.initData();
  245. })
  246. }
  247. },
  248. methods: {
  249. openDetail(item){
  250. this.$router.push({ path: '/me', query: { reviewId: item.reviewId, employeeId: item.employeeId } });
  251. },
  252. async initData() {
  253. // this.activeName = '1'
  254. this.deptName = '全部部门'
  255. this.deptIds = [];
  256. this.department_list = [];
  257. this.users = [];
  258. this.filterUsers = [];
  259. this.tableData1 = [];
  260. this.tableData2 = [];
  261. this.filterTableData1 = [];
  262. this.filterTableData2 = [];
  263. let { indicators, startTime, endTime, distribution: { items }, users } = this.detailInfo
  264. console.log(this.detailInfo)
  265. this.startTime = startTime;
  266. this.endTime = endTime;
  267. this.scoreList = items;
  268. this.distributionId = this.scoreList[0].id
  269. this.users = users;
  270. this.tableData1 = indicators;
  271. this.tableData1.forEach(item => {
  272. this.users.forEach(user => {
  273. if (user.employeeId == item.employeeId) {
  274. item.departments = user.departments
  275. }
  276. })
  277. if (item.target && item.result) {
  278. item.difference = item.result - item.target
  279. } else {
  280. item.difference = '--'
  281. }
  282. })
  283. this.filterTableData1 = this.tableData1;
  284. if(this.tableData1 && this.tableData1.length > 0) {
  285. let groups = _.groupBy(this.tableData1, item => `${item.title}(_)${item.target === null || item.target === '' ? 'null' : item.target}(_)${item.unit === null || item.unit === '' ? 'null' : item.unit}`);
  286. Object.keys(groups).forEach(key => {
  287. let group = {
  288. title: '',
  289. target: '',
  290. unit: '',
  291. departments: [],
  292. userCount: 0,
  293. scoredCount: 0,
  294. standardCount: 0,
  295. failCount: 0,
  296. standardRate: '--',
  297. totalScore: 0,
  298. totalResult: 0,
  299. avgScore: 0,
  300. avgResult: 0,
  301. standardResultRate: '--'
  302. };
  303. groups[key].forEach(indicator => {
  304. group.title = indicator.title; // 指标名称
  305. group.target = indicator.target; // 目标
  306. group.unit = indicator.unit; // 单位
  307. group.departments = indicator.departments; // 单位
  308. let standardCount = indicator.difference !== '--' && indicator.difference >= 0 ? 1 : 0; //
  309. group.userCount += 1;
  310. group.scoredCount += indicator.score !== null ? 1 : 0;
  311. group.standardCount += standardCount;
  312. group.failCount += standardCount === 1 ? 0 : 1;
  313. if (indicator.score !== null) group.totalScore += indicator.score;
  314. if (indicator.result !== null) group.totalResult += indicator.result;
  315. });
  316. group.standardCount = group.standardCount;
  317. if (group.userCount > 0) {
  318. let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
  319. let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
  320. let avgResult = Math.floor(group.totalResult / group.userCount * 100 * 0.01);
  321. group.standardRate = rate > 0 ? `${rate}%` : '--';
  322. group.avgScore = avgScore !== 0 ? avgScore : '--';
  323. group.avgResult = avgResult !== 0 ? avgResult : '--';
  324. if (group.target !== null && group.avgResult !== '--') {
  325. let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100);
  326. group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
  327. }
  328. }
  329. this.tableData2.push(group);
  330. this.filterTableData2 = this.tableData2;
  331. console.log("this.tableData2")
  332. console.log(this.tableData2)
  333. })
  334. }
  335. this.userTotal = 0;
  336. this.userComplete = 0;
  337. this.userIncomplete = 0;
  338. let distribution = [];
  339. let userScores = []
  340. this.scoreList.forEach(item => {
  341. item.level = item.name;
  342. item.ratio = item.scale / 100
  343. distribution.push(item)
  344. })
  345. if (this.users && this.users.length > 0) {
  346. this.users.forEach(user => {
  347. this.userTotal++;
  348. this.userComplete += user.status === 1 ? 1 : 0;
  349. user.level = this.findGrade(user.score, this.gradeLevels);
  350. // this.rankList.push(user.level || '未评分')
  351. userScores.push(user.score)
  352. })
  353. this.infos = [
  354. { label: "总人数", num: this.userTotal },
  355. { label: "已完成", num: this.userComplete },
  356. { label: "未评分", num: this.userIncomplete },
  357. ]
  358. let scoreResult = this.assignLevels(userScores, distribution);
  359. this.users.forEach(item => {
  360. item.dept_list = this.$getEmployeeMapItem(item.employeeId).employee_detail.dept_list
  361. if(item.dept_list && item.dept_list.length > 0) {
  362. item.dept_list.forEach(dept => {
  363. dept.id = dept.dept_id
  364. item.select = false
  365. this.department_list.push(dept)
  366. })
  367. }
  368. scoreResult.forEach(result => {
  369. if (result.scores.includes(item.score)) {
  370. item.scoreResult = result.level
  371. }
  372. })
  373. })
  374. // 按分数排序
  375. this.users = this.users.slice().sort((a, b) => b.score - a.score);
  376. // this.filterUsers = cloneDeep(this.users)
  377. this.filterUsers = this.users
  378. // this.getResult();
  379. let pieData = []
  380. if(this.users && this.users.length > 0) {
  381. pieData = this.gradeLevels.map(item => ({
  382. name: item.name + ' ' + this.users.filter(user => user.level === item.name).length + '人',
  383. value: this.users.filter(user => user.level === item.name).length
  384. }))
  385. this.statmentAnalysesPie(pieData)
  386. }
  387. }
  388. },
  389. statmentAnalysesPie(data) {
  390. let colors = ['rgb(38, 162, 255)', '#f36f2a', '#fecb09', '#00b6bd', '#e85d53', '#fecb09'];
  391. let legendDataLeng = data.length;
  392. if (legendDataLeng > colors.length) {
  393. let colorsLeng = colors.length;
  394. for (let i = 0; i <= legendDataLeng - colorsLeng; i++) {
  395. colors.push('rgb(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ')');
  396. }
  397. }
  398. //
  399. const chart = this.$refs.statmentAnalysePie;
  400. if (chart) {
  401. const myChart = this.$echarts.init(chart);
  402. const option = {
  403. legend: [
  404. {
  405. type: 'scroll',
  406. orient: 'vertical',
  407. icon: 'square',
  408. left: '55%',
  409. align: 'left',
  410. top: 'center',
  411. itemGap: 20,
  412. itemWidth: 5, // 图形宽度。
  413. itemHeight: 5, // 图形高度。
  414. // bottom:'50%',
  415. textStyle: {
  416. fontSize: 14,
  417. color: 'rgb(48, 49, 51)'
  418. }
  419. // data: Name
  420. }
  421. ],
  422. color: colors,
  423. emphasis: {
  424. scale: true,
  425. scaleSize: 10,
  426. label: {
  427. formatter: '总人数',
  428. show: true,
  429. fontSize: '20'
  430. }
  431. },
  432. series: [
  433. {
  434. type: 'pie',
  435. radius: ['43%', '53%'],
  436. center: ['30%', '50%'],
  437. avoidLabelOverlap: false,
  438. itemStyle: {
  439. borderRadius: 10,
  440. borderColor: '#fff',
  441. borderWidth: 2
  442. },
  443. label: {
  444. show: false,
  445. position: 'center'
  446. },
  447. labelLine: {
  448. show: false
  449. },
  450. data
  451. }
  452. ]
  453. };
  454. myChart.setOption(option);
  455. setTimeout(() => {
  456. myChart.resize();
  457. }, 200);
  458. }
  459. },
  460. // 查找分数对应的等级
  461. findGrade(score, gradeLevels) {
  462. for (let i = 0; i < gradeLevels.length; i++) {
  463. if (score && score >= gradeLevels[i].min && score && score <= gradeLevels[i].max) {
  464. return gradeLevels[i].name; // 返回对应的等级描述
  465. } else if (score && score <= gradeLevels[0].min) {
  466. return gradeLevels[0].name; // 返回对应的等级描述
  467. } else if (score && score >= gradeLevels[gradeLevels.length - 1].max) {
  468. return gradeLevels[gradeLevels.length - 1].name; // 返回对应的等级描述
  469. }
  470. }
  471. return "未评级"; // 如果分数不在任何范围内
  472. },
  473. assignLevels(scores, levelConfigs) {
  474. // 降序排序并去重(假设分数不重复,可省略去重)
  475. const sortedScores = [...scores].sort((a, b) => b - a);
  476. const total = sortedScores.length;
  477. if (total === 0) return [];
  478. // 归一化处理比例
  479. const totalRatio = levelConfigs.reduce((sum, cfg) => sum + cfg.ratio, 0);
  480. const normalized = levelConfigs.map(cfg => cfg.ratio / totalRatio);
  481. // 计算每个等级的初始人数
  482. let counts = normalized.map(ratio => Math.floor(total * ratio));
  483. let remainder = total - counts.reduce((sum, c) => sum + c, 0);
  484. // 分配剩余人数,按优先级顺序
  485. let idx = 0;
  486. while (remainder > 0 && idx < counts.length) {
  487. counts[idx]++;
  488. remainder--;
  489. idx++;
  490. }
  491. // 构建结果:按人数切割数组
  492. let start = 0;
  493. return counts.map((count, i) => {
  494. const end = start + count;
  495. const levelScores = sortedScores.slice(start, end);
  496. start = end;
  497. return {
  498. level: levelConfigs[i].level,
  499. scores: levelScores
  500. };
  501. });
  502. },
  503. openPanel(){
  504. this.pullonThePanel = true;
  505. this.$nextTick(() => {
  506. this.theEchoVanPicker()
  507. })
  508. },
  509. // 考核包搜索
  510. onConfirm (data, list) { // 确认
  511. console.log(data)
  512. console.log(list)
  513. let cycle = this.cycleOptions[list[0]].value
  514. let time = this.cycleOptions[list[0]].children[list[1]].value
  515. this.getResultAnalyze(cycle, time)
  516. this.pullonThePanel = false
  517. },
  518. // 获取全局等级设置
  519. async getAllLevelSet() {
  520. let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
  521. let data = res.data.data;
  522. let levels = data.level_scope.levels;
  523. let gradeLevels = [];
  524. let max = 0;//最大值
  525. let colorList = ['#5F7294', '#08EEA1', '#f56c6c', '#0887EE', '#92EE08', '#f56c6c']
  526. if (levels && levels.length > 0) {
  527. levels.forEach((item, index) => {
  528. var obj;
  529. if (index == 0) {
  530. obj = { name: item.name, max: Number(item.value), min: 0 };
  531. } else {
  532. obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
  533. }
  534. obj.color = colorList[index]
  535. max = item.value;
  536. gradeLevels.push(obj);
  537. })
  538. this.gradeLevels = gradeLevels
  539. console.log("this.gradeLevels")
  540. console.log(this.gradeLevels)
  541. }
  542. },
  543. // 获取结果分析数据
  544. getResultAnalyze(cycle, time) {
  545. let url = `/performance/statistics/cycle/${this.$userInfo().site_id}/${cycle}`
  546. this.$axiosUser("get", url, { value: `${time}` }).then(res => {
  547. console.log("获取结果分析数据")
  548. this.detailInfo = res.data.data
  549. this.skeletonLoad = false
  550. console.log(this.isYou)
  551. })
  552. },
  553. getData() {
  554. let url = `/performance/statistics/reviews/${this.userInfo.site_id}`;
  555. let params = {
  556. cycleType: 4,
  557. startDate: '',
  558. endDate: '',
  559. deptIds: '',
  560. cycleValue: 7,
  561. year: 2025
  562. }
  563. this.$axiosUser("get", url, params).then(res => {
  564. if (res.data.code == 1) {
  565. let recordList = res.data.data.list;
  566. if(recordList.length == 0) {
  567. this.isYou = false
  568. return false
  569. } else {
  570. this.isYou = true
  571. let theOf = this.theProgressOf;
  572. theOf.complete = recordList.filter(item => item.status == 1).length || 0 // 已完成人数
  573. theOf.theTotalNum = res.data.data.total; // 总人数
  574. let rate = (theOf.complete / theOf.theTotalNum) * 100; // 进度条百分比
  575. if(!isNaN(rate)){
  576. this.rate = rate; // 进度条百分比
  577. }
  578. recordList.forEach(item => {
  579. item.userInfo = this.$getEmployeeMapItem(item.employeeId);
  580. item.dept_list = this.$getEmployeeMapItem(item.employeeId).employee_detail.dept_list
  581. })
  582. }
  583. console.log("---------------------------------")
  584. console.log(recordList)
  585. this.recordList = recordList
  586. this.skeletonLoad = false
  587. }
  588. })
  589. },
  590. theEchoVanPicker() {
  591. // 回显
  592. this.$refs.van_picker.setIndexes(this.selectPftiTheEcho);
  593. },
  594. onCancel() {
  595. // 取消
  596. this.pullonThePanel = false;
  597. },
  598. statementTabs(key, tit) {
  599. },
  600. statementTIme() {
  601. this.pullonThePanel = true;
  602. this.$nextTick(() => {
  603. this.theEchoVanPicker();
  604. });
  605. },
  606. keyVal: _debounce(function() {
  607. if(!this.keyword) this.filterUsers = this.recordList
  608. // let filterUsers = []
  609. if(this.recordList && this.recordList.length > 0) {
  610. this.filterUsers = this.recordList.filter(item => item.employeeName.includes(this.keyword.trim()))
  611. }
  612. }),
  613. handleConfirm() {
  614. this.deptIds = this.department_list.filter(item => item.select).map(item => item.dept_id)
  615. if(this.deptIds && this.deptIds.length > 0) {
  616. this.deptName = ''
  617. this.department_list.filter(item => item.select).forEach(dept => {
  618. this.deptName += dept.dept_name + ","
  619. })
  620. this.deptName = this.deptName.substring(0, this.deptName.length - 1)
  621. this.filterUsers = this.users.filter((item) => {
  622. const departmentMatch = this.deptIds.length === 0 || this.deptIds.some((depId) => item.departments.some(dep => dep.id == depId));
  623. return departmentMatch;
  624. });
  625. }else {
  626. this.deptName = '全部部门'
  627. }
  628. this.showDept = false;
  629. },
  630. handleCancel() {
  631. this.showDept = false;
  632. },
  633. getColumnsInfo(cycle) {
  634. if (cycle < 0) {
  635. cycle = 0
  636. return
  637. }
  638. // this.loading = true
  639. // 周期种类 0-自定义 1-年度 2-半年度 3-季度 4-月度
  640. let url = `/performance/statistics/cycle/info/${this.$userInfo().site_id}/${cycle}`
  641. this.$axiosUser("get", url, {}).then(res => {
  642. console.log("获取周期数据")
  643. console.log(res)
  644. // this.loading = false;
  645. let { data: { data: { items, cycleType } } } = res
  646. let index = parseInt(4 - cycle)
  647. this.cycleOptions[index].children = items
  648. if (items && items.length > 0) {
  649. items.forEach(item => {
  650. item.name = item.remark
  651. item.text = item.remark
  652. item.id = item.value
  653. })
  654. let cycle = this.cycleOptions[index].value
  655. let time = this.cycleOptions[index].children[[0]].value
  656. // if(!(this.detailInfo && this.detailInfo.users && this.detailInfo.users.length > 0)) {
  657. // if(time) {
  658. // this.getResultAnalyze(cycle, time)
  659. // }
  660. // }
  661. // this.getColumnsInfo(parseInt(cycle) - 1) // 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
  662. } else {
  663. this.cycleOptions[index].children = [
  664. {name: '', text: '', value: '', id: ''}
  665. ]
  666. }
  667. })
  668. },
  669. },
  670. mounted() {
  671. this.$nextTick(async () => {
  672. // 获取全局等级
  673. await this.getAllLevelSet();
  674. // 获取周期列表数据
  675. await this.getColumnsInfo(4);
  676. await this.getColumnsInfo(3);
  677. await this.getColumnsInfo(2);
  678. await this.getColumnsInfo(1);
  679. await this.getColumnsInfo(0);
  680. for(let i = 4; i >=0; i--) {
  681. console.log(i)
  682. }
  683. this.getResultAnalyze(4, '2025:5')
  684. })
  685. },
  686. created() {
  687. }
  688. };
  689. </script>
  690. <style scoped lang="less">
  691. .color-red {
  692. padding: 0.05rem 0.2rem;
  693. box-sizing: border-box;
  694. background-color: #fef0f0;
  695. border: 0.02rem solid #fde2e2;
  696. color: #f56c6c !important;
  697. }
  698. .color-green {
  699. padding: 0.05rem 0.2rem;
  700. box-sizing: border-box;
  701. background-color: #f0f9eb;
  702. border: 0.02rem solid #e1f3d8;
  703. color: #67c23a !important;
  704. }
  705. .color-gray {
  706. padding: 0.05rem 0.2rem;
  707. box-sizing: border-box;
  708. background-color: #f4f4f5;
  709. border: 0.02rem solid #e9e9eb;
  710. color: #909399 !important;
  711. }
  712. .dept-list {
  713. padding: 0.2rem;
  714. box-sizing: border-box;
  715. .dept-item {
  716. box-sizing: border-box;
  717. padding-left: 0.3rem;
  718. margin-bottom: 0.2rem;
  719. &-name {
  720. font-size: 0.28rem;
  721. color: #89919F;
  722. }
  723. }
  724. }
  725. .statementHnum {
  726. padding: 0.2rem;
  727. padding-bottom: 0rem;
  728. .Hnumvfor {
  729. width: 22.8%;
  730. position: relative;
  731. background-color: #ECF5FF;
  732. border-radius: 3px;
  733. text-align: center;
  734. padding: 0.1rem 0.2rem;
  735. box-sizing: border-box;
  736. margin-bottom: 0.2rem;
  737. .smHnLnum {
  738. font-size: 0.42rem;
  739. color: #00a1ff;
  740. }
  741. .smHnLtit {
  742. font-size: 0.26rem;
  743. color: #8a8a8a;
  744. }
  745. }
  746. }
  747. .selectItem {
  748. height: 0.8rem;
  749. line-height: 0.8rem;
  750. background-color: #fff;
  751. border-top: 0.02rem solid #f1f1f1;
  752. text-align: center;
  753. font-size: 0.28rem;
  754. i {
  755. margin: 0.23rem 0 0 0.1rem;
  756. color: #c3c3c3;
  757. }
  758. }
  759. header {
  760. .selector {
  761. z-index: 1;
  762. display: flex;
  763. align-items: center;
  764. justify-content: center;
  765. width: 100%;
  766. height: 0.8rem;
  767. line-height: 0.8rem;
  768. background-color: #fff;
  769. text-align: center;
  770. font-size: 0.28rem;
  771. i {
  772. margin: 0 0 0 0.1rem;
  773. color: #c3c3c3;
  774. }
  775. span {
  776. max-width: 3.5rem;
  777. overflow: hidden;
  778. }
  779. }
  780. }
  781. .all {
  782. height: calc(100% - 1.92rem) !important;
  783. position: relative !important;
  784. background-color: #f5f7fa;
  785. .statementHead {
  786. width: 100%;
  787. text-align: center;
  788. // padding-top: 0.35rem;
  789. background-color: #fff;
  790. .statementpropnum {
  791. background-color: #f5f7fa;
  792. padding: 0.3rem 0 0.1rem;
  793. .propnumrel1 {
  794. /deep/ .van-progress__pivot {
  795. margin-left: -0.11rem;
  796. }
  797. }
  798. .propnumrel {
  799. position: relative;
  800. padding: 0 0.15rem;
  801. /deep/ .van-progress {
  802. border-radius: 0.5rem;
  803. .van-progress__portion {
  804. border-radius: 0.14rem;
  805. .van-progress__pivot {
  806. border-radius: 50%;
  807. min-width: 0.33rem !important;
  808. height: 0.33rem;
  809. right: 0;
  810. // margin-left: -.11rem;
  811. }
  812. }
  813. }
  814. .propnumcol {
  815. position: absolute;
  816. top: 0;
  817. right: 0.4rem;
  818. font-size: 0.25rem;
  819. color: #fff;
  820. }
  821. .numelzi {
  822. font-size: 0.26rem;
  823. margin: 0.25rem 0;
  824. .numelzh {
  825. color: #000000;
  826. }
  827. .numelzl {
  828. color: #0597ff;
  829. display: flex;
  830. align-items: center;
  831. justify-content: center;
  832. i {
  833. margin: 0.06rem 0 0 0.02rem;
  834. }
  835. }
  836. }
  837. }
  838. }
  839. .circle {
  840. margin-top: 0.7rem;
  841. .circletit {
  842. font-size: 0.24rem;
  843. color: #6f6f6f;
  844. }
  845. .circleclic {
  846. color: #4ba0f1;
  847. .circleclics1 {
  848. font-size: 0.42rem;
  849. display: inline-block;
  850. margin: 0.1rem 0;
  851. }
  852. .circleclics2 {
  853. font-size: 0.25rem;
  854. display: flex;
  855. justify-content: center;
  856. i {
  857. margin: 0.06rem 0 0 0.02rem;
  858. }
  859. }
  860. }
  861. }
  862. .statementHnum {
  863. padding: 0.2rem;
  864. padding-bottom: 0rem;
  865. .Hnumvfor {
  866. width: 22.8%;
  867. position: relative;
  868. background-color: #ECF5FF;
  869. border-radius: 3px;
  870. text-align: center;
  871. padding: 0.1rem 0.2rem;
  872. box-sizing: border-box;
  873. margin-bottom: 0.2rem;
  874. .smHnLnum {
  875. font-size: 0.42rem;
  876. color: #00a1ff;
  877. }
  878. .smHnLtit {
  879. font-size: 0.26rem;
  880. color: #8a8a8a;
  881. }
  882. }
  883. }
  884. }
  885. .statementFoot {
  886. background-color: #fff;
  887. margin-top: 0.2rem;
  888. .statmentperson {
  889. padding: 0.25rem 0.2rem;
  890. font-size: 0.32rem;
  891. box-sizing: border-box;
  892. .score-result {
  893. width: 0.4rem;
  894. height: 0.4rem;
  895. border: 0.02rem solid #409EFF;
  896. color: #409EFF;
  897. border-radius: 50%;
  898. font-size: 0.26rem;
  899. text-align: center;
  900. line-height: 0.4rem;
  901. margin-right: 0.1rem;
  902. }
  903. }
  904. }
  905. }
  906. .takI {
  907. font-size: 0.27rem;
  908. color: #f7b461;
  909. i {
  910. margin: 0.1rem 0.05rem 0 0;
  911. }
  912. }
  913. .statmentAnalysePie {
  914. position: relative;
  915. .PiePropNum {
  916. width: 1.5rem;
  917. text-align: center;
  918. position: absolute;
  919. left: 20%;
  920. top: 42%;
  921. font-size: 0.3rem;
  922. }
  923. }
  924. .review-status {
  925. // padding: 0.01rem 0.1rem;
  926. width: 1.2rem;
  927. height: 0.4rem;
  928. text-align: center;
  929. line-height: 0.4rem;
  930. color: #fff;
  931. border-radius: 2px;
  932. font-size: 0.26rem;
  933. background-color: #67c23a;
  934. margin-right: 0.14rem;
  935. margin-bottom: 0.1rem;
  936. }
  937. .indicator-list-title {
  938. padding: 0.2rem 0.2rem 0 0.2rem;
  939. box-sizing: border-box;
  940. font-size: 0.28rem;
  941. }
  942. .indicator-list {
  943. padding: 0.2rem;
  944. box-sizing: border-box;
  945. .indicator-item {
  946. background-color: white;
  947. margin-bottom: 0.2rem;
  948. padding: 0.2rem;
  949. box-sizing: border-box;
  950. border-radius: 8px;
  951. .indicator-item-info {
  952. display: flex;
  953. align-items: center;
  954. font-size: 0.28rem;
  955. margin-bottom: 0.2rem;
  956. .indicator-info-label {
  957. width: 2rem;
  958. color: black;
  959. }
  960. .indicator-info-value {
  961. // flex: 1;
  962. color: #89919F;
  963. }
  964. }
  965. }
  966. }
  967. </style>