index.uvue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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">{{ 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.navigateTo({
  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. onUnmounted(() => {
  273. if (countdownTimer != null) {
  274. clearInterval(countdownTimer as number);
  275. countdownTimer = null;
  276. }
  277. });
  278. return {
  279. countdown,
  280. showSplash,
  281. checked,
  282. userData,
  283. closeSplash,
  284. getRankingIcon,
  285. getRankingSizeClass
  286. };
  287. }
  288. };
  289. </script>
  290. <style lang="scss">
  291. .splash-container {
  292. position: fixed;
  293. top: 0;
  294. left: 0;
  295. width: 100%;
  296. height: 100%;
  297. display: flex;
  298. flex-direction: column;
  299. align-items: center;
  300. justify-content: center;
  301. z-index: 9999;
  302. /* background-image: url('/static/images/splash/blackground.png');
  303. background-size: cover;
  304. background-position: center; */
  305. }
  306. .splash-background-image {
  307. position: absolute;
  308. top: 0;
  309. left: 0;
  310. width: 100%;
  311. height: 100%;
  312. z-index: -1;
  313. }
  314. .splash-content {
  315. width: 100%;
  316. max-width: 400px;
  317. margin: 0 auto;
  318. }
  319. .module {
  320. background: linear-gradient(to right, rgba(40, 80, 140, 0.7), rgba(20, 40, 80, 0.8));
  321. border-radius: 16px;
  322. padding: 40rpx;
  323. border: 1.5px solid #93c5fd;
  324. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
  325. margin-bottom: 20rpx;
  326. margin-left: auto;
  327. margin-right: auto;
  328. width: 90%;
  329. }
  330. .personal-ranking {
  331. background: linear-gradient(to right, rgba(40, 80, 140, 0.6), rgba(20, 40, 80, 0.7));
  332. border-radius: 16px;
  333. padding: 45rpx;
  334. border: 1px solid rgba(147, 197, 253, 0.2);
  335. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  336. margin-bottom: 20rpx;
  337. margin-left: auto;
  338. margin-right: auto;
  339. width: 90%;
  340. }
  341. .monthly-score {
  342. text-align: center;
  343. margin-bottom: 10rpx;
  344. position: relative;
  345. top: -60rpx;
  346. z-index: 10;
  347. padding: 70rpx 40rpx !important;
  348. margin-top: 60rpx;
  349. border-radius: 30px !important;
  350. border: 1.5px solid #93c5fd !important;
  351. background: linear-gradient(to right, rgba(30, 70, 130, 0.8), rgba(10, 30, 60, 0.9)) !important;
  352. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
  353. }
  354. .monthly-score .module-title {
  355. font-size: 50rpx;
  356. font-weight: bold;
  357. margin-bottom: 30rpx;
  358. transform: translateY(-10rpx);
  359. }
  360. .personal-ranking .module-title {
  361. text-align: left !important;
  362. display: flex;
  363. align-items: center;
  364. justify-content: flex-start;
  365. height: 100%;
  366. padding: 0;
  367. margin-bottom: 0;
  368. }
  369. .module-title {
  370. color: #ffffff;
  371. font-size: 36rpx;
  372. font-weight: bold;
  373. margin-bottom: 0;
  374. text-align: center;
  375. }
  376. .score-container {
  377. display: flex;
  378. flex-direction: row;
  379. align-items: center;
  380. justify-content: center;
  381. }
  382. .score-icon {
  383. font-size: 80rpx;
  384. margin-right: 30rpx;
  385. }
  386. .score-value {
  387. color: #38bdf8;
  388. font-size: 96rpx;
  389. font-weight: bold;
  390. }
  391. .ranking-item {
  392. display: flex;
  393. flex-direction: row;
  394. align-items: center;
  395. width: 100%;
  396. padding-left: 0;
  397. margin-left: 0;
  398. }
  399. .ranking-icon {
  400. font-size: 48rpx;
  401. width: 48rpx;
  402. height: 48rpx;
  403. margin-right: 20rpx;
  404. margin-left: 0rpx;
  405. color: #94a3b8;
  406. }
  407. .ranking-icon[image] {
  408. width: 48rpx;
  409. height: 48rpx;
  410. }
  411. .ranking-name {
  412. color: #ffffff;
  413. font-size: 32rpx;
  414. font-weight: normal;
  415. flex: 1;
  416. margin-left: -10rpx;
  417. }
  418. .ranking-number {
  419. background: #38bdf8;
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. box-shadow: 0 0 20px rgba(59, 130, 246, 0.6);
  424. }
  425. .ranking-circle-wrapper {
  426. width: 120rpx;
  427. display: flex;
  428. justify-content: center;
  429. align-items: center;
  430. margin: 0 20rpx;
  431. margin-left: 0;
  432. }
  433. .ranking-number.size-1 {
  434. width: 80rpx;
  435. height: 80rpx;
  436. border-radius: 40rpx;
  437. }
  438. .ranking-number.size-2 {
  439. width: 90rpx;
  440. height: 90rpx;
  441. border-radius: 45rpx;
  442. }
  443. .ranking-number.size-3 {
  444. width: 100rpx;
  445. height: 100rpx;
  446. border-radius: 50rpx;
  447. }
  448. .ranking-number-text {
  449. color: #ffffff;
  450. font-weight: normal;
  451. text-align: center;
  452. font-size: 28rpx;
  453. }
  454. .ranking-number.size-1 .ranking-number-text {
  455. font-size: 22rpx;
  456. }
  457. .ranking-number.size-2 .ranking-number-text {
  458. font-size: 24rpx;
  459. }
  460. .ranking-number.size-3 .ranking-number-text {
  461. font-size: 26rpx;
  462. }
  463. .ranking-label {
  464. color: #ffffff;
  465. font-size: 32rpx;
  466. white-space: nowrap;
  467. margin-right: 20rpx;
  468. }
  469. .splash-footer {
  470. position: absolute;
  471. top: 80rpx;
  472. right: 30rpx;
  473. display: flex;
  474. flex-direction: row;
  475. justify-content: space-between;
  476. align-items: center;
  477. width: 250rpx;
  478. height: 60rpx;
  479. background: rgba(0, 0, 0, 0.3);
  480. border-radius: 30rpx;
  481. border: 1px solid rgba(147, 197, 253, 0.1);
  482. padding: 0 20rpx;
  483. }
  484. .splash-footer .countdown {
  485. margin-bottom: 0;
  486. background: transparent;
  487. border: none;
  488. padding: 0;
  489. }
  490. .splash-footer .close-button {
  491. background: transparent;
  492. border: none;
  493. padding: 0;
  494. margin-left: 10rpx;
  495. }
  496. .countdown-text {
  497. color: #ffffff;
  498. font-size: 28rpx;
  499. font-weight: bold;
  500. }
  501. .close-text {
  502. color: #ffffff;
  503. font-size: 28rpx;
  504. font-weight: normal;
  505. }
  506. </style>