index.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. <template>
  2. <view class="dept-profile-page">
  3. <view class="page-header">
  4. <view class="header-title">
  5. <view class="title-main">部门综合信息展示</view>
  6. <view class="header-right">
  7. <view class="current-time">{{ currentTime }}</view>
  8. </view>
  9. </view>
  10. <view class="time-filter">
  11. <scroll-view scroll-x class="time-scroll">
  12. <view class="time-tags">
  13. <view v-for="(tag, index) in timeTags" :key="index"
  14. :class="['time-tag', { active: selectedTimeTag === index }]" @click="onTimeTagClick(index)">
  15. {{ tag }}
  16. </view>
  17. </view>
  18. </scroll-view>
  19. <view v-if="selectedTimeTag === 4" class="date-range-picker">
  20. <picker mode="date" :value="startDate" @change="onStartDateChange">
  21. <view class="date-input" :class="{ filled: startDate }">
  22. {{ startDate || '开始日期' }}
  23. </view>
  24. </picker>
  25. <text class="date-separator">至</text>
  26. <picker mode="date" :value="endDate" @change="onEndDateChange">
  27. <view class="date-input" :class="{ filled: endDate }">
  28. {{ endDate || '结束日期' }}
  29. </view>
  30. </picker>
  31. </view>
  32. </view>
  33. <view class="dept-selector">
  34. <view class="dept-select-trigger" :class="{ 'dept-select-trigger-disabled': !isStationMaster() }"
  35. @click="isStationMaster() && (showDeptPicker = true)">
  36. <u-icon name="list" color="#A78BFA" size="16"></u-icon>
  37. <text class="dept-name-text">{{ selectedDeptName || '请选择部门' }}</text>
  38. <u-icon name="arrow-down" color="#A78BFA" size="14"></u-icon>
  39. </view>
  40. </view>
  41. <view class="tab-nav">
  42. <view class="tab-item" :class="{ active: activeTab === 'profile' }" @click="activeTab = 'profile'">
  43. 能力画像
  44. </view>
  45. <view class="tab-item" :class="{ active: activeTab === 'data' }" @click="activeTab = 'data'">
  46. 运行数据
  47. </view>
  48. </view>
  49. </view>
  50. <view class="page-content">
  51. <SectionTitle title="部门概况">
  52. <DeptStats :statsData="deptStatsData" />
  53. </SectionTitle>
  54. <view v-if="activeTab === 'profile'">
  55. <SectionTitle title="五维得分一览">
  56. <ProfileRadar :chartsData="radarData" />
  57. </SectionTitle>
  58. <SectionTitle title="团队成员">
  59. <TeamMemberTable :chartsData="teamMemberData" />
  60. </SectionTitle>
  61. <SectionTitle title="成员基本情况分布">
  62. <MemberBasicDistribution :chartsData="memberBasicData" />
  63. </SectionTitle>
  64. <SectionTitle title="成员职位情况分布">
  65. <MemberPositionDistribution :chartsData="memberPositionData" />
  66. </SectionTitle>
  67. </view>
  68. <view v-if="activeTab === 'data'">
  69. <SectionTitle title="通道高峰过检率">
  70. <PassengerChart :chartsData="passengerData" />
  71. </SectionTitle>
  72. <SectionTitle title="查获信息展示">
  73. <SeizureInfo :chartsData="seizureInfoData" />
  74. </SectionTitle>
  75. <SectionTitle title="查获物品分布">
  76. <ItemDistribution :chartsData="itemDistributionData" />
  77. </SectionTitle>
  78. <SectionTitle title="每日查获数量(总表)">
  79. <SeizedNumAll :chartsData="dailySeizureTotalData" />
  80. </SectionTitle>
  81. <SectionTitle title="每日查获数量">
  82. <DailySeizureChart :chartsData="dailySeizureData" :title="'班组对比'"/>
  83. </SectionTitle>
  84. <SectionTitle title="查获工作区域分布">
  85. <AreaDistribution :chartsData="areaDistributionData" />
  86. </SectionTitle>
  87. <SectionTitle title="不安全事件物品分布">
  88. <UnsafeItemsChart :chartsData="unsafeItemsData" />
  89. </SectionTitle>
  90. <SectionTitle title="不安全事件类型分布">
  91. <UnsafeTypesChart :chartsData="unsafeTypesData" />
  92. </SectionTitle>
  93. <SectionTitle title="不安全事件岗位分布">
  94. <UnsafePositionChart :chartsData="unsafePositionData" />
  95. </SectionTitle>
  96. <SecurityTestCharts :chartsData="securityTestData" />
  97. <SectionTitle title="各岗位监察问题分布">
  98. <SupervisionDistribution :chartsData="supervisionData" />
  99. </SectionTitle>
  100. <SectionTitle title="实时质控拦截物品分布">
  101. <InterceptionDistribution :chartsData="interceptionData" />
  102. </SectionTitle>
  103. </view>
  104. </view>
  105. <DeptSelector :show.sync="showDeptPicker" v-model="selectedDeptId" :options="deptList" :loading="deptLoading"
  106. @change="onDeptSelectChange" />
  107. </view>
  108. </template>
  109. <script>
  110. import SectionTitle from '@/components/SectionTitle.vue'
  111. import TeamMemberTable from '../components/TeamMemberTable.vue'
  112. import MemberBasicDistribution from '../components/MemberBasicDistribution.vue'
  113. import MemberPositionDistribution from '../components/MemberPositionDistribution.vue'
  114. import PassengerChart from '../components/PassengerChart.vue'
  115. import SeizureInfo from '../components/SeizureInfo.vue'
  116. import ItemDistribution from '../components/ItemDistribution.vue'
  117. import DailySeizureChart from '../components/DailySeizureChart.vue'
  118. import AreaDistribution from '../components/AreaDistribution.vue'
  119. import UnsafeItemsChart from '../components/UnsafeItemsChart.vue'
  120. import UnsafeTypesChart from '../components/UnsafeTypesChart.vue'
  121. import UnsafePositionChart from '../components/UnsafePositionChart.vue'
  122. import SecurityTestCharts from '../components/SecurityTestCharts.vue'
  123. import ProfileRadar from '../components/ProfileRadar.vue'
  124. import SeizedNumAll from '../components/SeizedNumAll.vue'
  125. import SupervisionDistribution from '../components/SupervisionDistribution.vue'
  126. import InterceptionDistribution from '../components/InterceptionDistribution.vue'
  127. import DeptStats from '../components/DeptStats.vue'
  128. import DeptSelector from '../components/DeptSelector.vue'
  129. import {
  130. countStationTeamStats,
  131. getDeptMemberDistribution,
  132. getDeptPositionDistribution,
  133. countStationHourlyThroughput,
  134. countSeizureInfoItem,
  135. countSeizeSubjectCategoryQuantity,
  136. countSeizureTotalQuantity,
  137. countSeizureSingleQuantity,
  138. countSeizeAreaQuantity,
  139. countSeizureStatsItem,
  140. countSeizureStatsType,
  141. countSeizureStatsPost,
  142. securityTestItemClassification,
  143. securityTestPassingStatus,
  144. securityTestRegion,
  145. getDimensionScoreOverview,
  146. countLanePeakThroughput,
  147. realtimeInterceptionItem,
  148. supervisionProblemPosition,
  149. countDeptTeamStats
  150. } from '@/api/portraitManagement/portraitManagement'
  151. import { getDeptUserTree } from '@/api/system/user'
  152. const itemColors = ['#60A5FA', '#34D399', '#FBBF24', '#EF4444', '#9CA3AF', '#A78BFA', '#F472B6', '#6EE7B7']
  153. export default {
  154. name: 'DeptProfile',
  155. components: {
  156. SectionTitle,
  157. TeamMemberTable,
  158. MemberBasicDistribution,
  159. MemberPositionDistribution,
  160. PassengerChart,
  161. SeizureInfo,
  162. ItemDistribution,
  163. DailySeizureChart,
  164. AreaDistribution,
  165. UnsafeItemsChart,
  166. UnsafeTypesChart,
  167. UnsafePositionChart,
  168. SecurityTestCharts,
  169. ProfileRadar,
  170. SeizedNumAll,
  171. SupervisionDistribution,
  172. InterceptionDistribution,
  173. DeptStats,
  174. DeptSelector
  175. },
  176. data() {
  177. return {
  178. activeTab: 'profile',
  179. selectedTimeTag: 3,
  180. timeTags: ['近一周', '近一月', '近三月', '近一年', '自定义时间范围'],
  181. currentTime: '',
  182. timer: null,
  183. startDate: '',
  184. endDate: '',
  185. // 部门选择相关
  186. selectedDeptId: null,
  187. selectedDeptName: '',
  188. showDeptPicker: false,
  189. deptList: [],
  190. deptLoading: false,
  191. radarData: [],
  192. teamMemberData: [],
  193. memberBasicData: {
  194. gender: { labels: [], data: [] },
  195. ethnicity: { labels: [], data: [] },
  196. political: { labels: [], data: [] }
  197. },
  198. memberPositionData: {
  199. qualification: { labels: [], data: [] },
  200. experience: { labels: [], data: [] },
  201. position: { labels: [], data: [] }
  202. },
  203. passengerData: {
  204. labels: [],
  205. areas: {},
  206. totalFlow: []
  207. },
  208. seizureInfoData: {
  209. total: 0,
  210. depts: {
  211. labels: [],
  212. data: []
  213. }
  214. },
  215. itemDistributionData: {
  216. items: []
  217. },
  218. dailySeizureTotalData: [],
  219. dailySeizureData: {
  220. total: {
  221. labels: [],
  222. data: []
  223. },
  224. dept: {
  225. labels: [],
  226. data: {}
  227. }
  228. },
  229. areaDistributionData: {
  230. labels: [],
  231. data: []
  232. },
  233. unsafeItemsData: {
  234. items: []
  235. },
  236. unsafeTypesData: {
  237. types: []
  238. },
  239. unsafePositionData: {
  240. total: 0,
  241. positions: {
  242. labels: [],
  243. data: []
  244. }
  245. },
  246. securityTestData: {
  247. items: {
  248. labels: [],
  249. data: []
  250. },
  251. results: {
  252. labels: [],
  253. data: []
  254. },
  255. areas: {
  256. labels: [],
  257. data: []
  258. }
  259. },
  260. supervisionData: [],
  261. interceptionData: [],
  262. deptStatsData: {}
  263. }
  264. },
  265. computed: {
  266. currentDate() {
  267. const now = new Date()
  268. const year = now.getFullYear()
  269. const month = String(now.getMonth() + 1).padStart(2, '0')
  270. const day = String(now.getDate()).padStart(2, '0')
  271. const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
  272. const weekDay = weekDays[now.getDay()]
  273. return `${year}年${month}月${day}日 ${weekDay}`
  274. }
  275. },
  276. mounted() {
  277. this.updateTime()
  278. this.timer = setInterval(() => {
  279. this.updateTime()
  280. }, 1000)
  281. this.fetchDeptList()
  282. },
  283. beforeDestroy() {
  284. if (this.timer) {
  285. clearInterval(this.timer)
  286. }
  287. },
  288. methods: {
  289. updateTime() {
  290. const now = new Date()
  291. const hours = String(now.getHours()).padStart(2, '0')
  292. const minutes = String(now.getMinutes()).padStart(2, '0')
  293. const seconds = String(now.getSeconds()).padStart(2, '0')
  294. this.currentTime = `${hours}:${minutes}:${seconds}`
  295. },
  296. // 部门选择改变
  297. onDeptSelectChange(item) {
  298. this.selectedDeptId = item.deptId
  299. this.selectedDeptName = item.deptName
  300. // 重新请求数据
  301. const params = this.buildTimeParams()
  302. this.fetchAllData(params)
  303. },
  304. getUserRoles() {
  305. const userInfo = this.$store.state.user
  306. return userInfo && userInfo.roles ? userInfo.roles : []
  307. },
  308. isStationMaster() {
  309. const roles = this.getUserRoles()
  310. return roles.includes('test') || roles.includes('zhijianke')
  311. },
  312. fetchDeptList() {
  313. this.deptLoading = true
  314. const userInfo = this.$store.state.user?.userInfo;
  315. let params = this.isStationMaster() ? { parentId: userInfo.deptId } : { deptId: userInfo.deptId }
  316. getDeptUserTree(params).then(res => {
  317. if (res.code === 200) {
  318. let allDepts = this.flattenDeptTree(res.data || [])
  319. this.deptList = this.isStationMaster() ? allDepts.filter(d => d.deptType === 'BRIGADE') : allDepts
  320. if (userInfo && userInfo.deptId) {
  321. const dept = this.deptList.find(d => d.deptId === userInfo.deptId)
  322. if (dept) {
  323. this.selectedDeptId = dept.deptId
  324. this.selectedDeptName = dept.deptName
  325. } else if (this.deptList.length > 0) {
  326. this.selectedDeptId = this.deptList[0].deptId
  327. this.selectedDeptName = this.deptList[0].deptName
  328. }
  329. } else if (this.deptList.length > 0) {
  330. this.selectedDeptId = this.deptList[0].deptId
  331. this.selectedDeptName = this.deptList[0].deptName
  332. }
  333. // 选择完部门后请求数据
  334. this.onTimeTagClick(3)
  335. }
  336. }).catch(() => {
  337. }).finally(() => {
  338. this.deptLoading = false
  339. })
  340. },
  341. flattenDeptTree(tree) {
  342. const result = []
  343. const traverse = (nodes) => {
  344. nodes.forEach(node => {
  345. if (node.id) {
  346. result.push({
  347. deptId: node.id,
  348. deptName: node.label,
  349. deptType: node.deptType
  350. })
  351. }
  352. if (node.children && node.children.length > 0) {
  353. traverse(node.children)
  354. }
  355. })
  356. }
  357. traverse(tree)
  358. return result
  359. },
  360. fetchAllData(params) {
  361. this.fetchDeptStats(params)
  362. this.fetchRadarData(params)
  363. this.fetchTeamData(params)
  364. this.fetchMemberDistribution(params)
  365. this.fetchPositionDistribution(params)
  366. const copyParams = { startDate: params.startDate, endDate: params.endDate }
  367. this.fetchPassengerData(params)
  368. this.fetchSeizureInfo(params)
  369. this.fetchItemDistribution(params)
  370. this.fetchDailySeizure(params)
  371. this.fetchAreaDistribution(params)
  372. this.fetchUnsafeItems(params)
  373. this.fetchUnsafeTypes(params)
  374. this.fetchUnsafePosition(params)
  375. this.fetchSecurityTestData(params)
  376. this.fetchSupervisionData(params)
  377. this.fetchInterceptionData(params)
  378. },
  379. buildTimeParams() {
  380. const now = new Date()
  381. const today = this.formatDate(now)
  382. const deptId = this.selectedDeptId || 100
  383. if (this.selectedTimeTag === 4) {
  384. if (this.startDate && this.endDate) {
  385. return { startDate: this.startDate, endDate: this.endDate, deptId }
  386. }
  387. return { deptId }
  388. }
  389. let startDate
  390. switch (this.selectedTimeTag) {
  391. case 0:
  392. startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
  393. break
  394. case 1:
  395. startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate())
  396. break
  397. case 2:
  398. startDate = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
  399. break
  400. case 3:
  401. startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
  402. break
  403. default:
  404. return { deptId }
  405. }
  406. return { startDate: this.formatDate(startDate), endDate: today, deptId }
  407. },
  408. formatDate(date) {
  409. const y = date.getFullYear()
  410. const m = String(date.getMonth() + 1).padStart(2, '0')
  411. const d = String(date.getDate()).padStart(2, '0')
  412. return `${y}-${m}-${d}`
  413. },
  414. onTimeTagClick(index) {
  415. this.selectedTimeTag = index
  416. if (index === 4) {
  417. this.startDate = ''
  418. this.endDate = ''
  419. return
  420. }
  421. const params = this.buildTimeParams()
  422. this.fetchAllData(params)
  423. },
  424. onStartDateChange(e) {
  425. this.startDate = e.detail.value
  426. if (this.startDate && this.endDate) {
  427. this.fetchAllData(this.buildTimeParams())
  428. }
  429. },
  430. onEndDateChange(e) {
  431. this.endDate = e.detail.value
  432. if (this.startDate && this.endDate) {
  433. this.fetchAllData(this.buildTimeParams())
  434. }
  435. },
  436. fetchDeptStats(params) {
  437. countDeptTeamStats(params).then(res => {
  438. if (res.code === 200 && res.data) {
  439. this.deptStatsData = res.data
  440. }
  441. }).catch(() => { })
  442. },
  443. fetchRadarData(params) {
  444. getDimensionScoreOverview(params).then(res => {
  445. if (res.code === 200 && res.data) {
  446. // 支持两种数据结构
  447. if (res.data.dimensions && Array.isArray(res.data.dimensions)) {
  448. this.radarData = res.data.dimensions
  449. } else {
  450. this.radarData = []
  451. }
  452. }
  453. }).catch(() => {
  454. this.radarData = []
  455. })
  456. },
  457. fetchTeamData(params) {
  458. countStationTeamStats(params).then(res => {
  459. if (res.code === 200 && res.data) {
  460. this.teamMemberData = (res.data || []).map(item => ({
  461. department: item.deptName,
  462. employeeCount: item.employeeCount,
  463. partyMemberCount: item.partyMemberCount,
  464. avgAge: item.avgAge,
  465. avgTenure: item.avgWorkYears,
  466. certificateCount: item.qualificationLevel,
  467. machineYears: item.avgXrayOperatorYears,
  468. comprehensiveScore: item.totalScore
  469. }))
  470. }
  471. }).catch(() => { })
  472. },
  473. fetchMemberDistribution(params) {
  474. getDeptMemberDistribution(params).then(res => {
  475. if (res.code === 200 && res.data) {
  476. this.memberBasicData = {
  477. gender: {
  478. labels: (res.data.sexDistribution || []).map(item => item.name),
  479. data: (res.data.sexDistribution || []).map(item => item.count)
  480. },
  481. ethnicity: {
  482. labels: (res.data.nationDistribution || []).map(item => item.name),
  483. data: (res.data.nationDistribution || []).map(item => item.count)
  484. },
  485. political: {
  486. labels: (res.data.politicalDistribution || []).map(item => item.name),
  487. data: (res.data.politicalDistribution || []).map(item => item.count)
  488. }
  489. }
  490. }
  491. }).catch(() => { })
  492. },
  493. fetchPositionDistribution(params) {
  494. getDeptPositionDistribution(params).then(res => {
  495. if (res.code === 200 && res.data) {
  496. this.memberPositionData = {
  497. qualification: {
  498. labels: (res.data.qualificationDistribution || []).map(item => item.name),
  499. data: (res.data.qualificationDistribution || []).map(item => item.count)
  500. },
  501. experience: {
  502. labels: (res.data.xrayYearDistribution || []).map(item => item.name),
  503. data: (res.data.xrayYearDistribution || []).map(item => item.count)
  504. },
  505. position: {
  506. labels: (res.data.positionDistribution || []).map(item => item.name),
  507. data: (res.data.positionDistribution || []).map(item => item.count)
  508. }
  509. }
  510. }
  511. }).catch(() => { })
  512. },
  513. fetchPassengerData(params) {
  514. countLanePeakThroughput(params).then(res => {
  515. if (res.code === 200 && res.data) {
  516. const rawData = res.data || []
  517. const hours = [...new Set(rawData.map(item => item.hour))]
  518. const areaMap = {}
  519. rawData.forEach(item => {
  520. (item.laneList || []).forEach(lane => {
  521. if (!areaMap[lane.laneName]) {
  522. areaMap[lane.laneName] = []
  523. }
  524. areaMap[lane.laneName].push(lane.throughputRate)
  525. })
  526. })
  527. this.passengerData = {
  528. labels: hours,
  529. areas: areaMap,
  530. totalFlow: rawData.map(item => item.totalLaneThroughput)
  531. }
  532. }
  533. }).catch(() => { })
  534. },
  535. fetchSeizureInfo(params) {
  536. countSeizureInfoItem(params).then(res => {
  537. if (res.code === 200 && res.data) {
  538. const itemList = res.data.itemList || []
  539. this.seizureInfoData = {
  540. total: res.data.totalSeizeNum || 0,
  541. depts: {
  542. labels: itemList.map(item => item.name),
  543. data: itemList.map(item => item.seizeNum)
  544. }
  545. }
  546. }
  547. }).catch(() => { })
  548. },
  549. fetchItemDistribution(params) {
  550. countSeizeSubjectCategoryQuantity(params).then(res => {
  551. if (res.code === 200 && res.data) {
  552. const items = (res.data || []).map((item, index) => ({
  553. name: item.itemName,
  554. value: item.itemNum,
  555. color: itemColors[index % itemColors.length]
  556. }))
  557. this.itemDistributionData = { items }
  558. }
  559. }).catch(() => { })
  560. },
  561. fetchDailySeizure(params) {
  562. countSeizureTotalQuantity(params).then(res => {
  563. if (res.code === 200 && res.data) {
  564. this.dailySeizureTotalData = res.data || []
  565. this.dailySeizureData.total = {
  566. labels: (res.data || []).map(item => item.recordDate),
  567. data: (res.data || []).map(item => item.seizeQuantity)
  568. }
  569. }
  570. }).catch(() => { })
  571. countSeizureSingleQuantity(params).then(res => {
  572. if (res.code === 200 && res.data) {
  573. const rawData = res.data || []
  574. const dates = rawData.map(item => item.recordDate)
  575. const deptMap = {}
  576. rawData.forEach(dayItem => {
  577. (dayItem.items || []).forEach(subItem => {
  578. if (!deptMap[subItem.groupName]) {
  579. deptMap[subItem.groupName] = []
  580. }
  581. deptMap[subItem.groupName].push(subItem.seizeQuantity)
  582. })
  583. })
  584. this.dailySeizureData.dept = {
  585. labels: dates,
  586. deptData: Object.keys(deptMap).map(name => ({
  587. name,
  588. data: deptMap[name]
  589. }))
  590. }
  591. }
  592. }).catch(() => { })
  593. },
  594. fetchAreaDistribution(params) {
  595. countSeizeAreaQuantity(params).then(res => {
  596. if (res.code === 200 && res.data) {
  597. const data = res.data || []
  598. this.areaDistributionData = {
  599. labels: data.map(item => item.workArea),
  600. data: data.map(item => item.areaSeizeNum)
  601. }
  602. }
  603. }).catch(() => { })
  604. },
  605. fetchUnsafeItems(params) {
  606. countSeizureStatsItem(params).then(res => {
  607. if (res.code === 200 && res.data) {
  608. const data = res.data || []
  609. this.unsafeItemsData = {
  610. items: data.map(item => ({
  611. name: item.itemName,
  612. value: item.itemNum
  613. }))
  614. }
  615. }
  616. }).catch(() => { })
  617. },
  618. fetchUnsafeTypes(params) {
  619. countSeizureStatsType(params).then(res => {
  620. if (res.code === 200 && res.data) {
  621. const data = res.data || []
  622. this.unsafeTypesData = {
  623. types: data.map(item => ({
  624. name: item.eventType,
  625. value: item.eventTypeNum
  626. }))
  627. }
  628. }
  629. }).catch(() => { })
  630. },
  631. fetchUnsafePosition(params) {
  632. countSeizureStatsPost(params).then(res => {
  633. if (res.code === 200 && res.data) {
  634. const data = res.data || []
  635. const total = data.reduce((sum, item) => sum + (item.count || 0), 0)
  636. this.unsafePositionData = {
  637. total: total,
  638. positions: {
  639. labels: data.map(item => item.positionName),
  640. data: data.map(item => item.positionNum)
  641. }
  642. }
  643. }
  644. }).catch(() => { })
  645. },
  646. fetchSecurityTestData(params) {
  647. securityTestItemClassification(params).then(res => {
  648. if (res.code === 200 && res.data) {
  649. const data = res.data || []
  650. this.securityTestData.items = {
  651. labels: data.map(item => item.name),
  652. data: data.map(item => item.total)
  653. }
  654. }
  655. }).catch(() => { })
  656. securityTestPassingStatus(params).then(res => {
  657. if (res.code === 200 && res.data) {
  658. const data = res.data || []
  659. this.securityTestData.results = {
  660. labels: data.map(item => item.name),
  661. data: data.map(item => item.total)
  662. }
  663. }
  664. }).catch(() => { })
  665. securityTestRegion(params).then(res => {
  666. if (res.code === 200 && res.data) {
  667. const data = res.data || []
  668. this.securityTestData.areas = {
  669. labels: data.map(item => item.name),
  670. data: data.map(item => item.total)
  671. }
  672. }
  673. }).catch(() => { })
  674. },
  675. fetchSupervisionData(params) {
  676. supervisionProblemPosition(params).then(res => {
  677. if (res.code === 200 && res.data) {
  678. this.supervisionData = (res.data || []).map(item => ({
  679. num: item.total,
  680. name: item.name
  681. }))
  682. }
  683. }).catch(() => { })
  684. },
  685. fetchInterceptionData(params) {
  686. realtimeInterceptionItem(params).then(res => {
  687. if (res.code === 200 && res.data) {
  688. this.interceptionData = (res.data || []).map(item => ({
  689. num: item.total,
  690. name: item.name
  691. }))
  692. }
  693. }).catch(() => { })
  694. }
  695. }
  696. }
  697. </script>
  698. <style lang="scss" scoped>
  699. .dept-selector {
  700. padding: 16rpx 32rpx;
  701. background: #fff;
  702. }
  703. .dept-select-trigger {
  704. display: flex;
  705. align-items: center;
  706. justify-content: center;
  707. gap: 12rpx;
  708. padding: 16rpx 24rpx;
  709. background: #f5f5f5;
  710. border: 1rpx solid #e0e0e0;
  711. border-radius: 50rpx;
  712. }
  713. .dept-select-trigger-disabled {
  714. opacity: 0.5;
  715. pointer-events: none;
  716. }
  717. .dept-name-text {
  718. font-size: 26rpx;
  719. color: #333;
  720. }
  721. .dept-profile-page {
  722. min-height: 100vh;
  723. background: #fff;
  724. padding-bottom: 40rpx;
  725. }
  726. .page-header {
  727. position: sticky;
  728. top: 0;
  729. z-index: 100;
  730. background: #fff;
  731. backdrop-filter: blur(10px);
  732. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
  733. }
  734. .header-title {
  735. padding: 24rpx 32rpx;
  736. display: flex;
  737. justify-content: space-between;
  738. align-items: center;
  739. .title-main {
  740. font-size: 36rpx;
  741. font-weight: bold;
  742. color: #333;
  743. }
  744. }
  745. .header-right {
  746. display: flex;
  747. align-items: center;
  748. gap: 16rpx;
  749. .current-time {
  750. font-size: 24rpx;
  751. color: #999;
  752. }
  753. }
  754. .time-filter {
  755. padding: 16rpx 32rpx;
  756. background: #fff;
  757. }
  758. .time-scroll {
  759. width: 100%;
  760. }
  761. .time-tags {
  762. display: flex;
  763. gap: 16rpx;
  764. white-space: nowrap;
  765. }
  766. .time-tag {
  767. padding: 8rpx 10rpx;
  768. border-radius: 50rpx;
  769. background: #f0f0f0;
  770. font-size: 24rpx;
  771. color: #666;
  772. transition: all 0.3s;
  773. &.active {
  774. background: #60A5FA;
  775. color: #fff;
  776. font-weight: 500;
  777. }
  778. }
  779. .date-range-picker {
  780. display: flex;
  781. align-items: center;
  782. gap: 16rpx;
  783. margin-top: 16rpx;
  784. padding-top: 16rpx;
  785. border-top: 1rpx solid #e0e0e0;
  786. }
  787. .date-input {
  788. flex: 1;
  789. padding: 12rpx 24rpx;
  790. border-radius: 12rpx;
  791. background: #f5f5f5;
  792. font-size: 24rpx;
  793. color: #999;
  794. text-align: center;
  795. &.filled {
  796. color: #333;
  797. }
  798. }
  799. .date-separator {
  800. font-size: 24rpx;
  801. color: #999;
  802. flex-shrink: 0;
  803. }
  804. .tab-nav {
  805. padding: 16rpx 32rpx;
  806. display: flex;
  807. justify-content: center;
  808. gap: 64rpx;
  809. }
  810. .tab-item {
  811. font-size: 28rpx;
  812. color: #999;
  813. padding-bottom: 8rpx;
  814. border-bottom: 2rpx solid transparent;
  815. transition: all 0.3s;
  816. &.active {
  817. color: #333;
  818. border-bottom-color: #333;
  819. }
  820. }
  821. .page-content {
  822. padding: 32rpx;
  823. display: flex;
  824. flex-direction: column;
  825. background-color: #fff;
  826. }
  827. </style>