index.vue 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  1. <template>
  2. <view>
  3. <div class="title">
  4. <user-info :name="checkerName" :sub-title="subTitle" :hello="'欢迎您'" class="userInfo" />
  5. <!-- 排名区块 -->
  6. <div class="rank-container" v-if="!isZhanZhang">
  7. <div v-for="(item, index) in rankItems" :key="index" class="rank-item">
  8. <div class="rank-content">
  9. <div class="rank-label">{{ item.label }}</div>
  10. <div class="rank-value">{{ item.value }}</div>
  11. <image :src="item.iconSrc" class="rank-image" />
  12. </div>
  13. </div>
  14. </div>
  15. <!-- 出勤信息区块 -->
  16. <div class="attendance-container">
  17. <div class="attendance-grid">
  18. <div v-for="(item, index) in attendanceItems" :key="index" class="attendance-item"
  19. @click="handleAttendanceClick(item)">
  20. <div class="item-label">{{ item.label }}</div>
  21. <div class="item-value">{{ item.value }}</div>
  22. </div>
  23. </div>
  24. </div>
  25. <!-- 角色切换组件(班组长显示) -->
  26. <SelectTag v-if="role.includes('banzuzhang')" :tags="roleTags" :selected-value="selectedRole"
  27. @change="handleRoleChange" class="role-selector" />
  28. </div>
  29. <div class="content">
  30. <Notice ref="notice" />
  31. <!-- 时间范围选择 -->
  32. <div class="time-range-section">
  33. <div class="time-tags-container">
  34. <div v-for="tag in timeTags" :key="tag.value"
  35. :class="['time-tag', { 'active': selectedTimeRange === tag.value, 'time-range-label': tag.isLabel }]"
  36. @click="tag.isLabel ? null : handleTimeTagClick(tag)">
  37. {{ tag.label }}
  38. </div>
  39. <!-- 自定义时间显示 - 紧跟自定义时间标签 -->
  40. <div v-if="selectedTimeRange === 'custom'" class="custom-time-inline">
  41. <uni-datetime-picker v-model="dateRange" type="daterange" rangeSeparator="至"
  42. @change="handleDateRangeChange" class="date-range-picker" :clear-icon="false" />
  43. <div class="days-count">共 {{ totalDays }} 天</div>
  44. </div>
  45. </div>
  46. </div>
  47. <!-- TypeDetail组件 -->
  48. <TypeDetail :data-list="typeDetailData" @sort-change="handleSortChange" />
  49. <!-- TotalDetail组件 -->
  50. <TotalDetail :homePageWholeData="homePageWholeData" :start-date="startDate" :end-date="endDate"
  51. :time-range="selectedTimeRange" :selectedRole="selectedRole" />
  52. </div>
  53. </view>
  54. </template>
  55. <script>
  56. import HomeContainer from "@/components/HomeContainer.vue";
  57. import UserInfo from "@/components/UserInfo.vue";
  58. import HeadTitle from "@/components/HeadTitle.vue";
  59. import Notice from "@/pages/home/components/notice.vue";
  60. import SelectTag from "@/components/select-tag/select-tag.vue";
  61. import TypeDetail from "@/pages/home-new/components/type-detail.vue";
  62. import TotalDetail from "@/pages/home-new/components/total-detail.vue";
  63. import { getUserProfile, getAppListByRoleId, getAppList } from "@/api/system/user";
  64. import { getAttendanceList } from "@/api/attendance/attendance"
  65. import { getUnreadCount } from "@/api/check/checkTask"
  66. import { checkRolePermission } from "@/utils/common.js";
  67. import { getHomePage, getSeizureReport, getAttendanceStats, getAccuracyStatistics, getSeizureRanking, getCheckRanking, getHomePageWhole, selectUserListByRoleKey } from "@/api/home-new/home-new";
  68. // 导入uni-datetime-picker组件
  69. import uniDatetimePicker from "@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue";
  70. export default {
  71. components: {
  72. HeadTitle,
  73. UserInfo,
  74. HomeContainer,
  75. Notice,
  76. SelectTag,
  77. TypeDetail,
  78. TotalDetail,
  79. uniDatetimePicker
  80. },
  81. data() {
  82. return {
  83. checkerId: this.$store.state.user.id,
  84. userInfo: '',
  85. todayCheckInTime: '未打卡',
  86. unreadCount: 0,
  87. appList: [], // 存储接口返回的应用列表
  88. // 角色切换相关数据
  89. selectedRole: 'individual',
  90. roleTags: [
  91. { label: '个人', value: 'individual' },
  92. { label: '班组', value: 'team' }
  93. ],
  94. // 排序状态管理
  95. sortStates: {
  96. '查获上报': { teamSortType: 'asc', deptSortType: 'asc' },
  97. '抽问抽答': { teamSortType: 'asc', deptSortType: 'asc' },
  98. '巡检': { teamSortType: 'asc', deptSortType: 'asc' }
  99. },
  100. // 排名数据
  101. rankData: {
  102. seizeRank: 0,
  103. seizeModalType: 0,
  104. answerRank: 0,
  105. answerModalType: 0,
  106. checkRank: 0,
  107. checkModalType: 0
  108. },
  109. // 出勤信息数据
  110. attendanceData: {},
  111. // 时间选择相关数据
  112. showTimePicker: false,
  113. startDate: '',
  114. endDate: '',
  115. dateRange: [],
  116. totalDays: 0,
  117. selectedTimeRange: 'year',
  118. timeTags: [
  119. { label: '时间范围:', value: 'label', isLabel: true },
  120. { label: '近一周', value: 'week' },
  121. { label: '近一月', value: 'month' },
  122. { label: '近三月', value: 'quarter' },
  123. { label: '近半年', value: 'halfYear' },
  124. { label: '近一年', value: 'year' },
  125. { label: '自定义时间', value: 'custom' }
  126. ],
  127. inspectionData: {},
  128. seizeData: {},
  129. attendanceStats: {},
  130. accuracyStatistics: {},
  131. homePageWholeData: [],
  132. subTitleText: '',
  133. }
  134. },
  135. computed: {
  136. subTitle() {
  137. return this.isZhanZhang ? `今日上岗科长:${this.subTitleText}` : this.userInfo
  138. },
  139. checkerName() {
  140. if (this.$store.state.user) {
  141. if (this.$store.state.user.userInfo && this.$store.state.user.userInfo.nickName) {
  142. return this.$store.state.user.userInfo.nickName
  143. }
  144. }
  145. return this.$store.state.user.name
  146. },
  147. role() {
  148. return this.$store?.state?.user?.roles
  149. },
  150. currentUser() {
  151. return this.$store.state.user.userInfo
  152. },
  153. // 是否为班组视图:角色是班组长或者selectedRole为team
  154. isTeamView() {
  155. return this.role.includes('banzuzhang') ? this.selectedRole === 'team' : this.role.includes('banzuzhang')
  156. },
  157. // 是否为个人视图:selectedRole为individual或者角色是SecurityCheck
  158. isIndividualView() {
  159. return this.role.includes('banzuzhang') ? this.selectedRole === 'individual' : this.role.includes('SecurityCheck')
  160. },
  161. isBrigade() {
  162. return this.role.includes('jingli') || this.role.includes('xingzheng')
  163. },
  164. // 是否为站长或质检科角色
  165. isZhanZhang() {
  166. return this.role.includes('test') || this.role.includes('zhijianke')
  167. },
  168. // TypeDetail组件数据
  169. typeDetailData() {
  170. console.log('this.seizeData', this.seizeData, this.seizeData.stationMasterData)
  171. return [
  172. {
  173. title: '查获上报',
  174. linkText: this.isZhanZhang ? "" : this.isIndividualView ? (this.seizePendingCount ? `${this.seizePendingCount}条草稿待处理` : '') : (this.seizePendingCount ? `${this.seizePendingCount}条数据待处理` : ''),
  175. link: this.isIndividualView ? '/pages/voiceSubmissionDraft/index' : '/pages/myToDoList/index',
  176. statTitle: '查获数量',
  177. teamSortType: this.sortStates['查获上报'].teamSortType,
  178. deptSortType: this.sortStates['查获上报'].deptSortType,
  179. dividerIndex: this.isBrigade ? 1 : 0,
  180. dataItems: this.getSeizeDataItems(),
  181. rankList: this.getSeizeRankList(),
  182. departmentRank: this.seizeData.stationMasterData && this.seizeData.stationMasterData.topThreeDepartmentRankings ? this.seizeData.stationMasterData.topThreeDepartmentRankings.map(item => ({ ...item, passRate: item.seizureCount.toFixed(2), name: item.departmentName })) : [],
  183. bottomDepartmentRank: this.seizeData.stationMasterData && this.seizeData.stationMasterData.botomThreeDepartmentRankings && this.seizeData.stationMasterData.departmentRankings ? [...this.seizeData.stationMasterData.departmentRankings].sort((a, b) => (a.seizureCount || 0) - (b.seizureCount || 0)).slice(0, 3).map(item => ({ ...item, passRate: item.seizureCount.toFixed(2), name: item.departmentName })) : [],
  184. teamRank: this.seizeData.stationMasterData && this.seizeData.stationMasterData.topThreeTeamRankings ? this.seizeData.stationMasterData.topThreeTeamRankings.map(item => ({ ...item, passRate: item.seizureCount.toFixed(2), name: item.teamName })) : [],
  185. bottomTeamRank: this.seizeData.stationMasterData && this.seizeData.stationMasterData.botomThreeTeamRankings ? this.seizeData.stationMasterData.botomThreeTeamRankings.map(item => ({ ...item, passRate: item.seizureCount.toFixed(2), name: item.teamName })) : [],
  186. },
  187. {
  188. title: '抽问抽答',
  189. linkText: this.isIndividualView || this.isTeamView ? this.accuracyStatistics.hasPendingTask ? '待答题' : '' : '',
  190. link: '/pages/daily-exam/task-list/index',
  191. statTitle: '正确率(%)',
  192. teamSortType: this.sortStates['抽问抽答'].teamSortType,
  193. deptSortType: this.sortStates['抽问抽答'].deptSortType,
  194. dividerIndex: this.isBrigade ? 1 : 0,
  195. dataItems: this.getQuestionDataItems(),
  196. rankList: this.getQuestionRankList(),
  197. departmentRank: this.accuracyStatistics.topDepts && this.accuracyStatistics.topDepts.map(item => ({ ...item, passRate: item.accuracy.toFixed(2), name: item.name })),
  198. bottomDepartmentRank: this.accuracyStatistics.bottomDepts && [...this.accuracyStatistics.topDepts].sort((a, b) => (a.accuracy || 0) - (b.accuracy || 0)).map(item => ({ ...item, passRate: item.accuracy.toFixed(2), name: item.name })),
  199. teamRank: this.accuracyStatistics.topTeamsInSite && this.accuracyStatistics.topTeamsInSite.map(item => ({ ...item, passRate: item.accuracy.toFixed(2), name: item.name })),
  200. bottomTeamRank: this.accuracyStatistics.bottomTeamsInSite && this.accuracyStatistics.bottomTeamsInSite.map(item => ({ ...item, passRate: item.accuracy.toFixed(2), name: item.name })),
  201. },
  202. {
  203. title: '巡检',
  204. linkText: this.isZhanZhang ? "" : this.inspectionData.toDoNumber ? `${this.inspectionData.toDoNumber}条待处理任务` : "",
  205. link: '/pages/myToDoList/index',
  206. statTitle: '合格率(%)',
  207. teamSortType: this.sortStates['巡检'].teamSortType,
  208. deptSortType: this.sortStates['巡检'].deptSortType,
  209. dividerIndex: this.isBrigade ? 1 : 0,
  210. dataItems: this.getInspectionDataItems(),
  211. completed: this.inspectionData.doneNumber,
  212. pending: this.inspectionData.doingNumber,
  213. rankList: this.getRankList(),
  214. departmentRank: this.inspectionData.departmentRankingList && this.inspectionData.departmentRankingList.map(item => ({ ...item, passRate: (item.passRate * 100).toFixed(2) })),
  215. bottomDepartmentRank: this.inspectionData.reverseDepartmentRankingList && [...this.inspectionData.reverseDepartmentRankingList].sort((a, b) => (a.passRate || 0) - (b.passRate || 0)).slice(0, 5).map(item => ({ ...item, passRate: (item.passRate * 100).toFixed(2) })),
  216. teamRank: this.inspectionData.teamRankingList && this.inspectionData.teamRankingList.map(item => ({ ...item, passRate: (item.passRate * 100).toFixed(2) })),
  217. bottomTeamRank: this.inspectionData.reverseTeamRankingList && this.inspectionData.reverseTeamRankingList.map(item => ({ ...item, passRate: (item.passRate * 100).toFixed(2) })),
  218. }
  219. ]
  220. },
  221. // 排名项数组
  222. rankItems() {
  223. return [
  224. {
  225. label: '查获排名',
  226. value: `${this.rankData.seizeRank}`,
  227. iconSrc: this.getRankImage(this.rankData.seizeModalType)
  228. },
  229. {
  230. label: '答题排名',
  231. value: `${this.rankData.answerRank}`,
  232. iconSrc: this.getRankImage(this.rankData.answerModalType)
  233. },
  234. {
  235. label: '检查排名',
  236. value: `${this.rankData.checkRank}`,
  237. iconSrc: this.getRankImage(this.rankData.checkModalType)
  238. }
  239. ];
  240. },
  241. // 根据角色返回不同的出勤信息数组
  242. attendanceItems() {
  243. let res = {};
  244. if (this.role.includes('kezhang') || this.isZhanZhang || this.isBrigade) {
  245. // 班组长选择班组视图
  246. if (this.isZhanZhang) {
  247. res = this.attendanceStats.stationLeaderStats
  248. }
  249. if (this.role.includes('kezhang')) {
  250. res = this.attendanceStats.sectionLeaderStats
  251. }
  252. if (this.isBrigade) {
  253. res = this.attendanceStats.brigadeLeaderStats
  254. }
  255. // 抽离复杂的判断逻辑
  256. const isZhanZhang = this.isZhanZhang;
  257. const firstLabel = isZhanZhang ? '在岗大队' : this.isBrigade ? '执勤大队' : '在岗班组';
  258. const firstValue = (isZhanZhang || this.isBrigade) ? (res?.dutyDeptName || '暂无') : res?.onDutyTeamCount;
  259. const secondLabel = isZhanZhang || this.isBrigade ? '在岗班组' : '在岗人员';
  260. const secondValue = isZhanZhang || this.isBrigade ? res?.onDutyTeamCount : res?.onDutyPersonnelCount;
  261. const thirdLabel = isZhanZhang || this.isBrigade ? '在岗人员' : '出勤时间';
  262. const thirdValue = (isZhanZhang || this.isBrigade) ? res?.onDutyPersonnelCount : (res?.attendanceTime || res?.attendanceTimeTips);
  263. const thirdLink = isZhanZhang ? '' : (res?.attendanceTime ? '' : '/pages/attendance/index');
  264. return [
  265. { label: firstLabel, value: firstValue },
  266. { label: secondLabel, value: secondValue },
  267. { label: thirdLabel, value: thirdValue, link: thirdLink },
  268. { label: '今日查获上报', value: res?.todaySeizureReportCount },
  269. { label: '今日巡检问题', value: res?.todayCheckCorrectionCount },
  270. { label: '今日抽问抽答', value: `${this.accuracyStatistics?.todayTaskCompletion?.completedCount || 0}/${this.accuracyStatistics?.todayTaskCompletion?.totalCount || 0}` }
  271. ];
  272. } else {
  273. res = this.attendanceStats.teamLeaderStats || this.attendanceStats.securityCheckStats
  274. // 个人视图或非班组长角色
  275. return [
  276. { label: '出勤班组', value: res?.attendanceTeamName },
  277. { label: '出勤通道', value: res?.attendanceChannel },
  278. { label: '出勤时间', value: res?.attendanceTime || res?.attendanceTimeTips, link: res?.attendanceTime ? '' : '/pages/attendance/index' }
  279. ];
  280. }
  281. },
  282. seizePendingCount() {
  283. // 根据角色返回不同的排名数据
  284. if (this.isIndividualView) {
  285. const { securityCheckerData } = this.seizeData;
  286. const { pendingCount } = securityCheckerData || {};
  287. return pendingCount;
  288. } else if (this.role.includes('kezhang')) {
  289. const { sectionMasterData } = this.seizeData;
  290. const { pendingCount } = sectionMasterData || {};
  291. // 科长、SecurityCheck角色:显示班组、科级和站级排名
  292. return pendingCount;
  293. } else if (this.isTeamView) {
  294. const { teamLeaderData } = this.seizeData;
  295. const { pendingCount } = teamLeaderData || {}
  296. return pendingCount;
  297. } else {
  298. }
  299. }
  300. },
  301. async onShow() {
  302. // 使用统一的loadAllData方法并行加载所有数据
  303. await this.loadAllData();
  304. // 数据获取完成后执行其他函数
  305. this.updateCurrentDate();
  306. this.getUnreadCount();
  307. // 页面显示时,刷新子组件的数据 - 使用$nextTick确保DOM已渲染完成
  308. this.$nextTick(() => {
  309. console.log("notice====", this.$refs.notice)
  310. this.$refs.notice?.refreshData();
  311. this.$refs.announcement?.refreshData();
  312. });
  313. },
  314. onLoad() {
  315. this.getUser()
  316. this.loadTodayRecord();
  317. this.setDefaultDates();
  318. },
  319. methods: {
  320. // 设置默认日期
  321. setDefaultDates() {
  322. const today = new Date();
  323. const yesterday = new Date(today);
  324. yesterday.setDate(today.getDate() - 1);
  325. // 与calculateDateRange方法中的近一年计算方式保持一致:从365天前到昨天
  326. const startDate = new Date(yesterday);
  327. startDate.setDate(yesterday.getDate() - 364);
  328. // 使用本地时间格式化,避免UTC时间转换问题
  329. this.startDate = this.formatLocalDate(startDate)
  330. this.endDate = this.formatLocalDate(yesterday)
  331. // 初始化dateRange
  332. this.dateRange = [this.startDate, this.endDate]
  333. this.calculateDays()
  334. },
  335. // 格式化本地日期为YYYY-MM-DD格式
  336. formatLocalDate(date) {
  337. const year = date.getFullYear();
  338. const month = String(date.getMonth() + 1).padStart(2, '0');
  339. const day = String(date.getDate()).padStart(2, '0');
  340. return `${year}-${month}-${day}`;
  341. },
  342. // 计算天数
  343. calculateDays() {
  344. if (this.startDate && this.endDate) {
  345. const start = new Date(this.startDate)
  346. const end = new Date(this.endDate)
  347. const diffTime = Math.abs(end - start)
  348. const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1
  349. this.totalDays = diffDays
  350. }
  351. },
  352. // 处理日期范围变化
  353. async handleDateRangeChange(e) {
  354. if (e && Array.isArray(e) && e.length === 2) {
  355. this.startDate = e[0]
  356. this.endDate = e[1]
  357. this.calculateDays()
  358. await this.loadAllData();
  359. }
  360. },
  361. // 统一加载所有数据的方法
  362. async loadAllData() {
  363. try {
  364. // 使用Promise.allSettled并行执行所有数据接口请求
  365. const results = await Promise.allSettled([
  366. this.getInspectionData(), // 巡检数据
  367. this.getSeizeData(), // 查获上报数据
  368. this.getAttendanceStats(), // 出勤统计数据
  369. this.fetchAccuracyStatistics(), // 抽问抽答数据
  370. this.getRank(), // 排名数据
  371. this.getHomePageWholeData(), // 首页整体数据
  372. this.getTodayOnDutyKeZhang() // 今日上岗科长信息
  373. ]);
  374. // 检查每个接口的请求结果
  375. results.forEach((result, index) => {
  376. const apiNames = [
  377. 'getInspectionData',
  378. 'getSeizeData',
  379. 'getAttendanceStats',
  380. 'fetchAccuracyStatistics',
  381. 'getRank',
  382. 'getHomePageWholeData',
  383. 'getTodayOnDutyKeZhang'
  384. ];
  385. if (result.status === 'rejected') {
  386. console.warn(`${apiNames[index]} 接口请求失败:`, result.reason);
  387. } else {
  388. console.log(`${apiNames[index]} 接口请求成功`);
  389. }
  390. });
  391. return results;
  392. } catch (error) {
  393. console.error('加载数据失败:', error);
  394. throw error;
  395. }
  396. },
  397. getRank() {
  398. return new Promise((resolve, reject) => {
  399. let params = {};
  400. // 根据时间范围计算开始时间和结束时间(不包括今天)
  401. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  402. params.startDate = startDate;
  403. params.endDate = endDate;
  404. params.dataSource = this.selectedRole;
  405. let idObj = {}
  406. if (this.isIndividualView) {
  407. idObj = { userId: this.currentUser.userId }
  408. } else {
  409. // 如果是班组视图,使用当前用户的部门ID
  410. if (this.selectedRole === 'team') {
  411. idObj = { deptId: this.currentUser.deptId }
  412. } else {
  413. // 个人视图,使用当前用户的部门ID
  414. idObj = { deptId: this.currentUser.deptId }
  415. }
  416. }
  417. // 构建API请求数组
  418. const apiRequests = [getSeizureRanking(params)];
  419. // 如果角色不是test,才请求getCheckRanking接口
  420. if (!this.role.includes('test')) {
  421. apiRequests.push(getCheckRanking({ ...params, ...idObj }));
  422. }
  423. // 使用Promise.allSettled并行执行API请求,即使有接口失败也不影响其他接口
  424. Promise.allSettled(apiRequests).then((results) => {
  425. // 处理每个接口的结果
  426. results.forEach((result, index) => {
  427. if (result.status === 'fulfilled') {
  428. const res = result.value;
  429. if (index === 0) {
  430. // 处理查获排名数据
  431. const { data: seizureData } = res || {};
  432. this.rankData.seizeRank = seizureData[0]?.rank;
  433. this.rankData.seizeModalType = seizureData[0]?.medalType;
  434. } else if (index === 1) {
  435. // 处理检查排名数据
  436. const { data: checkData } = res;
  437. const { checkLargeScreenHomePageRankingDto, dailyTaskAccuracyRankingDto } = checkData
  438. this.rankData.checkRank = checkLargeScreenHomePageRankingDto.rank;
  439. this.rankData.checkModalType = checkLargeScreenHomePageRankingDto.medalType;
  440. this.rankData.answerRank = dailyTaskAccuracyRankingDto.rank;
  441. this.rankData.answerModalType = dailyTaskAccuracyRankingDto.medalType;
  442. }
  443. } else {
  444. console.warn(`第${index + 1}个接口请求失败:`, result.reason);
  445. }
  446. });
  447. resolve({
  448. seizureData: results[0]?.status === 'fulfilled' ? results[0].value.data : null,
  449. checkData: results[1]?.status === 'fulfilled' ? results[1].value.data : null
  450. });
  451. });
  452. });
  453. },
  454. getInspectionData() {
  455. return new Promise((resolve, reject) => {
  456. let params = {}
  457. // 根据角色和selectedRole设置参数
  458. if (this.isIndividualView) {
  459. params = { userId: this.currentUser.userId }
  460. } else {
  461. // 如果是班组视图,使用当前用户的部门ID
  462. if (this.selectedRole === 'team') {
  463. params = { deptId: this.currentUser.deptId }
  464. } else {
  465. // 个人视图,使用当前用户的部门ID
  466. params = { deptId: this.currentUser.deptId }
  467. }
  468. }
  469. // 根据时间范围计算开始时间和结束时间(不包括今天)
  470. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  471. params.startDate = startDate;
  472. params.endDate = endDate;
  473. getHomePage(params).then(res => {
  474. // console.log("res", res)
  475. this.inspectionData = res.data || {};
  476. resolve(res.data);
  477. }).catch(error => {
  478. console.error("获取巡检数据失败:", error);
  479. reject(error);
  480. });
  481. });
  482. },
  483. getHomePageWholeData() {
  484. return new Promise((resolve, reject) => {
  485. let params = {}
  486. // 根据时间范围计算开始时间和结束时间(不包括今天)
  487. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  488. params.startDate = startDate;
  489. params.endDate = endDate;
  490. params.dataSource = this.selectedRole;
  491. getHomePageWhole(params).then(res => {
  492. this.homePageWholeData = res.data || [];
  493. // 这里可以处理getHomePageWhole接口返回的数据
  494. resolve(res.data);
  495. }).catch(error => {
  496. console.error("获取整体数据失败:", error);
  497. reject(error);
  498. });
  499. });
  500. },
  501. getSeizeData() {
  502. return new Promise((resolve, reject) => {
  503. let params = {}
  504. // 根据时间范围计算开始时间和结束时间(不包括今天)
  505. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  506. params.startDate = startDate;
  507. params.endDate = endDate;
  508. if (!this.isZhanZhang && !this.role.includes('kezhang')) {
  509. params.dataSource = this.selectedRole;
  510. }
  511. getSeizureReport(params).then(res => {
  512. // console.log("res", res)
  513. this.seizeData = res.data || {};
  514. resolve(res.data);
  515. }).catch(error => {
  516. console.error("获取查获数据失败:", error);
  517. reject(error);
  518. });
  519. });
  520. },
  521. getAttendanceStats() {
  522. return new Promise((resolve, reject) => {
  523. let params = {}
  524. // 根据时间范围计算开始时间和结束时间(不包括今天)
  525. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  526. params.startDate = startDate;
  527. params.endDate = endDate;
  528. params.dataSource = this.selectedRole;
  529. getAttendanceStats(params).then(res => {
  530. console.log("出勤统计数据:", res)
  531. this.attendanceStats = res.data || {};
  532. resolve(res.data);
  533. }).catch(error => {
  534. console.error("获取出勤统计数据失败:", error);
  535. reject(error);
  536. });
  537. });
  538. },
  539. fetchAccuracyStatistics() {
  540. return new Promise((resolve, reject) => {
  541. let params = {}
  542. // 根据时间范围计算开始时间和结束时间(不包括今天)
  543. const { startDate, endDate } = this.calculateDateRange(this.selectedTimeRange);
  544. params.startDate = startDate;
  545. params.endDate = endDate;
  546. params.dataSource = this.selectedRole;
  547. getAccuracyStatistics(params).then(res => {
  548. console.log("准确率统计数据:", res)
  549. this.accuracyStatistics = res.data || {};
  550. // this.rankData.answerRank = this.accuracyStatistics.deptInSiteRanking.rank;
  551. // this.rankData.answerRank = this.accuracyStatistics.deptInSiteRanking.rank;
  552. resolve(res.data);
  553. }).catch(error => {
  554. console.error("获取准确率统计数据失败:", error);
  555. reject(error);
  556. });
  557. });
  558. },
  559. // 根据时间范围计算开始时间和结束时间(不包括今天)
  560. calculateDateRange(timeRange) {
  561. const today = new Date();
  562. const yesterday = new Date(today);
  563. yesterday.setDate(today.getDate() - 1);
  564. let startDate = new Date(yesterday);
  565. let endDate = new Date(yesterday);
  566. switch (timeRange) {
  567. case 'week':
  568. // 近一周:从7天前到昨天
  569. startDate.setDate(yesterday.getDate() - 6);
  570. break;
  571. case 'month':
  572. // 近一月:从30天前到昨天
  573. startDate.setDate(yesterday.getDate() - 29);
  574. break;
  575. case 'quarter':
  576. // 近三月:从90天前到昨天
  577. startDate.setDate(yesterday.getDate() - 89);
  578. break;
  579. case 'halfYear':
  580. // 近半年:从180天前到昨天
  581. startDate.setDate(yesterday.getDate() - 179);
  582. break;
  583. case 'year':
  584. // 近一年:从365天前到昨天
  585. startDate.setDate(yesterday.getDate() - 364);
  586. break;
  587. case 'custom':
  588. // 自定义时间:使用用户选择的日期
  589. if (this.startDate && this.endDate) {
  590. startDate = new Date(this.startDate);
  591. endDate = new Date(this.endDate);
  592. }
  593. break;
  594. default:
  595. // 默认近一周
  596. startDate.setDate(yesterday.getDate() - 364);
  597. }
  598. return {
  599. startDate: this.formatDateForInput(startDate),
  600. endDate: this.formatDateForInput(endDate)
  601. };
  602. },
  603. // 处理角色切换
  604. async handleRoleChange(roleValue) {
  605. this.selectedRole = roleValue;
  606. // 根据角色切换重新加载所有数据
  607. try {
  608. // 使用Promise.allSettled并行执行所有接口请求,即使有接口失败也不影响其他接口
  609. const results = await Promise.allSettled([
  610. this.getInspectionData(),
  611. this.getSeizeData(),
  612. this.getAttendanceStats(),
  613. this.fetchAccuracyStatistics(),
  614. this.getRank(),
  615. this.getHomePageWholeData()
  616. ]);
  617. // 检查是否有接口失败
  618. results.forEach((result, index) => {
  619. if (result.status === 'rejected') {
  620. console.warn(`第${index + 1}个接口请求失败:`, result.reason);
  621. }
  622. });
  623. console.log('角色切换后数据重新加载完成');
  624. } catch (error) {
  625. console.error('角色切换后数据加载失败:', error);
  626. uni.showToast({
  627. title: '数据加载失败',
  628. icon: 'none'
  629. });
  630. }
  631. },
  632. // 显示自定义时间选择器
  633. showCustomTimePicker(timeRange) {
  634. this.selectedTimeRange = timeRange;
  635. this.showTimePicker = true;
  636. // 优先回显自定义的时间,如果已有自定义时间则使用,否则设置默认日期
  637. if (!this.startDate || !this.endDate) {
  638. const today = new Date();
  639. const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
  640. this.startDate = this.formatDateForInput(oneWeekAgo);
  641. this.endDate = this.formatDateForInput(today);
  642. }
  643. },
  644. // 处理时间标签点击
  645. handleTimeTagClick(tag) {
  646. if (tag.value === 'custom') {
  647. // 自定义时间:显示时间选择器
  648. this.showCustomTimePicker(tag.value);
  649. } else {
  650. // 预设时间范围:直接切换
  651. this.selectedTimeRange = tag.value;
  652. this.showTimePicker = false;
  653. this.loadDataByTimeRange(tag.value);
  654. }
  655. },
  656. // 处理日期变化
  657. handleDateChange() {
  658. // 只有当两个时间都选择了才请求接口
  659. if (this.startDate && this.endDate) {
  660. const customRange = {
  661. startDate: this.startDate,
  662. endDate: this.endDate
  663. };
  664. this.loadDataByCustomRange(customRange);
  665. }
  666. },
  667. // 处理排序切换
  668. handleSortChange(title, type, sortType) {
  669. if (this.sortStates[title]) {
  670. if (type === 'team') {
  671. this.sortStates[title].teamSortType = sortType;
  672. } else if (type === 'dept') {
  673. this.sortStates[title].deptSortType = sortType;
  674. }
  675. }
  676. },
  677. // 处理时间范围变化
  678. handleTimeRangeChange(timeRange) {
  679. this.selectedTimeRange = timeRange;
  680. this.showTimePicker = false;
  681. this.loadDataByTimeRange(timeRange);
  682. },
  683. // 取消时间选择
  684. cancelTimePicker() {
  685. this.showTimePicker = false;
  686. },
  687. // 格式化日期为输入框格式
  688. formatDateForInput(date) {
  689. const year = date.getFullYear();
  690. const month = String(date.getMonth() + 1).padStart(2, '0');
  691. const day = String(date.getDate()).padStart(2, '0');
  692. return `${year}-${month}-${day}`;
  693. },
  694. // 根据时间范围加载数据
  695. async loadDataByTimeRange(timeRange) {
  696. try {
  697. console.log('加载时间范围数据:', timeRange);
  698. // 使用统一的loadAllData方法加载所有数据
  699. await this.loadAllData();
  700. console.log('时间范围切换后数据重新加载完成');
  701. } catch (error) {
  702. console.error('加载时间范围数据失败:', error);
  703. uni.showToast({
  704. title: '数据加载失败',
  705. icon: 'none'
  706. });
  707. }
  708. },
  709. handleAttendanceClick(item) {
  710. if (item.link) {
  711. uni.navigateTo({
  712. url: item.link
  713. });
  714. }
  715. },
  716. // 根据自定义时间范围加载数据
  717. async loadDataByCustomRange(customRange) {
  718. try {
  719. console.log('加载自定义时间范围数据:', customRange);
  720. // 使用统一的loadAllData方法加载所有数据
  721. await this.loadAllData();
  722. console.log('自定义时间范围切换后数据重新加载完成');
  723. } catch (error) {
  724. console.error('加载自定义时间范围数据失败:', error);
  725. uni.showToast({
  726. title: '数据加载失败',
  727. icon: 'none'
  728. });
  729. }
  730. },
  731. // 根据排名类型和排名值获取图片路径
  732. getRankImage(type) {
  733. console.log('获取排名图片:', type);
  734. switch (type) {
  735. case 1: return '/static/images/icon/gold.png';
  736. case 2: return '/static/images/icon/silver.png';
  737. case 3: return '/static/images/icon/bronze.png';
  738. case 4: return '/static/images/icon/red-face.png';
  739. case 5: return '/static/images/icon/orange-face.png';
  740. case 6: return '/static/images/icon/yellow-face.png';
  741. }
  742. },
  743. // 根据角色获取检查项标签文字
  744. getInspectionItemLabel() {
  745. if (this.isIndividualView) {
  746. return '本人';
  747. } else if (this.isTeamView) {
  748. return '班组';
  749. } else if (this.role.includes('kezhang')) {
  750. return '主管';
  751. } else if (this.isZhanZhang) {
  752. return '全站';
  753. } else {
  754. return '检查项'; // 默认值
  755. }
  756. },
  757. // 根据角色获取巡检数据项数组
  758. getInspectionDataItems() {
  759. const { personalPassRate, teamPassRate, departmentPassRate, stationPassRate, teamRankingList, departmentRankingList, brigadeRankingList, brigadePassRate } = this.inspectionData;
  760. if (this.isIndividualView) {
  761. // SecurityCheck角色:本人、班平均、主管平均、站平均
  762. return [
  763. { label: '本人', value: ((personalPassRate || 0) * 100).toFixed(2), isImage: false },
  764. { label: '班平均', value: ((teamPassRate || 0) * 100).toFixed(2), isImage: false, color: teamPassRate < personalPassRate ? '#00AE41' : '#F96060' },
  765. { label: '主管平均', value: ((departmentPassRate || 0) * 100).toFixed(2), isImage: false, color: departmentPassRate < personalPassRate ? '#00AE41' : '#F96060' },
  766. { label: '大队平均', value: ((brigadePassRate || 0) * 100).toFixed(2), isImage: false, color: brigadePassRate < personalPassRate ? '#00AE41' : '#F96060' },
  767. { label: '站平均', value: ((stationPassRate || 0) * 100).toFixed(2), isImage: false, color: stationPassRate < personalPassRate ? '#00AE41' : '#F96060' }
  768. ];
  769. } else if (this.isTeamView) {
  770. // banzuzhang角色:班组、主管平均、站平均
  771. return [
  772. { label: '班组', value: ((teamPassRate || 0) * 100).toFixed(2), isImage: false },
  773. { label: '主管平均', value: ((departmentPassRate || 0) * 100).toFixed(2), isImage: false, color: departmentPassRate < teamPassRate ? '#00AE41' : '#F96060' },
  774. { label: '大队平均', value: ((brigadePassRate || 0) * 100).toFixed(2), isImage: false, color: brigadePassRate < personalPassRate ? '#00AE41' : '#F96060' },
  775. { label: '站平均', value: ((stationPassRate || 0) * 100).toFixed(2), isImage: false, color: stationPassRate < teamPassRate ? '#00AE41' : '#F96060' }
  776. ];
  777. } else if (this.role.includes('kezhang')) {
  778. // kezhang角色:主管、站平均、前三名班组(isImage: true)
  779. return [
  780. { label: '主管', value: ((departmentPassRate || 0) * 100).toFixed(2), isImage: false },
  781. { label: '大队平均', value: ((brigadePassRate || 0) * 100).toFixed(2), isImage: false, color: brigadePassRate < personalPassRate ? '#00AE41' : '#F96060' },
  782. { label: '站平均', value: ((stationPassRate || 0) * 100).toFixed(2), isImage: false, color: stationPassRate < departmentPassRate ? '#00AE41' : '#F96060' },
  783. ];
  784. } else if (this.isBrigade) {
  785. // brigade角色:主管、站平均、前三名班组(isImage: true)
  786. return [
  787. { label: '大队', value: ((brigadePassRate || 0) * 100).toFixed(2), isImage: false },
  788. { label: '站平均', value: ((stationPassRate || 0) * 100).toFixed(2), isImage: false, color: stationPassRate < brigadePassRate ? '#00AE41' : '#F96060' },
  789. { label: this.getObjByRank(departmentRankingList, 1).name, value: '/static/images/icon/one.png', isImage: true },
  790. { label: this.getObjByRank(departmentRankingList, 2).name, value: '/static/images/icon/two.png', isImage: true },
  791. { label: this.getObjByRank(departmentRankingList, 3).name, value: '/static/images/icon/three.png', isImage: true }
  792. ];
  793. } else if (this.isZhanZhang) {
  794. // test/zhijianke角色:全站、前三名的科室(isImage: true)
  795. return [
  796. { label: '全站', value: ((stationPassRate || 0) * 100).toFixed(2), isImage: false },
  797. { label: this.getObjByRank(brigadeRankingList, 1).name, value: '/static/images/icon/one.png', isImage: true },
  798. { label: this.getObjByRank(brigadeRankingList, 2).name, value: '/static/images/icon/two.png', isImage: true },
  799. { label: this.getObjByRank(brigadeRankingList, 3).name, value: '/static/images/icon/three.png', isImage: true }
  800. ];
  801. } else {
  802. // 默认角色:检查项、完成率
  803. return [
  804. { label: '检查项', value: '12', isImage: false },
  805. { label: '完成率', value: '92%', isImage: false }
  806. ];
  807. }
  808. },
  809. //获取抽问抽答
  810. getQuestionDataItems() {
  811. const { personalAccuracy, teamAvgAccuracy, deptAvgAccuracy, siteAvgAccuracy, topDepts, topKezhangInBrigade, brigadeAvgAccuracy, brigadeRankingList } = this.accuracyStatistics;
  812. if (this.isIndividualView) {
  813. // SecurityCheck角色:本人、班平均、主管平均、站平均
  814. return [
  815. { label: '本人', value: (personalAccuracy || 0), isImage: false },
  816. { label: '班平均', value: (teamAvgAccuracy || 0), isImage: false, color: teamAvgAccuracy < personalAccuracy ? '#00AE41' : '#F96060' },
  817. { label: '主管平均', value: (deptAvgAccuracy || 0), isImage: false, color: deptAvgAccuracy < personalAccuracy ? '#00AE41' : '#F96060' },
  818. { label: '大队平均', value: (brigadeAvgAccuracy || 0), isImage: false, color: deptAvgAccuracy < personalAccuracy ? '#00AE41' : '#F96060' },
  819. { label: '站平均', value: (siteAvgAccuracy || 0), isImage: false, color: siteAvgAccuracy < personalAccuracy ? '#00AE41' : '#F96060' }
  820. ];
  821. } else if (this.isTeamView) {
  822. // banzuzhang角色:班组、主管平均、站平均
  823. return [
  824. { label: '班组', value: (teamAvgAccuracy || 0), isImage: false },
  825. { label: '主管平均', value: (deptAvgAccuracy || 0), isImage: false, color: deptAvgAccuracy < teamAvgAccuracy ? '#00AE41' : '#F96060' },
  826. { label: '大队平均', value: (brigadeAvgAccuracy || 0), isImage: false, color: brigadeAvgAccuracy < teamAvgAccuracy ? '#00AE41' : '#F96060' },
  827. { label: '站平均', value: (siteAvgAccuracy || 0), isImage: false, color: siteAvgAccuracy < teamAvgAccuracy ? '#00AE41' : '#F96060' }
  828. ];
  829. } else if (this.role.includes('kezhang')) {
  830. // kezhang角色:主管、站平均、前三名班组(isImage: true)
  831. return [
  832. { label: '主管', value: (deptAvgAccuracy || 0), isImage: false, },
  833. { label: '大队平均', value: (brigadeAvgAccuracy || 0), isImage: false, color: brigadeAvgAccuracy < deptAvgAccuracy ? '#00AE41' : '#F96060' },
  834. { label: '站平均', value: (siteAvgAccuracy || 0), isImage: false, color: siteAvgAccuracy < deptAvgAccuracy ? '#00AE41' : '#F96060' },
  835. ];
  836. } else if (this.isBrigade) {
  837. // brigade角色:主管、站平均、前三名班组(isImage: true)
  838. return [
  839. { label: '大队', value: (brigadeAvgAccuracy || 0), isImage: false, },
  840. { label: '站平均', value: (siteAvgAccuracy || 0), isImage: false, color: siteAvgAccuracy < deptAvgAccuracy ? '#00AE41' : '#F96060' },
  841. { label: this.getObjByRank(topKezhangInBrigade, 1).name, value: '/static/images/icon/one.png', isImage: true },
  842. { label: this.getObjByRank(topKezhangInBrigade, 2).name, value: '/static/images/icon/two.png', isImage: true },
  843. { label: this.getObjByRank(topKezhangInBrigade, 3).name, value: '/static/images/icon/three.png', isImage: true }
  844. ];
  845. } else if (this.isZhanZhang) {
  846. // test/zhijianke角色:全站、前三名的科室(isImage: true)
  847. return [
  848. { label: '全站', value: (siteAvgAccuracy || 0), isImage: false },
  849. { label: this.getObjByRank(brigadeRankingList, 1).name, value: '/static/images/icon/one.png', isImage: true },
  850. { label: this.getObjByRank(brigadeRankingList, 2).name, value: '/static/images/icon/two.png', isImage: true },
  851. { label: this.getObjByRank(brigadeRankingList, 3).name, value: '/static/images/icon/three.png', isImage: true }
  852. ];
  853. } else {
  854. // 默认角色:检查项、完成率
  855. return [
  856. { label: '检查项', value: '12', isImage: false },
  857. { label: '完成率', value: '92%', isImage: false }
  858. ];
  859. }
  860. },
  861. //根据角色获取查获数据项目组
  862. getSeizeDataItems() {
  863. if (this.isIndividualView) {
  864. // SecurityCheck角色:从securityCheckerData解构
  865. const { securityCheckerData } = this.seizeData;
  866. const { selfSeizureCount, teamAverage, departmentAverage, brigadeAverage, stationAverage } = securityCheckerData || {}
  867. // SecurityCheck角色:本人、班平均、主管平均、站平均
  868. return [
  869. { label: '本人', value: selfSeizureCount || 0, isImage: false },
  870. { label: '班平均', value: teamAverage || 0, isImage: false, color: teamAverage < selfSeizureCount ? '#00AE41' : '#F96060' },
  871. { label: '主管平均', value: departmentAverage || 0, isImage: false, color: departmentAverage < selfSeizureCount ? '#00AE41' : '#F96060' },
  872. { label: '大队平均', value: brigadeAverage || 0, isImage: false, color: brigadeAverage < selfSeizureCount ? '#00AE41' : '#F96060' },
  873. { label: '站平均', value: (stationAverage && stationAverage.toFixed(2)) || 0, isImage: false, color: stationAverage < selfSeizureCount ? '#00AE41' : '#F96060' }
  874. ];
  875. } else if (this.isTeamView) {
  876. // banzuzhang角色:从teamLeaderData解构
  877. const { teamLeaderData } = this.seizeData;
  878. const { departmentAverage, brigadeAverage, stationAverage, teamAverage } = teamLeaderData || {};
  879. // banzuzhang角色:班组、主管平均、站平均
  880. return [
  881. { label: '班组', value: teamAverage || 0, isImage: false },
  882. { label: '主管平均', value: departmentAverage || 0, isImage: false, color: departmentAverage < teamAverage ? '#00AE41' : '#F96060' },
  883. { label: '大队平均', value: brigadeAverage || 0, isImage: false, color: brigadeAverage < teamAverage ? '#00AE41' : '#F96060' },
  884. { label: '站平均', value: (stationAverage && stationAverage.toFixed(2)) || 0, isImage: false, color: stationAverage < teamAverage ? '#00AE41' : '#F96060' }
  885. ];
  886. } else if (this.role.includes('kezhang')) {
  887. // kezhang角色:从securityCheckerData解构
  888. const { sectionMasterData } = this.seizeData;
  889. const { brigadeAverage, departmentAverage, stationAverage } = sectionMasterData || {}
  890. // kezhang角色:主管、站平均、前三名班组(isImage: true)
  891. return [
  892. { label: '主管', value: departmentAverage || 0, isImage: false },
  893. { label: '大队平均', value: brigadeAverage || 0, isImage: false, color: brigadeAverage < departmentAverage ? '#00AE41' : '#F96060' },
  894. { label: '站平均', value: (stationAverage && stationAverage.toFixed(2)) || 0, isImage: false, color: stationAverage < departmentAverage ? '#00AE41' : '#F96060' }
  895. // { label: this.getObjByRank(topThreeTeams, 1).teamName, value: '/static/images/icon/one.png', isImage: true },
  896. // { label: this.getObjByRank(topThreeTeams, 2).teamName, value: '/static/images/icon/two.png', isImage: true },
  897. // { label: this.getObjByRank(topThreeTeams, 3).teamName, value: '/static/images/icon/three.png', isImage: true }
  898. ];
  899. } else if (this.isBrigade) {
  900. // brigade角色:从securityCheckerData解构
  901. const { brigadeMasterData } = this.seizeData;
  902. const { stationAverage, topThreeDepartment, brigadeAverage } = brigadeMasterData || {}
  903. // brigade角色:主管、站平均、前三名班组(isImage: true)
  904. return [
  905. { label: '大队', value: brigadeAverage || 0, isImage: false },
  906. { label: '站平均', value: (stationAverage && stationAverage.toFixed(2)) || 0, isImage: false, color: stationAverage < brigadeAverage ? '#00AE41' : '#F96060' },
  907. { label: this.getObjByRank(topThreeDepartment, 1).departmentName, value: '/static/images/icon/one.png', isImage: true },
  908. { label: this.getObjByRank(topThreeDepartment, 2).departmentName, value: '/static/images/icon/two.png', isImage: true },
  909. { label: this.getObjByRank(topThreeDepartment, 3).departmentName, value: '/static/images/icon/three.png', isImage: true }
  910. ];
  911. } else if (this.isZhanZhang) {
  912. // test/zhijianke角色:从securityCheckerData解构
  913. const { stationMasterData } = this.seizeData;
  914. const { totalStationSeizure, brigadeRankings } = stationMasterData || {}
  915. // 对departmentRankings数组按照seizureCount由大到小排序
  916. const sortedBrigadeRankings = Array.isArray(brigadeRankings)
  917. ? [...brigadeRankings].sort((a, b) => (b.seizureCount || 0) - (a.seizureCount || 0))
  918. : [];
  919. if (sortedBrigadeRankings.length == 0) {
  920. return []
  921. }
  922. // test/zhijianke角色:全站、前三名的科室(isImage: true)
  923. return [
  924. { label: '全站', value: totalStationSeizure || 0, isImage: false },
  925. { label: sortedBrigadeRankings[0].brigadeName, value: '/static/images/icon/one.png', isImage: true },
  926. { label: sortedBrigadeRankings[1].brigadeName, value: '/static/images/icon/two.png', isImage: true },
  927. { label: sortedBrigadeRankings[2].brigadeName, value: '/static/images/icon/three.png', isImage: true }
  928. ];
  929. } else {
  930. // 默认角色:检查项、完成率
  931. return [
  932. { label: '检查项', value: '12', isImage: false },
  933. { label: '完成率', value: '92%', isImage: false }
  934. ];
  935. }
  936. },
  937. getObjByRank(arr, rank) {
  938. if (!Array.isArray(arr)) return {}
  939. let res = arr.find(item => item.rank == rank)
  940. return res || {}
  941. },
  942. // 根据角色获取巡检排名列表
  943. getRankList() {
  944. const { teamRanking, teamTotal, departmentRanking, departmentTotal, stationRanking, stationTotal, brigadeTotal, brigadeRanking } = this.inspectionData;
  945. // 根据角色返回不同的排名数据
  946. if (this.isIndividualView) {
  947. // 科长、SecurityCheck角色:显示班组、科级和站级排名
  948. return [
  949. { label: '班组排名', current: teamRanking || 0, total: teamTotal || 0, percentage: teamTotal ? ((teamRanking || 0) / teamTotal) * 100 : 0 },
  950. { label: '主管排名', current: departmentRanking || 0, total: departmentTotal || 0, percentage: departmentTotal ? ((departmentRanking || 0) / departmentTotal) * 100 : 0 },
  951. { label: '大队排名', current: brigadeRanking || 0, total: brigadeTotal || 0, percentage: brigadeTotal ? ((brigadeRanking || 0) / brigadeTotal) * 100 : 0 },
  952. { label: '站级排名', current: stationRanking || 0, total: stationTotal || 0, percentage: stationTotal ? ((stationRanking || 0) / stationTotal) * 100 : 0, type: 'station' }
  953. ];
  954. } else if (this.role.includes('kezhang')) {
  955. return [
  956. { label: '大队排名', current: brigadeRanking || 0, total: brigadeTotal || 0, percentage: brigadeTotal ? ((brigadeRanking || 0) / brigadeTotal) * 100 : 0 },
  957. { label: '站级排名', current: stationRanking || 0, total: stationTotal || 0, percentage: stationTotal ? ((stationRanking || 0) / stationTotal) * 100 : 0, type: 'station' }
  958. ];
  959. } else if (this.isBrigade) {
  960. return [
  961. { label: '站级排名', current: stationRanking || 0, total: stationTotal || 0, percentage: stationTotal ? ((stationRanking || 0) / stationTotal) * 100 : 0, type: 'station' }
  962. ];
  963. } else if (this.isTeamView) {
  964. // 班组长角色:显示科级和站级排名
  965. return [
  966. { label: '主管排名', current: departmentRanking || 0, total: departmentTotal || 0, percentage: departmentTotal ? ((departmentRanking || 0) / departmentTotal) * 100 : 0 },
  967. { label: '大队排名', current: departmentRanking || 0, total: departmentTotal || 0, percentage: departmentTotal ? ((departmentRanking || 0) / departmentTotal) * 100 : 0 },
  968. { label: '站级排名', current: stationRanking || 0, total: stationTotal || 0, percentage: stationTotal ? ((stationRanking || 0) / stationTotal) * 100 : 0, type: 'station' }
  969. ];
  970. } else {
  971. // 默认角色:显示个人排名
  972. return [];
  973. }
  974. },
  975. //获取抽问抽答排名
  976. getQuestionRankList() {
  977. // 根据角色返回不同的排名数据
  978. if (this.isIndividualView) {
  979. const { teamRanking, deptRanking, siteRanking } = this.accuracyStatistics;
  980. // 科长、SecurityCheck角色:显示班组、科级和站级排名
  981. return [
  982. { label: '班组排名', current: teamRanking?.rank || 0, total: teamRanking?.total || 0, percentage: teamRanking?.total ? ((teamRanking.rank || 0) / teamRanking.total) * 100 : 0 },
  983. { label: '主管排名', current: deptRanking?.rank || 0, total: deptRanking?.total || 0, percentage: deptRanking?.total ? ((deptRanking.rank || 0) / deptRanking.total) * 100 : 0 },
  984. { label: '大队排名', current: deptRanking?.rank || 0, total: deptRanking?.total || 0, percentage: deptRanking?.total ? ((deptRanking.rank || 0) / deptRanking.total) * 100 : 0 },
  985. { label: '站级排名', current: siteRanking?.rank || 0, total: siteRanking?.total || 0, percentage: siteRanking?.total ? ((siteRanking.rank || 0) / siteRanking.total) * 100 : 0, type: 'station' }
  986. ];
  987. } else if (this.role.includes('kezhang')) {
  988. const { deptInSiteRanking, deptInBrigadeRanking } = this.accuracyStatistics;
  989. return [
  990. { label: '大队排名', current: deptInBrigadeRanking?.rank || 0, total: deptInBrigadeRanking?.total || 0, percentage: deptInBrigadeRanking?.total ? ((deptInBrigadeRanking.rank || 0) / deptInBrigadeRanking.total) * 100 : 0 },
  991. { label: '站级排名', current: deptInSiteRanking?.rank || 0, total: deptInSiteRanking?.total || 0, percentage: deptInSiteRanking?.total ? ((deptInSiteRanking?.rank || 0) / deptInSiteRanking?.total) * 100 : 0, type: 'station' }
  992. ];
  993. } else if (this.isBrigade) {
  994. const { brigadeInSiteRanking } = this.accuracyStatistics;
  995. return [
  996. { label: '站级排名', current: brigadeInSiteRanking?.rank || 0, total: brigadeInSiteRanking?.total || 0, percentage: brigadeInSiteRanking?.total ? ((brigadeInSiteRanking?.rank || 0) / brigadeInSiteRanking?.total) * 100 : 0, type: 'station' }
  997. ];
  998. } else if (this.isTeamView) {
  999. const { teamInDeptRanking, deptRanking, teamInSiteRanking, teamInBrigadeRanking } = this.accuracyStatistics;
  1000. // 班组长角色:显示科级和站级排名
  1001. return [
  1002. { label: '主管排名', current: teamInDeptRanking?.rank || 0, total: teamInDeptRanking?.total || 0, percentage: teamInDeptRanking?.total ? ((teamInDeptRanking.rank || 0) / teamInDeptRanking.total) * 100 : 0 },
  1003. { label: '大队排名', current: teamInBrigadeRanking?.rank || 0, total: teamInBrigadeRanking?.total || 0, percentage: teamInBrigadeRanking?.total ? ((teamInBrigadeRanking.rank || 0) / teamInBrigadeRanking.total) * 100 : 0 },
  1004. { label: '站级排名', current: teamInSiteRanking?.rank || 0, total: teamInSiteRanking?.total || 0, percentage: teamInSiteRanking?.total ? ((teamInSiteRanking.rank || 0) / teamInSiteRanking.total) * 100 : 0, type: 'station' }
  1005. ];
  1006. } else {
  1007. // 默认角色:显示个人排名
  1008. return [];
  1009. }
  1010. },
  1011. // 根据角色获取巡检排名列表
  1012. getSeizeRankList() {
  1013. // 根据角色返回不同的排名数据
  1014. if (this.isIndividualView) {
  1015. const { securityCheckerData } = this.seizeData;
  1016. const { teamRanking, departmentRanking, stationRanking, brigadeRanking } = securityCheckerData || {}
  1017. // 科长、SecurityCheck角色:显示班组、科级和站级排名
  1018. return [
  1019. { label: '班组排名', current: teamRanking?.currentRank || 0, total: teamRanking?.totalItems || 0, percentage: teamRanking?.totalItems ? ((teamRanking.currentRank || 0) / teamRanking.totalItems) * 100 : 0 },
  1020. { label: '主管排名', current: departmentRanking?.currentRank || 0, total: departmentRanking?.totalItems || 0, percentage: departmentRanking?.totalItems ? ((departmentRanking.currentRank || 0) / departmentRanking.totalItems) * 100 : 0 },
  1021. { label: '大队排名', current: brigadeRanking?.currentRank || 0, total: brigadeRanking?.totalItems || 0, percentage: brigadeRanking?.totalItems ? ((brigadeRanking.currentRank || 0) / brigadeRanking.totalItems) * 100 : 0 },
  1022. { label: '站级排名', current: stationRanking?.currentRank || 0, total: stationRanking?.totalItems || 0, percentage: stationRanking?.totalItems ? ((stationRanking.currentRank || 0) / stationRanking.totalItems) * 100 : 0, type: 'station' }
  1023. ];
  1024. } else if (this.role.includes('kezhang')) {
  1025. const { sectionMasterData } = this.seizeData;
  1026. const { stationRanking, departmentRanking, brigadeRanking } = sectionMasterData || {};
  1027. // 科长、SecurityCheck角色:显示班组、科级和站级排名
  1028. return [
  1029. { label: '大队排名', current: brigadeRanking?.currentRank || 0, total: brigadeRanking?.totalItems || 0, percentage: brigadeRanking?.totalItems ? ((brigadeRanking.currentRank || 0) / brigadeRanking.totalItems) * 100 : 0 },
  1030. { label: '站级排名', current: stationRanking?.currentRank || 0, total: stationRanking?.totalItems || 0, percentage: stationRanking?.totalItems ? ((stationRanking.currentRank || 0) / stationRanking.totalItems) * 100 : 0, type: 'station' }
  1031. ];
  1032. } else if (this.isBrigade) {
  1033. const { brigadeMasterData } = this.seizeData;
  1034. const { stationRanking } = brigadeMasterData || {};
  1035. // brigade角色:显示站级排名
  1036. return [
  1037. { label: '站级排名', current: stationRanking?.currentRank || 0, total: stationRanking?.totalItems || 0, percentage: stationRanking?.totalItems ? ((stationRanking.currentRank || 0) / stationRanking.totalItems) * 100 : 0, type: 'station' }
  1038. ];
  1039. } else if (this.isTeamView) {
  1040. const { teamLeaderData } = this.seizeData;
  1041. const { departmentRanking, stationRanking, brigadeRanking } = teamLeaderData || {}
  1042. // 班组长角色:显示科级和站级排名
  1043. return [
  1044. { label: '主管排名', current: departmentRanking?.currentRank || 0, total: departmentRanking?.totalItems || 0, percentage: departmentRanking?.totalItems ? ((departmentRanking.currentRank || 0) / departmentRanking.totalItems) * 100 : 0 },
  1045. { label: '大队排名', current: brigadeRanking?.currentRank || 0, total: brigadeRanking?.totalItems || 0, percentage: brigadeRanking?.totalItems ? ((brigadeRanking.currentRank || 0) / brigadeRanking.totalItems) * 100 : 0 },
  1046. { label: '站级排名', current: stationRanking?.currentRank || 0, total: stationRanking?.totalItems || 0, percentage: stationRanking?.totalItems ? ((stationRanking.currentRank || 0) / stationRanking.totalItems) * 100 : 0, type: 'station' }
  1047. ];
  1048. } else {
  1049. // 默认角色:显示个人排名
  1050. return [];
  1051. }
  1052. },
  1053. goworkDocu() {
  1054. uni.navigateTo({
  1055. url: '/pages/workDocu/index'
  1056. });
  1057. },
  1058. async getUnreadCount() {
  1059. const res = await getUnreadCount();
  1060. this.unreadCount = res.data
  1061. },
  1062. async getUser() {
  1063. const data = await getUserProfile();
  1064. console.log("data====", data)
  1065. //data.postGroup + " " +
  1066. this.userInfo = data.data.dept.deptName
  1067. await this.loadAppList(data.data);
  1068. // this.formData.securityLocationText = data.data.dept.deptName
  1069. // this.formData.positionText = data.postGroup
  1070. },
  1071. formatDate(date) {
  1072. const year = date.getFullYear();
  1073. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  1074. const day = date.getDate().toString().padStart(2, '0');
  1075. const week = ['日', '一', '二', '三', '四', '五', '六'][date.getDay()];
  1076. return `${year}年${month}月${day}日 星期${week}`;
  1077. },
  1078. updateCurrentDate() {
  1079. this.currentDate = this.formatDate(new Date());
  1080. },
  1081. handleGridClick(item) {
  1082. console.log('点击了宫格:', item);
  1083. if (item.path) {
  1084. uni.navigateTo({
  1085. url: item.path
  1086. });
  1087. } else {
  1088. this.$modal.showToast('功能开发中,敬请期待');
  1089. }
  1090. },
  1091. goToAttendance() {
  1092. uni.navigateTo({
  1093. url: '/pages/attendance/index'
  1094. });
  1095. },
  1096. changeGrid(e) {
  1097. const index = e.detail.index;
  1098. const item = this.items[index];
  1099. this.handleGridClick(item);
  1100. },
  1101. goToPage(path) {
  1102. // if (checkRolePermission(this.role, path)) {
  1103. uni.navigateTo({ url: path });
  1104. // } else {
  1105. // this.$modal.showToast('此角色不需要查看该功能,无需操作~');
  1106. // }
  1107. },
  1108. async loadAppList(userInfo) {
  1109. // const roleId = userInfo.roles && userInfo.roles.length ? userInfo.roles[0].roleId : null;
  1110. try {
  1111. // if (roleId) {
  1112. const res = await getAppList({ homePage: 1 });
  1113. if (res.code === 200) {
  1114. this.appList = res.data;
  1115. }
  1116. // }
  1117. } catch (error) {
  1118. console.error('获取应用列表失败:', error);
  1119. this.appList = [];
  1120. }
  1121. },
  1122. async loadTodayRecord() {
  1123. if (this.yesterday) {
  1124. return;
  1125. }
  1126. const today = this.formatDate(new Date());
  1127. const { data } = await getAttendanceList({
  1128. type: 1,
  1129. checkInDate: today,
  1130. userId: this.$store.state.user.id
  1131. });
  1132. // 接口 data 可能是多条(按天),我们取第一条里的 items
  1133. const todayData = data && data.length ? data[0] : null;
  1134. const items = todayData ? todayData.items : [];
  1135. const lastMorning = items.find(r => r.checkInType == 'CLOCK_IN');
  1136. if (lastMorning) {
  1137. console.log(lastMorning);
  1138. this.todayCheckInTime = '已打卡 ' + lastMorning.checkInTime
  1139. } else {
  1140. }
  1141. },
  1142. // 日期格式化(昨天、今天查询用)
  1143. formatDate(date) {
  1144. const y = date.getFullYear();
  1145. const m = String(date.getMonth() + 1).padStart(2, '0');
  1146. const d = String(date.getDate()).padStart(2, '0');
  1147. return `${y}-${m}-${d}`;
  1148. },
  1149. // 获取今日上岗科长信息
  1150. async getTodayOnDutyKeZhang() {
  1151. return new Promise((resolve, reject) => {
  1152. // 只有当角色包含test时才调用此接口
  1153. if (!this.isZhanZhang) {
  1154. resolve(null);
  1155. return;
  1156. }
  1157. // 调用接口获取今日上岗科长列表
  1158. selectUserListByRoleKey(["kezhang"]).then(res => {
  1159. if (res.code === 200 && res.data && res.data.length > 0) {
  1160. this.subTitleText = res.data && res.data.map(user => user.nickName).join('、');
  1161. resolve(res.data);
  1162. } else {
  1163. this.subTitleText = '';
  1164. resolve(null);
  1165. }
  1166. }).catch(error => {
  1167. console.error("获取今日上岗科长信息失败:", error);
  1168. this.subTitleText = '';
  1169. reject(error);
  1170. });
  1171. });
  1172. },
  1173. }
  1174. }
  1175. </script>
  1176. <style lang="scss" scoped>
  1177. img {
  1178. display: inline-flex;
  1179. }
  1180. .title {
  1181. padding: 34rpx 34rpx 34rpx;
  1182. height: auto;
  1183. border-radius: 0 0 20rpx 20rpx;
  1184. background: linear-gradient(136deg, #8FA5EC 0%, #978CED 99.29%);
  1185. transition: all 0.3s ease;
  1186. }
  1187. .content {
  1188. padding: 34rpx 34rpx 34rpx;
  1189. }
  1190. ::v-deep .userInfo {
  1191. .user-info {
  1192. font-size: 34rpx;
  1193. background-size: auto 90%;
  1194. padding: 4rpx 0 4rpx 100rpx;
  1195. background-position: 0rpx 6rpx;
  1196. }
  1197. }
  1198. /* 角色选择器样式 */
  1199. .role-selector {
  1200. margin-bottom: 10rpx;
  1201. }
  1202. /* 排名容器样式 */
  1203. .rank-container {
  1204. display: flex;
  1205. justify-content: flex-start;
  1206. margin: 0 0 34rpx 0;
  1207. }
  1208. .rank-item {
  1209. display: flex;
  1210. align-items: center;
  1211. background: rgba(255, 255, 255, 0.2);
  1212. border-radius: 20rpx;
  1213. padding: 10rpx 20rpx;
  1214. margin-right: 13rpx;
  1215. }
  1216. .rank-content {
  1217. text-align: center;
  1218. display: flex;
  1219. align-items: center;
  1220. font-size: 22rpx;
  1221. color: #fff;
  1222. }
  1223. .rank-value {
  1224. margin: 0 10rpx;
  1225. line-height: 24rpx;
  1226. }
  1227. .rank-label {
  1228. line-height: 24rpx;
  1229. }
  1230. .rank-image {
  1231. width: 24rpx;
  1232. height: 24rpx;
  1233. border-radius: 8rpx;
  1234. }
  1235. /* 出勤信息容器样式 */
  1236. .attendance-container {
  1237. margin-top: 20rpx;
  1238. }
  1239. .attendance-grid {
  1240. background: rgba(255, 255, 255, 0.15);
  1241. display: grid;
  1242. grid-template-columns: repeat(3, 1fr);
  1243. gap: 0;
  1244. border-radius: 20rpx;
  1245. position: relative;
  1246. }
  1247. .attendance-item {
  1248. padding: 24rpx 16rpx;
  1249. text-align: center;
  1250. position: relative;
  1251. }
  1252. /* 为每个attendance-item添加右侧竖线分隔线 */
  1253. .attendance-item:not(:nth-child(3n))::after {
  1254. content: '';
  1255. position: absolute;
  1256. right: 0;
  1257. top: 50%;
  1258. transform: translateY(-50%);
  1259. width: 1rpx;
  1260. height: 50%;
  1261. background: rgba(255, 255, 255, 0.5);
  1262. }
  1263. .item-value {
  1264. line-height: 1;
  1265. font-size: 32rpx;
  1266. font-weight: bold;
  1267. color: #FFFFFF;
  1268. margin-top: 13rpx;
  1269. }
  1270. .item-label {
  1271. font-size: 25rpx;
  1272. font-weight: 400;
  1273. color: rgba(255, 255, 255, 0.6);
  1274. opacity: 0.6;
  1275. }
  1276. /* 时间范围选择样式 */
  1277. .time-range-section {
  1278. margin: 15rpx 0;
  1279. padding: 0 0rpx;
  1280. }
  1281. .time-range-container {
  1282. display: flex;
  1283. align-items: flex-start;
  1284. gap: 20rpx;
  1285. }
  1286. .time-range-label {
  1287. font-size: 28rpx;
  1288. color: #333;
  1289. font-weight: 500;
  1290. white-space: nowrap;
  1291. line-height: 60rpx;
  1292. flex-shrink: 0;
  1293. }
  1294. .time-tags-container {
  1295. display: flex;
  1296. flex-wrap: wrap;
  1297. align-items: center;
  1298. flex: 1;
  1299. }
  1300. .time-tag {
  1301. margin-left: 4rpx;
  1302. padding: 7rpx 15rpx;
  1303. background: #fff;
  1304. border-radius: 20rpx;
  1305. font-size: 24rpx;
  1306. color: #666;
  1307. cursor: pointer;
  1308. transition: all 0.3s ease;
  1309. border: 2rpx solid transparent;
  1310. white-space: nowrap;
  1311. flex-shrink: 0;
  1312. }
  1313. .time-tag.active {
  1314. background: #8FA5EC;
  1315. color: #fff;
  1316. border-color: #8FA5EC;
  1317. }
  1318. /* 时间范围标签样式 - 作为普通文字显示 */
  1319. .time-tag.time-range-label {
  1320. background: transparent !important;
  1321. border: none !important;
  1322. padding: 0 !important;
  1323. font-size: 28rpx;
  1324. color: #333;
  1325. font-weight: 500;
  1326. cursor: default !important;
  1327. pointer-events: none;
  1328. margin-right: 0;
  1329. }
  1330. .time-tag.time-range-label:hover {
  1331. background: transparent !important;
  1332. border: none !important;
  1333. }
  1334. /* 内联自定义时间显示样式 */
  1335. .custom-time-inline {
  1336. position: relative;
  1337. display: flex;
  1338. align-items: center;
  1339. gap: 12rpx;
  1340. margin-left: 15rpx;
  1341. flex-wrap: wrap;
  1342. // width: 350rpx;
  1343. }
  1344. .date-range-picker {
  1345. ::v-deep .icon-calendar {
  1346. display: none !important;
  1347. }
  1348. ::v-deep .uni-date-range {
  1349. font-size: 16rpx !important;
  1350. background: transparent !important;
  1351. color: black !important;
  1352. }
  1353. ::v-deep .uni-date-x {
  1354. height: 25rpx !important;
  1355. line-height: 25rpx !important;
  1356. min-width: auto !important;
  1357. .range-separator {
  1358. font-size: 23rpx !important;
  1359. padding: 0 8rpx !important;
  1360. margin: 0 !important;
  1361. }
  1362. .uni-date__x-input {
  1363. width: auto !important;
  1364. min-width: auto !important;
  1365. height: 25rpx !important;
  1366. line-height: 25rpx !important;
  1367. padding: 0 !important;
  1368. margin: 0 !important;
  1369. }
  1370. }
  1371. ::v-deep .uni-date-x--border {
  1372. border: none !important;
  1373. padding: 0 !important;
  1374. margin: 0 !important;
  1375. }
  1376. }
  1377. .days-count {
  1378. // position: absolute;
  1379. // right: -102rpx;
  1380. // top: -8rpx;
  1381. height: 25rpx;
  1382. line-height: 25rpx;
  1383. font-size: 27rpx;
  1384. color: black;
  1385. font-weight: 500;
  1386. white-space: nowrap;
  1387. }
  1388. .speret {
  1389. font-size: 26rpx;
  1390. color: #999;
  1391. }
  1392. </style>