Files

576 lines
16 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="about-page theme-transition" :style="{ background: themeConfig.bgColor }">
<!-- 顶部品牌区 -->
<view class="header-card" :style="{ background: headerGradient }">
<view class="header-decoration">
<view class="decoration-circle c1"></view>
<view class="decoration-circle c2"></view>
<view class="decoration-circle c3"></view>
<view class="decoration-circle c4"></view>
</view>
<view class="header-content">
<view class="logo-wrapper">
<view class="logo-box" v-if="!logoLoaded" :style="{ background: 'rgba(255,255,255,0.95)' }">
<text class="logo-text-fallback" :style="{ color: themeConfig.primaryColor }">M</text>
</view>
<image
class="app-logo"
:src="logoConfig.path"
mode="aspectFit"
@load="logoLoaded = true"
@error="logoLoaded = false"
v-show="logoLoaded"
/>
</view>
<text class="app-name">{{ logoConfig.name }}</text>
<text class="app-subtitle">{{ logoConfig.subtitle }}</text>
<view class="version-tag">
<text class="version-text">{{ logoConfig.version }}</text>
</view>
</view>
</view>
<view class="content-area">
<!-- 联系与支持 -->
<view class="info-card theme-transition" :style="{ background: themeConfig.cardBgColor }">
<view class="card-header">
<view class="card-icon" :style="{ background: themeConfig.primaryColor + '18' }">
<FaIcon icon="headphones" :size="16" :color="themeConfig.primaryColor" />
</view>
<text class="card-title" :style="{ color: themeConfig.textPrimary }">联系与支持</text>
</view>
<view class="info-list">
<view class="info-row">
<view class="info-icon-box" :style="{ background: themeConfig.primaryColor + '12' }">
<FaIcon icon="mobile" :size="16" :color="themeConfig.primaryColor" />
</view>
<view class="info-body">
<text class="info-label" :style="{ color: themeConfig.textSecondary }">版本号</text>
<text class="info-value" :style="{ color: themeConfig.textPrimary }">{{ logoConfig.version }}</text>
</view>
</view>
<view class="info-row divider" @click="copyEmail">
<view class="info-icon-box" :style="{ background: (themeConfig.warningColor || '#FFAA33') + '15' }">
<FaIcon icon="envelope" :size="16" :color="themeConfig.warningColor || '#FFAA33'" />
</view>
<view class="info-body">
<text class="info-label" :style="{ color: themeConfig.textSecondary }">反馈邮箱</text>
<text class="info-value" :style="{ color: themeConfig.textPrimary }">{{ logoConfig.email }}</text>
</view>
<view class="copy-btn" :style="{ color: themeConfig.primaryColor, borderColor: themeConfig.primaryColor + '40', background: themeConfig.primaryColor + '10' }">
<FaIcon icon="copy" :size="12" :color="themeConfig.primaryColor" />
<text class="copy-btn-text" :style="{ color: themeConfig.primaryColor }">复制</text>
</view>
</view>
<view class="info-row" @click="copyWechat">
<view class="info-icon-box" :style="{ background: (themeConfig.successColor || '#22C55E') + '15' }">
<FaIcon icon="comment" :size="16" :color="themeConfig.successColor || '#22C55E'" />
</view>
<view class="info-body">
<text class="info-label" :style="{ color: themeConfig.textSecondary }">微信公众号</text>
<text class="info-value" :style="{ color: themeConfig.textPrimary }">{{ logoConfig.wechat.name }}</text>
</view>
<view class="copy-btn" :style="{ color: themeConfig.primaryColor, borderColor: themeConfig.primaryColor + '40', background: themeConfig.primaryColor + '10' }">
<FaIcon icon="copy" :size="12" :color="themeConfig.primaryColor" />
<text class="copy-btn-text" :style="{ color: themeConfig.primaryColor }">复制</text>
</view>
</view>
</view>
</view>
<!-- 法律与协议 -->
<view class="menu-card theme-transition" :style="{ background: themeConfig.cardBgColor }">
<view class="menu-item" @click="gotoUserAgreement">
<view class="menu-icon-box" :style="{ background: themeConfig.primaryColor + '15' }">
<FaIcon icon="file-lines" :size="16" :color="themeConfig.primaryColor" />
</view>
<text class="menu-text" :style="{ color: themeConfig.textPrimary }">用户协议</text>
<FaIcon icon="chevron-right" :size="18" :color="themeConfig.textDisabled" />
</view>
<view class="menu-item" @click="gotoPrivacyPolicy">
<view class="menu-icon-box" :style="{ background: themeConfig.secondaryColor + '15' }">
<FaIcon icon="shield-halved" :size="16" :color="themeConfig.secondaryColor" />
</view>
<text class="menu-text" :style="{ color: themeConfig.textPrimary }">隐私政策</text>
<FaIcon icon="chevron-right" :size="18" :color="themeConfig.textDisabled" />
</view>
<view class="menu-item logout-item" @click="handleLogout">
<view class="menu-icon-box" :style="{ background: (themeConfig.dangerColor || '#F53F3F') + '15' }">
<FaIcon icon="right-from-bracket" :size="16" :color="themeConfig.dangerColor || '#F53F3F'" />
</view>
<text class="menu-text" :style="{ color: themeConfig.dangerColor || '#F53F3F' }">退出登录</text>
<FaIcon icon="chevron-right" :size="18" :color="themeConfig.textDisabled" />
</view>
</view>
<!-- 品牌理念 -->
<view class="brand-card theme-transition" :style="{ background: themeConfig.cardBgColor }">
<view class="brand-inner" :style="{ background: brandBgGradient }">
<view class="brand-icon-wrap" :style="{ background: themeConfig.primaryColor + '20' }">
<FaIcon icon="sparkles" :size="22" :color="themeConfig.primaryColor" />
</view>
<text class="brand-slogan" :style="{ color: themeConfig.textPrimary }">{{ logoConfig.slogan }}</text>
<text class="brand-tagline" :style="{ color: themeConfig.primaryColor }">用心记录每一刻</text>
</view>
</view>
</view>
<view class="footer-area">
<text class="copyright" :style="{ color: themeConfig.textSecondary }">{{ logoConfig.copyright }}</text>
</view>
</view>
</template>
<script>
import { getThemeConfigFromStorage, THEME_PRESETS } from '@/utils/theme'
import { refreshNavBarTheme } from '@/utils/themeGlobal'
import { LOGO_CONFIG } from '@/utils/constants'
import { logout as logoutApi } from '@/api'
import { logout as authLogout } from '@/utils/auth'
import FaIcon from '@/components/FaIcon.vue'
export default {
components: {
FaIcon
},
data() {
return {
themeConfig: getThemeConfigFromStorage() || THEME_PRESETS['default'],
logoConfig: LOGO_CONFIG,
logoLoaded: false
}
},
computed: {
headerGradient() {
const c = this.themeConfig.primaryColor || '#9B8AFB'
const c2 = this.themeConfig.secondaryColor || this.lightenColor(c, -15)
return `linear-gradient(135deg, ${c} 0%, ${c2} 60%, ${this.lightenColor(c, -25)} 100%)`
},
brandBgGradient() {
const c = this.themeConfig.primaryColor || '#9B8AFB'
return `linear-gradient(135deg, ${c}12 0%, ${c}08 100%)`
}
},
onLoad() {
refreshNavBarTheme()
this.themeChangeHandler = (themeData) => {
let newTheme = null
if (themeData?.key && THEME_PRESETS[themeData.key]) {
newTheme = THEME_PRESETS[themeData.key]
} else if (themeData?.config) {
newTheme = themeData.config
}
if (newTheme) {
this.themeConfig = newTheme
refreshNavBarTheme()
}
}
uni.$on('themeChanged', this.themeChangeHandler)
},
onShow() {
const latestTheme = getThemeConfigFromStorage() || THEME_PRESETS['default']
if (JSON.stringify(latestTheme) !== JSON.stringify(this.themeConfig)) {
this.themeConfig = latestTheme
}
refreshNavBarTheme()
},
onUnload() {
uni.$off('themeChanged', this.themeChangeHandler)
},
methods: {
lightenColor(hex, percent) {
// 简单变暗:hex 形如 #RRGGBBpercent 负数表示变暗
const num = parseInt(hex.replace('#', ''), 16)
const r = Math.max(0, Math.min(255, (num >> 16) + Math.round(255 * percent / 100)))
const g = Math.max(0, Math.min(255, ((num >> 8) & 0xff) + Math.round(255 * percent / 100)))
const b = Math.max(0, Math.min(255, (num & 0xff) + Math.round(255 * percent / 100)))
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
},
gotoUserAgreement() {
uni.navigateTo({ url: '/pages/userAgreement/userAgreement' })
},
gotoPrivacyPolicy() {
uni.navigateTo({ url: '/pages/privacyPolicy/privacyPolicy' })
},
copyEmail() {
uni.vibrateShort && uni.vibrateShort({ type: 'light' })
uni.setClipboardData({
data: this.logoConfig.email,
success: () => {
uni.showToast({ title: '邮箱已复制', icon: 'success' })
}
})
},
copyWechat() {
uni.vibrateShort && uni.vibrateShort({ type: 'light' })
uni.setClipboardData({
data: this.logoConfig.wechat.account,
success: () => {
uni.showToast({ title: '公众号已复制', icon: 'success' })
}
})
},
async handleLogout() {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
confirmColor: this.themeConfig.dangerColor || '#F53F3F',
success: async (res) => {
if (res.confirm) {
try {
await logoutApi()
} catch (e) {
console.error('退出登录失败:', e)
} finally {
authLogout()
}
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
@import '@/uni.scss';
.about-page {
min-height: 100vh;
padding: 20rpx 24rpx;
box-sizing: border-box;
}
.theme-transition {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
.header-card {
border-radius: 28rpx;
padding: 72rpx 32rpx 60rpx;
position: relative;
overflow: hidden;
margin-bottom: 24rpx;
box-shadow: 0 12rpx 40rpx rgba(155, 138, 251, 0.28);
}
.header-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.decoration-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
}
.decoration-circle.c1 {
width: 220rpx;
height: 220rpx;
top: -80rpx;
right: -60rpx;
}
.decoration-circle.c2 {
width: 120rpx;
height: 120rpx;
bottom: -20rpx;
left: -30rpx;
background: rgba(255, 255, 255, 0.08);
}
.decoration-circle.c3 {
width: 70rpx;
height: 70rpx;
top: 50%;
right: 20%;
background: rgba(255, 255, 255, 0.1);
}
.decoration-circle.c4 {
width: 40rpx;
height: 40rpx;
top: 20%;
left: 15%;
background: rgba(255, 255, 255, 0.08);
}
.header-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.logo-wrapper {
margin-bottom: 24rpx;
position: relative;
width: 128rpx;
height: 128rpx;
display: flex;
align-items: center;
justify-content: center;
}
.logo-box {
position: absolute;
width: 128rpx;
height: 128rpx;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10rpx 32rpx rgba(0, 0, 0, 0.18);
}
.logo-text-fallback {
font-size: 60rpx;
font-weight: 800;
letter-spacing: -2rpx;
}
.app-logo {
position: absolute;
width: 128rpx;
height: 128rpx;
border-radius: 32rpx;
box-shadow: 0 10rpx 32rpx rgba(0, 0, 0, 0.18);
}
.app-name {
font-size: 42rpx;
font-weight: 700;
color: #fff;
margin-bottom: 8rpx;
letter-spacing: 1.5rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.18);
}
.app-subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.88);
letter-spacing: 1rpx;
margin-bottom: 20rpx;
}
.version-tag {
padding: 6rpx 18rpx;
background: rgba(255, 255, 255, 0.22);
border-radius: 24rpx;
backdrop-filter: blur(8rpx);
-webkit-backdrop-filter: blur(8rpx);
}
.version-text {
font-size: 22rpx;
color: #fff;
letter-spacing: 1rpx;
}
.content-area {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.info-card,
.menu-card,
.brand-card {
border-radius: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.info-card { padding: 28rpx; }
.menu-card { overflow: hidden; }
.brand-card { padding: 12rpx; }
.card-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 24rpx;
}
.card-icon {
width: 48rpx;
height: 48rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
letter-spacing: 0.5rpx;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
align-items: center;
padding: 22rpx 0;
transition: all 0.2s ease;
}
.info-row.divider {
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
margin: 4rpx -28rpx;
padding-left: 28rpx;
padding-right: 28rpx;
}
.info-row:active {
background: rgba(155, 138, 251, 0.05);
}
.info-icon-box {
width: 56rpx;
height: 56rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 18rpx;
flex-shrink: 0;
}
.info-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
min-width: 0;
}
.info-label {
font-size: 24rpx;
opacity: 0.55;
letter-spacing: 0.5rpx;
}
.info-value {
font-size: 28rpx;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.copy-btn {
display: flex;
align-items: center;
gap: 4rpx;
padding: 8rpx 16rpx;
border: 1rpx solid;
border-radius: 24rpx;
font-size: 22rpx;
font-weight: 500;
flex-shrink: 0;
transition: all 0.2s ease;
}
.copy-btn:active {
transform: scale(0.94);
opacity: 0.8;
}
.copy-btn-text {
font-size: 22rpx;
font-weight: 500;
letter-spacing: 0.5rpx;
}
.menu-item {
display: flex;
align-items: center;
padding: 28rpx 28rpx;
transition: all 0.2s ease;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
}
.menu-item:last-child { border-bottom: none; }
.menu-item:active { background: rgba(155, 138, 251, 0.06); }
.menu-icon-box {
width: 56rpx;
height: 56rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 18rpx;
flex-shrink: 0;
}
.menu-text {
flex: 1;
font-size: 28rpx;
letter-spacing: 0.5rpx;
}
.brand-inner {
border-radius: 16rpx;
padding: 36rpx 28rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.brand-icon-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
animation: floatBrand 3s ease-in-out infinite;
}
@keyframes floatBrand {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-4rpx) scale(1.04); }
}
.brand-slogan {
font-size: 26rpx;
font-weight: 500;
letter-spacing: 0.5rpx;
text-align: center;
line-height: 1.7;
white-space: pre-line;
}
.brand-tagline {
font-size: 22rpx;
font-weight: 600;
letter-spacing: 2rpx;
padding: 6rpx 18rpx;
background: rgba(255, 255, 255, 0.6);
border-radius: 16rpx;
}
.footer-area {
padding: 36rpx 24rpx 60rpx;
display: flex;
justify-content: center;
}
.copyright {
font-size: 22rpx;
opacity: 0.45;
letter-spacing: 0.5rpx;
}
</style>