index.uvue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <template>
  2. <view class="splash-container" v-if="checked && showSplash && userData.rankingItems.length > 0">
  3. <!-- 使用image组件作为背景图 -->
  4. <image class="splash-background-image" src="/static/images/splash/blackground.png" mode="aspectFill"></image>
  5. <view class="splash-content">
  6. <!-- 本月工分模块 -->
  7. <view class="module monthly-score">
  8. <text class="module-title">本月工分</text>
  9. <view class="score-container">
  10. <text class="score-icon">🏆</text>
  11. <text class="score-value">{{ formatNumber(userData.monthlyScore) }}</text>
  12. </view>
  13. </view>
  14. <!-- 个人排名模块 -->
  15. <view class="module personal-ranking">
  16. <text class="module-title">个人排名</text>
  17. </view>
  18. <!-- 排名模块 -->
  19. <view class="module station-ranking" v-for="(item, index) in userData.rankingItems" :key="index">
  20. <view class="ranking-item">
  21. <template v-if="getRankingIcon(index).type === 'emoji'">
  22. <text class="ranking-icon">{{ getRankingIcon(index).value }}</text>
  23. </template>
  24. <template v-else>
  25. <image class="ranking-icon" :src="getRankingIcon(index).value" mode="aspectFit"></image>
  26. </template>
  27. <text class="ranking-name">{{ item.name }}</text>
  28. <view class="ranking-circle-wrapper">
  29. <view class="ranking-number" :class="getRankingSizeClass(index)">
  30. <text class="ranking-number-text">{{ item.rank }}/{{ item.total }}</text>
  31. </view>
  32. </view>
  33. <text class="ranking-label">第{{ item.rank }}名</text>
  34. </view>
  35. </view>
  36. </view>
  37. <!-- 倒计时和关闭按钮 -->
  38. <view class="splash-footer">
  39. <view class="countdown" @tap="closeSplash">
  40. <text class="countdown-text">{{ countdown }}s 后跳过</text>
  41. </view>
  42. <view class="close-button" @tap="closeSplash">
  43. <text class="close-text">关闭</text>
  44. </view>
  45. </view>
  46. </view>
  47. </template>
  48. <script lang="uts">
  49. import { ref, onMounted, onUnmounted } from 'vue';
  50. import { getConfigKey } from '@/api/system/config';
  51. import { selectHomePageData } from '@/api/index/index';
  52. // 用户数据类型定义
  53. type RankingItem = {
  54. name: string;
  55. rank: number;
  56. total: number;
  57. }
  58. type UserData = {
  59. monthlyScore: number;
  60. rankingItems: RankingItem[]
  61. }
  62. type SplashConfig = {
  63. enable: boolean
  64. }
  65. type RankingIcon = {
  66. type: 'emoji' | 'image';
  67. value: string;
  68. }
  69. type ApiResponse<T> = {
  70. data: T;
  71. }
  72. export default {
  73. setup() {
  74. // 响应式数据
  75. const countdown = ref<number>(3);
  76. const showSplash = ref<boolean>(false); // 默认不显示
  77. const checked = ref<boolean>(false); // 是否已完成配置检查
  78. /* const userData = ref<UserData>({
  79. monthlyScore: 120,
  80. rankingItems: [
  81. { name: '东山电场', rank: 3, total: 15 },
  82. { name: '陆上设备维护中心', rank: 12, total: 86 },
  83. { name: '公司', rank: 45, total: 320 }
  84. ]
  85. }); */
  86. const userData = ref<UserData>({
  87. monthlyScore: 0,
  88. rankingItems: []
  89. });
  90. let countdownTimer: number | null = null;
  91. let hasRedirected = false; // 防止重复跳转
  92. // 关闭跳转页
  93. const closeSplash = (): void => {
  94. if (hasRedirected) return; // 防止重复跳转
  95. if (countdownTimer != null) {
  96. clearInterval(countdownTimer as number);
  97. countdownTimer = null;
  98. }
  99. hasRedirected = true;
  100. // 跳转到主页
  101. uni.redirectTo({
  102. url: '/pages/index/index'
  103. });
  104. };
  105. // 检查后端配置是否启用跳转页
  106. const checkSplashConfig = async (): Promise<void> => {
  107. try {
  108. // 调用后端API获取配置
  109. const response = await getConfigKey('gxt.app.splash');
  110. console.log("完整响应:", response);
  111. let showSplashValue = false;
  112. const respObj = response as UTSJSONObject;
  113. if (respObj.hasOwnProperty('msg')) {
  114. const msgValue = respObj['msg'] as string;
  115. showSplashValue = msgValue == '1';
  116. console.log("msgValue:", msgValue);
  117. }
  118. showSplash.value = showSplashValue;
  119. checked.value = true; // 标记已检查完配置
  120. console.log("showSplash.value:", showSplash.value);
  121. // 如果不启用跳转页,直接关闭
  122. if (!showSplashValue) {
  123. closeSplash();
  124. }
  125. } catch (error) {
  126. console.error('获取跳转页配置失败:', error);
  127. // 出错时默认不显示跳转页
  128. showSplash.value = false;
  129. checked.value = true;
  130. closeSplash();
  131. }
  132. };
  133. // 获取用户数据
  134. const fetchUserData = async (): Promise<void> => {
  135. try {
  136. // 调用后端API获取用户数据
  137. const response = await selectHomePageData();
  138. console.log("response====",response)
  139. // 根据响应结果更新userData
  140. if (response != null) {
  141. const respObj = response as UTSJSONObject;
  142. const data = respObj['data'] as UTSJSONObject;
  143. // 更新月度工分
  144. if (data.hasOwnProperty('score')) {
  145. const scoreVal = data['score'] as number;
  146. userData.value.monthlyScore = Math.round(scoreVal * 100) / 100;
  147. }
  148. // 初始化默认值
  149. let deptName = '未知部门';
  150. let center = '未知中心';
  151. let companyName = '未知公司';
  152. // 从scoreDept中提取部门、中心和公司名称
  153. if (data.hasOwnProperty('scoreDept') && Array.isArray(data['scoreDept']) && (data['scoreDept'] as any[]).length > 0) {
  154. const scoreDept = data['scoreDept'] as UTSJSONObject[];
  155. const firstItem = scoreDept[0];
  156. if (firstItem.hasOwnProperty('deptName')) {
  157. deptName = firstItem['deptName'] as string;
  158. }
  159. if (firstItem.hasOwnProperty('center')) {
  160. center = firstItem['center'] as string;
  161. }
  162. if (firstItem.hasOwnProperty('companyName')) {
  163. companyName = firstItem['companyName'] as string;
  164. }
  165. }
  166. // 更新排名信息
  167. const rankingItems : RankingItem[] = [];
  168. // 部门排名(东山电场)
  169. if (data.hasOwnProperty('deptSort') && data.hasOwnProperty('scoreDept')) {
  170. const deptSort = typeof data['deptSort'] === 'number' ? data['deptSort'] as number : 0;
  171. const scoreDept = Array.isArray(data['scoreDept']) ? data['scoreDept'] as any[] : [];
  172. // 只有当部门排序大于0且scoreDept数组不为空时才添加
  173. if (deptSort > 0 && scoreDept.length > 0) {
  174. rankingItems.push({
  175. name: deptName,
  176. rank: deptSort,
  177. total: scoreDept.length
  178. });
  179. }
  180. }
  181. // 中心排名(陆上设备维护中心)
  182. if (data.hasOwnProperty('centerSort') && data.hasOwnProperty('scoreCenter')) {
  183. const centerSort = typeof data['centerSort'] === 'number' ? data['centerSort'] as number : 0;
  184. const scoreCenter = Array.isArray(data['scoreCenter']) ? data['scoreCenter'] as any[] : [];
  185. // 只有当中心排序大于0且scoreCenter数组不为空时才添加
  186. if (centerSort > 0 && scoreCenter.length > 0) {
  187. rankingItems.push({
  188. name: center,
  189. rank: centerSort,
  190. total: scoreCenter.length
  191. });
  192. }
  193. }
  194. // 公司排名
  195. if (data.hasOwnProperty('companySort') && data.hasOwnProperty('scoreCompany')) {
  196. const companySort = typeof data['companySort'] === 'number' ? data['companySort'] as number : 0;
  197. const scoreCompany = Array.isArray(data['scoreCompany']) ? data['scoreCompany'] as any[] : [];
  198. // 只有当公司排序大于0且scoreCompany数组不为空时才添加
  199. if (companySort > 0 && scoreCompany.length > 0) {
  200. rankingItems.push({
  201. name: companyName,
  202. rank: companySort,
  203. total: scoreCompany.length
  204. });
  205. }
  206. }
  207. // 更新rankingItems
  208. userData.value.rankingItems = rankingItems;
  209. // 如果rankingItems为空,直接跳转到登录页
  210. if (rankingItems.length === 0) {
  211. closeSplash();
  212. }
  213. } else {
  214. // 如果没有获取到数据,直接跳转到登录页
  215. closeSplash();
  216. }
  217. } catch (error) {
  218. console.error('获取用户数据失败:', error);
  219. // 出错时直接跳转到登录页
  220. closeSplash();
  221. }
  222. };
  223. // 开始倒计时
  224. const startCountdown = (): void => {
  225. if (hasRedirected) return; // 如果已经跳转则不启动倒计时
  226. countdownTimer = setInterval(() => {
  227. if (countdown.value > 1) {
  228. countdown.value--;
  229. } else {
  230. closeSplash();
  231. }
  232. }, 1000);
  233. };
  234. // 生命周期钩子
  235. onMounted(() => {
  236. checkSplashConfig().then(() => {
  237. // 只有在确定显示开屏页的情况下才获取数据和启动倒计时
  238. if (showSplash.value) {
  239. fetchUserData().then(() => {
  240. // 只有在rankingItems有数据的情况下才启动倒计时
  241. if (userData.value.rankingItems.length > 0) {
  242. startCountdown();
  243. } else {
  244. // 否则直接跳转
  245. closeSplash();
  246. }
  247. });
  248. }
  249. });
  250. });
  251. // 根据索引获取对应的图标信息
  252. const getRankingIcon = (index: number): RankingIcon => {
  253. //索引0对应电场,索引1对应维护中心,索引2对应公司
  254. const icons: RankingIcon[] = [
  255. { type: 'emoji', value: '🏭' },
  256. { type: 'emoji', value: '⚙️' },
  257. { type: 'emoji', value: '🏢' }
  258. ];
  259. /* const icons = [
  260. { type: 'image', value: '/static/icons/power-plant.png' },
  261. { type: 'image', value: '/static/icons/maintenance-center.png' },
  262. { type: 'image', value: '/static/icons/company.png' }
  263. ]; */
  264. return index >= 0 && index < icons.length ? icons[index] : { type: 'emoji', value: '' };
  265. };
  266. // 根据索引获取排名圆圈的大小类
  267. const getRankingSizeClass = (index: number): string => {
  268. // size-1 对应最小的圆圈,size-3 对应最大的圆圈
  269. const size = Math.min(index + 1, 3);
  270. return `size-${size}`;
  271. };
  272. function formatNumber(value: number | null) {
  273. if (value == null) return '0.0'
  274. return value.toFixed(2)
  275. }
  276. onUnmounted(() => {
  277. if (countdownTimer != null) {
  278. clearInterval(countdownTimer as number);
  279. countdownTimer = null;
  280. }
  281. });
  282. return {
  283. countdown,
  284. showSplash,
  285. checked,
  286. userData,
  287. closeSplash,
  288. getRankingIcon,
  289. getRankingSizeClass,
  290. formatNumber
  291. };
  292. }
  293. };
  294. </script>
  295. <style lang="scss">
  296. .splash-container {
  297. position: fixed;
  298. top: 0;
  299. left: 0;
  300. width: 100%;
  301. height: 100%;
  302. display: flex;
  303. flex-direction: column;
  304. align-items: center;
  305. justify-content: center;
  306. z-index: 9999;
  307. /* background-image: url('/static/images/splash/blackground.png');
  308. background-size: cover;
  309. background-position: center; */
  310. }
  311. .splash-background-image {
  312. position: absolute;
  313. top: 0;
  314. left: 0;
  315. width: 100%;
  316. height: 100%;
  317. z-index: -1;
  318. }
  319. .splash-content {
  320. width: 100%;
  321. max-width: 400px;
  322. margin: 0 auto;
  323. }
  324. .module {
  325. background: linear-gradient(to right, rgba(40, 80, 140, 0.7), rgba(20, 40, 80, 0.8));
  326. border-radius: 16px;
  327. padding: 40rpx;
  328. border: 1.5px solid #93c5fd;
  329. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
  330. margin-bottom: 20rpx;
  331. margin-left: auto;
  332. margin-right: auto;
  333. width: 90%;
  334. }
  335. .personal-ranking {
  336. background: linear-gradient(to right, rgba(40, 80, 140, 0.6), rgba(20, 40, 80, 0.7));
  337. border-radius: 16px;
  338. padding: 45rpx;
  339. border: 1px solid rgba(147, 197, 253, 0.2);
  340. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  341. margin-bottom: 20rpx;
  342. margin-left: auto;
  343. margin-right: auto;
  344. width: 90%;
  345. }
  346. .monthly-score {
  347. text-align: center;
  348. margin-bottom: 10rpx;
  349. position: relative;
  350. top: -60rpx;
  351. z-index: 10;
  352. padding: 70rpx 40rpx !important;
  353. margin-top: 60rpx;
  354. border-radius: 30px !important;
  355. border: 1.5px solid #93c5fd !important;
  356. background: linear-gradient(to right, rgba(30, 70, 130, 0.8), rgba(10, 30, 60, 0.9)) !important;
  357. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
  358. }
  359. .monthly-score .module-title {
  360. font-size: 50rpx;
  361. font-weight: bold;
  362. margin-bottom: 30rpx;
  363. transform: translateY(-10rpx);
  364. }
  365. .personal-ranking .module-title {
  366. text-align: left !important;
  367. display: flex;
  368. align-items: center;
  369. justify-content: flex-start;
  370. height: 100%;
  371. padding: 0;
  372. margin-bottom: 0;
  373. }
  374. .module-title {
  375. color: #ffffff;
  376. font-size: 36rpx;
  377. font-weight: bold;
  378. margin-bottom: 0;
  379. text-align: center;
  380. }
  381. .score-container {
  382. display: flex;
  383. flex-direction: row;
  384. align-items: center;
  385. justify-content: center;
  386. }
  387. .score-icon {
  388. font-size: 80rpx;
  389. margin-right: 30rpx;
  390. }
  391. .score-value {
  392. color: #38bdf8;
  393. font-size: 96rpx;
  394. font-weight: bold;
  395. }
  396. .ranking-item {
  397. display: flex;
  398. flex-direction: row;
  399. align-items: center;
  400. width: 100%;
  401. padding-left: 0;
  402. margin-left: 0;
  403. }
  404. .ranking-icon {
  405. font-size: 48rpx;
  406. width: 48rpx;
  407. height: 48rpx;
  408. margin-right: 20rpx;
  409. margin-left: 0rpx;
  410. color: #94a3b8;
  411. }
  412. .ranking-icon[image] {
  413. width: 48rpx;
  414. height: 48rpx;
  415. }
  416. .ranking-name {
  417. color: #ffffff;
  418. font-size: 32rpx;
  419. font-weight: normal;
  420. flex: 1;
  421. margin-left: -10rpx;
  422. }
  423. .ranking-number {
  424. background: #38bdf8;
  425. display: flex;
  426. align-items: center;
  427. justify-content: center;
  428. }
  429. .ranking-circle-wrapper {
  430. width: 120rpx;
  431. display: flex;
  432. justify-content: center;
  433. align-items: center;
  434. margin: 0 20rpx;
  435. margin-left: 0;
  436. }
  437. .ranking-number.size-1 {
  438. width: 80rpx;
  439. height: 80rpx;
  440. border-radius: 40rpx;
  441. }
  442. .ranking-number.size-2 {
  443. width: 90rpx;
  444. height: 90rpx;
  445. border-radius: 45rpx;
  446. }
  447. .ranking-number.size-3 {
  448. width: 100rpx;
  449. height: 100rpx;
  450. border-radius: 50rpx;
  451. }
  452. .ranking-number-text {
  453. color: #ffffff;
  454. font-weight: normal;
  455. text-align: center;
  456. font-size: 28rpx;
  457. }
  458. .ranking-number.size-1 .ranking-number-text {
  459. font-size: 22rpx;
  460. }
  461. .ranking-number.size-2 .ranking-number-text {
  462. font-size: 24rpx;
  463. }
  464. .ranking-number.size-3 .ranking-number-text {
  465. font-size: 26rpx;
  466. }
  467. .ranking-label {
  468. color: #ffffff;
  469. font-size: 32rpx;
  470. white-space: nowrap;
  471. margin-right: 20rpx;
  472. }
  473. .splash-footer {
  474. position: absolute;
  475. top: 80rpx;
  476. right: 30rpx;
  477. display: flex;
  478. flex-direction: row;
  479. justify-content: space-between;
  480. align-items: center;
  481. width: 250rpx;
  482. height: 60rpx;
  483. background: rgba(0, 0, 0, 0.3);
  484. border-radius: 30rpx;
  485. border: 1px solid rgba(147, 197, 253, 0.1);
  486. padding: 0 20rpx;
  487. }
  488. .splash-footer .countdown {
  489. margin-bottom: 0;
  490. background: transparent;
  491. border: none;
  492. padding: 0;
  493. }
  494. .splash-footer .close-button {
  495. background: transparent;
  496. border: none;
  497. padding: 0;
  498. margin-left: 10rpx;
  499. }
  500. .countdown-text {
  501. color: #ffffff;
  502. font-size: 28rpx;
  503. font-weight: bold;
  504. }
  505. .close-text {
  506. color: #ffffff;
  507. font-size: 28rpx;
  508. font-weight: normal;
  509. }
  510. </style>