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>
@@ -20,3 +20,7 @@ dist
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# Dev artifacts
|
||||
.playwright-mcp/
|
||||
*.png
|
||||
!src/static/**/*.png
|
||||
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
After Width: | Height: | Size: 437 B |
|
After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 498 B |
|
Before Width: | Height: | Size: 67 B |
|
Before Width: | Height: | Size: 67 B |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||