index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <template>
  2. <div class="page-container">
  3. <el-row type="flex" justify="space-between" :gutter="24">
  4. <el-col :span="17">
  5. <div class="evaluate">
  6. <div class="title">
  7. <div class="name">年满意度评价</div>
  8. <div class="select">
  9. <el-select v-model="year" class="year-sel border" placeholder="请选择年份" @change="changeYear">
  10. <el-option
  11. v-for="item in yearList"
  12. :key="item"
  13. :label="item+'年'"
  14. :value="item"
  15. />
  16. </el-select>
  17. </div>
  18. </div>
  19. <div class="statistics">
  20. <div class="number">
  21. <div class="total">
  22. <span>总评分</span>
  23. <span class="score">{{ yearOverview.score }}</span>
  24. <span class="unit">分</span>
  25. </div>
  26. <div class="other">
  27. <div class="item">
  28. <div class="score small">{{ yearOverview.recordQty }}<span class="unit">次</span></div>
  29. <div class="name">评价次数</div>
  30. </div>
  31. <div class="item">
  32. <div class="score small">{{ yearOverview.groupQty }}<span class="unit">个</span></div>
  33. <div class="name">科室</div>
  34. </div>
  35. <div class="item">
  36. <div class="score small">{{ yearOverview.doctorQty }}<span class="unit">人</span></div>
  37. <div class="name">医务人员</div>
  38. </div>
  39. </div>
  40. </div>
  41. <div class="chart-container">
  42. <FullYearChart :values="showYearScore(yearOverview.monthScoreCounter)" />
  43. </div>
  44. </div>
  45. </div>
  46. <div class="evaluate month">
  47. <div class="title">
  48. <div class="name">月满意度评价</div>
  49. <div class="select">
  50. <el-select v-model="year" placeholder="请选择年份" class="year-sel" @change="changeYear">
  51. <el-option
  52. v-for="item in yearList"
  53. :key="item"
  54. :label="item+'年'"
  55. :value="item"
  56. />
  57. </el-select>
  58. <el-select v-model="month" placeholder="请选择月份" class="month-sel" @change="changeMonth">
  59. <el-option
  60. v-for="item in 12"
  61. :key="item"
  62. :label="item+'月'"
  63. :value="item"
  64. />
  65. </el-select>
  66. </div>
  67. </div>
  68. <div class="statistics">
  69. <div class="number">
  70. <div class="total">
  71. <span>总评分</span>
  72. <span class="score">{{ yearOverview.score }}</span>
  73. <span class="unit">分</span>
  74. </div>
  75. <div class="other">
  76. <div class="item">
  77. <div class="score small">{{ yearOverview.recordQty }}<span class="unit">次</span></div>
  78. <div class="name">评价次数</div>
  79. </div>
  80. <div class="item">
  81. <div class="score small">{{ yearOverview.groupQty }}<span class="unit">个</span></div>
  82. <div class="name">科室</div>
  83. </div>
  84. <div class="item">
  85. <div class="score small">{{ yearOverview.doctorQty }}<span class="unit">人</span></div>
  86. <div class="name">医务人员</div>
  87. </div>
  88. </div>
  89. </div>
  90. <div class="chart-container">
  91. <FullYearChart :values="showYearScore(yearOverview.monthScoreCounter)" />
  92. </div>
  93. </div>
  94. </div>
  95. <el-row v-if="false" style="margin-top:24px">
  96. <el-col class="group-box-month">
  97. <div class="group-box-head-container"><div class="group-box-head">月满意度评价</div></div>
  98. <div class="monthOverview-container">
  99. <div v-for="(monthItem,mIndex) in monthOverview" :key="mIndex" class="group-box border">
  100. <div class="group-title">
  101. <div>{{ monthItem.score }}</div>
  102. <div>{{ monthItem.groupName }}</div>
  103. </div>
  104. <div class="panorama-scoreIcon-container">
  105. <div class="item">
  106. <div class="score small">{{ checklist.score }}</div>
  107. <div class="name">得分</div>
  108. </div>
  109. <div class="item">
  110. <div class="score small">{{ checklist.targetQty }}</div>
  111. <div class="name">总人次</div>
  112. </div>
  113. <div class="item">
  114. <div class="score small">{{ checklist.recordQty }}</div>
  115. <div class="name">评价数</div>
  116. </div>
  117. </div>
  118. <div class="group-content">
  119. <div v-for="(checklist,cklIndex) in monthItem.checklistList" :key="cklIndex" class="group-content-item block">
  120. <div class="checklist-title">{{ checklist.checklistTitle }}</div>
  121. <div class="checklist-content flex-start">
  122. <div class="chart-container">
  123. <monthChart :id="'monthChart-'+cklIndex" :key="'checklist-'+cklIndex" :keys="monthScoreKeys" :values="formatCheckListValues(checklist.dayScoreList)" :axis-label="false" />
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. </div>
  129. </div>
  130. </el-col>
  131. </el-row>
  132. <el-row style="margin-top:24px" type="flex" justify="space-between" :gutter="12">
  133. <el-col :span="12">
  134. <div class="rank-list">
  135. <div class="item-title">
  136. <div class="name">科室-英雄榜</div>
  137. <div class="more" @click="handleMoreAssess">更多</div>
  138. </div>
  139. <el-row>
  140. <vuescroll :ops="ops" style="height: calc(100vh - 666px)">
  141. <GroupScoreRank :data="groupDataList" />
  142. </vuescroll>
  143. </el-row>
  144. </div>
  145. </el-col>
  146. <el-col :span="12">
  147. <div class="rank-list">
  148. <div class="item-title">
  149. <div class="name">医生-英雄榜</div>
  150. <div class="more" @click="handleMoreAssess">更多</div>
  151. </div>
  152. <el-row>
  153. <vuescroll :ops="ops" style="height: calc(100vh - 666px)">
  154. <DoctorScoreRank :data="groupDataList" />doctorDataList
  155. </vuescroll>
  156. </el-row>
  157. </div>
  158. </el-col>
  159. </el-row>
  160. </el-col>
  161. <el-col :span="7">
  162. <div class="todayNews">
  163. <div class="head">
  164. <div class="item-title">
  165. <div class="name">今日动态</div>
  166. <div class="more" @click="linkTo('/hos/satisfy/news/index')">更多</div>
  167. </div>
  168. <div class="statistics">
  169. <div class="item">
  170. <img class="icon" src="./images/notice.png" alt="总次数">
  171. <div class="cont">
  172. <div class="top">
  173. <span class="number">12</span>
  174. <span class="unit">次</span>
  175. </div>
  176. <div class="name">总次数</div>
  177. </div>
  178. </div>
  179. <div class="item">
  180. <img class="icon" src="./images/doctor.png" alt="医生">
  181. <div class="cont">
  182. <div class="top">
  183. <span class="number">12</span>
  184. <span class="unit">人</span>
  185. </div>
  186. <div class="name">医生</div>
  187. </div>
  188. </div>
  189. <div class="item">
  190. <img class="icon" src="./images/office.png" alt="科室">
  191. <div class="cont">
  192. <div class="top">
  193. <span class="number">12</span>
  194. <span class="unit">个</span>
  195. </div>
  196. <div class="name">科室</div>
  197. </div>
  198. </div>
  199. </div>
  200. </div>
  201. </div>
  202. <RecentRecord more="/hos/satisfy/news/index" height="calc(100vh - 260px)" :data="todayOverview.data" />
  203. </el-col>
  204. </el-row>
  205. </div>
  206. </template>
  207. <script>
  208. import { gridByList, groupByList, doctorByList } from '@/api/hos/satisfyStatisApi'
  209. import { yearOverview, monthOverview, todayOverview } from '@/api/hos/satisfyPanoramaApi'
  210. import monthChart from './components/monthChart'
  211. import FullYearChart from './components/FullYearChart'
  212. import RecentRecord from './components/RecentRecord'
  213. import DoctorScoreRank from '@/views/hos/panorama/components/DoctorScoreRank'
  214. import GroupScoreRank from '@/views/hos/panorama/components/GroupScoreRank'
  215. import vuescroll from 'vuescroll'
  216. import mqtt from 'mqtt'
  217. import { mqttUrl } from '@/settings'
  218. export default {
  219. name: 'SatisfyIndex',
  220. components: {
  221. monthChart,
  222. FullYearChart,
  223. RecentRecord,
  224. DoctorScoreRank,
  225. GroupScoreRank,
  226. vuescroll
  227. },
  228. data() {
  229. return {
  230. ops: {
  231. bar: {
  232. keepShow: false,
  233. background: 'rgba(144, 147, 153, 0.4)',
  234. onlyShowBarOnScroll: false
  235. }
  236. },
  237. year: '',
  238. month: '',
  239. yearList: [],
  240. yearScoreValues: [10, 20, 30, 40, 50, 60, 50, 30, 35, 20, 55],
  241. monthScoreKeys: [],
  242. monthScoreValues: [],
  243. conditions: {},
  244. todayOverview: {
  245. data: [],
  246. doctorRank: [],
  247. gridRank: [],
  248. groupName: '',
  249. groupRank: [],
  250. recordQty: 0,
  251. score: 0
  252. },
  253. yearOverview: {
  254. doctorQty: 0,
  255. gridQty: 0,
  256. groupName: '',
  257. groupQty: 0,
  258. monthScoreCounter: {},
  259. recordQty: 0,
  260. score: 0
  261. },
  262. monthOverview: [],
  263. doctorDataList: [],
  264. groupDataList: [],
  265. gridDataList: []
  266. }
  267. },
  268. mounted() {
  269. this.init()
  270. },
  271. destroyed() {
  272. if (this.client.connected) {
  273. try {
  274. this.client.end(false, () => {
  275. console.log('Successfully disconnected!')
  276. })
  277. } catch (error) {
  278. console.log('Disconnect failed', error.toString())
  279. }
  280. }
  281. },
  282. methods: {
  283. init() {
  284. const d = new Date()
  285. this.year = d.getFullYear()
  286. this.month = d.getMonth() + 1
  287. this.initYearList()
  288. this.monthScoreKeys = this.getmonthScoreKeys()
  289. this.getData()
  290. // this.initMqtt()
  291. },
  292. initYearList() {
  293. const d = new Date()
  294. const yearList = []
  295. for (let i = -5; i < 1; i++) {
  296. yearList.push(d.getFullYear() + i)
  297. }
  298. this.yearList = yearList
  299. },
  300. initMqtt() {
  301. const self = this
  302. const options = {
  303. clean: true,
  304. connectTimeout: 4000,
  305. clientId: 'emqx_test',
  306. username: 'emqx_test',
  307. password: 'emqx_test'
  308. }
  309. const client = mqtt.connect(mqttUrl, options)
  310. client.on('connect', function() {
  311. console.log('Connected')
  312. client.subscribe('topic/#', function(err) {
  313. if (!err) {
  314. // client.publish('topic/1', 'Hello mqtt')
  315. }
  316. })
  317. })
  318. client.on('message', function(topic, message) {
  319. self.mqttResponse(message)
  320. })
  321. this.client = client
  322. },
  323. handleAssessDetail(row) {},
  324. viewIndexScoreDetail(data) {},
  325. // Get Data
  326. getData() {
  327. this.getyearOverview()
  328. this.getMonthOverview()
  329. this.getTodayOverview()
  330. this.getGridByList()
  331. this.getDoctorByList()
  332. this.getGroupByList()
  333. },
  334. getTodayOverview() {
  335. todayOverview().then((resp) => {
  336. const { data } = resp
  337. if (data) {
  338. this.todayOverview = data
  339. } else {
  340. this.$set(this.todayOverview, { data: [], doctorRank: [], gridRank: [], groupName: '', groupRank: [], recordQty: 0, score: 0 })
  341. }
  342. })
  343. },
  344. getyearOverview() {
  345. yearOverview(this.year).then((resp) => {
  346. const { data } = resp
  347. if (data) {
  348. this.yearOverview = data
  349. } else {
  350. this.$set(this.yearOverview, { doctorQty: 0, gridQty: 0, groupName: '', groupQty: 0, monthScoreCounter: {}, recordQty: 0, score: 0 })
  351. }
  352. })
  353. },
  354. getMonthOverview() {
  355. monthOverview(this.year, this.month).then((resp) => {
  356. const { data } = resp
  357. if (data) {
  358. this.monthOverview = data.data
  359. } else {
  360. this.monthOverview = []
  361. }
  362. })
  363. },
  364. getGridByList() {
  365. gridByList(this.conditions).then((resp) => {
  366. const { code, msg, data } = resp
  367. if (code === 200) {
  368. this.gridDataList = data
  369. } else {
  370. this.$message.error(msg)
  371. }
  372. })
  373. },
  374. getGroupByList() {
  375. groupByList(this.conditions).then((resp) => {
  376. const { code, msg, data } = resp
  377. if (code === 200) {
  378. this.groupDataList = data
  379. } else {
  380. this.$message.error(msg)
  381. }
  382. })
  383. },
  384. getDoctorByList() {
  385. doctorByList(this.conditions).then((resp) => {
  386. const { code, msg, data } = resp
  387. if (code === 200) {
  388. this.doctorDataList = data
  389. } else {
  390. this.$message.error(msg)
  391. }
  392. })
  393. },
  394. getmonthScoreKeys(date = (new Date()), unit = '') {
  395. var days = []
  396. if (typeof (date) === 'string') { // 2022/09
  397. date = new Date(date)
  398. }
  399. const y = date.getFullYear()
  400. const m = date.getMonth()
  401. const lastDay = new Date(y, m + 1, 0).getDate()
  402. for (let i = 1; i <= lastDay; i++) {
  403. days.push(i)
  404. }
  405. return days
  406. },
  407. showYearScore(data) {
  408. if (Array.isArray(data)) return data
  409. var arr = []
  410. for (let i = 1; i <= 12; i++) {
  411. arr.push(data[`m${i}`] ? data[`m${i}`] : 0)
  412. }
  413. return arr
  414. },
  415. handleMoreAssess() {
  416. this.$router.push({
  417. path: '/hos/satisfy/assess/index'
  418. })
  419. },
  420. formatCheckListValues(data) {
  421. if (!data) return []
  422. const enums = {}
  423. const len = (this.getmonthScoreKeys(`${this.year}/${this.month}`)).length
  424. const months = []
  425. data.forEach(item => { enums[item.day] = item })
  426. for (let i = 1; i <= len; i++) {
  427. months[i - 1] = enums[i] ? enums[i].score : 0
  428. }
  429. return months
  430. },
  431. mqttResponse(message) {
  432. // console.log('message', message.toString())
  433. this.getData()
  434. },
  435. changeYear(params) {
  436. this.getyearOverview()
  437. },
  438. changeMonth() {
  439. this.monthScoreKeys = this.getmonthScoreKeys(`${this.year}/${this.month}`)
  440. this.getMonthOverview()
  441. },
  442. linkTo(url) {
  443. if (url) {
  444. this.$router.push(url)
  445. }
  446. }
  447. }
  448. }
  449. </script>
  450. <style lang="scss" scoped>
  451. .page-container {
  452. padding: 24px 30px;
  453. color: #FFF;
  454. position: relative;
  455. .evaluate{
  456. height: 228px;
  457. padding: 20px;
  458. background-color: #1F3C51;
  459. border: 5px 0px 0px 0px;
  460. border-top: 5px solid rgba(31, 60, 81, 1);
  461. background: linear-gradient(0deg, #1B3144, #1B3144),linear-gradient(0deg, #1F3C51, #1F3C51);
  462. &.month{
  463. margin-top: 24px;
  464. }
  465. .title{
  466. display: flex;
  467. justify-content: space-between;
  468. align-items: center;
  469. position: relative;
  470. margin-top: -8px;
  471. .name{
  472. font-family: Microsoft YaHei UI;
  473. font-size: 16px;
  474. font-weight: 700;
  475. line-height: 16px;
  476. color: #fff;
  477. background-image:linear-gradient(180deg, #FFFFFF 0%, #98A8B7 100%);
  478. background-clip:text;
  479. -webkit-text-fill-color:transparent;
  480. }
  481. .select{
  482. position: absolute;
  483. right: 0;
  484. top: -10px;
  485. &::v-deep{
  486. .el-select {
  487. width: 75px;
  488. height: 26px;
  489. line-height: 1;
  490. border-radius: 20px;
  491. background: rgba(255, 255, 255, 0.12);
  492. display: inline-block;
  493. font-size: 14px;
  494. border: 0;
  495. &.year-sel{
  496. border-radius: 20px 0 0 20px;
  497. }
  498. &.month-sel{
  499. width: 50px;
  500. border-radius: 0 20px 20px 0;
  501. }
  502. &.border{
  503. border-radius: 20px;
  504. }
  505. .el-input--medium .el-input__inner,.el-input{
  506. height: 100%;
  507. line-height: inherit;
  508. border: 0;
  509. border-radius:inherit;
  510. padding: 0;
  511. .el-input__icon{
  512. line-height: 1;
  513. }
  514. }
  515. .el-input__suffix{
  516. right: 1px;
  517. }
  518. }
  519. input{
  520. border-radius:inherit;
  521. background: rgba(255, 255, 255, 0.12);
  522. box-sizing: border-box;
  523. text-indent: 5px;
  524. }
  525. }
  526. }
  527. }
  528. .statistics{
  529. display: flex;
  530. justify-content: space-between;
  531. align-items: center;
  532. .number{
  533. width: 342px;
  534. .total{
  535. height: 68px;
  536. font-family: Be Vietnam;
  537. font-size: 14px;
  538. font-weight: 400;
  539. line-height: 14px;
  540. color: #fff;
  541. padding-left: 20px;
  542. border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  543. .score{
  544. font-size: 48px;
  545. font-weight: 700;
  546. line-height: 48px;
  547. padding: 0 8px 0 20px;
  548. }
  549. .unit{
  550. color: rgba(134, 144, 156, 1);
  551. }
  552. }
  553. .other{
  554. display: flex;
  555. align-items: center;
  556. padding-left: 24px;
  557. margin-top: 24px;
  558. .item{
  559. width: 33.33%;
  560. .score{
  561. color: #fff;
  562. font-size: 24px;
  563. font-weight: 700;
  564. .unit{
  565. font-size: 12px;
  566. font-weight: 400;
  567. color: #86909C;
  568. padding-left: 2px;
  569. }
  570. }
  571. .name{
  572. font-size: 14px;
  573. line-height: 1;
  574. padding-top: 8px;
  575. }
  576. }
  577. }
  578. }
  579. .chart-container{
  580. flex: 1;
  581. height: 160px;
  582. padding-left: 20px;
  583. }
  584. }
  585. }
  586. .todayNews{
  587. background: linear-gradient(0deg, #1B3144, #1B3144),linear-gradient(0deg, #1F3C51, #1F3C51);
  588. .head{
  589. padding: 20px;
  590. .statistics{
  591. display: flex;
  592. justify-content: center;
  593. align-items: center;
  594. margin-top: 42px;
  595. .item{
  596. width: 33.33%;
  597. display: flex;
  598. align-items: center;
  599. .icon{
  600. display: block;
  601. width: 56px;
  602. margin-right: 16px;
  603. }
  604. .top{
  605. .number{
  606. font-size: 24px;
  607. line-height: 24px;
  608. font-weight: 700;
  609. }
  610. .unit{
  611. font-size: 12px;
  612. font-weight: 400;
  613. color: #86909C;
  614. padding-left: 2px;
  615. }
  616. }
  617. .name{
  618. font-size: 14px;
  619. line-height: 14px;
  620. padding-top: 8px;
  621. }
  622. }
  623. }
  624. }
  625. }
  626. .rank-list{
  627. padding: 20px;
  628. background: linear-gradient(0deg, #1B3144, #1B3144),linear-gradient(0deg, #1F3C51, #1F3C51);
  629. }
  630. .item-title{
  631. display: flex;
  632. justify-content: space-between;
  633. align-items: center;
  634. background: linear-gradient(0deg, #1B3144, #1B3144),linear-gradient(0deg, #1F3C51, #1F3C51);
  635. .more{
  636. font-size: 14px;
  637. line-height: 14px;
  638. color: #C1C6C8;
  639. position: relative;
  640. padding-right: 10px;
  641. background: url(./images/more.png) no-repeat center right;
  642. background-size: 5px 8px;
  643. cursor: pointer;
  644. }
  645. }
  646. }
  647. </style>