Files
gym-manage/gym-manage-coach/App.vue
T

369 lines
8.2 KiB
Vue

<script>
import { loadCourseStatuses } from '@/utils/index.js'
export default {
onLaunch: function() {
console.log('Coach App Launch')
// 从后端加载课程状态映射(避免硬编码)
loadCourseStatuses()
},
onShow: function() {
console.log('Coach App Show')
},
onHide: function() {
console.log('Coach App Hide')
}
}
</script>
<style>
/* ============================================
全局样式 - 基于 gym-manage-cuit 风格适配手机端
============================================ */
page {
background-color: #f6f8fc;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: #1e293b;
font-size: 14px;
line-height: 1.5;
}
view, text, input, button, image, scroll-view {
box-sizing: border-box;
}
/* ============================================
页面进入动画
============================================ */
.page-enter {
opacity: 0;
transform: translateY(12px);
animation: pageSlideIn 0.35s ease-out forwards;
}
@keyframes pageSlideIn {
0% { opacity: 0; transform: translateY(12px); }
100% { opacity: 1; transform: translateY(0); }
}
/* ============================================
通用卡片
============================================ */
.card {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin: 12px 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
animation: cardFadeIn 0.4s ease-out both;
}
@keyframes cardFadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============================================
通用按钮
============================================ */
.btn-primary {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(145deg, #4f7cff, #3b5fd9);
color: #ffffff;
border: none;
border-radius: 10px;
padding: 12px 24px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.btn-primary::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.15) 0%, transparent 60%);
opacity: 0;
transition: opacity 0.2s;
}
.btn-primary:active::after { opacity: 1; }
.btn-primary:active {
transform: scale(0.97);
}
.btn-outline {
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
color: #4f7cff;
border: 1px solid #4f7cff;
border-radius: 10px;
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-outline:active {
background: #eef3ff;
transform: scale(0.97);
}
/* ============================================
按钮加载 Spinner
============================================ */
@keyframes btnSpin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.btn-spinner {
display: inline-block;
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #ffffff;
border-radius: 50%;
animation: btnSpin 0.6s linear infinite;
margin-right: 8px;
}
/* ============================================
标签
============================================ */
.tag {
display: inline-flex;
align-items: center;
padding: 2px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
transition: all 0.25s ease;
}
.tag-green { background: #e6f7ee; color: #10b981; }
.tag-orange { background: #fff7ed; color: #f59e0b; }
.tag-blue { background: #eef3ff; color: #4f7cff; }
.tag-gray { background: #f1f5f9; color: #94a3b8; }
/* ============================================
骨架屏
============================================ */
@keyframes skeletonShimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, #f0f3f8 25%, #e2e8f0 50%, #f0f3f8 75%);
background-size: 200% 100%;
animation: skeletonShimmer 1.5s ease-in-out infinite;
border-radius: 6px;
}
@keyframes skeletonPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton-pulse {
animation: skeletonPulse 1.5s ease-in-out infinite;
}
/* 骨架屏行 */
.sk-row {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #ffffff;
border-radius: 12px;
margin-bottom: 10px;
}
.sk-circle {
width: 40px;
height: 40px;
border-radius: 50%;
}
.sk-line {
height: 14px;
border-radius: 7px;
}
.sk-line-short { width: 60%; }
.sk-line-medium { width: 80%; }
.sk-line-long { width: 100%; }
.sk-line-sm { height: 10px; border-radius: 5px; }
/* ============================================
空状态
============================================ */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
color: #94a3b8;
animation: emptyFadeIn 0.5s ease-out both;
}
@keyframes emptyFadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.empty-state text {
font-size: 14px;
margin-top: 12px;
}
/* ============================================
脉冲圆点
============================================ */
@keyframes dotPulse {
0%, 100% { transform: scale(0.6); opacity: 0.4; }
50% { transform: scale(1); opacity: 1; }
}
.dot-pulse {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4f7cff;
animation: dotPulse 1s ease-in-out infinite;
}
.dot-pulse:nth-child(2) { animation-delay: 0.2s; }
.dot-pulse:nth-child(3) { animation-delay: 0.4s; }
/* ============================================
列表交错入场
============================================ */
@keyframes listItemIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
.list-item-enter {
animation: listItemIn 0.35s ease-out both;
}
/* ============================================
数字跳动
============================================ */
@keyframes numberPop {
0% { transform: scale(1); }
50% { transform: scale(1.15); }
100% { transform: scale(1); }
}
.number-animate {
animation: numberPop 0.5s ease-out;
}
/* ============================================
加载中
============================================ */
@keyframes loadingDot {
0%, 80%, 100% { transform: scale(0); opacity: 0; }
40% { transform: scale(1); opacity: 1; }
}
.loading-dots {
display: flex;
align-items: center;
gap: 6px;
justify-content: center;
padding: 32px;
}
.loading-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #4f7cff;
animation: loadingDot 1.2s ease-in-out infinite;
}
.loading-dot:nth-child(2) { animation-delay: 0.15s; }
.loading-dot:nth-child(3) { animation-delay: 0.3s; }
.loading-center {
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
color: #94a3b8;
font-size: 14px;
}
/* ============================================
弹性缩放
============================================ */
@keyframes elasticIn {
0% { transform: scale(0.3); opacity: 0; }
50% { transform: scale(1.05); }
70% { transform: scale(0.95); }
100% { transform: scale(1); opacity: 1; }
}
.elastic-in {
animation: elasticIn 0.5s ease-out both;
}
/* ============================================
淡入
============================================ */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in-up {
animation: fadeInUp 0.4s ease-out both;
}
/* ============================================
安全区适配
============================================ */
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
/* 分隔线 */
.divider {
height: 1px;
background: #e9edf4;
margin: 12px 0;
}
/* 文本省略 */
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ============================================
flex 工具
============================================ */
.flex-row { display: flex; flex-direction: row; align-items: center; }
.flex-between { display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.flex-1 { flex: 1; }
</style>