index.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  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': !isBrigade()&&!isStationMaster() }"
  35. @click="chooseDept">
  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" title="班组" />
  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. //站长
  309. isStationMaster() {
  310. const roles = this.getUserRoles()
  311. return roles.includes('test') || roles.includes('zhijianke')
  312. },
  313. //部门经理
  314. isBrigade() {
  315. const roles = this.getUserRoles()
  316. return roles.includes('bumenjingli')
  317. },
  318. //班组长
  319. // isManager() {
  320. // const roles = this.getUserRoles()
  321. // return roles.includes('banzuzhang')
  322. // },
  323. chooseDept() {
  324. if (this.isStationMaster() || this.isBrigade()) {
  325. this.showDeptPicker = true
  326. }
  327. },
  328. fetchDeptList() {
  329. this.deptLoading = true
  330. const userInfo = this.$store.state.user?.userInfo;
  331. let params = this.isBrigade() ? { deptId: userInfo.deptId } : { deptId: userInfo.deptId }
  332. getDeptUserTree(params).then(res => {
  333. if (res.code === 200) {
  334. let allDepts = this.flattenDeptTree(res.data || [])
  335. this.deptList = allDepts.filter(d => d.deptType === 'MANAGER')
  336. if (userInfo && userInfo.deptId) {
  337. const dept = this.deptList.find(d => d.deptId === userInfo.deptId)
  338. if (dept) {
  339. this.selectedDeptId = dept.deptId
  340. this.selectedDeptName = dept.deptName
  341. } else if (this.deptList.length > 0) {
  342. this.selectedDeptId = this.deptList[0].deptId
  343. this.selectedDeptName = this.deptList[0].deptName
  344. }
  345. } else if (this.deptList.length > 0) {
  346. this.selectedDeptId = this.deptList[0].deptId
  347. this.selectedDeptName = this.deptList[0].deptName
  348. }
  349. // 选择完部门后请求数据
  350. this.onTimeTagClick(3)
  351. }
  352. }).catch(() => {
  353. }).finally(() => {
  354. this.deptLoading = false
  355. })
  356. },
  357. flattenDeptTree(tree) {
  358. const result = []
  359. const traverse = (nodes) => {
  360. nodes.forEach(node => {
  361. if (node.id) {
  362. result.push({
  363. deptId: node.id,
  364. deptName: node.label,
  365. deptType: node.deptType
  366. })
  367. }
  368. if (node.children && node.children.length > 0) {
  369. traverse(node.children)
  370. }
  371. })
  372. }
  373. traverse(tree)
  374. return result
  375. },
  376. fetchAllData(params) {
  377. this.fetchDeptStats(params)
  378. this.fetchRadarData(params)
  379. this.fetchTeamData(params)
  380. this.fetchMemberDistribution(params)
  381. this.fetchPositionDistribution(params)
  382. const copyParams = { ...params }
  383. delete copyParams.deptId
  384. this.fetchPassengerData(copyParams)
  385. this.fetchSeizureInfo(copyParams)
  386. this.fetchItemDistribution(copyParams)
  387. this.fetchDailySeizure(copyParams)
  388. this.fetchAreaDistribution(copyParams)
  389. this.fetchUnsafeItems(copyParams)
  390. this.fetchUnsafeTypes(copyParams)
  391. this.fetchUnsafePosition(copyParams)
  392. this.fetchSecurityTestData(copyParams)
  393. this.fetchSupervisionData(copyParams)
  394. this.fetchInterceptionData(copyParams)
  395. },
  396. buildTimeParams() {
  397. const now = new Date()
  398. const today = this.formatDate(now)
  399. const deptId = this.selectedDeptId;
  400. const teamId = this.selectedDeptId;
  401. if(!this.selectedDeptId){
  402. return
  403. }
  404. if (this.selectedTimeTag === 4) {
  405. if (this.startDate && this.endDate) {
  406. return { startDate: this.startDate, endDate: this.endDate, deptId, teamId }
  407. }
  408. return { deptId, teamId }
  409. }
  410. let startDate
  411. switch (this.selectedTimeTag) {
  412. case 0:
  413. startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
  414. break
  415. case 1:
  416. startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate())
  417. break
  418. case 2:
  419. startDate = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
  420. break
  421. case 3:
  422. startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
  423. break
  424. default:
  425. return { deptId, teamId }
  426. }
  427. return { startDate: this.formatDate(startDate), endDate: today, deptId, teamId }
  428. },
  429. formatDate(date) {
  430. const y = date.getFullYear()
  431. const m = String(date.getMonth() + 1).padStart(2, '0')
  432. const d = String(date.getDate()).padStart(2, '0')
  433. return `${y}-${m}-${d}`
  434. },
  435. onTimeTagClick(index) {
  436. this.selectedTimeTag = index
  437. if (index === 4) {
  438. this.startDate = ''
  439. this.endDate = ''
  440. return
  441. }
  442. const params = this.buildTimeParams()
  443. this.fetchAllData(params)
  444. },
  445. onStartDateChange(e) {
  446. this.startDate = e.detail.value
  447. if (this.startDate && this.endDate) {
  448. this.fetchAllData(this.buildTimeParams())
  449. }
  450. },
  451. onEndDateChange(e) {
  452. this.endDate = e.detail.value
  453. if (this.startDate && this.endDate) {
  454. this.fetchAllData(this.buildTimeParams())
  455. }
  456. },
  457. fetchDeptStats(params) {
  458. countDeptTeamStats(params).then(res => {
  459. if (res.code === 200 && res.data) {
  460. this.deptStatsData = res.data
  461. }
  462. }).catch(() => { })
  463. },
  464. fetchRadarData(params) {
  465. getDimensionScoreOverview(params).then(res => {
  466. if (res.code === 200 && res.data) {
  467. // 支持两种数据结构
  468. if (res.data.dimensions && Array.isArray(res.data.dimensions)) {
  469. this.radarData = res.data.dimensions
  470. } else {
  471. this.radarData = []
  472. }
  473. }
  474. }).catch(() => {
  475. this.radarData = []
  476. })
  477. },
  478. fetchTeamData(params) {
  479. countStationTeamStats(params).then(res => {
  480. if (res.code === 200 && res.data) {
  481. this.teamMemberData = (res.data || []).map(item => ({
  482. department: item.deptName,
  483. employeeCount: item.employeeCount,
  484. partyMemberCount: item.partyMemberCount,
  485. avgAge: item.avgAge,
  486. avgTenure: item.avgWorkYears,
  487. certificateCount: item.qualificationLevel,
  488. machineYears: item.avgXrayOperatorYears,
  489. comprehensiveScore: item.totalScore
  490. }))
  491. }
  492. }).catch(() => { })
  493. },
  494. fetchMemberDistribution(params) {
  495. getDeptMemberDistribution(params).then(res => {
  496. if (res.code === 200 && res.data) {
  497. this.memberBasicData = {
  498. gender: {
  499. labels: (res.data.sexDistribution || []).map(item => item.name),
  500. data: (res.data.sexDistribution || []).map(item => item.count)
  501. },
  502. ethnicity: {
  503. labels: (res.data.nationDistribution || []).map(item => item.name),
  504. data: (res.data.nationDistribution || []).map(item => item.count)
  505. },
  506. political: {
  507. labels: (res.data.politicalDistribution || []).map(item => item.name),
  508. data: (res.data.politicalDistribution || []).map(item => item.count)
  509. }
  510. }
  511. }
  512. }).catch(() => { })
  513. },
  514. fetchPositionDistribution(params) {
  515. getDeptPositionDistribution(params).then(res => {
  516. if (res.code === 200 && res.data) {
  517. this.memberPositionData = {
  518. qualification: {
  519. labels: (res.data.qualificationDistribution || []).map(item => item.name),
  520. data: (res.data.qualificationDistribution || []).map(item => item.count)
  521. },
  522. experience: {
  523. labels: (res.data.xrayYearDistribution || []).map(item => item.name),
  524. data: (res.data.xrayYearDistribution || []).map(item => item.count)
  525. },
  526. position: {
  527. labels: (res.data.positionDistribution || []).map(item => item.name),
  528. data: (res.data.positionDistribution || []).map(item => item.count)
  529. }
  530. }
  531. }
  532. }).catch(() => { })
  533. },
  534. fetchPassengerData(params) {
  535. countLanePeakThroughput(params).then(res => {
  536. if (res.code === 200 && res.data) {
  537. const rawData = res.data || []
  538. const hours = [...new Set(rawData.map(item => item.hour))]
  539. const areaMap = {}
  540. rawData.forEach(item => {
  541. (item.laneList || []).forEach(lane => {
  542. if (!areaMap[lane.laneName]) {
  543. areaMap[lane.laneName] = []
  544. }
  545. areaMap[lane.laneName].push(lane.throughputRate)
  546. })
  547. })
  548. this.passengerData = {
  549. labels: hours,
  550. areas: areaMap,
  551. totalFlow: rawData.map(item => item.totalLaneThroughput)
  552. }
  553. }
  554. }).catch(() => { })
  555. },
  556. fetchSeizureInfo(params) {
  557. countSeizureInfoItem(params).then(res => {
  558. if (res.code === 200 && res.data) {
  559. const itemList = res.data.itemList || []
  560. this.seizureInfoData = {
  561. total: res.data.totalSeizeNum || 0,
  562. depts: {
  563. labels: itemList.map(item => item.name),
  564. data: itemList.map(item => item.seizeNum)
  565. }
  566. }
  567. }
  568. }).catch(() => { })
  569. },
  570. fetchItemDistribution(params) {
  571. countSeizeSubjectCategoryQuantity(params).then(res => {
  572. if (res.code === 200 && res.data) {
  573. const items = (res.data || []).map((item, index) => ({
  574. name: item.itemName,
  575. value: item.itemNum,
  576. color: itemColors[index % itemColors.length]
  577. }))
  578. this.itemDistributionData = { items }
  579. }
  580. }).catch(() => { })
  581. },
  582. fetchDailySeizure(params) {
  583. countSeizureTotalQuantity(params).then(res => {
  584. if (res.code === 200 && res.data) {
  585. this.dailySeizureTotalData = res.data || []
  586. this.dailySeizureData.total = {
  587. labels: (res.data || []).map(item => item.recordDate),
  588. data: (res.data || []).map(item => item.seizeQuantity)
  589. }
  590. }
  591. }).catch(() => { })
  592. countSeizureSingleQuantity(params).then(res => {
  593. if (res.code === 200 && res.data) {
  594. const rawData = res.data || []
  595. const dates = rawData.map(item => item.recordDate)
  596. const deptMap = {}
  597. rawData.forEach(dayItem => {
  598. (dayItem.items || []).forEach(subItem => {
  599. if (!deptMap[subItem.groupName]) {
  600. deptMap[subItem.groupName] = []
  601. }
  602. deptMap[subItem.groupName].push(subItem.seizeQuantity)
  603. })
  604. })
  605. this.dailySeizureData.dept = {
  606. labels: dates,
  607. deptData: Object.keys(deptMap).map(name => ({
  608. name,
  609. data: deptMap[name]
  610. }))
  611. }
  612. }
  613. }).catch(() => { })
  614. },
  615. fetchAreaDistribution(params) {
  616. countSeizeAreaQuantity(params).then(res => {
  617. if (res.code === 200 && res.data) {
  618. const data = res.data || []
  619. this.areaDistributionData = {
  620. labels: data.map(item => item.workArea),
  621. data: data.map(item => item.areaSeizeNum)
  622. }
  623. }
  624. }).catch(() => { })
  625. },
  626. fetchUnsafeItems(params) {
  627. countSeizureStatsItem(params).then(res => {
  628. if (res.code === 200 && res.data) {
  629. const data = res.data || []
  630. this.unsafeItemsData = {
  631. items: data.map(item => ({
  632. name: item.itemName,
  633. value: item.itemNum
  634. }))
  635. }
  636. }
  637. }).catch(() => { })
  638. },
  639. fetchUnsafeTypes(params) {
  640. countSeizureStatsType(params).then(res => {
  641. if (res.code === 200 && res.data) {
  642. const data = res.data || []
  643. this.unsafeTypesData = {
  644. types: data.map(item => ({
  645. name: item.eventType,
  646. value: item.eventTypeNum
  647. }))
  648. }
  649. }
  650. }).catch(() => { })
  651. },
  652. fetchUnsafePosition(params) {
  653. countSeizureStatsPost(params).then(res => {
  654. if (res.code === 200 && res.data) {
  655. const data = res.data || []
  656. const total = data.reduce((sum, item) => sum + (item.count || 0), 0)
  657. this.unsafePositionData = {
  658. total: total,
  659. positions: {
  660. labels: data.map(item => item.positionName),
  661. data: data.map(item => item.positionNum)
  662. }
  663. }
  664. }
  665. }).catch(() => { })
  666. },
  667. fetchSecurityTestData(params) {
  668. securityTestItemClassification(params).then(res => {
  669. if (res.code === 200 && res.data) {
  670. const data = res.data || []
  671. this.securityTestData.items = {
  672. labels: data.map(item => item.name),
  673. data: data.map(item => item.total)
  674. }
  675. }
  676. }).catch(() => { })
  677. securityTestPassingStatus(params).then(res => {
  678. if (res.code === 200 && res.data) {
  679. const data = res.data || []
  680. this.securityTestData.results = {
  681. labels: data.map(item => item.name),
  682. data: data.map(item => item.total)
  683. }
  684. }
  685. }).catch(() => { })
  686. securityTestRegion(params).then(res => {
  687. if (res.code === 200 && res.data) {
  688. const data = res.data || []
  689. this.securityTestData.areas = {
  690. labels: data.map(item => item.name),
  691. data: data.map(item => item.total)
  692. }
  693. }
  694. }).catch(() => { })
  695. },
  696. fetchSupervisionData(params) {
  697. supervisionProblemPosition(params).then(res => {
  698. if (res.code === 200 && res.data) {
  699. this.supervisionData = (res.data || []).map(item => ({
  700. num: item.total,
  701. name: item.name
  702. }))
  703. }
  704. }).catch(() => { })
  705. },
  706. fetchInterceptionData(params) {
  707. realtimeInterceptionItem(params).then(res => {
  708. if (res.code === 200 && res.data) {
  709. this.interceptionData = (res.data || []).map(item => ({
  710. num: item.total,
  711. name: item.name
  712. }))
  713. }
  714. }).catch(() => { })
  715. }
  716. }
  717. }
  718. </script>
  719. <style lang="scss" scoped>
  720. .dept-selector {
  721. padding: 16rpx 32rpx;
  722. background: #fff;
  723. }
  724. .dept-select-trigger {
  725. display: flex;
  726. align-items: center;
  727. justify-content: center;
  728. gap: 12rpx;
  729. padding: 16rpx 24rpx;
  730. background: #f5f5f5;
  731. border: 1rpx solid #e0e0e0;
  732. border-radius: 50rpx;
  733. }
  734. .dept-select-trigger-disabled {
  735. opacity: 0.5;
  736. pointer-events: none;
  737. }
  738. .dept-name-text {
  739. font-size: 26rpx;
  740. color: #333;
  741. }
  742. .dept-profile-page {
  743. min-height: 100vh;
  744. background: #fff;
  745. padding-bottom: 40rpx;
  746. }
  747. .page-header {
  748. position: sticky;
  749. top: 0;
  750. z-index: 100;
  751. background: #fff;
  752. backdrop-filter: blur(10px);
  753. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
  754. }
  755. .header-title {
  756. padding: 24rpx 32rpx;
  757. display: flex;
  758. justify-content: space-between;
  759. align-items: center;
  760. .title-main {
  761. font-size: 36rpx;
  762. font-weight: bold;
  763. color: #333;
  764. }
  765. }
  766. .header-right {
  767. display: flex;
  768. align-items: center;
  769. gap: 16rpx;
  770. .current-time {
  771. font-size: 24rpx;
  772. color: #999;
  773. }
  774. }
  775. .time-filter {
  776. padding: 16rpx 32rpx;
  777. background: #fff;
  778. }
  779. .time-scroll {
  780. width: 100%;
  781. }
  782. .time-tags {
  783. display: flex;
  784. gap: 16rpx;
  785. white-space: nowrap;
  786. }
  787. .time-tag {
  788. padding: 8rpx 10rpx;
  789. border-radius: 50rpx;
  790. background: #f0f0f0;
  791. font-size: 24rpx;
  792. color: #666;
  793. transition: all 0.3s;
  794. &.active {
  795. background: #60A5FA;
  796. color: #fff;
  797. font-weight: 500;
  798. }
  799. }
  800. .date-range-picker {
  801. display: flex;
  802. align-items: center;
  803. gap: 16rpx;
  804. margin-top: 16rpx;
  805. padding-top: 16rpx;
  806. border-top: 1rpx solid #e0e0e0;
  807. }
  808. .date-input {
  809. flex: 1;
  810. padding: 12rpx 24rpx;
  811. border-radius: 12rpx;
  812. background: #f5f5f5;
  813. font-size: 24rpx;
  814. color: #999;
  815. text-align: center;
  816. &.filled {
  817. color: #333;
  818. }
  819. }
  820. .date-separator {
  821. font-size: 24rpx;
  822. color: #999;
  823. flex-shrink: 0;
  824. }
  825. .tab-nav {
  826. padding: 16rpx 32rpx;
  827. display: flex;
  828. justify-content: center;
  829. gap: 64rpx;
  830. }
  831. .tab-item {
  832. font-size: 28rpx;
  833. color: #999;
  834. padding-bottom: 8rpx;
  835. border-bottom: 2rpx solid transparent;
  836. transition: all 0.3s;
  837. &.active {
  838. color: #333;
  839. border-bottom-color: #333;
  840. }
  841. }
  842. .page-content {
  843. padding: 32rpx;
  844. display: flex;
  845. flex-direction: column;
  846. }
  847. </style>