Kaynağa Gözat

chore: 剩余页面微调(custom-tab-bar/assessment/games/membership)

Ultraworked with Sisyphus
User 3 ay önce
ebeveyn
işleme
ce98d70e4a

+ 61 - 0
cfc-frontend/components/BaseBadge.vue

@@ -0,0 +1,61 @@
+<template>
+  <view
+    class="base-badge"
+    :class="['base-badge--' + variant, 'base-badge--' + size]"
+  >
+    <slot />
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'BaseBadge',
+  props: {
+    variant: { type: String, default: 'primary' },
+    // 'primary' | 'success' | 'warning' | 'error' | 'accent' | 'muted'
+    size: { type: String, default: 'sm' }
+    // 'xs' | 'sm' | 'md'
+  }
+}
+</script>
+
+<style scoped>
+.base-badge {
+  display: inline-flex;
+  align-items: center;
+  font-weight: 600;
+  border-radius: 999rpx;
+  white-space: nowrap;
+}
+
+/* Sizes */
+.base-badge--xs { font-size: 20rpx; padding: 4rpx 14rpx; line-height: 1.4; }
+.base-badge--sm { font-size: 22rpx; padding: 6rpx 18rpx; line-height: 1.4; }
+.base-badge--md { font-size: 24rpx; padding: 8rpx 24rpx; line-height: 1.5; }
+
+/* Variants */
+.base-badge--primary {
+  background: rgba(249, 115, 22, 0.12);
+  color: var(--color-primary, #F97316);
+}
+.base-badge--success {
+  background: rgba(34, 197, 94, 0.12);
+  color: var(--color-success, #22C55E);
+}
+.base-badge--warning {
+  background: rgba(245, 158, 11, 0.12);
+  color: var(--color-warning, #F59E0B);
+}
+.base-badge--error {
+  background: rgba(239, 68, 68, 0.12);
+  color: var(--color-error, #EF4444);
+}
+.base-badge--accent {
+  background: rgba(14, 165, 233, 0.12);
+  color: var(--color-accent, #0EA5E9);
+}
+.base-badge--muted {
+  background: rgba(148, 163, 184, 0.12);
+  color: var(--muted, #94A3B8);
+}
+</style>

+ 66 - 0
cfc-frontend/components/BaseEmpty.vue

@@ -0,0 +1,66 @@
+<template>
+  <view class="base-empty" :class="{ 'base-empty--compact': compact }">
+    <view class="base-empty__icon" v-if="icon || $slots.icon">
+      <slot name="icon">
+        <text class="base-empty__emoji">{{ icon }}</text>
+      </slot>
+    </view>
+    <text class="base-empty__title" v-if="title">{{ title }}</text>
+    <text class="base-empty__description" v-if="description">{{ description }}</text>
+    <view class="base-empty__action" v-if="$slots.action">
+      <slot name="action"></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'BaseEmpty',
+  props: {
+    icon: { type: String, default: '' },
+    title: { type: String, default: '' },
+    description: { type: String, default: '' },
+    compact: { type: Boolean, default: false }
+  }
+}
+</script>
+
+<style scoped>
+.base-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 80rpx 32rpx;
+  min-height: 400rpx;
+}
+.base-empty--compact {
+  padding: 48rpx 32rpx;
+  min-height: auto;
+}
+.base-empty__icon {
+  margin-bottom: 24rpx;
+}
+.base-empty__emoji {
+  font-size: 80rpx;
+  display: block;
+  text-align: center;
+}
+.base-empty__title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: var(--text, #3D2E1E);
+  margin-bottom: 12rpx;
+  text-align: center;
+}
+.base-empty__description {
+  font-size: 26rpx;
+  color: var(--text-secondary, #6B5A4A);
+  text-align: center;
+  line-height: 1.6;
+  max-width: 500rpx;
+}
+.base-empty__action {
+  margin-top: 32rpx;
+}
+</style>

+ 74 - 0
cfc-frontend/components/BaseLoading.vue

@@ -0,0 +1,74 @@
+<template>
+  <view class="base-loading" :class="{ 'base-loading--overlay': overlay }" v-if="loading">
+    <view class="base-loading__spinner" v-if="type === 'spinner'">
+      <view class="base-loading__dot" v-for="i in 3" :key="i"></view>
+    </view>
+    <text class="base-loading__text" v-if="text">{{ text }}</text>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'BaseLoading',
+  props: {
+    loading: { type: Boolean, default: false },
+    type: { type: String, default: 'spinner' },
+    // 'spinner' | 'skeleton'
+    text: { type: String, default: '' },
+    overlay: { type: Boolean, default: false }
+  }
+}
+</script>
+
+<style scoped>
+.base-loading {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 48rpx;
+}
+.base-loading--overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(255, 255, 255, 0.8);
+  z-index: 999;
+  padding: 0;
+}
+
+/* Spinner dots */
+.base-loading__spinner {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+  margin-bottom: 16rpx;
+}
+.base-loading__dot {
+  width: 16rpx;
+  height: 16rpx;
+  border-radius: 50%;
+  background: var(--color-primary, #F97316);
+  animation: dot-bounce 1.2s ease-in-out infinite;
+}
+.base-loading__dot:nth-child(2) { animation-delay: 0.2s; }
+.base-loading__dot:nth-child(3) { animation-delay: 0.4s; }
+
+.base-loading__text {
+  font-size: 24rpx;
+  color: var(--muted, #94A3B8);
+}
+
+@keyframes dot-bounce {
+  0%, 80%, 100% {
+    transform: scale(0.6);
+    opacity: 0.4;
+  }
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+</style>