index.vue 20 KB

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