- 新增 configStore 管理品牌文案(appName, appSlogan, appDescription) - 关于弹窗、分享标题使用 configStore 读取品牌名 - 所有数据页面添加下拉刷新功能 - 所有数据页面 onShow 时自动刷新数据
735 lines
19 KiB
Vue
735 lines
19 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="header-fixed">
|
||
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||
<text class="page-title">我的</text>
|
||
</view>
|
||
|
||
<view class="profile-card" @tap="goProfileEdit">
|
||
<view class="p-avatar">
|
||
<image v-if="userStore.avatarUrl" :src="userStore.avatarUrl" class="p-avatar-img" mode="aspectFill" />
|
||
<Icon v-else name="user" :size="64" color="#FFFFFF" />
|
||
</view>
|
||
<view class="p-info">
|
||
<text class="p-name">{{ userStore.nickname }}</text>
|
||
<text class="p-tagline">{{ userStore.slogan }}</text>
|
||
<text class="p-streak">累计 {{ txStore.total }} 笔记录</text>
|
||
</view>
|
||
<Icon name="chevronRight" :size="28" color="rgba(255,255,255,0.6)" />
|
||
</view>
|
||
|
||
<!-- 身份切换 -->
|
||
<view class="identity-card" @tap="showIdentityPicker">
|
||
<Icon name="user" :size="32" color="#FF8C69" />
|
||
<view class="id-info">
|
||
<text class="id-name">{{ groupStore.isGroupMode ? groupStore.currentGroup?.name : '个人账本' }}</text>
|
||
<text class="id-desc">{{ groupStore.isGroupMode ? groupStore.currentGroup?.member_count + ' 人共享' : '仅自己可见' }}</text>
|
||
</view>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
|
||
<view class="quick-stats">
|
||
<view class="qs-card">
|
||
<text class="qs-label">本月支出</text>
|
||
<text class="qs-value" v-if="!loading">{{ formatAmount(overview.expense) }}</text>
|
||
<view class="qs-skeleton" v-else></view>
|
||
</view>
|
||
<view class="qs-card">
|
||
<text class="qs-label">本月收入</text>
|
||
<text class="qs-value income" v-if="!loading">{{ formatAmount(overview.income) }}</text>
|
||
<view class="qs-skeleton" v-else></view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="menu-card">
|
||
<view class="menu-item" @tap="goBudget">
|
||
<text class="mi-label">预算设置</text>
|
||
<text class="mi-extra" v-if="budget">{{ formatAmount(budget.amount) }}</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
<view class="menu-item" @tap="goCategoryManage">
|
||
<text class="mi-label">分类管理</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
<view class="menu-item" @tap="exportData">
|
||
<text class="mi-label">数据导出</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="menu-card mt">
|
||
<view class="menu-item" @tap="goFeedback">
|
||
<text class="mi-label">意见反馈</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
<view class="menu-item" @tap="showAbout = true">
|
||
<text class="mi-label">关于小菜</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
<view class="menu-item" @tap="goPrivacy">
|
||
<text class="mi-label">隐私政策</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 管理员入口 -->
|
||
<view v-if="userStore.userInfo?.role === 'admin'" class="menu-card mt">
|
||
<view class="menu-item admin-entry" @tap="goAdmin">
|
||
<Icon name="settings" :size="32" color="#FF8C69" />
|
||
<text class="mi-label">管理后台</text>
|
||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 关于弹窗 -->
|
||
<view class="modal-mask" v-if="showAbout" @tap="showAbout = false">
|
||
<view class="modal" @tap.stop>
|
||
<text class="about-title">{{ configStore.appName }}</text>
|
||
<text class="about-version">v{{ configStore.config.app_version || '1.0.0' }}</text>
|
||
<text class="about-desc">{{ configStore.appSlogan }}</text>
|
||
<text class="about-desc">{{ configStore.appDescription }}</text>
|
||
<view class="modal-btns modal-btns-top">
|
||
<view class="modal-btn confirm" @tap="showAbout = false">知道了</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 身份选择弹窗 -->
|
||
<view class="modal-mask" v-if="showIdentity" @tap="showIdentity = false">
|
||
<view class="identity-modal" @tap.stop>
|
||
<text class="modal-title">切换账本</text>
|
||
|
||
<view class="id-option" :class="{ active: !groupStore.isGroupMode }" @tap="switchToPersonal">
|
||
<Icon name="user" :size="36" :color="!groupStore.isGroupMode ? '#FF8C69' : '#8B7E7E'" />
|
||
<text class="id-option-name">个人账本</text>
|
||
<Icon v-if="!groupStore.isGroupMode" name="check" :size="28" color="#FF8C69" />
|
||
</view>
|
||
|
||
<view v-for="g in groupStore.groups" :key="g.id"
|
||
class="id-option" :class="{ active: groupStore.currentGroupId === g.id }"
|
||
@tap="switchToGroup(g.id)">
|
||
<Icon name="user" :size="36" :color="groupStore.currentGroupId === g.id ? '#FF8C69' : '#8B7E7E'" />
|
||
<view class="id-option-info">
|
||
<text class="id-option-name">{{ g.name }}</text>
|
||
<text class="id-option-desc">{{ g.member_count }} 人</text>
|
||
</view>
|
||
<Icon v-if="groupStore.currentGroupId === g.id" name="check" :size="28" color="#FF8C69" />
|
||
</view>
|
||
|
||
<view class="id-actions">
|
||
<view class="id-action-btn" @tap="promptCreateGroup">
|
||
<text class="id-action-text">创建群组</text>
|
||
</view>
|
||
<view class="id-action-btn" @tap="promptJoinGroup">
|
||
<text class="id-action-text">加入群组</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="id-manage" @tap="goGroupManage">
|
||
<text class="id-manage-text">管理群组</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
||
import { useTransactionStore } from '@/stores/transaction'
|
||
import { useStatsStore } from '@/stores/stats'
|
||
import { useBudgetStore } from '@/stores/budget'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { useGroupStore } from '@/stores/group'
|
||
import { useConfigStore } from '@/stores/config'
|
||
import { waitForReady } from '@/utils/app-ready'
|
||
import { getTransactions } from '@/api/transaction'
|
||
import { formatAmount } from '@/utils/format'
|
||
import { statusBarHeight } from '@/utils/system'
|
||
import Icon from '@/components/Icon/Icon.vue'
|
||
|
||
const txStore = useTransactionStore()
|
||
const statsStore = useStatsStore()
|
||
const budgetStore = useBudgetStore()
|
||
const userStore = useUserStore()
|
||
const groupStore = useGroupStore()
|
||
const configStore = useConfigStore()
|
||
|
||
const overview = computed(() => statsStore.overview)
|
||
const budget = computed(() => budgetStore.budget)
|
||
const loading = ref(true)
|
||
const showAbout = ref(false)
|
||
const showIdentity = ref(false)
|
||
const initialLoaded = ref(false)
|
||
|
||
onMounted(async () => {
|
||
await waitForReady()
|
||
loading.value = true
|
||
try {
|
||
await Promise.all([
|
||
statsStore.fetchOverview(),
|
||
budgetStore.fetchBudget(),
|
||
txStore.fetchTransactions({ page: 1, pageSize: 1 }),
|
||
userStore.fetchUserInfo(),
|
||
groupStore.fetchGroups()
|
||
])
|
||
} catch (e) {
|
||
console.error('Profile load error:', e)
|
||
} finally {
|
||
loading.value = false
|
||
initialLoaded.value = true
|
||
}
|
||
})
|
||
|
||
// 返回页面时静默刷新
|
||
onShow(async () => {
|
||
if (!initialLoaded.value) return
|
||
try {
|
||
await Promise.all([
|
||
statsStore.fetchOverview(),
|
||
budgetStore.fetchBudget(),
|
||
txStore.fetchTransactions({ page: 1, pageSize: 1 }),
|
||
groupStore.fetchGroups(),
|
||
userStore.fetchUserInfo()
|
||
])
|
||
} catch {}
|
||
})
|
||
|
||
onPullDownRefresh(async () => {
|
||
try {
|
||
await Promise.all([
|
||
statsStore.fetchOverview(),
|
||
budgetStore.fetchBudget(),
|
||
txStore.fetchTransactions({ page: 1, pageSize: 1 }),
|
||
groupStore.fetchGroups(),
|
||
userStore.fetchUserInfo(),
|
||
configStore.fetchConfig()
|
||
])
|
||
} catch {}
|
||
uni.stopPullDownRefresh()
|
||
})
|
||
|
||
function goBudget() {
|
||
uni.navigateTo({ url: '/pages/budget/index' })
|
||
}
|
||
|
||
function goCategoryManage() {
|
||
uni.navigateTo({ url: '/pages/category-manage/index' })
|
||
}
|
||
|
||
function goPrivacy() {
|
||
uni.navigateTo({ url: '/pages/privacy/index' })
|
||
}
|
||
|
||
function goFeedback() {
|
||
uni.navigateTo({ url: '/pages/feedback/index' })
|
||
}
|
||
|
||
function goProfileEdit() {
|
||
uni.navigateTo({ url: '/pages/profile-edit/index' })
|
||
}
|
||
|
||
function showIdentityPicker() {
|
||
showIdentity.value = true
|
||
}
|
||
|
||
function switchToPersonal() {
|
||
groupStore.switchToPersonal()
|
||
showIdentity.value = false
|
||
}
|
||
|
||
function switchToGroup(id: number) {
|
||
groupStore.switchToGroup(id)
|
||
showIdentity.value = false
|
||
}
|
||
|
||
function goGroupManage() {
|
||
showIdentity.value = false
|
||
uni.navigateTo({ url: '/pages/group-manage/index' })
|
||
}
|
||
|
||
function goAdmin() {
|
||
uni.navigateTo({ url: '/pages/admin/index' })
|
||
}
|
||
|
||
function promptCreateGroup() {
|
||
// #ifdef MP-WEIXIN
|
||
// 小程序不支持 prompt,使用输入框页面
|
||
uni.showModal({
|
||
title: '创建群组',
|
||
editable: true,
|
||
placeholderText: '请输入群组名称',
|
||
success: async (res) => {
|
||
if (res.confirm && res.content) {
|
||
try {
|
||
const result = await groupStore.createGroup(res.content.trim())
|
||
uni.setClipboardData({
|
||
data: result.invite_code,
|
||
success: () => uni.showToast({ title: '邀请码已复制', icon: 'success' })
|
||
})
|
||
} catch (e: any) {
|
||
uni.showToast({ title: e.message || '创建失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
// #endif
|
||
// #ifdef H5
|
||
const name = window.prompt('请输入群组名称')
|
||
if (name) {
|
||
groupStore.createGroup(name.trim()).then((result) => {
|
||
navigator.clipboard?.writeText(result.invite_code)
|
||
uni.showToast({ title: `邀请码 ${result.invite_code} 已复制`, icon: 'success' })
|
||
}).catch((e: any) => {
|
||
uni.showToast({ title: e.message || '创建失败', icon: 'none' })
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
function promptJoinGroup() {
|
||
// #ifdef MP-WEIXIN
|
||
uni.showModal({
|
||
title: '加入群组',
|
||
editable: true,
|
||
placeholderText: '请输入邀请码',
|
||
success: async (res) => {
|
||
if (res.confirm && res.content) {
|
||
try {
|
||
const result = await groupStore.joinGroup(res.content.trim())
|
||
uni.showToast({ title: `已加入「${result.name}」`, icon: 'success' })
|
||
} catch (e: any) {
|
||
uni.showToast({ title: e.message || '加入失败', icon: 'none' })
|
||
}
|
||
}
|
||
}
|
||
})
|
||
// #endif
|
||
// #ifdef H5
|
||
const code = window.prompt('请输入邀请码')
|
||
if (code) {
|
||
groupStore.joinGroup(code.trim()).then((result) => {
|
||
uni.showToast({ title: `已加入「${result.name}」`, icon: 'success' })
|
||
}).catch((e: any) => {
|
||
uni.showToast({ title: e.message || '加入失败', icon: 'none' })
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
|
||
function getLocalDateStr(): string {
|
||
const d = new Date()
|
||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||
}
|
||
|
||
async function exportData() {
|
||
uni.showLoading({ title: '导出中 0%' })
|
||
try {
|
||
// 分页获取全部数据(服务端 pageSize 上限 100)
|
||
const allList: any[] = []
|
||
let page = 1
|
||
let total = 0
|
||
do {
|
||
const data = await getTransactions({ page, pageSize: 100, group_id: groupStore.currentGroupId })
|
||
allList.push(...data.list)
|
||
total = data.total
|
||
page++
|
||
// 更新进度
|
||
const progress = Math.min(100, Math.round((allList.length / total) * 100))
|
||
uni.showLoading({ title: `导出中 ${progress}%` })
|
||
} while (allList.length < total)
|
||
|
||
if (allList.length === 0) {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '暂无数据', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
function csvEscape(val: string): string {
|
||
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
||
return '"' + val.replace(/"/g, '""') + '"'
|
||
}
|
||
return val
|
||
}
|
||
const header = '日期,类型,分类,金额(元),备注\n'
|
||
const rows = allList.map(t => {
|
||
const amount = (t.amount / 100).toFixed(2)
|
||
const type = t.type === 'expense' ? '支出' : '收入'
|
||
return [t.date, type, t.category_name || '未分类', amount, t.note || ''].map(csvEscape).join(',')
|
||
}).join('\n')
|
||
|
||
const csv = '' + header + rows
|
||
|
||
// #ifdef H5
|
||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `小菜记账_${getLocalDateStr()}.csv`
|
||
a.click()
|
||
URL.revokeObjectURL(url)
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
const fs = uni.getFileSystemManager()
|
||
const filePath = `${wx.env.USER_DATA_PATH}/小菜记账_${getLocalDateStr()}.csv`
|
||
fs.writeFileSync(filePath, csv, 'utf-8')
|
||
uni.hideLoading()
|
||
uni.openDocument({
|
||
filePath,
|
||
fileType: 'csv',
|
||
success: () => {},
|
||
fail: () => uni.showToast({ title: '导出失败', icon: 'none' })
|
||
})
|
||
// #endif
|
||
} catch {
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '导出失败', icon: 'none' })
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/styles/mixins.scss';
|
||
|
||
.page {
|
||
@include page-base;
|
||
}
|
||
|
||
.header-fixed {
|
||
@include sticky-header;
|
||
}
|
||
|
||
.status-bar {
|
||
@include status-bar;
|
||
}
|
||
|
||
.page-title {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: $font-2xl;
|
||
font-weight: 600;
|
||
color: $text;
|
||
padding: $space-sm 0 $space-md;
|
||
}
|
||
|
||
.profile-card {
|
||
margin: 0 $space-xl;
|
||
padding: $space-2xl;
|
||
background: linear-gradient(135deg, $primary, #FFB89A);
|
||
border-radius: $radius-2xl;
|
||
box-shadow: 8rpx 8rpx 24rpx rgba(45, 27, 27, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-lg;
|
||
|
||
&:active { opacity: 0.85; }
|
||
}
|
||
|
||
.p-avatar {
|
||
width: 128rpx;
|
||
height: 128rpx;
|
||
border-radius: 50%;
|
||
border: 6rpx solid rgba(255, 255, 255, 0.5);
|
||
background: rgba(255, 255, 255, 0.25);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.p-name {
|
||
font-size: $font-3xl;
|
||
font-weight: 600;
|
||
color: $surface;
|
||
display: block;
|
||
}
|
||
|
||
.p-tagline {
|
||
font-size: $font-lg;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
display: block;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.p-streak {
|
||
font-size: $font-md;
|
||
color: rgba(255, 255, 255, 0.65);
|
||
display: block;
|
||
margin-top: $space-sm;
|
||
}
|
||
|
||
.quick-stats {
|
||
display: flex;
|
||
gap: $space-md;
|
||
padding: 0 $space-xl;
|
||
margin-top: $space-lg;
|
||
}
|
||
|
||
.qs-card {
|
||
flex: 1;
|
||
padding: $space-lg $space-md;
|
||
background: $surface;
|
||
border-radius: 32rpx;
|
||
border: 2rpx solid $border;
|
||
box-shadow: $shadow-md;
|
||
text-align: center;
|
||
}
|
||
|
||
.qs-label {
|
||
font-size: $font-md;
|
||
color: $text-sec;
|
||
display: block;
|
||
margin-bottom: $space-xs;
|
||
}
|
||
|
||
.qs-value {
|
||
font-family: 'Fredoka', sans-serif;
|
||
font-size: $font-xl;
|
||
font-weight: 600;
|
||
color: $text;
|
||
|
||
&.income { color: $success; }
|
||
}
|
||
|
||
.qs-skeleton {
|
||
width: 160rpx;
|
||
height: 40rpx;
|
||
@include shimmer;
|
||
border-radius: $radius-xs;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.qs-skeleton {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
.menu-card {
|
||
margin: $space-xl $space-xl 0;
|
||
background: $surface;
|
||
border-radius: $radius-2xl;
|
||
border: 2rpx solid $border;
|
||
box-shadow: $shadow-md;
|
||
overflow: hidden;
|
||
|
||
&.mt { margin-top: $space-lg; }
|
||
}
|
||
|
||
.menu-item {
|
||
height: 112rpx;
|
||
padding: 0 $space-xl;
|
||
display: flex;
|
||
align-items: center;
|
||
border-bottom: 1rpx solid $border;
|
||
|
||
&:last-child { border-bottom: none; }
|
||
&:active { background: $bg; }
|
||
}
|
||
|
||
.mi-label { flex: 1; font-size: $font-lg; font-weight: 500; color: $text; }
|
||
.mi-extra { font-size: $font-lg; color: $primary; font-weight: 600; margin-right: $space-sm; }
|
||
|
||
.admin-entry {
|
||
gap: $space-sm;
|
||
.mi-label { color: $primary; font-weight: 600; }
|
||
}
|
||
|
||
.about-title {
|
||
font-size: $font-3xl;
|
||
font-weight: 600;
|
||
color: $primary;
|
||
display: block;
|
||
text-align: center;
|
||
}
|
||
|
||
.about-version {
|
||
font-size: $font-md;
|
||
color: $text-muted;
|
||
display: block;
|
||
text-align: center;
|
||
margin: $space-xs 0 $space-md;
|
||
}
|
||
|
||
.about-desc {
|
||
font-size: $font-lg;
|
||
color: $text-sec;
|
||
display: block;
|
||
text-align: center;
|
||
margin-bottom: $space-xs;
|
||
}
|
||
|
||
.modal-mask {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999;
|
||
}
|
||
|
||
.modal {
|
||
width: 600rpx;
|
||
background: $surface;
|
||
border-radius: $space-lg;
|
||
padding: $space-2xl;
|
||
}
|
||
|
||
.modal-btns {
|
||
display: flex;
|
||
gap: $space-md;
|
||
}
|
||
|
||
.modal-btns-top {
|
||
margin-top: $space-xl;
|
||
}
|
||
|
||
.modal-btn {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
border-radius: $space-md;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: $font-lg;
|
||
font-weight: 600;
|
||
|
||
&.confirm {
|
||
background: linear-gradient(135deg, $primary, #E67355);
|
||
color: $surface;
|
||
}
|
||
}
|
||
|
||
.p-avatar-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.identity-card {
|
||
margin: $space-md $space-xl 0;
|
||
padding: $font-lg $space-lg;
|
||
background: $surface;
|
||
border-radius: $radius-xl;
|
||
border: 2rpx solid $border;
|
||
box-shadow: $shadow-md;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-lg;
|
||
|
||
&:active { background: $surface-warm; }
|
||
}
|
||
|
||
.id-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.id-name {
|
||
font-size: $font-lg;
|
||
font-weight: 600;
|
||
color: $text;
|
||
}
|
||
|
||
.id-desc {
|
||
font-size: $font-md;
|
||
color: $text-muted;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.identity-modal {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: $surface;
|
||
border-radius: $radius-2xl $radius-2xl 0 0;
|
||
padding: $space-xl;
|
||
padding-bottom: calc($space-xl + env(safe-area-inset-bottom));
|
||
max-height: 70vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: $font-xl;
|
||
font-weight: 600;
|
||
color: $text;
|
||
display: block;
|
||
text-align: center;
|
||
margin-bottom: $space-lg;
|
||
}
|
||
|
||
.id-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-lg;
|
||
padding: $font-lg $space-md;
|
||
border-radius: $radius-lg;
|
||
margin-bottom: 12rpx;
|
||
|
||
&.active { background: $surface-warm; }
|
||
&:active { background: $bg; }
|
||
}
|
||
|
||
.id-option-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.id-option-name {
|
||
font-size: $font-lg;
|
||
font-weight: 500;
|
||
color: $text;
|
||
}
|
||
|
||
.id-option-desc {
|
||
font-size: $font-md;
|
||
color: $text-muted;
|
||
margin-top: 2rpx;
|
||
}
|
||
|
||
.id-actions {
|
||
display: flex;
|
||
gap: $space-md;
|
||
margin-top: $space-md;
|
||
padding-top: $space-md;
|
||
border-top: 1rpx solid $border;
|
||
}
|
||
|
||
.id-action-btn {
|
||
flex: 1;
|
||
height: 80rpx;
|
||
border-radius: $radius-lg;
|
||
background: $surface-warm;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:active { background: darken($surface-warm, 3%); }
|
||
}
|
||
|
||
.id-action-text {
|
||
font-size: $font-lg;
|
||
font-weight: 500;
|
||
color: $primary;
|
||
}
|
||
|
||
.id-manage {
|
||
margin-top: $space-sm;
|
||
padding: $space-md $space-lg;
|
||
text-align: center;
|
||
|
||
&:active { background: $surface-warm; border-radius: $radius-md; }
|
||
}
|
||
|
||
.id-manage-text {
|
||
font-size: $font-base;
|
||
color: $text-sec;
|
||
}
|
||
</style>
|