App.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <template>
  2. <view>
  3. <slot></slot>
  4. </view>
  5. </template>
  6. <script setup>
  7. import { onLaunch } from '@dcloudio/uni-app'
  8. import { loadDicts } from './utils/dict.js'
  9. import { storage } from './utils/index.js'
  10. // 应用启动时执行
  11. onLaunch(() => {
  12. console.log('App launched')
  13. // 如果已有登录token,预加载字典数据
  14. if (storage.get('token')) {
  15. loadDicts()
  16. }
  17. })
  18. </script>
  19. <style lang="scss">
  20. /* 全局样式 — Soft UI Evolution */
  21. @import './uni.scss';
  22. page {
  23. font-family: $uni-font-family;
  24. font-size: $uni-font-size-base;
  25. line-height: $uni-line-height-base;
  26. color: $uni-text-color;
  27. background-color: $uni-page-bg-color;
  28. -webkit-font-smoothing: antialiased;
  29. -moz-osx-font-smoothing: grayscale;
  30. -webkit-user-select: text;
  31. user-select: text;
  32. /* App 体验:去除点击高亮,抑制边缘过度滚动 */
  33. -webkit-tap-highlight-color: transparent;
  34. overscroll-behavior-y: none;
  35. }
  36. /* 全局启用文本选择,uni-app H5 模式下默认不可选中 */
  37. text {
  38. -webkit-user-select: text;
  39. user-select: text;
  40. }
  41. /* ===== Global Keyframes ===== */
  42. @keyframes app-spin {
  43. to { transform: rotate(360deg); }
  44. }
  45. /* ===== Motion Library — 全局动效关键帧库 ===== */
  46. @keyframes app-fade-up {
  47. from { opacity: 0; transform: translateY(24rpx); }
  48. to { opacity: 1; transform: translateY(0); }
  49. }
  50. @keyframes app-fade-in {
  51. from { opacity: 0; }
  52. to { opacity: 1; }
  53. }
  54. @keyframes app-pop {
  55. 0% { transform: scale(0.55); opacity: 0; }
  56. 60% { transform: scale(1.12); opacity: 1; }
  57. 100% { transform: scale(1); }
  58. }
  59. @keyframes app-slide-in-right {
  60. from { opacity: 0; transform: translateX(48rpx); }
  61. to { opacity: 1; transform: translateX(0); }
  62. }
  63. @keyframes app-shimmer {
  64. from { background-position: -400rpx 0; }
  65. to { background-position: 400rpx 0; }
  66. }
  67. /* 页面进入过渡:仅 opacity,避免 transform 生成 containing block 影响 fixed 子元素(TabBar/悬浮按钮) */
  68. page {
  69. animation: app-fade-in $uni-duration-enter $uni-ease-decelerate;
  70. }
  71. /* 带底部导航的页面不做整页淡入:底部导航是 fixed 定位于页面内,整页淡入会让导航栏在切换 Tab 时一起闪一下 */
  72. page:has(.tab-bar) {
  73. animation: none;
  74. }
  75. /* 入场工具类:页面内元素可主动挂载 */
  76. .fade-up { animation: app-fade-up $uni-duration-motion $uni-ease-out-quint backwards; }
  77. .fade-in { animation: app-fade-in $uni-duration-enter $uni-ease-decelerate backwards; }
  78. .pop-in { animation: app-pop $uni-duration-enter $uni-ease-spring backwards; }
  79. /* ===== Skeleton Shimmer (通用骨架屏微光) ===== */
  80. .skeleton-shimmer {
  81. background: linear-gradient(90deg, #ECEFF3 25%, #F7F9FB 37%, #ECEFF3 63%);
  82. background-size: 800rpx 100%;
  83. animation: app-shimmer 1.4s linear infinite;
  84. }
  85. /* ===== Loading Spinner (unified CSS, replaces emoji 🔄) ===== */
  86. .loading-spinner {
  87. width: 64rpx;
  88. height: 64rpx;
  89. border: 4rpx solid rgba(198, 23, 30, 0.1);
  90. border-top-color: $uni-color-primary;
  91. border-radius: 50%;
  92. animation: app-spin 0.8s linear infinite;
  93. }
  94. /* ===== Loading State Container ===== */
  95. .loading-state {
  96. display: flex;
  97. flex-direction: column;
  98. align-items: center;
  99. justify-content: center;
  100. padding: 120rpx 0;
  101. }
  102. .loading-text {
  103. font-size: $uni-font-size-sm;
  104. color: $uni-text-color-light;
  105. margin-top: 20rpx;
  106. }
  107. /* ===== Empty State ===== */
  108. .empty-state {
  109. display: flex;
  110. flex-direction: column;
  111. align-items: center;
  112. justify-content: center;
  113. padding: 100rpx 0;
  114. }
  115. .empty-icon-wrapper {
  116. width: 120rpx;
  117. height: 120rpx;
  118. border-radius: 50%;
  119. background: #F5F5F5;
  120. display: flex;
  121. align-items: center;
  122. justify-content: center;
  123. margin-bottom: 24rpx;
  124. }
  125. .empty-text {
  126. font-size: 28rpx;
  127. color: $uni-text-color-light;
  128. margin-bottom: 32rpx;
  129. }
  130. .empty-refresh-btn {
  131. padding: 16rpx 48rpx;
  132. background: $uni-color-primary;
  133. color: #FFFFFF;
  134. border: none;
  135. border-radius: $uni-border-radius-sm;
  136. font-size: $uni-font-size-sm;
  137. font-weight: $uni-font-weight-medium;
  138. transition: all $uni-transition-duration-base $uni-transition-timing;
  139. }
  140. .empty-refresh-btn:active {
  141. transform: translateY(1px);
  142. }
  143. /* ===== Tab Bar ===== */
  144. .uni-tabbar {
  145. --tabbar-height: 50px;
  146. --tabbar-bg-color: #FFFFFF;
  147. --tabbar-border-color: #E0E0E0;
  148. --tabbar-item-active-color: #C6171E;
  149. --tabbar-item-color: #999999;
  150. --tabbar-item-font-size: 14px;
  151. --tabbar-item-icon-size: 24px;
  152. --tabbar-item-spacing: 4px;
  153. }
  154. .uni-tabbar__item:active {
  155. transform: scale(0.95);
  156. transition: transform $uni-transition-duration-fast $uni-transition-timing;
  157. }
  158. .uni-tabbar__item-icon { font-size: var(--tabbar-item-icon-size); }
  159. .uni-tabbar__item-text {
  160. font-size: var(--tabbar-item-font-size);
  161. margin-top: var(--tabbar-item-spacing);
  162. }
  163. /* ===== Flex Utilities ===== */
  164. .flex { display: flex; }
  165. .flex-center { display: flex; align-items: center; justify-content: center; }
  166. .flex-just-between { display: flex; align-items: center; justify-content: space-between; }
  167. .flex-column { display: flex; flex-direction: column; }
  168. /* ===== Container ===== */
  169. .container {
  170. width: 100%;
  171. padding: 0 $uni-spacing-base;
  172. }
  173. /* ===== Card (Soft UI Evolution standard) ===== */
  174. .card {
  175. background-color: $uni-card-bg-color;
  176. border-radius: $uni-border-radius-md;
  177. box-shadow: $uni-box-shadow-base;
  178. padding: $uni-spacing-base;
  179. transition: all $uni-transition-duration-base $uni-transition-timing;
  180. /* 卡片交错入场:backwards 填充,动画结束后不锁定 transform,:active 反馈不受影响 */
  181. animation: app-fade-up $uni-duration-motion $uni-ease-out-quint backwards;
  182. }
  183. .card:nth-child(1) { animation-delay: 0.02s; }
  184. .card:nth-child(2) { animation-delay: 0.07s; }
  185. .card:nth-child(3) { animation-delay: 0.12s; }
  186. .card:nth-child(4) { animation-delay: 0.17s; }
  187. .card:nth-child(5) { animation-delay: 0.22s; }
  188. .card:nth-child(6) { animation-delay: 0.27s; }
  189. .card:nth-child(7) { animation-delay: 0.32s; }
  190. .card:nth-child(8) { animation-delay: 0.37s; }
  191. .card:active {
  192. transform: translateY(-2px);
  193. box-shadow: $uni-box-shadow-lg;
  194. }
  195. /* ===== Buttons (Soft UI) ===== */
  196. .btn-primary {
  197. background-color: $uni-color-primary;
  198. color: #FFFFFF;
  199. border: none;
  200. border-radius: $uni-border-radius-sm;
  201. padding: 12px 24px;
  202. font-size: $uni-font-size-sm;
  203. font-weight: $uni-font-weight-semibold;
  204. box-shadow: $uni-box-shadow-sm;
  205. transition: all $uni-transition-duration-base $uni-transition-timing;
  206. }
  207. .btn-primary:active {
  208. transform: scale(0.97);
  209. box-shadow: none;
  210. }
  211. .btn-primary:disabled {
  212. opacity: 0.5;
  213. box-shadow: none;
  214. }
  215. .btn-secondary {
  216. background-color: $uni-bg-color;
  217. color: $uni-text-color;
  218. border: 1px solid $uni-border-color;
  219. border-radius: $uni-border-radius-sm;
  220. padding: 12px 24px;
  221. font-size: $uni-font-size-sm;
  222. font-weight: $uni-font-weight-medium;
  223. transition: all $uni-transition-duration-base $uni-transition-timing;
  224. }
  225. .btn-secondary:active {
  226. transform: scale(0.97);
  227. background-color: $uni-page-bg-color;
  228. }
  229. /* ===== Status Tags (pill shape) ===== */
  230. .status-tag {
  231. display: inline-flex;
  232. align-items: center;
  233. padding: 4px 12px;
  234. border-radius: $uni-border-radius-pill;
  235. font-size: $uni-font-size-xs;
  236. font-weight: $uni-font-weight-medium;
  237. }
  238. .status-tag-success {
  239. background-color: rgba(76, 175, 80, 0.1);
  240. color: $uni-color-success;
  241. }
  242. .status-tag-warning {
  243. background-color: rgba(255, 152, 0, 0.1);
  244. color: $uni-color-warning;
  245. }
  246. .status-tag-error {
  247. background-color: rgba(244, 67, 54, 0.1);
  248. color: $uni-color-error;
  249. }
  250. .status-tag-info {
  251. background-color: rgba(33, 150, 243, 0.1);
  252. color: $uni-color-info;
  253. }
  254. /* ===== Form Input (Soft UI standard) ===== */
  255. .input-field {
  256. width: 100%;
  257. padding: 16rpx 20rpx;
  258. background: $uni-page-bg-color;
  259. border: 2rpx solid $uni-border-color;
  260. border-radius: $uni-border-radius-sm;
  261. font-size: $uni-font-size-sm;
  262. color: $uni-text-color;
  263. transition: border-color $uni-transition-duration-base $uni-transition-timing,
  264. box-shadow $uni-transition-duration-base $uni-transition-timing;
  265. box-sizing: border-box;
  266. }
  267. .input-field:focus {
  268. border-color: $uni-color-primary;
  269. box-shadow: $uni-focus-ring;
  270. background: #FFFFFF;
  271. outline: none;
  272. }
  273. .input-field::placeholder {
  274. color: $uni-text-color-placeholder;
  275. }
  276. </style>