feat: UI redesign with uv-ui color system and landscape imagery

- Replace all colors with uv-ui design tokens (primary #3c9cff, warning #f9ae3d, etc.)
- Hero section: Wugong Mountain landscape photo with dark overlay
- Super Player card: photo background, large typography, warning-color price/CTA, value comparison (¥198 vs ¥3800+)
- Spots section: landscape thumbnails instead of CSS gradients, warning-color prices
- Profile page: landscape hero header, CSS menu icons, floating card layout
- Card list page: landscape hero header, filter pills, empty state design
- TabBar: rename '入园码' to '旅游卡', Lucide-style PNG icons (home/card/profile)
- Typography: corrected font weights (semibold for prices, medium for labels)
- Remove broken font-loader (lxgw-wen-kai CDN 404)
- Skip wx.getPrivacySetting in devtools to avoid framework timeout
- Replace all inline SVG with CSS-only icons for mini-program compatibility
- Add design system files (styles/, components/, animations)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-06-25 01:07:21 +08:00
co-authored by Claude
parent c062dee3e6
commit 5f1ab737e9
25 changed files with 2110 additions and 390 deletions
+4
View File
@@ -20,3 +20,7 @@ dist
*.njsproj
*.sln
*.sw?
# Dev artifacts
.playwright-mcp/
*.png
!src/static/**/*.png
+60
View File
@@ -150,3 +150,63 @@ dist/build/mp-weixin/ # 用微信开发者工具导入此目录
4. **条件编译**:`#ifdef MP-WEIXIN` 包裹微信特有逻辑
5. **隐私协议**:`App.vue` 中 `wx.getPrivacySetting` 检查,未同意前不能调授权 API
6. **微信开发者工具调试**:需勾选「不校验合法域名」才能请求 localhost
<frontend_aesthetics>
You tend to converge toward generic, "on distribution" outputs.
In frontend design, this creates what users call the "AI slop"
aesthetic. Avoid this: make creative, distinctive frontends that
surprise and delight. Focus on:
Typography: Choose fonts that are beautiful, unique, and interesting.
Avoid generic fonts like Arial and Inter; opt instead for distinctive
choices that elevate the frontend's aesthetics.
Color & Theme: Commit to a cohesive aesthetic. Use CSS variables for
consistency. Dominant colors with sharp accents outperform timid,
evenly-distributed palettes.
Motion: Use animations for effects and micro-interactions.
Focus on high-impact moments: one well-orchestrated page load
with staggered reveals creates more delight than scattered
micro-interactions.
Backgrounds: Create atmosphere and depth rather than defaulting
to solid colors. Layer CSS gradients, use geometric patterns,
or add contextual effects.
Avoid generic AI-generated aesthetics:
- Overused font families (Inter, Roboto, Arial, system fonts)
- Clichéd color schemes (particularly purple gradients on white)
- Predictable layouts and component patterns
- Cookie-cutter design that lacks context-specific character
Interpret creatively and make unexpected choices that feel
genuinely designed for the context.
</frontend_aesthetics>
<use_interesting_fonts>
Typography instantly signals quality. Avoid boring, generic fonts.
Never use: Inter, Roboto, Open Sans, Lato, default system fonts
Impact choices:
- Code aesthetic: JetBrains Mono, Fira Code, Space Grotesk
- Editorial: Playfair Display, Crimson Pro, Fraunces
- Startup: Clash Display, Satoshi, Cabinet Grotesk
- Technical: IBM Plex family, Source Sans 3
- Distinctive: Bricolage Grotesque, Obviously, Newsreader
Pairing principle: High contrast = interesting.
Display + monospace, serif + geometric sans.
Use extremes: 100/200 weight vs 800/900, not 400 vs 600.
Size jumps of 3x+, not 1.5x.
</use_interesting_fonts>
# Design System
Always refer to DESIGN.md when generating or modifying any UI component.
Use only colors, fonts, and spacing values defined in DESIGN.md.
Do not invent new values or use defaults from any framework.
Match component states (hover, focus, active, disabled) to patterns
in DESIGN.md.
+17 -11
View File
@@ -4,15 +4,20 @@ import { useUserStore } from './store/user'
onLaunch(() => {
// #ifdef MP-WEIXIN
wx.getPrivacySetting({
success: (res: any) => {
if (res.needAuthorization) {
uni.setStorageSync('needPrivacy', true)
} else {
uni.removeStorageSync('needPrivacy')
}
}
})
// 仅真机检查隐私协议,模拟器中跳过(getPrivacySetting 在模拟器会触发框架级 timeout)
const sysInfo = uni.getSystemInfoSync()
if (sysInfo.platform !== 'devtools' && wx.getPrivacySetting) {
wx.getPrivacySetting({
success: (res: any) => {
if (res.needAuthorization) {
uni.setStorageSync('needPrivacy', true)
} else {
uni.removeStorageSync('needPrivacy')
}
},
fail: () => {},
})
}
// #endif
const userStore = useUserStore()
@@ -25,9 +30,10 @@ onLaunch(() => {
})
</script>
<style>
<style lang="scss">
@import './styles/main.scss';
page {
min-height: 100vh;
background-color: #f5f5f5;
}
</style>
+123
View File
@@ -0,0 +1,123 @@
<template>
<view
class="design-button"
:class="[
`design-button--${variant}`,
`design-button--${size}`,
{ 'design-button--block': block, 'design-button--disabled': disabled, 'design-button--loading': loading }
]"
@tap="handleTap"
>
<view v-if="loading" class="design-button__spinner" />
<slot v-else />
</view>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
variant?: 'primary' | 'secondary' | 'ghost' | 'outline'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
block?: boolean
}>(), {
variant: 'primary',
size: 'md',
disabled: false,
loading: false,
block: false,
})
const emit = defineEmits<{
(e: 'click', event: any): void
}>()
function handleTap(event: any) {
if (props.disabled || props.loading) return
emit('click', event)
}
</script>
<style lang="scss" scoped>
.design-button {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: $radius-base;
font-family: $font-family-body;
font-weight: $font-weight-semibold;
transition: all $transition-fast;
position: relative;
overflow: hidden;
&:active:not(.design-button--disabled):not(.design-button--loading) {
transform: scale(0.98);
}
// --- Variants ---
&--primary {
background: $gradient-accent;
color: $color-accent-foreground;
box-shadow: $shadow-sm;
}
&--secondary {
background: $color-muted;
color: $color-foreground;
}
&--ghost {
background: transparent;
color: $color-accent;
}
&--outline {
background: transparent;
border: 2rpx solid $color-border;
color: $color-foreground;
}
// --- Sizes ---
&--sm {
height: 64rpx;
padding: 0 $spacing-4;
font-size: $font-size-sm;
border-radius: $radius-sm;
}
&--md {
height: 88rpx;
padding: 0 $spacing-6;
font-size: $font-size-base;
}
&--lg {
height: 104rpx;
padding: 0 $spacing-8;
font-size: $font-size-lg;
border-radius: $radius-lg;
}
// --- States ---
&--block {
width: 100%;
}
&--disabled {
opacity: 0.5;
}
&--loading {
opacity: 0.8;
}
&__spinner {
width: 32rpx;
height: 32rpx;
border: 3rpx solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: rotate 0.8s linear infinite;
}
}
</style>
+84
View File
@@ -0,0 +1,84 @@
<template>
<!-- featured variant 使用嵌套 div 实现渐变边框 -->
<view
v-if="variant === 'featured'"
class="design-card design-card--featured"
:class="[`design-card--padding-${padding}`]"
@tap="handleTap"
>
<view class="design-card__inner">
<slot />
</view>
</view>
<view
v-else
class="design-card"
:class="[
`design-card--${variant}`,
`design-card--padding-${padding}`
]"
@tap="handleTap"
>
<slot />
</view>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
variant?: 'default' | 'featured' | 'interactive'
padding?: 'none' | 'sm' | 'md' | 'lg'
}>(), {
variant: 'default',
padding: 'md',
})
const emit = defineEmits<{
(e: 'click', event: any): void
}>()
function handleTap(event: any) {
emit('click', event)
}
</script>
<style lang="scss" scoped>
.design-card {
background: $color-card;
border-radius: $radius-lg;
// --- Variants ---
&--default {
box-shadow: $shadow-base;
}
&--interactive {
box-shadow: $shadow-base;
transition: all $transition-fast;
&:active {
transform: scale(0.98);
box-shadow: $shadow-sm;
}
}
// Featured: 嵌套 div 渐变边框
&--featured {
background: $gradient-accent;
padding: 2rpx;
box-shadow: $shadow-lg;
.design-card__inner {
background: $color-card;
border-radius: calc(#{$radius-lg} - 2rpx);
height: 100%;
}
}
// --- Padding ---
&--padding-none { padding: 0; }
&--padding-sm { padding: $spacing-3; }
&--padding-md { padding: $spacing-5; }
&--padding-lg { padding: $spacing-8; }
}
</style>
+122
View File
@@ -0,0 +1,122 @@
<template>
<view
class="design-input"
:class="{ 'design-input--error': error, 'design-input--disabled': disabled }"
>
<text v-if="label" class="design-input__label">{{ label }}</text>
<textarea
v-if="type === 'textarea'"
class="design-input__field design-input__textarea"
:placeholder="placeholder"
:value="value"
:disabled="disabled"
:maxlength="maxlength"
placeholder-class="design-input__placeholder"
@input="onInput"
/>
<input
v-else
class="design-input__field"
:type="type"
:password="type === 'password'"
:placeholder="placeholder"
:value="value"
:disabled="disabled"
:maxlength="maxlength"
placeholder-class="design-input__placeholder"
@input="onInput"
/>
<text v-if="error && errorMessage" class="design-input__error-msg">
{{ errorMessage }}
</text>
</view>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
type?: 'text' | 'password' | 'number' | 'textarea'
label?: string
placeholder?: string
value?: string | number
disabled?: boolean
error?: boolean
errorMessage?: string
maxlength?: number
}>(), {
type: 'text',
placeholder: '',
value: '',
disabled: false,
error: false,
errorMessage: '',
maxlength: 140,
})
const emit = defineEmits<{
(e: 'update:value', value: string): void
}>()
function onInput(event: any) {
emit('update:value', event.detail.value)
}
</script>
<style lang="scss" scoped>
.design-input {
display: flex;
flex-direction: column;
gap: $spacing-1;
&__label {
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $color-foreground;
margin-bottom: $spacing-1;
}
&__field {
width: 100%;
height: 88rpx;
padding: 0 $spacing-4;
background: $color-card;
border: 2rpx solid $color-border;
border-radius: $radius-base;
font-family: $font-family-body;
font-size: $font-size-base;
color: $color-foreground;
transition: border-color $transition-fast;
}
&__textarea {
height: 200rpx;
padding: $spacing-3 $spacing-4;
}
&__placeholder {
color: $color-muted-foreground;
}
&__error-msg {
font-size: $font-size-xs;
color: $color-error;
margin-top: $spacing-1;
}
// --- States ---
&--error {
.design-input__field {
border-color: $color-error;
}
}
&--disabled {
opacity: 0.5;
.design-input__field {
background: $color-muted;
}
}
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<template>
<view class="section-label">
<view class="section-label__dot" :style="{ backgroundColor: dotColor }" />
<text class="section-label__text">{{ text }}</text>
</view>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
text: string
dotColor?: string
}>(), {
dotColor: '#0052FF',
})
</script>
<style lang="scss" scoped>
.section-label {
display: inline-flex;
align-items: center;
gap: $spacing-2;
background: rgba(0, 82, 255, 0.05);
border: 1rpx solid rgba(0, 82, 255, 0.15);
border-radius: $radius-full;
padding: $spacing-1 $spacing-3;
&__dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: $color-accent;
animation: pulse 2s ease-in-out infinite;
}
&__text {
font-family: $font-family-mono;
font-size: $font-size-xs;
font-weight: $font-weight-semibold;
color: $color-accent;
letter-spacing: 0.1em;
}
}
</style>
+204 -21
View File
@@ -1,9 +1,19 @@
<template>
<view class="page">
<!-- 搜索栏 -->
<view class="search-bar">
<uni-search-bar v-model="keyword" placeholder="搜索年卡" @confirm="handleSearch" @clear="handleSearch" />
<!-- 顶部风景头图 + 标题 -->
<view class="list-hero">
<image
class="hero-bg"
src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=750&h=300&fit=crop"
mode="aspectFill"
/>
<view class="hero-overlay" />
<view class="hero-inner">
<text class="hero-title">旅游卡</text>
<text class="hero-sub">畅游赣湘,一卡在手</text>
</view>
</view>
<!-- 分类筛选 -->
<view class="filter-bar">
<view
@@ -16,11 +26,24 @@
<text>{{ cat.label }}</text>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-bar">
<uni-search-bar v-model="keyword" placeholder="搜索景区、年卡" @confirm="handleSearch" @clear="handleSearch" />
</view>
<!-- 年卡列表 -->
<view class="card-list">
<view v-if="loading" class="loading"><text>加载中...</text></view>
<view v-else-if="cardList.length === 0">
<empty-state icon="🎫" text="暂无符合条件的年卡" />
<view v-if="loading" class="loading">
<view class="loading-dot" />
<text>加载中...</text>
</view>
<view v-else-if="cardList.length === 0" class="empty-state">
<view class="empty-icon">
<view class="icon-ticket" />
</view>
<text class="empty-text">暂无符合条件的年卡</text>
<text class="empty-hint">换个关键词试试</text>
</view>
<view v-else>
<card-item v-for="card in cardList" :key="card.id" :card="card" @click="goDetail" />
@@ -34,7 +57,6 @@ import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getCardList } from '../../../../api/card'
import CardItem from '../../../../components/card-item.vue'
import EmptyState from '../../../../components/empty-state.vue'
import type { CardInfo } from '../../../../api/types'
const cardList = ref<CardInfo[]>([])
@@ -42,7 +64,6 @@ const loading = ref(false)
const keyword = ref('')
const currentCategory = ref('')
/** 分类选项 */
const categories = [
{ label: '全部', value: '' },
{ label: '旅游年卡', value: 'travel' },
@@ -50,7 +71,6 @@ const categories = [
{ label: '情侣年卡', value: 'couple' }
]
/** 获取年卡列表 */
async function fetchCardList(params?: { keyword?: string; category?: string }) {
loading.value = true
try {
@@ -60,34 +80,197 @@ async function fetchCardList(params?: { keyword?: string; category?: string }) {
}
}
/** 页面加载时获取年卡列表 */
onLoad(() => {
fetchCardList()
})
/** 执行搜索 */
function handleSearch() {
fetchCardList({ keyword: keyword.value, category: currentCategory.value })
}
/** 选择分类 */
function selectCategory(value: string) {
currentCategory.value = value
handleSearch()
}
/** 跳转年卡详情 */
function goDetail(card: CardInfo) {
uni.navigateTo({ url: `/packages/user/pages/card/detail?id=${card.id}` })
}
</script>
<style scoped>
.page { min-height: 100vh; background: #f5f5f5; }
.search-bar { background: #fff; padding: 12rpx 20rpx; }
.filter-bar { display: flex; gap: 16rpx; padding: 16rpx 20rpx; background: #fff; border-bottom: 1rpx solid #f0f0f0; }
.filter-tag { padding: 10rpx 24rpx; border-radius: 30rpx; font-size: 24rpx; color: #666; background: #f5f5f5; }
.filter-tag.active { background: #4A90D9; color: #fff; }
.card-list { padding: 20rpx; }
.loading { text-align: center; padding: 60rpx; color: #999; font-size: 28rpx; }
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $uv-bg-color;
}
/* Hero header */
.list-hero {
position: relative;
height: 280rpx;
overflow: hidden;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(48, 49, 51, 0.3) 0%,
rgba(48, 49, 51, 0.6) 100%
);
}
.hero-inner {
position: relative;
z-index: 2;
padding: $spacing-12 $spacing-4 $spacing-4;
display: flex;
flex-direction: column;
}
.hero-title {
font-family: $font-family-display;
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: #fff;
line-height: $line-height-tight;
margin-bottom: $spacing-1;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.hero-sub {
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.8);
letter-spacing: 2rpx;
}
/* Filter bar */
.filter-bar {
display: flex;
gap: $spacing-2;
padding: $spacing-3 $spacing-3 $spacing-1;
background: $uv-bg-color;
}
.filter-tag {
padding: $spacing-1 $spacing-3;
border-radius: $radius-full;
font-size: $font-size-sm;
color: $uv-content-color;
background: $color-card;
border: $border-width solid $uv-border-color;
font-weight: $font-weight-medium;
transition: all $transition-fast;
&.active {
background: $uv-primary;
color: #fff;
border-color: $uv-primary;
font-weight: $font-weight-semibold;
}
&:active {
transform: scale(0.96);
}
}
/* Search bar */
.search-bar {
padding: $spacing-1 $spacing-3 $spacing-2;
background: $uv-bg-color;
}
/* Card list */
.card-list {
padding: 0 $spacing-3 $spacing-3;
}
/* Loading */
.loading {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-10 0;
gap: $spacing-2;
text {
font-size: $font-size-sm;
color: $uv-tips-color;
}
}
.loading-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: $uv-primary;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
/* Empty state */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-10 0;
}
.empty-icon {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background: $uv-primary-light;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $spacing-3;
}
.icon-ticket {
width: 40rpx;
height: 28rpx;
border: 3rpx solid $uv-primary;
border-radius: 6rpx;
position: relative;
&::after {
content: '';
position: absolute;
top: 50%;
left: -3rpx;
right: -3rpx;
height: 3rpx;
background: $uv-primary;
transform: translateY(-50%);
}
}
.empty-text {
font-size: $font-size-base;
color: $uv-main-color;
font-weight: $font-weight-medium;
margin-bottom: $spacing-1;
}
.empty-hint {
font-size: $font-size-sm;
color: $uv-tips-color;
}
</style>
+5 -5
View File
@@ -78,11 +78,11 @@
"navigationBarTextStyle": "black",
"navigationBarTitleText": "爱上萍乡",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F5F5F5"
"backgroundColor": "#FAFAFA"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#4A90D9",
"selectedColor": "#3c9cff",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [
@@ -94,9 +94,9 @@
},
{
"pagePath": "pages/qrcode/index",
"text": "入园码",
"iconPath": "static/tabbar/qrcode.png",
"selectedIconPath": "static/tabbar/qrcode-active.png"
"text": "旅游卡",
"iconPath": "static/tabbar/card.png",
"selectedIconPath": "static/tabbar/card-active.png"
},
{
"pagePath": "pages/profile/index",
+497 -232
View File
@@ -1,92 +1,98 @@
<template>
<view class="page">
<!-- 头部区域:背景图 + 渐变遮罩 + 标题 + 搜索栏 -->
<view class="hero">
<!-- Hero: mountain landscape photo -->
<view class="hero animate-fade-in-up">
<image
class="hero-bg"
src="https://images.pexels.com/photos/28594448/pexels-photo-28594448.jpeg?auto=compress&cs=tinysrgb&w=750"
mode="aspectFill"
/>
<view class="hero-overlay" />
<view class="hero-content">
<view class="hero-inner">
<text class="hero-title">爱上萍乡</text>
<text class="hero-subtitle">赣湘之间 · 发现旅途美好</text>
<view class="search-bar" @click="goSearch">
<view class="search-icon-wrap">
<view class="search-icon-circle" />
</view>
<text class="search-placeholder">搜索景区、活动...</text>
</view>
<text class="hero-sub">赣湘之间 · 发现旅途美好</text>
</view>
</view>
<scroll-view scroll-y class="scroll-content">
<scroll-view scroll-y class="main-scroll">
<!-- 公告栏 -->
<view class="notice-bar" v-if="announcements.length">
<view class="notice-label">
<view class="notice-dot" />
<text class="notice-text">公告</text>
<view v-if="announcements.length" class="notice animate-fade-in-up stagger-1">
<view class="notice-tag">
<view class="notice-pip animate-pulse" />
<text class="notice-label">公告</text>
</view>
<swiper class="notice-swiper" vertical autoplay circular :interval="3000" :duration="500">
<swiper-item v-for="item in announcements" :key="item.id">
<text class="notice-content">{{ item.content }}</text>
<text class="notice-text">{{ item.content }}</text>
</swiper-item>
</swiper>
</view>
<!-- 卡片销售宣传 -->
<view class="card-section">
<view class="promo-card promo-card-gx" @click="goCardDetail('gx')">
<view class="promo-card-badge">工会会员专属</view>
<text class="promo-card-name">赣湘全域旅游年卡</text>
<view class="promo-card-price-row">
<text class="promo-card-currency">¥</text>
<text class="promo-card-price">300</text>
<text class="promo-card-unit">/套卡(2张)</text>
</view>
<view class="promo-card-features">
<view class="promo-card-feature"><view class="feature-dot" /><text>22家景区免门票</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>武功山·方特·君澜温泉</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>覆盖萍乡/长沙/株洲/湘潭</text></view>
</view>
<view class="promo-card-btn">
<text class="promo-card-btn-text">立即抢购</text>
<view class="btn-arrow" />
</view>
</view>
<view class="promo-card promo-card-super" @click="goCardDetail('super')">
<view class="promo-card-badge">大学生·市民专属</view>
<text class="promo-card-name">超级玩家卡</text>
<view class="promo-card-price-row">
<text class="promo-card-currency">¥</text>
<text class="promo-card-price">198</text>
</view>
<view class="promo-card-features">
<view class="promo-card-feature"><view class="feature-dot" /><text>40家景区免门票</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>武功山·方特·韶山·滴水洞</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>含外骨骼登山·青年旅社等</text></view>
</view>
<view class="promo-card-btn">
<text class="promo-card-btn-text">立即抢购</text>
<view class="btn-arrow" />
<!-- 旅游卡 — 大字报风格 -->
<view class="promo-wrap animate-fade-in-up stagger-2">
<view class="promo" @click="goCardDetail()">
<image
class="promo-bg"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=750&h=400&fit=crop"
mode="aspectFill"
/>
<view class="promo-overlay" />
<view class="promo-content">
<view class="promo-badge">大学生·市民专属</view>
<text class="promo-name">超级玩家卡</text>
<view class="promo-price-row">
<view class="promo-price-block">
<text class="promo-currency">¥</text>
<text class="promo-price">198</text>
</view>
<view class="promo-value-block">
<text class="promo-value-label">景区总价值</text>
<text class="promo-value">¥3800+</text>
</view>
</view>
<text class="promo-desc">40家景区免门票 · 武功山·方特·韶山·滴水洞</text>
<view class="promo-cta">
<text class="cta-label">立即抢购</text>
<view class="cta-arrow" />
</view>
</view>
</view>
</view>
<!-- 热门景点列表 -->
<!-- 热门景点 -->
<view class="spots-section">
<view class="spots-header">
<view class="spots-head animate-fade-in-up stagger-3">
<text class="spots-title">热门景点</text>
<view class="spots-more" @click="goMore">
<text>查看全部</text>
<view class="chevron-right" />
<text class="spots-more-text">查看全部</text>
<view class="chevron" />
</view>
</view>
<view class="spot-card" v-for="spot in scenicSpots" :key="spot.id" @click="goSpot(spot)">
<view class="spot-image" :style="{ background: getGradient(spot.region) }">
<view class="spot-region-tag">{{ spot.region }}</view>
</view>
<view class="spot-info">
<text class="spot-name">{{ spot.name }}</text>
<view class="spot-meta">
<text class="spot-price">¥{{ spot.price }}</text>
<view class="spot-free-tag">持卡免门票</view>
<view class="spots-grid">
<view
v-for="(spot, idx) in scenicSpots.slice(0, 6)"
:key="spot.id"
class="spot animate-fade-in-up"
:class="`stagger-${Math.min(idx + 4, 10)}`"
@click="goSpot(spot)"
>
<view class="spot-cover">
<image class="spot-img" :src="getSpotImage(spot.id)" mode="aspectFill" />
<view class="spot-region">
<view class="pin-dot" />
<text>{{ spot.region }}</text>
</view>
</view>
<view class="spot-body">
<text class="spot-name">{{ spot.name }}</text>
<view class="spot-meta">
<text class="spot-price">¥{{ spot.price }}</text>
<view class="spot-free">
<view class="free-dot" />
<text>免门票</text>
</view>
</view>
</view>
</view>
</view>
@@ -107,202 +113,461 @@ const announcements = ref([
{ id: 3, content: '武功山金顶景区已开放,持卡可免费入园1次。' },
])
/** 区域对应的渐变色(景点图片占位) */
const regionGradients: Record<string, string> = {
'萍乡': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
'长沙': 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
'株洲': 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
'湘潭': 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
/** 景点山水风景图 */
const spotImages: Record<number, string> = {
1: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=400&h=250&fit=crop',
2: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=400&h=250&fit=crop',
3: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=400&h=250&fit=crop',
4: 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?fm=jpg&q=80&w=400&h=250&fit=crop',
5: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?fm=jpg&q=80&w=400&h=250&fit=crop',
6: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=400&h=250&fit=crop',
}
function getGradient(region: string): string {
return regionGradients[region] || regionGradients['萍乡']
function getSpotImage(id: number): string {
return spotImages[id] || `https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=400&h=250&fit=crop`
}
/** 跳转搜索(暂不实现) */
function goSearch() {
uni.showToast({ title: '搜索功能开发中', icon: 'none' })
}
/** 跳转卡片详情 */
function goCardDetail(type: 'gx' | 'super') {
function goCardDetail() {
uni.navigateTo({ url: '/packages/user/pages/card/list' })
}
/** 跳转景点详情 */
function goSpot(spot: ScenicSpotItem) {
uni.showToast({ title: `${spot.name} 详情开发中`, icon: 'none' })
}
/** 查看全部景点 */
function goMore() {
uni.showToast({ title: '景点列表开发中', icon: 'none' })
}
</script>
<style scoped>
.page { display: flex; flex-direction: column; min-height: 100vh; background: #f5f5f5; }
<style lang="scss" scoped>
/* ============================================
Page
============================================ */
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: $color-background;
}
/* === 头部区域 === */
.hero { position: relative; height: 360rpx; overflow: hidden; }
.hero-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(180deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.6) 100%);
z-index: 1;
}
.hero::before {
content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(135deg, #1a237e 0%, #4a148c 50%, #e65100 100%);
}
.hero-content {
position: relative; z-index: 2;
padding: 80rpx 30rpx 30rpx;
display: flex; flex-direction: column;
}
.hero-title { font-size: 42rpx; font-weight: bold; color: #fff; margin-bottom: 8rpx; }
.hero-subtitle { font-size: 24rpx; color: rgba(255,255,255,0.8); margin-bottom: 24rpx; }
/* 搜索栏 */
.search-bar {
display: flex; align-items: center;
background: rgba(255,255,255,0.95); border-radius: 40rpx;
padding: 18rpx 24rpx;
min-height: 88rpx;
transition: opacity 0.2s;
}
.search-bar:active { opacity: 0.7; }
.search-icon-wrap {
width: 36rpx; height: 36rpx; margin-right: 12rpx;
display: flex; align-items: center; justify-content: center;
}
.search-icon-circle {
width: 24rpx; height: 24rpx;
border: 3rpx solid #999; border-radius: 50%;
/* ============================================
Hero — mountain landscape photo
============================================ */
.hero {
position: relative;
}
.search-icon-circle::after {
content: ''; position: absolute;
width: 3rpx; height: 10rpx;
background: #999; border-radius: 2rpx;
bottom: -10rpx; right: -4rpx;
transform: rotate(-45deg);
}
.search-placeholder { font-size: 26rpx; color: #999; }
/* === 公告栏 === */
.notice-bar {
display: flex; align-items: center;
background: #fff; padding: 20rpx 24rpx; margin: 0;
border-bottom: 1rpx solid #f0f0f0;
}
.notice-label { display: flex; align-items: center; margin-right: 16rpx; flex-shrink: 0; }
.notice-dot {
width: 14rpx; height: 14rpx; border-radius: 50%;
background: #E74C3C; margin-right: 8rpx;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.notice-text { font-size: 24rpx; color: #E74C3C; font-weight: 500; }
.notice-swiper { flex: 1; height: 40rpx; }
.notice-content { font-size: 24rpx; color: #666; line-height: 40rpx; }
/* === 卡片销售宣传 === */
.card-section { padding: 24rpx; display: flex; flex-direction: column; gap: 24rpx; }
.promo-card {
position: relative; border-radius: 24rpx; padding: 40rpx 32rpx;
height: 340rpx;
overflow: hidden;
transition: transform 0.15s ease;
}
.promo-card:active { transform: scale(0.98); }
.promo-card-gx {
background: linear-gradient(135deg, #1a237e 0%, #283593 40%, #ffd54f 100%);
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.promo-card-super {
background: linear-gradient(135deg, #e65100 0%, #bf360c 40%, #7b1fa2 100%);
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.45) 0%,
rgba(0, 0, 0, 0.3) 40%,
rgba(0, 0, 0, 0.65) 100%
);
}
.promo-card-badge {
.hero-inner {
position: relative;
z-index: 2;
padding: $spacing-16 $spacing-4 $spacing-4;
display: flex;
flex-direction: column;
}
.hero-title {
font-family: $font-family-display;
font-size: $font-size-3xl;
font-weight: $font-weight-bold;
color: #fff;
line-height: $line-height-tight;
margin-bottom: $spacing-1;
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.4);
}
.hero-sub {
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 4rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
/* ============================================
CSS-only icons
============================================ */
.notice-pip {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: $color-accent;
flex-shrink: 0;
}
.chevron {
width: 10rpx;
height: 10rpx;
border-top: 2rpx solid $color-muted-foreground;
border-right: 2rpx solid $color-muted-foreground;
transform: rotate(45deg);
flex-shrink: 0;
}
.pin-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #fff;
flex-shrink: 0;
}
.free-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $color-accent;
flex-shrink: 0;
}
/* ============================================
Notice bar
============================================ */
.notice {
display: flex;
align-items: center;
background: $color-card;
padding: $spacing-2 $spacing-3;
border-bottom: $border-width solid $color-border;
}
.notice-tag {
display: flex;
align-items: center;
margin-right: $spacing-2;
flex-shrink: 0;
gap: $spacing-1;
}
.notice-label {
font-size: $font-size-xs;
color: $color-accent;
font-weight: $font-weight-medium;
}
.notice-swiper {
flex: 1;
height: 40rpx;
}
.notice-text {
font-size: $font-size-sm;
color: $color-muted-foreground;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ============================================
Promo card — landscape photo hero card
============================================ */
.promo-wrap {
padding: $spacing-3;
}
.promo {
position: relative;
border-radius: $radius-xl;
overflow: hidden;
min-height: 400rpx;
transition: transform $transition-fast;
&:active {
transform: scale(0.98);
}
}
.promo-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.promo-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
160deg,
rgba($color-foreground, 0.88) 0%,
rgba($color-foreground, 0.6) 55%,
rgba($color-foreground, 0.3) 100%
);
}
.promo-content {
position: relative;
z-index: 2;
padding: $spacing-5 $spacing-4;
display: flex;
flex-direction: column;
}
.promo-badge {
display: inline-block;
background: rgba(255,255,255,0.2); border-radius: 20rpx;
padding: 6rpx 16rpx; margin-bottom: 16rpx;
font-size: 22rpx; color: rgba(255,255,255,0.9);
}
.promo-card-name { font-size: 34rpx; font-weight: bold; color: #fff; display: block; margin-bottom: 12rpx; }
.promo-card-price-row { display: flex; align-items: baseline; margin-bottom: 20rpx; }
.promo-card-currency { font-size: 32rpx; color: #fff; font-weight: bold; }
.promo-card-price { font-size: 64rpx; color: #fff; font-weight: bold; line-height: 1; }
.promo-card-unit { font-size: 24rpx; color: rgba(255,255,255,0.8); margin-left: 8rpx; }
.promo-card-features { display: flex; flex-direction: column; gap: 10rpx; margin-bottom: 24rpx; }
.promo-card-feature {
display: flex; align-items: center; gap: 10rpx;
font-size: 24rpx; color: rgba(255,255,255,0.9);
}
.feature-dot {
width: 8rpx; height: 8rpx; border-radius: 50%;
background: rgba(255,255,255,0.7); flex-shrink: 0;
}
.promo-card-btn {
display: flex; align-items: center; justify-content: center; gap: 8rpx;
background: #fff; border-radius: 44rpx;
padding: 22rpx 0;
min-height: 88rpx;
transition: opacity 0.2s;
}
.promo-card-btn:active { opacity: 0.8; }
.promo-card-btn-text { font-size: 28rpx; font-weight: bold; color: #333; }
.btn-arrow {
width: 12rpx; height: 12rpx;
border-top: 3rpx solid #333;
border-right: 3rpx solid #333;
transform: rotate(45deg);
align-self: flex-start;
background: rgba($color-accent, 0.2);
border: $border-width solid rgba($color-accent, 0.4);
border-radius: $radius-full;
padding: 6rpx $spacing-3;
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.95);
font-weight: $font-weight-semibold;
margin-bottom: $spacing-3;
}
/* === 热门景点列表 === */
.spots-section { padding: 24rpx; }
.spots-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 20rpx;
.promo-name {
font-family: $font-family-display;
font-size: 56rpx;
font-weight: $font-weight-bold;
color: #fff;
display: block;
margin-bottom: $spacing-3;
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.4);
letter-spacing: 2rpx;
}
.spots-title { font-size: 34rpx; font-weight: bold; color: #333; }
.promo-price-row {
display: flex;
align-items: flex-end;
gap: $spacing-4;
margin-bottom: $spacing-3;
}
.promo-price-block {
display: flex;
align-items: baseline;
}
.promo-currency {
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: $uv-warning;
}
.promo-price {
font-size: 96rpx;
font-weight: $font-weight-bold;
color: $uv-warning;
line-height: 0.85;
letter-spacing: -4rpx;
}
.promo-value-block {
display: flex;
flex-direction: column;
padding-bottom: $spacing-2;
}
.promo-value-label {
font-size: $font-size-xs;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 4rpx;
}
.promo-value {
font-size: $font-size-xl;
color: rgba(255, 255, 255, 0.45);
text-decoration: line-through;
font-weight: $font-weight-medium;
}
.promo-desc {
font-size: $font-size-base;
color: rgba(255, 255, 255, 0.8);
display: block;
margin-bottom: $spacing-4;
line-height: $line-height-relaxed;
}
.promo-cta {
display: flex;
align-items: center;
justify-content: center;
gap: $spacing-2;
align-self: flex-start;
padding: $spacing-2 $spacing-8;
border-radius: $radius-full;
background: $uv-warning;
transition: transform $transition-fast;
&:active {
transform: scale(0.96);
}
.cta-label {
font-size: $font-size-base;
font-weight: $font-weight-bold;
color: $uv-main-color;
}
.cta-arrow {
width: 10rpx;
height: 10rpx;
border-top: 3rpx solid $uv-main-color;
border-right: 3rpx solid $uv-main-color;
transform: rotate(45deg);
}
}
/* ============================================
Spots section
============================================ */
.spots-section {
padding: $spacing-3;
}
.spots-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-3;
}
.spots-title {
font-family: $font-family-display;
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $color-foreground;
}
.spots-more {
display: flex; align-items: center; gap: 4rpx;
font-size: 24rpx; color: #999;
}
.chevron-right {
width: 10rpx; height: 10rpx;
border-top: 2rpx solid #999;
border-right: 2rpx solid #999;
transform: rotate(45deg);
display: flex;
align-items: center;
gap: $spacing-1;
}
.spot-card {
background: #fff; border-radius: 20rpx; overflow: hidden;
margin-bottom: 20rpx;
transition: transform 0.15s ease;
}
.spot-card:active { transform: scale(0.98); }
.spot-image {
width: 100%; height: 320rpx; position: relative;
}
.spot-region-tag {
position: absolute; top: 16rpx; left: 16rpx;
background: rgba(0,0,0,0.5); border-radius: 16rpx;
padding: 6rpx 16rpx;
font-size: 22rpx; color: #fff;
}
.spot-info { padding: 20rpx 24rpx; }
.spot-name { font-size: 30rpx; font-weight: 600; color: #333; margin-bottom: 12rpx; display: block; }
.spot-meta { display: flex; justify-content: space-between; align-items: center; }
.spot-price { font-size: 32rpx; color: #E74C3C; font-weight: bold; }
.spot-free-tag {
background: #E8F5E9; border-radius: 16rpx;
padding: 6rpx 16rpx;
font-size: 22rpx; color: #2E7D32; font-weight: 500;
.spots-more-text {
font-size: $font-size-sm;
color: $color-muted-foreground;
}
/* === 滚动区域 === */
.scroll-content { flex: 1; }
/* Spots grid — 2 column */
.spots-grid {
display: flex;
flex-wrap: wrap;
gap: $spacing-2;
}
.spot {
width: calc(50% - #{$spacing-1});
background: $color-card;
border-radius: $radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
transition: transform $transition-fast, box-shadow $transition-base;
&:active {
transform: scale(0.97);
box-shadow: $shadow-base;
}
}
.spot-cover {
width: 100%;
height: 220rpx;
position: relative;
background: $color-muted;
}
.spot-img {
width: 100%;
height: 100%;
}
.spot-region {
position: absolute;
top: $spacing-2;
left: $spacing-2;
display: flex;
align-items: center;
gap: 4rpx;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8rpx);
border-radius: $radius-sm;
padding: 4rpx $spacing-2;
text {
font-size: $font-size-xs;
color: #fff;
}
}
.spot-body {
padding: $spacing-2;
}
.spot-name {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: $color-foreground;
display: block;
margin-bottom: $spacing-1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.spot-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.spot-price {
font-size: $font-size-base;
color: $uv-warning;
font-weight: $font-weight-semibold;
}
.spot-free {
display: flex;
align-items: center;
gap: 4rpx;
background: rgba($color-accent, 0.08);
border-radius: $radius-sm;
padding: 4rpx $spacing-1;
text {
font-size: $font-size-xs;
color: $color-accent;
font-weight: $font-weight-medium;
}
}
/* ============================================
Scroll area
============================================ */
.main-scroll {
flex: 1;
}
</style>
+296 -44
View File
@@ -1,34 +1,57 @@
<template>
<view class="page">
<view class="user-card">
<view class="avatar-wrap">
<image class="avatar" :src="userStore.userInfo?.avatarUrl || defaultAvatar" mode="aspectFill" />
</view>
<view class="user-info">
<text class="nickname">{{ userStore.userInfo?.nickname || '未登录' }}</text>
<text class="user-desc" v-if="userStore.isLogin">欢迎使用爱上萍乡</text>
<text class="user-desc" v-else @click="goLogin">点击登录</text>
<!-- 头部:风景图 + 用户信息 -->
<view class="profile-hero">
<image
class="hero-bg"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=750&h=400&fit=crop"
mode="aspectFill"
/>
<view class="hero-overlay" />
<view class="hero-content">
<view class="avatar-wrap">
<image class="avatar" :src="userStore.userInfo?.avatarUrl || defaultAvatar" mode="aspectFill" />
</view>
<view class="user-info">
<text class="nickname">{{ userStore.userInfo?.nickname || '未登录' }}</text>
<text class="user-desc" v-if="userStore.isLogin">欢迎使用爱上萍乡</text>
<text class="user-desc user-desc--link" v-else @click="goLogin">点击登录 →</text>
</view>
</view>
</view>
<view class="menu-card">
<view class="menu-item" @click="goTo('/packages/user/pages/card/bind')">
<text class="menu-icon">🎫</text>
<text class="menu-text">我的年卡</text>
<text class="menu-arrow">›</text>
</view>
<view class="menu-item" @click="showComingSoon">
<text class="menu-icon">📦</text>
<text class="menu-text">订单管理</text>
<text class="menu-arrow">›</text>
</view>
<view class="menu-item" @click="showComingSoon">
<text class="menu-icon">📋</text>
<text class="menu-text">使用记录</text>
<text class="menu-arrow">›</text>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-card">
<view class="menu-item" @click="goTo('/packages/user/pages/card/bind')">
<view class="menu-icon-wrap menu-icon-wrap--primary">
<view class="icon-card" />
</view>
<text class="menu-text">我的年卡</text>
<view class="menu-chevron" />
</view>
<view class="menu-item" @click="showComingSoon">
<view class="menu-icon-wrap menu-icon-wrap--warning">
<view class="icon-order" />
</view>
<text class="menu-text">订单管理</text>
<view class="menu-chevron" />
</view>
<view class="menu-item menu-item--last" @click="showComingSoon">
<view class="menu-icon-wrap menu-icon-wrap--success">
<view class="icon-history" />
</view>
<text class="menu-text">使用记录</text>
<view class="menu-chevron" />
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="btn-area" v-if="userStore.isLogin">
<button class="btn-logout" @click="handleLogout">退出登录</button>
<view class="btn-logout" @click="handleLogout">
<text>退出登录</text>
</view>
</view>
</view>
</template>
@@ -39,16 +62,13 @@ import { useUserStore } from '../../store/user'
const userStore = useUserStore()
const defaultAvatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
/** 跳转登录页 */
function goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
/** 需要登录的页面跳转 */
function goTo(url: string) {
if (!userStore.isLogin) { uni.navigateTo({ url: '/pages/login/login' }); return }
uni.navigateTo({ url })
}
/** 退出登录 */
function handleLogout() {
uni.showModal({
title: '提示', content: '确定要退出登录吗?',
@@ -62,24 +82,256 @@ function handleLogout() {
})
}
/** 即将上线提示 */
function showComingSoon() { uni.showToast({ title: '即将上线,敬请期待', icon: 'none' }) }
</script>
<style scoped>
.page { min-height: 100vh; background: #f5f5f5; }
.user-card { background: linear-gradient(135deg, #4A90D9, #357ABD); padding: 50rpx 30rpx 40rpx; display: flex; align-items: center; }
.avatar-wrap { margin-right: 24rpx; }
.avatar { width: 120rpx; height: 120rpx; border-radius: 50%; border: 4rpx solid rgba(255,255,255,0.5); background: #eee; }
.user-info { display: flex; flex-direction: column; }
.nickname { font-size: 34rpx; font-weight: bold; color: #fff; margin-bottom: 8rpx; }
.user-desc { font-size: 24rpx; color: rgba(255,255,255,0.8); }
.menu-card { background: #fff; border-radius: 16rpx; margin: 20rpx; overflow: hidden; }
.menu-item { display: flex; align-items: center; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
.menu-item:last-child { border-bottom: none; }
.menu-icon { font-size: 36rpx; margin-right: 20rpx; }
.menu-text { flex: 1; font-size: 28rpx; color: #333; }
.menu-arrow { font-size: 36rpx; color: #ccc; }
.btn-area { padding: 40rpx 20rpx; }
.btn-logout { width: 100%; height: 88rpx; line-height: 88rpx; background: #fff; color: #E74C3C; border-radius: 44rpx; font-size: 30rpx; border: 1rpx solid #f0f0f0; }
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $uv-bg-color;
}
/* Hero header */
.profile-hero {
position: relative;
height: 360rpx;
overflow: hidden;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(48, 49, 51, 0.5) 0%,
rgba(48, 49, 51, 0.7) 100%
);
}
.hero-content {
position: relative;
z-index: 2;
display: flex;
align-items: center;
padding: $spacing-12 $spacing-4 $spacing-4;
}
.avatar-wrap {
margin-right: $spacing-3;
flex-shrink: 0;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 4rpx solid rgba(255, 255, 255, 0.4);
background: $uv-info-light;
}
.user-info {
display: flex;
flex-direction: column;
}
.nickname {
font-family: $font-family-display;
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: #fff;
margin-bottom: $spacing-1;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.user-desc {
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.7);
&--link {
color: $uv-warning;
}
}
/* Menu section */
.menu-section {
padding: $spacing-3;
margin-top: -40rpx;
position: relative;
z-index: 3;
}
.menu-card {
background: $color-card;
border-radius: $radius-xl;
box-shadow: $shadow-base;
overflow: hidden;
}
.menu-item {
display: flex;
align-items: center;
padding: $spacing-3 $spacing-4;
border-bottom: $border-width solid $uv-border-color;
transition: background $transition-fast;
&:active {
background: $uv-info-light;
}
&--last {
border-bottom: none;
}
}
.menu-icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: $radius-lg;
display: flex;
align-items: center;
justify-content: center;
margin-right: $spacing-3;
flex-shrink: 0;
&--primary {
background: $uv-primary-light;
}
&--warning {
background: $uv-warning-light;
}
&--success {
background: $uv-success-light;
}
}
/* CSS icons */
.icon-card {
width: 28rpx;
height: 20rpx;
border: 3rpx solid $uv-primary;
border-radius: 4rpx;
position: relative;
&::after {
content: '';
position: absolute;
top: 6rpx;
left: 0;
width: 100%;
height: 3rpx;
background: $uv-primary;
}
}
.icon-order {
width: 22rpx;
height: 26rpx;
border: 3rpx solid $uv-warning;
border-radius: 4rpx;
position: relative;
&::before {
content: '';
position: absolute;
top: 6rpx;
left: 4rpx;
width: 10rpx;
height: 3rpx;
background: $uv-warning;
}
&::after {
content: '';
position: absolute;
top: 14rpx;
left: 4rpx;
width: 8rpx;
height: 3rpx;
background: $uv-warning;
}
}
.icon-history {
width: 24rpx;
height: 24rpx;
border: 3rpx solid $uv-success;
border-radius: 50%;
position: relative;
&::before {
content: '';
position: absolute;
top: 4rpx;
left: 8rpx;
width: 3rpx;
height: 8rpx;
background: $uv-success;
}
&::after {
content: '';
position: absolute;
top: 9rpx;
left: 8rpx;
width: 6rpx;
height: 3rpx;
background: $uv-success;
}
}
.menu-text {
flex: 1;
font-size: $font-size-base;
color: $uv-main-color;
font-weight: $font-weight-medium;
}
.menu-chevron {
width: 10rpx;
height: 10rpx;
border-top: 2rpx solid $uv-light-color;
border-right: 2rpx solid $uv-light-color;
transform: rotate(45deg);
flex-shrink: 0;
}
/* Logout button */
.btn-area {
padding: $spacing-5 $spacing-3;
}
.btn-logout {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
background: $color-card;
border-radius: $radius-full;
box-shadow: $shadow-sm;
transition: transform $transition-fast;
&:active {
transform: scale(0.98);
}
text {
font-size: $font-size-base;
color: $uv-error;
font-weight: $font-weight-medium;
}
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

+81
View File
@@ -0,0 +1,81 @@
// ============================================
// Keyframe Animations
// ============================================
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10rpx); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(24rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.92);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30rpx);
}
to {
opacity: 1;
transform: translateX(0);
}
}
// === Animation utility classes ===
.animate-pulse {
animation: pulse 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-rotate {
animation: rotate 1s linear infinite;
}
.animate-fade-in-up {
animation: fadeInUp 0.4s ease-out forwards;
}
.animate-scale-in {
animation: scaleIn 0.3s ease-out forwards;
}
// Stagger delays for list entrance
@for $i from 1 through 10 {
.stagger-#{$i} {
animation-delay: #{$i * 0.08}s;
opacity: 0;
}
}
+66
View File
@@ -0,0 +1,66 @@
// ============================================
// SCSS Mixins
// ============================================
// === FLEX ===
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
@mixin flex-column {
display: flex;
flex-direction: column;
}
// === TYPOGRAPHY ===
@mixin text-display {
font-family: $font-family-display;
font-weight: $font-weight-bold;
line-height: $line-height-tight;
}
@mixin text-body {
font-family: $font-family-body;
font-weight: $font-weight-normal;
line-height: $line-height-normal;
}
@mixin text-mono {
font-family: $font-family-mono;
letter-spacing: 0.05em;
}
// === VISUAL ===
@mixin gradient-text {
background: $gradient-accent;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@mixin card-base {
background: $color-card;
border-radius: $radius-lg;
box-shadow: $shadow-base;
}
// === INTERACTION ===
@mixin press-effect {
transition: transform $transition-fast, opacity $transition-fast;
&:active {
transform: scale(0.98);
opacity: 0.8;
}
}
+143
View File
@@ -0,0 +1,143 @@
// ============================================
// Design Tokens — Minimalist Modern
// ============================================
// === COLORS ===
// Brand accent — Electric Blue
$color-accent: #0052FF;
$color-accent-secondary: #4D7CFF;
$color-accent-foreground: #FFFFFF;
// Semantic
$color-success: #10B981;
$color-warning: #F59E0B;
$color-error: #EF4444;
// Neutrals
$color-background: #FAFAFA;
$color-foreground: #0F172A;
$color-muted: #F1F5F9;
$color-muted-foreground: #64748B;
$color-border: #E2E8F0;
$color-card: #FFFFFF;
$color-ring: #0052FF;
// Gradients
$gradient-accent: linear-gradient(135deg, #{$color-accent}, #{$color-accent-secondary});
// === TYPOGRAPHY ===
$font-family-display: 'Noto Serif SC', 'STSong', 'SimSun', serif;
$font-family-body: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'HarmonyOS Sans SC', 'Microsoft YaHei', sans-serif;
$font-family-mono: 'Menlo', 'Monaco', 'Courier New', monospace;
$font-size-xs: 20rpx;
$font-size-sm: 24rpx;
$font-size-base: 28rpx;
$font-size-lg: 32rpx;
$font-size-xl: 36rpx;
$font-size-2xl: 44rpx;
$font-size-3xl: 56rpx;
$font-size-display: 72rpx;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
$line-height-tight: 1.2;
$line-height-normal: 1.5;
$line-height-relaxed: 1.75;
// === SPACING (rpx) ===
$spacing-0: 0;
$spacing-1: 8rpx;
$spacing-2: 16rpx;
$spacing-3: 24rpx;
$spacing-4: 32rpx;
$spacing-5: 40rpx;
$spacing-6: 48rpx;
$spacing-8: 64rpx;
$spacing-10: 80rpx;
$spacing-12: 96rpx;
$spacing-16: 128rpx;
$spacing-20: 160rpx;
// === BORDER RADIUS ===
$radius-sm: 8rpx;
$radius-base: 16rpx;
$radius-lg: 24rpx;
$radius-xl: 32rpx;
$radius-2xl: 48rpx;
$radius-full: 9999rpx;
// === SHADOWS ===
$shadow-sm: 0 2rpx 8rpx rgba(15, 23, 42, 0.04);
$shadow-base: 0 4rpx 16rpx rgba(15, 23, 42, 0.08);
$shadow-lg: 0 8rpx 32rpx rgba(15, 23, 42, 0.12);
$shadow-xl: 0 16rpx 48rpx rgba(15, 23, 42, 0.16);
$shadow-accent: 0 8rpx 24rpx rgba(0, 82, 255, 0.25);
// === TRANSITIONS ===
$transition-fast: 150ms ease;
$transition-base: 250ms ease;
$transition-slow: 400ms ease;
// === Z-INDEX ===
$z-base: 0;
$z-dropdown: 100;
$z-sticky: 200;
$z-modal: 300;
$z-toast: 400;
// === BORDER ===
$border-width: 1rpx;
// ============================================
// Legacy $uni-* compatibility (for uni-ui)
// ============================================
$uni-color-primary: $color-accent;
$uni-color-success: $color-success;
$uni-color-warning: $color-warning;
$uni-color-error: $color-error;
$uni-text-color: $color-foreground;
$uni-text-color-inverse: #FFFFFF;
$uni-text-color-grey: $color-muted-foreground;
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
$uni-bg-color: $color-background;
$uni-bg-color-grey: $color-muted;
$uni-bg-color-hover: #f1f1f1;
$uni-bg-color-mask: rgba(0, 0, 0, 0.4);
$uni-border-color: $color-border;
$uni-font-size-sm: $font-size-sm;
$uni-font-size-base: $font-size-base;
$uni-font-size-lg: $font-size-lg;
$uni-font-size-title: 40rpx;
$uni-font-size-subtitle: $font-size-xl;
$uni-font-size-paragraph: 30rpx;
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
$uni-border-radius-sm: $radius-sm;
$uni-border-radius-base: $radius-base;
$uni-border-radius-lg: $radius-lg;
$uni-border-radius-circle: 50%;
$uni-spacing-row-sm: 10rpx;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
$uni-spacing-col-sm: 8rpx;
$uni-spacing-col-base: 16rpx;
$uni-spacing-col-lg: 24rpx;
$uni-opacity-disabled: 0.3;
$uni-color-title: $color-foreground;
$uni-color-subtitle: $color-muted-foreground;
$uni-color-paragraph: #3f536e;
+145
View File
@@ -0,0 +1,145 @@
// ============================================
// Utility Classes
// ============================================
// === TEXT SIZE ===
.text-xs { font-size: $font-size-xs; }
.text-sm { font-size: $font-size-sm; }
.text-base { font-size: $font-size-base; }
.text-lg { font-size: $font-size-lg; }
.text-xl { font-size: $font-size-xl; }
.text-2xl { font-size: $font-size-2xl; }
.text-3xl { font-size: $font-size-3xl; }
.text-display { font-size: $font-size-display; }
// === FONT WEIGHT ===
.font-normal { font-weight: $font-weight-normal; }
.font-medium { font-weight: $font-weight-medium; }
.font-semibold { font-weight: $font-weight-semibold; }
.font-bold { font-weight: $font-weight-bold; }
// === TEXT COLOR ===
.text-primary { color: $color-foreground; }
.text-secondary { color: $color-muted-foreground; }
.text-accent { color: $color-accent; }
.text-success { color: $color-success; }
.text-error { color: $color-error; }
.text-white { color: #FFFFFF; }
.text-gradient {
background: $gradient-accent;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
// === FONT FAMILY ===
.font-display { font-family: $font-family-display; }
.font-body { font-family: $font-family-body; }
.font-mono { font-family: $font-family-mono; }
// === SPACING ===
$spacing-map: (
'0': $spacing-0,
'1': $spacing-1,
'2': $spacing-2,
'3': $spacing-3,
'4': $spacing-4,
'5': $spacing-5,
'6': $spacing-6,
'8': $spacing-8,
'10': $spacing-10,
);
@each $key, $val in $spacing-map {
.m-#{$key} { margin: $val; }
.mt-#{$key} { margin-top: $val; }
.mb-#{$key} { margin-bottom: $val; }
.ml-#{$key} { margin-left: $val; }
.mr-#{$key} { margin-right: $val; }
.mx-#{$key} { margin-left: $val; margin-right: $val; }
.my-#{$key} { margin-top: $val; margin-bottom: $val; }
.p-#{$key} { padding: $val; }
.pt-#{$key} { padding-top: $val; }
.pb-#{$key} { padding-bottom: $val; }
.pl-#{$key} { padding-left: $val; }
.pr-#{$key} { padding-right: $val; }
.px-#{$key} { padding-left: $val; padding-right: $val; }
.py-#{$key} { padding-top: $val; padding-bottom: $val; }
}
// === FLEX ===
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-row { flex-direction: row; }
.flex-wrap { flex-wrap: wrap; }
.flex-1 { flex: 1; }
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.gap-1 { gap: $spacing-1; }
.gap-2 { gap: $spacing-2; }
.gap-3 { gap: $spacing-3; }
.gap-4 { gap: $spacing-4; }
.gap-5 { gap: $spacing-5; }
// === BACKGROUND ===
.bg-page { background-color: $color-background; }
.bg-card { background-color: $color-card; }
.bg-muted { background-color: $color-muted; }
.bg-accent { background-color: $color-accent; }
.bg-foreground { background-color: $color-foreground; }
.bg-gradient-accent { background: $gradient-accent; }
// === BORDER RADIUS ===
.rounded-sm { border-radius: $radius-sm; }
.rounded { border-radius: $radius-base; }
.rounded-lg { border-radius: $radius-lg; }
.rounded-xl { border-radius: $radius-xl; }
.rounded-2xl { border-radius: $radius-2xl; }
.rounded-full { border-radius: $radius-full; }
// === SHADOW ===
.shadow-sm { box-shadow: $shadow-sm; }
.shadow { box-shadow: $shadow-base; }
.shadow-lg { box-shadow: $shadow-lg; }
.shadow-xl { box-shadow: $shadow-xl; }
// === INTERACTION ===
.press-effect {
transition: transform $transition-fast, opacity $transition-fast;
&:active {
transform: scale(0.98);
opacity: 0.8;
}
}
// === DISPLAY ===
.w-full { width: 100%; }
.h-full { height: 100%; }
.overflow-hidden { overflow: hidden; }
.relative { position: relative; }
.absolute { position: absolute; }
+38
View File
@@ -0,0 +1,38 @@
// ============================================
// Design System — Main Entry
// ============================================
// Imported globally via App.vue <style lang="scss">
// Token variables are separately injected via uni.scss
@import 'tokens';
@import 'mixins';
@import 'animations';
@import 'utilities';
// === GLOBAL BASE ===
page {
background-color: $color-background;
color: $color-foreground;
font-family: $font-family-body;
font-size: $font-size-base;
line-height: $line-height-normal;
}
view, text, scroll-view {
box-sizing: border-box;
}
// === TYPOGRAPHY CLASSES ===
.heading-display {
@include text-display;
}
.body-text {
@include text-body;
}
.mono-text {
@include text-mono;
}
+172 -67
View File
@@ -1,76 +1,181 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
* uni-app 全局 SCSS 变量
* uni-app 会自动将此文件内容注入到每个组件的 <style> 作用域
* 因此这里只放变量定义,不用 @import(路径在 node_modules 组件中无法解析)
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
// === COLORS — uv-ui Design System ===
// https://www.uvui.cn/components/color.html
/* 颜色变量 */
// Primary
$uv-primary: #3c9cff;
$uv-primary-dark: #398ade;
$uv-primary-disabled: #9acafc;
$uv-primary-light: #ecf5ff;
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
// Error
$uv-error: #f56c6c;
$uv-error-dark: #e45656;
$uv-error-disabled: #f7b2b2;
$uv-error-light: #fef0f0;
/* 文字基本颜色 */
$uni-text-color: #333; // 基本色
$uni-text-color-inverse: #fff; // 反色
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
// Warning
$uv-warning: #f9ae3d;
$uv-warning-dark: #f1a532;
$uv-warning-disabled: #f9d39b;
$uv-warning-light: #fdf6ec;
// Success
$uv-success: #5ac725;
$uv-success-dark: #53c21d;
$uv-success-disabled: #a9e08f;
$uv-success-light: #f5fff0;
// Info
$uv-info: #909399;
$uv-info-dark: #767a82;
$uv-info-disabled: #c4c6c9;
$uv-info-light: #f4f4f5;
// Aliases (backward compat)
$color-accent: $uv-primary;
$color-accent-secondary: $uv-primary-dark;
$color-accent-foreground: #FFFFFF;
$color-success: $uv-success;
$color-warning: $uv-warning;
$color-error: $uv-error;
// Typography & Neutrals
$uv-main-color: #303133;
$uv-content-color: #606266;
$uv-tips-color: #909193;
$uv-light-color: #c0c4cc;
$uv-disabled-color: #c8c9cc;
$uv-bg-color: #f3f4f6;
$uv-border-color: #dadbde;
// Aliases (backward compat)
$color-background: $uv-bg-color;
$color-foreground: $uv-main-color;
$color-muted: $uv-info-light;
$color-muted-foreground: $uv-content-color;
$color-border: $uv-border-color;
$color-card: #FFFFFF;
$color-ring: $uv-primary;
// Gradients
$gradient-accent: linear-gradient(135deg, #{$uv-primary}, #{$uv-primary-dark});
// === TYPOGRAPHY ===
$font-family-display: 'Noto Serif SC', 'STSong', 'SimSun', serif;
$font-family-body: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'HarmonyOS Sans SC', 'Microsoft YaHei', sans-serif;
$font-family-mono: 'Menlo', 'Monaco', 'Courier New', monospace;
$font-size-xs: 20rpx;
$font-size-sm: 24rpx;
$font-size-base: 28rpx;
$font-size-lg: 32rpx;
$font-size-xl: 36rpx;
$font-size-2xl: 44rpx;
$font-size-3xl: 56rpx;
$font-size-display: 72rpx;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
$line-height-tight: 1.2;
$line-height-normal: 1.5;
$line-height-relaxed: 1.75;
// === SPACING (rpx) ===
$spacing-0: 0;
$spacing-1: 8rpx;
$spacing-2: 16rpx;
$spacing-3: 24rpx;
$spacing-4: 32rpx;
$spacing-5: 40rpx;
$spacing-6: 48rpx;
$spacing-8: 64rpx;
$spacing-10: 80rpx;
$spacing-12: 96rpx;
$spacing-16: 128rpx;
$spacing-20: 160rpx;
// === BORDER RADIUS ===
$radius-sm: 8rpx;
$radius-base: 16rpx;
$radius-lg: 24rpx;
$radius-xl: 32rpx;
$radius-2xl: 48rpx;
$radius-full: 9999rpx;
// === SHADOWS ===
$shadow-sm: 0 2rpx 8rpx rgba(15, 23, 42, 0.04);
$shadow-base: 0 4rpx 16rpx rgba(15, 23, 42, 0.08);
$shadow-lg: 0 8rpx 32rpx rgba(15, 23, 42, 0.12);
$shadow-xl: 0 16rpx 48rpx rgba(15, 23, 42, 0.16);
$shadow-accent: 0 8rpx 24rpx rgba(60, 156, 255, 0.25);
// === TRANSITIONS ===
$transition-fast: 150ms ease;
$transition-base: 250ms ease;
$transition-slow: 400ms ease;
// === Z-INDEX ===
$z-base: 0;
$z-dropdown: 100;
$z-sticky: 200;
$z-modal: 300;
$z-toast: 400;
// === BORDER ===
$border-width: 1rpx;
// === Legacy $uni-* compatibility (for uni-ui) ===
$uni-color-primary: $color-accent;
$uni-color-success: $color-success;
$uni-color-warning: $color-warning;
$uni-color-error: $color-error;
$uni-text-color: $color-foreground;
$uni-text-color-inverse: #FFFFFF;
$uni-text-color-grey: $color-muted-foreground;
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #fff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 12px;
$uni-font-size-base: 14px;
$uni-font-size-lg: 16;
/* 图片尺寸 */
$uni-img-size-sm: 20px;
$uni-img-size-base: 26px;
$uni-img-size-lg: 40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-bg-color: $color-background;
$uni-bg-color-grey: $color-muted;
$uni-bg-color-hover: #f1f1f1;
$uni-bg-color-mask: rgba(0, 0, 0, 0.4);
$uni-border-color: $color-border;
$uni-font-size-sm: $font-size-sm;
$uni-font-size-base: $font-size-base;
$uni-font-size-lg: $font-size-lg;
$uni-font-size-title: 40rpx;
$uni-font-size-subtitle: $font-size-xl;
$uni-font-size-paragraph: 30rpx;
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
$uni-border-radius-sm: $radius-sm;
$uni-border-radius-base: $radius-base;
$uni-border-radius-lg: $radius-lg;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 20px;
$uni-color-subtitle: #555; // 二级标题颜色
$uni-font-size-subtitle: 18px;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 15px;
$uni-spacing-row-sm: 10rpx;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
$uni-spacing-col-sm: 8rpx;
$uni-spacing-col-base: 16rpx;
$uni-spacing-col-lg: 24rpx;
$uni-opacity-disabled: 0.3;
$uni-color-title: $color-foreground;
$uni-color-subtitle: $color-muted-foreground;
$uni-color-paragraph: #3f536e;