HomePageSystemStatus.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <template>
  2. <div class="system-status">
  3. <!-- 搜索条件 -->
  4. <div class="search-conditions">
  5. <div class="time-range-switch">
  6. <div class="time-range-label">切换时间范围:</div>
  7. <div class="time-range-buttons">
  8. <button v-for="range in timeRanges" :key="range.value" :class="{ active: activeTimeRange === range.value }"
  9. @click="handleTimeRangeChange(range.value)">
  10. {{ range.label }}
  11. </button>
  12. </div>
  13. </div>
  14. <div class="custom-time-range">
  15. <div class="custom-time-label">自定义时间范围:</div>
  16. <div class="date-pickers" :class="{ 'custom-time-range-active': !!activeTimeRange }">
  17. <el-date-picker class="date-picker" v-model="startDate" type="date" style="width:110px" placeholder="开始时间"
  18. @change="handleDateChange" />
  19. <span class="date-separator">-</span>
  20. <el-date-picker class="date-picker" v-model="endDate" type="date" style="width:110px" placeholder="结束时间"
  21. @change="handleDateChange" />
  22. </div>
  23. </div>
  24. </div>
  25. <!-- 今日上岗科长 -->
  26. <div class="today-duty-section" v-if="dutySectionMaster">
  27. <div class="duty-label">今日上岗科长:</div>
  28. <div class="duty-name">{{ dutySectionMaster }}</div>
  29. </div>
  30. <!-- 遍历数组展示 -->
  31. <div class="quantity-overview">
  32. <div class="quantity-overview-item" v-for="item in attendanceItems" :key="item.id">
  33. <div>{{ item.label }}</div>
  34. <div style="font-size: 32px;">{{ item.value }}{{ item.unit }}</div>
  35. </div>
  36. </div>
  37. </div>
  38. </template>
  39. <script setup>
  40. import { ref, reactive, onMounted, computed } from 'vue'
  41. import { getAttendanceStats, getAccuracyStatistics, selectUserListByRoleKey } from '@/api/largeScreen/largeScreen'
  42. import useUserStore from '@/store/modules/user'
  43. // 定义事件
  44. const emit = defineEmits(['timeRangeChange', 'dateChange'])
  45. // 时间范围选项
  46. const timeRanges = [
  47. { label: '近一周', value: 'week' },
  48. { label: '近一月', value: 'month' },
  49. { label: '近三月', value: 'quarter' },
  50. { label: '近半年', value: 'halfYear' },
  51. { label: '近一年', value: 'year' }
  52. ]
  53. // 内部状态管理
  54. const activeTimeRange = ref('year')
  55. const startDate = ref('')
  56. const endDate = ref('')
  57. // 出勤相关数据
  58. const attendanceStats = ref({})
  59. const accuracyStatistics = ref({})
  60. const userStore = useUserStore()
  61. // 今日上岗科长数据
  62. const dutySectionMaster = ref('')
  63. // 获取今日上岗科长数据
  64. const fetchDutySectionMaster = async () => {
  65. try {
  66. // 调用接口获取今日上岗用户列表
  67. const response = await selectUserListByRoleKey(['kezhang']) // 假设角色标识为'kezhang'
  68. const userList = response.data || []
  69. // 将数组中的userName拼接成字符串
  70. if (userList.length > 0) {
  71. const names = userList.map(user => user.nickName)
  72. dutySectionMaster.value = names.join('、')
  73. } else {
  74. // 如果没有数据,使用默认值
  75. dutySectionMaster.value = '暂无科长上岗'
  76. }
  77. } catch (error) {
  78. console.error('获取今日上岗科长数据失败:', error)
  79. // 出错时使用默认值
  80. dutySectionMaster.value = ''
  81. }
  82. }
  83. // 计算属性:用户角色
  84. const role = computed(() => {
  85. return userStore.roles || []
  86. })
  87. // 计算属性:当前用户信息
  88. const currentUser = computed(() => {
  89. return userStore.userInfo || {}
  90. })
  91. // 计算属性:是否为班组视图
  92. const isTeamView = computed(() => {
  93. return role.value.includes('banzuzhang') ? false : role.value.includes('banzuzhang')
  94. })
  95. // 计算属性:是否为个人视图
  96. const isIndividualView = computed(() => {
  97. return role.value.includes('banzuzhang') ? false : role.value.includes('SecurityCheck')
  98. })
  99. // 计算属性:出勤信息数组
  100. const attendanceItems = computed(() => {
  101. let res = {}
  102. if (role.value.includes('kezhang') || role.value.includes('test') || role.value.includes('zhijianke') || role.value.includes('admin')) {
  103. res = attendanceStats.value.stationLeaderStats || {}
  104. return [
  105. { id: 1, label: `在岗大队`, value: res?.dutyDeptName, unit: '' },
  106. { id: 2, label: '在岗班组', value: res?.onDutyTeamCount, unit: '' },
  107. { id: 3, label: '在岗人员', value: res?.onDutyPersonnelCount, unit: '' },
  108. { id: 4, label: '今日查获上报', value: res?.todaySeizureReportCount, unit: '' },
  109. { id: 5, label: '今日巡检问题', value: res?.todayCheckCorrectionCount, unit: '' },
  110. { id: 6, label: '今日抽问抽答', value: `${accuracyStatistics.value?.todayTaskCompletion?.completedCount || 0}/${accuracyStatistics.value?.todayTaskCompletion?.totalCount || 0}`, unit: '' }
  111. ]
  112. } else {
  113. res = attendanceStats.value.teamLeaderStats || attendanceStats.value.securityCheckStats || {}
  114. // 个人视图或非班组长角色
  115. return [
  116. { id: 1, label: '出勤班组', value: res?.attendanceTeamName, unit: '' },
  117. { id: 2, label: '出勤通道', value: res?.attendanceChannel, unit: '' },
  118. { id: 3, label: '出勤时间', value: res?.attendanceTime || res?.attendanceTimeTips, unit: '' }
  119. ]
  120. }
  121. })
  122. const handleTimeRangeChange = (range) => {
  123. activeTimeRange.value = range
  124. emit('timeRangeChange', range)
  125. }
  126. // 处理日期变化
  127. const handleDateChange = () => {
  128. if (startDate.value && endDate.value) {
  129. // 当选择自定义时间时,将activeTimeRange置为空
  130. activeTimeRange.value = ''
  131. emit('dateChange', {
  132. startDate: startDate.value,
  133. endDate: endDate.value
  134. })
  135. }
  136. }
  137. // 获取出勤统计数据
  138. const fetchAttendanceStats = async () => {
  139. try {
  140. const response = await getAttendanceStats({})
  141. attendanceStats.value = response.data || {}
  142. } catch (error) {
  143. console.error('获取出勤统计数据失败:', error)
  144. }
  145. }
  146. // 获取正确率统计数据
  147. const fetchAccuracyStatistics = async () => {
  148. try {
  149. const response = await getAccuracyStatistics({})
  150. accuracyStatistics.value = response.data || {}
  151. } catch (error) {
  152. console.error('获取正确率统计数据失败:', error)
  153. }
  154. }
  155. // 组件挂载时获取数据
  156. onMounted(() => {
  157. fetchAttendanceStats()
  158. fetchAccuracyStatistics()
  159. fetchDutySectionMaster()
  160. })
  161. </script>
  162. <style lang="scss" scoped>
  163. .system-status {
  164. height: 100%;
  165. display: flex;
  166. flex-direction: column;
  167. gap: 10px;
  168. .search-conditions {
  169. justify-content: center;
  170. display: flex;
  171. align-items: center;
  172. gap: 20px;
  173. padding: 15px 15px 0 15px;
  174. // background: rgba(255, 255, 255, 0.05);
  175. border-radius: 8px;
  176. .time-range-switch {
  177. display: flex;
  178. align-items: center;
  179. gap: 10px;
  180. .time-range-label {
  181. font-size: 14px;
  182. color: #fff;
  183. white-space: nowrap;
  184. }
  185. .time-range-buttons {
  186. display: flex;
  187. gap: 4px;
  188. button {
  189. padding: 3px 9px;
  190. border: none;
  191. background: rgba(255, 255, 255, 0.1);
  192. color: #797979;
  193. background-color: #0A3551;
  194. border-radius: 4px;
  195. cursor: pointer;
  196. font-size: 12px;
  197. transition: all 0.3s;
  198. // &:hover {
  199. // background: rgba(255, 255, 255, 0.2);
  200. // }
  201. &.active {
  202. background: #0C4F7A;
  203. color: #fff;
  204. }
  205. }
  206. }
  207. }
  208. .custom-time-range {
  209. display: flex;
  210. align-items: center;
  211. gap: 10px;
  212. .custom-time-label {
  213. font-size: 14px;
  214. color: #fff;
  215. white-space: nowrap;
  216. }
  217. .custom-time-range-active {
  218. :deep(.el-input__wrapper) {
  219. background-color: #0A3551 !important;
  220. border: none;
  221. box-shadow: none;
  222. .el-input__inner {
  223. color: #797979;
  224. }
  225. }
  226. }
  227. .date-pickers {
  228. display: flex;
  229. align-items: center;
  230. gap: 8px;
  231. .date-separator {
  232. color: #fff;
  233. font-size: 12px;
  234. }
  235. :deep(.el-input__icon) {
  236. display: none;
  237. }
  238. :deep(.el-input__wrapper) {
  239. background-color: #0C4F7A;
  240. border: none;
  241. box-shadow: none;
  242. .el-input__inner {
  243. // color: #797979;
  244. }
  245. }
  246. }
  247. }
  248. }
  249. .today-duty-section {
  250. display: flex;
  251. align-items: center;
  252. gap: 10px;
  253. padding: 10px 15px;
  254. background: rgba(255, 255, 255, 0.05);
  255. border: 1px solid #044878;
  256. border-radius: 8px;
  257. color: #fff;
  258. font-size: 16px;
  259. .duty-label {
  260. font-weight: bold;
  261. color: #35D8FF;
  262. }
  263. .duty-name {
  264. color: #70D3EE;
  265. font-weight: bold;
  266. }
  267. }
  268. .quantity-overview {
  269. height: 100%;
  270. width: 100%;
  271. display: flex;
  272. flex-wrap: wrap;
  273. column-gap: 25px;
  274. row-gap: 15px;
  275. // padding: 20px 20px 10px;
  276. box-sizing: border-box;
  277. color: #fff;
  278. font-size: 20px;
  279. .quantity-overview-item {
  280. flex: 0 0 calc(33.333% - 16.67px);
  281. // min-height:100px;
  282. background: #051E40;
  283. display: flex;
  284. flex-direction: column;
  285. align-items: center;
  286. justify-content: center;
  287. row-gap: 10px;
  288. border-radius: 5px;
  289. font-weight: bold;
  290. }
  291. }
  292. }
  293. </style>