ui更新
This commit is contained in:
378
resources/mobile/pages/account/bill/add.vue
Normal file
378
resources/mobile/pages/account/bill/add.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="记一笔"
|
||||
:show-back="true"
|
||||
>
|
||||
<view class="page-content">
|
||||
<!-- 收支类型切换 -->
|
||||
<view class="type-switch">
|
||||
<view
|
||||
class="type-item"
|
||||
:class="{active: form.type === 'expense'}"
|
||||
@tap="switchType('expense')"
|
||||
>
|
||||
<text>支出</text>
|
||||
</view>
|
||||
<view
|
||||
class="type-item"
|
||||
:class="{active: form.type === 'income'}"
|
||||
@tap="switchType('income')"
|
||||
>
|
||||
<text>收入</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 金额输入 -->
|
||||
<view class="amount-section">
|
||||
<view class="amount-input-wrapper">
|
||||
<text class="currency">¥</text>
|
||||
<input
|
||||
class="amount-input"
|
||||
type="digit"
|
||||
v-model="form.amount"
|
||||
placeholder="0.00"
|
||||
placeholder-style="color: #ddd"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类选择 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">选择分类</view>
|
||||
<view class="category-grid">
|
||||
<view
|
||||
v-for="category in currentCategories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{active: form.categoryId === category.id}"
|
||||
@tap="selectCategory(category)"
|
||||
>
|
||||
<view class="category-icon" :style="{background: category.color}">
|
||||
<text>{{ category.icon }}</text>
|
||||
</view>
|
||||
<text class="category-name">{{ category.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">备注</view>
|
||||
<view class="textarea-wrapper">
|
||||
<textarea
|
||||
v-model="form.remark"
|
||||
placeholder="添加备注(可选)"
|
||||
placeholder-style="color: #ccc"
|
||||
maxlength="100"
|
||||
/>
|
||||
<text class="char-count">{{ form.remark.length }}/100</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">日期</view>
|
||||
<picker mode="date" :value="form.date" @change="onDateChange">
|
||||
<view class="picker-wrapper">
|
||||
<text class="picker-text">{{ form.date }}</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn" @tap="handleSubmit" :loading="loading">
|
||||
保存
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
type: 'expense',
|
||||
amount: '',
|
||||
categoryId: '',
|
||||
remark: '',
|
||||
date: this.getTodayDate()
|
||||
},
|
||||
expenseCategories: [
|
||||
{ id: 1, name: '餐饮', icon: '🍜', color: '#FF6B6B' },
|
||||
{ id: 2, name: '交通', icon: '🚗', color: '#4ECDC4' },
|
||||
{ id: 3, name: '购物', icon: '🛒', color: '#45B7D1' },
|
||||
{ id: 4, name: '娱乐', icon: '🎮', color: '#96CEB4' },
|
||||
{ id: 5, name: '医疗', icon: '💊', color: '#FFEAA7' },
|
||||
{ id: 6, name: '教育', icon: '📚', color: '#DDA0DD' },
|
||||
{ id: 7, name: '居住', icon: '🏠', color: '#98D8C8' },
|
||||
{ id: 8, name: '其他', icon: '📦', color: '#BDC3C7' }
|
||||
],
|
||||
incomeCategories: [
|
||||
{ id: 101, name: '工资', icon: '💰', color: '#2ECC71' },
|
||||
{ id: 102, name: '奖金', icon: '🎁', color: '#E74C3C' },
|
||||
{ id: 103, name: '投资', icon: '📈', color: '#3498DB' },
|
||||
{ id: 104, name: '兼职', icon: '💼', color: '#9B59B6' },
|
||||
{ id: 105, name: '其他', icon: '💎', color: '#1ABC9C' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentCategories() {
|
||||
return this.form.type === 'expense' ? this.expenseCategories : this.incomeCategories
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTodayDate() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
switchType(type) {
|
||||
this.form.type = type
|
||||
this.form.categoryId = ''
|
||||
},
|
||||
selectCategory(category) {
|
||||
this.form.categoryId = category.id
|
||||
},
|
||||
onDateChange(e) {
|
||||
this.form.date = e.detail.value
|
||||
},
|
||||
async handleSubmit() {
|
||||
// 验证
|
||||
if (!this.form.amount || parseFloat(this.form.amount) <= 0) {
|
||||
uni.showToast({
|
||||
title: '请输入有效金额',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.form.categoryId) {
|
||||
uni.showToast({
|
||||
title: '请选择分类',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const res = await this.$api.bill.add.post({
|
||||
type: this.form.type,
|
||||
amount: parseFloat(this.form.amount),
|
||||
category_id: this.form.categoryId,
|
||||
remark: this.form.remark,
|
||||
date: this.form.date
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message || '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存账单失败', error)
|
||||
uni.showToast({
|
||||
title: '保存失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.type-switch {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.type-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 50rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.amount-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.amount-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.currency {
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.category-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.category-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
opacity: 0.6;
|
||||
transition: all 0.3s;
|
||||
|
||||
text {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.category-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.category-name {
|
||||
color: #667eea;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-wrapper {
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin-top: 40rpx;
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,9 +7,69 @@
|
||||
@tab-change="handleTabChange"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view class="empty-state">
|
||||
<uni-icons type="list" size="100" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">账单功能开发中</text>
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">本月支出</text>
|
||||
<text class="stat-value expense">¥{{ monthExpense.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">本月收入</text>
|
||||
<text class="stat-value income">¥{{ monthIncome.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 月份选择 -->
|
||||
<view class="month-selector">
|
||||
<picker mode="date" fields="month" :value="currentMonth" @change="onMonthChange">
|
||||
<view class="month-picker">
|
||||
<uni-icons type="left" size="20" color="#666"></uni-icons>
|
||||
<text class="month-text">{{ currentMonth }}</text>
|
||||
<uni-icons type="right" size="20" color="#666"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 账单列表 -->
|
||||
<view class="bill-list">
|
||||
<view v-if="loading" class="loading-state">
|
||||
<uni-load-more status="loading"></uni-load-more>
|
||||
</view>
|
||||
|
||||
<view v-else-if="billList.length === 0" class="empty-state">
|
||||
<uni-icons type="list" size="100" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">暂无账单记录</text>
|
||||
<button class="add-btn" @tap="addBill">
|
||||
<uni-icons type="plus" size="18" color="#fff"></uni-icons>
|
||||
<text>记一笔</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-else class="bill-group" v-for="(group, date) in groupedBills" :key="date">
|
||||
<view class="group-header">
|
||||
<text class="group-date">{{ date }}</text>
|
||||
<text class="group-amount">支出: ¥{{ group.expense.toFixed(2) }} 收入: ¥{{ group.income.toFixed(2) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="bill-item" v-for="bill in group.bills" :key="bill.id" @tap="editBill(bill)">
|
||||
<view class="bill-icon" :style="{background: getCategoryColor(bill.category_id)}">
|
||||
<text>{{ getCategoryIcon(bill.category_id) }}</text>
|
||||
</view>
|
||||
<view class="bill-info">
|
||||
<text class="bill-category">{{ getCategoryName(bill.category_id) }}</text>
|
||||
<text class="bill-remark" v-if="bill.remark">{{ bill.remark }}</text>
|
||||
</view>
|
||||
<view class="bill-amount" :class="bill.type">
|
||||
<text>{{ bill.type === 'expense' ? '-' : '+' }}¥{{ bill.amount.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view class="fab-button" @tap="addBill">
|
||||
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
@@ -17,9 +77,120 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentMonth: this.getCurrentMonth(),
|
||||
billList: [],
|
||||
monthExpense: 0,
|
||||
monthIncome: 0,
|
||||
categoryMap: {
|
||||
// 支出分类
|
||||
1: { name: '餐饮', icon: '🍜', color: '#FF6B6B' },
|
||||
2: { name: '交通', icon: '🚗', color: '#4ECDC4' },
|
||||
3: { name: '购物', icon: '🛒', color: '#45B7D1' },
|
||||
4: { name: '娱乐', icon: '🎮', color: '#96CEB4' },
|
||||
5: { name: '医疗', icon: '💊', color: '#FFEAA7' },
|
||||
6: { name: '教育', icon: '📚', color: '#DDA0DD' },
|
||||
7: { name: '居住', icon: '🏠', color: '#98D8C8' },
|
||||
8: { name: '其他', icon: '📦', color: '#BDC3C7' },
|
||||
// 收入分类
|
||||
101: { name: '工资', icon: '💰', color: '#2ECC71' },
|
||||
102: { name: '奖金', icon: '🎁', color: '#E74C3C' },
|
||||
103: { name: '投资', icon: '📈', color: '#3498DB' },
|
||||
104: { name: '兼职', icon: '💼', color: '#9B59B6' },
|
||||
105: { name: '其他', icon: '💎', color: '#1ABC9C' }
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
groupedBills() {
|
||||
const groups = {}
|
||||
this.billList.forEach(bill => {
|
||||
const date = bill.date
|
||||
if (!groups[date]) {
|
||||
groups[date] = {
|
||||
bills: [],
|
||||
expense: 0,
|
||||
income: 0
|
||||
}
|
||||
}
|
||||
groups[date].bills.push(bill)
|
||||
if (bill.type === 'expense') {
|
||||
groups[date].expense += bill.amount
|
||||
} else {
|
||||
groups[date].income += bill.amount
|
||||
}
|
||||
})
|
||||
return groups
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadBillList()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.loadBillList().then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleTabChange(path) {
|
||||
console.log('Tab changed to:', path)
|
||||
},
|
||||
getCurrentMonth() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
},
|
||||
onMonthChange(e) {
|
||||
this.currentMonth = e.detail.value
|
||||
this.loadBillList()
|
||||
},
|
||||
async loadBillList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const [year, month] = this.currentMonth.split('-')
|
||||
const res = await this.$api.bill.list.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
this.billList = res.data?.list || []
|
||||
this.monthExpense = res.data?.month_expense || 0
|
||||
this.monthIncome = res.data?.month_income || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载账单列表失败', error)
|
||||
uni.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
addBill() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/account/bill/add'
|
||||
})
|
||||
},
|
||||
editBill(bill) {
|
||||
// TODO: 实现编辑功能
|
||||
uni.showToast({
|
||||
title: '编辑功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
getCategoryName(categoryId) {
|
||||
return this.categoryMap[categoryId]?.name || '未知'
|
||||
},
|
||||
getCategoryIcon(categoryId) {
|
||||
return this.categoryMap[categoryId]?.icon || '📦'
|
||||
},
|
||||
getCategoryColor(categoryId) {
|
||||
return this.categoryMap[categoryId]?.color || '#BDC3C7'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,20 +198,201 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 40rpx 30rpx;
|
||||
padding: 30rpx;
|
||||
padding-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
.stats-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 40rpx;
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
|
||||
&.expense {
|
||||
color: #FFEAA7;
|
||||
}
|
||||
|
||||
&.income {
|
||||
color: #98D8C8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.month-selector {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.month-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 50rpx;
|
||||
padding: 16rpx 40rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.month-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bill-list {
|
||||
.loading-state {
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 30rpx 0 40rpx;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
padding: 20rpx 50rpx;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
uni-icons {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bill-group {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 10rpx;
|
||||
|
||||
.group-date {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.group-amount {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.bill-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
|
||||
text {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bill-category {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bill-remark {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.expense {
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
&.income {
|
||||
color: #2ECC71;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fab-button {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 160rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.5);
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,121 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="统计"
|
||||
:show-back="false"
|
||||
nav-bar-title="统计分析"
|
||||
:show-back="true"
|
||||
:show-tab-bar="true"
|
||||
@tab-change="handleTabChange"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view class="empty-state">
|
||||
<uni-icons type="chart" size="100" color="#ddd"></uni-icons>
|
||||
<text class="empty-text">统计功能开发中</text>
|
||||
<!-- 月份选择 -->
|
||||
<view class="month-selector">
|
||||
<picker mode="date" fields="month" :value="currentMonth" @change="onMonthChange">
|
||||
<view class="month-picker">
|
||||
<uni-icons type="left" size="20" color="#666"></uni-icons>
|
||||
<text class="month-text">{{ currentMonth }}</text>
|
||||
<uni-icons type="right" size="20" color="#666"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 概览卡片 -->
|
||||
<view class="overview-section">
|
||||
<view class="overview-card income">
|
||||
<text class="card-label">本月收入</text>
|
||||
<text class="card-amount">¥{{ overview.income.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="overview-card expense">
|
||||
<text class="card-label">本月支出</text>
|
||||
<text class="card-amount">¥{{ overview.expense.toFixed(2) }}</text>
|
||||
</view>
|
||||
<view class="overview-card balance">
|
||||
<text class="card-label">结余</text>
|
||||
<text class="card-amount">¥{{ overview.balance.toFixed(2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类统计 -->
|
||||
<view class="category-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">支出分类</text>
|
||||
<text class="section-total">共 ¥{{ categoryTotal.toFixed(2) }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-state">
|
||||
<uni-load-more status="loading"></uni-load-more>
|
||||
</view>
|
||||
|
||||
<view v-else-if="categoryList.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="category-list">
|
||||
<view class="category-item" v-for="(item, index) in categoryList" :key="index">
|
||||
<view class="category-info">
|
||||
<view class="category-icon" :style="{background: item.color}">
|
||||
<text>{{ item.icon }}</text>
|
||||
</view>
|
||||
<view class="category-details">
|
||||
<text class="category-name">{{ item.name }}</text>
|
||||
<view class="progress-bar">
|
||||
<view class="progress" :style="{width: item.percent + '%', background: item.color}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="category-amount">
|
||||
<text class="amount">¥{{ item.amount.toFixed(2) }}</text>
|
||||
<text class="percent">{{ item.percent }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收支趋势 -->
|
||||
<view class="trend-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">收支趋势</text>
|
||||
<text class="period-text">最近7天</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-state">
|
||||
<uni-load-more status="loading"></uni-load-more>
|
||||
</view>
|
||||
|
||||
<view v-else-if="trendList.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="trend-chart">
|
||||
<view class="chart-container">
|
||||
<view
|
||||
class="chart-bar"
|
||||
v-for="(item, index) in trendList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="bar-wrapper">
|
||||
<view
|
||||
class="bar income-bar"
|
||||
:style="{height: item.incomeHeight + '%'}"
|
||||
></view>
|
||||
<view
|
||||
class="bar expense-bar"
|
||||
:style="{height: item.expenseHeight + '%'}"
|
||||
></view>
|
||||
</view>
|
||||
<text class="bar-label">{{ item.dateLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chart-legend">
|
||||
<view class="legend-item">
|
||||
<view class="legend-color income-legend"></view>
|
||||
<text class="legend-text">收入</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="legend-color expense-legend"></view>
|
||||
<text class="legend-text">支出</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
@@ -17,9 +123,133 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentMonth: this.getCurrentMonth(),
|
||||
overview: {
|
||||
income: 0,
|
||||
expense: 0,
|
||||
balance: 0
|
||||
},
|
||||
categoryList: [],
|
||||
categoryTotal: 0,
|
||||
trendList: [],
|
||||
categoryMap: {
|
||||
1: { name: '餐饮', icon: '🍜', color: '#FF6B6B' },
|
||||
2: { name: '交通', icon: '🚗', color: '#4ECDC4' },
|
||||
3: { name: '购物', icon: '🛒', color: '#45B7D1' },
|
||||
4: { name: '娱乐', icon: '🎮', color: '#96CEB4' },
|
||||
5: { name: '医疗', icon: '💊', color: '#FFEAA7' },
|
||||
6: { name: '教育', icon: '📚', color: '#DDA0DD' },
|
||||
7: { name: '居住', icon: '🏠', color: '#98D8C8' },
|
||||
8: { name: '其他', icon: '📦', color: '#BDC3C7' }
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadStatistics()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.loadStatistics().then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleTabChange(path) {
|
||||
console.log('Tab changed to:', path)
|
||||
},
|
||||
getCurrentMonth() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
},
|
||||
onMonthChange(e) {
|
||||
this.currentMonth = e.detail.value
|
||||
this.loadStatistics()
|
||||
},
|
||||
async loadStatistics() {
|
||||
this.loading = true
|
||||
try {
|
||||
const [year, month] = this.currentMonth.split('-')
|
||||
|
||||
// 获取统计概览
|
||||
const overviewRes = await this.$api.statistics.overview.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
if (overviewRes.code === 200) {
|
||||
this.overview = overviewRes.data || { income: 0, expense: 0, balance: 0 }
|
||||
}
|
||||
|
||||
// 获取分类统计
|
||||
const categoryRes = await this.$api.statistics.category.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
if (categoryRes.code === 200) {
|
||||
this.processCategoryData(categoryRes.data || [])
|
||||
}
|
||||
|
||||
// 获取收支趋势
|
||||
const trendRes = await this.$api.statistics.trend.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
if (trendRes.code === 200) {
|
||||
this.processTrendData(trendRes.data || [])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败', error)
|
||||
uni.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
processCategoryData(data) {
|
||||
let total = 0
|
||||
const list = data.map(item => {
|
||||
total += item.amount
|
||||
return item
|
||||
})
|
||||
|
||||
this.categoryTotal = total
|
||||
this.categoryList = list.map(item => {
|
||||
const categoryInfo = this.categoryMap[item.category_id] || { name: '未知', icon: '📦', color: '#BDC3C7' }
|
||||
return {
|
||||
...categoryInfo,
|
||||
amount: item.amount,
|
||||
percent: total > 0 ? Math.round((item.amount / total) * 100) : 0
|
||||
}
|
||||
}).sort((a, b) => b.amount - a.amount)
|
||||
},
|
||||
processTrendData(data) {
|
||||
// 找出最大值用于计算高度百分比
|
||||
let maxIncome = 0
|
||||
let maxExpense = 0
|
||||
|
||||
data.forEach(item => {
|
||||
maxIncome = Math.max(maxIncome, item.income)
|
||||
maxExpense = Math.max(maxExpense, item.expense)
|
||||
})
|
||||
|
||||
this.trendList = data.map(item => {
|
||||
const date = new Date(item.date)
|
||||
const dateLabel = `${date.getMonth() + 1}/${date.getDate()}`
|
||||
|
||||
return {
|
||||
date: item.date,
|
||||
dateLabel,
|
||||
income: item.income,
|
||||
expense: item.expense,
|
||||
incomeHeight: maxIncome > 0 ? (item.income / maxIncome) * 100 : 0,
|
||||
expenseHeight: maxExpense > 0 ? (item.expense / maxExpense) * 100 : 0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,20 +257,296 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 40rpx 30rpx;
|
||||
padding: 30rpx;
|
||||
padding-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
.month-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 40rpx;
|
||||
.month-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 50rpx;
|
||||
padding: 16rpx 40rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.month-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.overview-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.card-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.card-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.income .card-amount {
|
||||
color: #2ECC71;
|
||||
}
|
||||
|
||||
&.expense .card-amount {
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
&.balance .card-amount {
|
||||
color: #667eea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-total {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.category-list {
|
||||
.category-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 2rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.category-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.category-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.category-details {
|
||||
flex: 1;
|
||||
|
||||
.category-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.progress {
|
||||
height: 100%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-amount {
|
||||
text-align: right;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.amount {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.percent {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trend-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.period-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.trend-chart {
|
||||
.chart-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: 300rpx;
|
||||
padding: 20rpx 0;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.bar-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 4rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.bar {
|
||||
width: 24rpx;
|
||||
min-height: 4rpx;
|
||||
border-radius: 4rpx;
|
||||
transition: height 0.3s;
|
||||
|
||||
&.income-bar {
|
||||
background: #2ECC71;
|
||||
}
|
||||
|
||||
&.expense-bar {
|
||||
background: #FF6B6B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40rpx;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.legend-color {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 8rpx;
|
||||
|
||||
&.income-legend {
|
||||
background: #2ECC71;
|
||||
}
|
||||
|
||||
&.expense-legend {
|
||||
background: #FF6B6B;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
182
resources/mobile/pages/family/create.vue
Normal file
182
resources/mobile/pages/family/create.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="创建家庭"
|
||||
:show-back="true"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view class="form-section">
|
||||
<view class="section-title">家庭名称</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="familyName"
|
||||
placeholder="请输入家庭名称"
|
||||
placeholder-style="color: #ccc"
|
||||
maxlength="20"
|
||||
/>
|
||||
<text class="char-count">{{ familyName.length }}/20</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips-section">
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">创建后您将成为家主,可以管理家庭成员</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">每个用户只能创建或加入一个家庭</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">创建家庭后,家庭成员可以查看和记录家庭账单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn" @tap="handleCreate" :loading="loading">
|
||||
创建家庭
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
familyName: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleCreate() {
|
||||
if (!this.familyName.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入家庭名称',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const res = await this.$store.dispatch('family/createFamily', this.familyName.trim())
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: '创建成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message || '创建失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建家庭失败', error)
|
||||
uni.showToast({
|
||||
title: '创建失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -30rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: rgba(102, 126, 234, 0.05);
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
uni-icons {
|
||||
margin-right: 12rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin-top: 60rpx;
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
588
resources/mobile/pages/family/index.vue
Normal file
588
resources/mobile/pages/family/index.vue
Normal file
@@ -0,0 +1,588 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="家庭管理"
|
||||
:show-back="true"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view v-if="loading" class="loading-state">
|
||||
<uni-load-more status="loading"></uni-load-more>
|
||||
</view>
|
||||
|
||||
<!-- 已加入家庭 -->
|
||||
<view v-else-if="hasFamily" class="family-container">
|
||||
<!-- 家庭信息卡片 -->
|
||||
<view class="family-card">
|
||||
<view class="family-header">
|
||||
<view class="family-icon">
|
||||
<text>🏠</text>
|
||||
</view>
|
||||
<view class="family-info">
|
||||
<text class="family-name">{{ familyInfo.name }}</text>
|
||||
<text class="family-role" v-if="isOwner">我是家主</text>
|
||||
<text class="family-role" v-else>家庭成员</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 家庭邀请码(仅家主可见) -->
|
||||
<view v-if="isOwner" class="invite-section">
|
||||
<view class="invite-info">
|
||||
<text class="invite-label">家庭邀请码</text>
|
||||
<view class="invite-code-wrapper">
|
||||
<text class="invite-code">{{ inviteCode || '加载中...' }}</text>
|
||||
<view class="copy-btn" @tap="copyInviteCode">
|
||||
<uni-icons type="copy" size="16" color="#667eea"></uni-icons>
|
||||
<text>复制</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<button class="regenerate-btn" @tap="regenerateInviteCode" :loading="regenerating">
|
||||
重新生成邀请码
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 家庭成员列表 -->
|
||||
<view class="members-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">家庭成员</text>
|
||||
<text class="member-count">{{ memberList.length }}人</text>
|
||||
</view>
|
||||
|
||||
<view class="member-list">
|
||||
<view class="member-item" v-for="member in memberList" :key="member.id">
|
||||
<view class="member-avatar">
|
||||
<text>{{ member.name ? member.name.charAt(0) : 'U' }}</text>
|
||||
</view>
|
||||
<view class="member-info">
|
||||
<text class="member-name">{{ member.name || member.username }}</text>
|
||||
<text class="member-role" v-if="member.is_owner">家主</text>
|
||||
</view>
|
||||
<!-- 移除成员按钮(仅家主可见) -->
|
||||
<view v-if="isOwner && !member.is_owner" class="remove-btn" @tap="removeMember(member)">
|
||||
<uni-icons type="clear" size="20" color="#FF6B6B"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转让家主(仅家主可见) -->
|
||||
<view v-if="isOwner && memberList.length > 1" class="action-btn">
|
||||
<button class="secondary-btn" @tap="transferOwner">
|
||||
转让家主
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出家庭按钮 -->
|
||||
<view class="action-btn">
|
||||
<button class="exit-btn" @tap="confirmLeaveFamily">
|
||||
退出家庭
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 未加入家庭 -->
|
||||
<view v-else class="no-family-container">
|
||||
<view class="no-family-icon">
|
||||
<text>🏠</text>
|
||||
</view>
|
||||
<text class="no-family-title">您还未加入任何家庭</text>
|
||||
<text class="no-family-desc">创建或加入家庭,与家人一起记账</text>
|
||||
|
||||
<view class="action-buttons">
|
||||
<button class="primary-btn" @tap="goToCreate">
|
||||
<uni-icons type="plus" size="20" color="#fff"></uni-icons>
|
||||
<text>创建家庭</text>
|
||||
</button>
|
||||
<button class="secondary-btn" @tap="goToJoin">
|
||||
<uni-icons type="personadd" size="20" color="#667eea"></uni-icons>
|
||||
<text>加入家庭</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
familyInfo: {},
|
||||
inviteCode: '',
|
||||
regenerating: false,
|
||||
memberList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFamily() {
|
||||
if (!this.$store || !this.$store.getters) {
|
||||
return false
|
||||
}
|
||||
return this.$store.getters['family/hasFamily'] || false
|
||||
},
|
||||
isOwner() {
|
||||
if (!this.$store || !this.$store.state || !this.$store.state.family) {
|
||||
return false
|
||||
}
|
||||
return this.$store.state.family.isOwner || false
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadFamilyData()
|
||||
},
|
||||
onShow() {
|
||||
// 从其他页面返回时刷新数据
|
||||
this.loadFamilyData()
|
||||
},
|
||||
methods: {
|
||||
async loadFamilyData() {
|
||||
if (!this.hasFamily) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
// 获取家庭信息
|
||||
const familyRes = await this.$api.family.info.get()
|
||||
if (familyRes.code === 200) {
|
||||
this.familyInfo = familyRes.data
|
||||
this.$store.commit('family/setFamilyInfo', familyRes.data)
|
||||
}
|
||||
|
||||
// 获取邀请码(仅家主)
|
||||
if (this.isOwner) {
|
||||
const codeRes = await this.$api.family.inviteCode.get()
|
||||
if (codeRes.code === 200) {
|
||||
this.inviteCode = codeRes.data.invite_code
|
||||
}
|
||||
}
|
||||
|
||||
// 获取家庭成员列表
|
||||
const membersRes = await this.$api.family.members.get()
|
||||
if (membersRes.code === 200) {
|
||||
this.memberList = membersRes.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载家庭数据失败', error)
|
||||
uni.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
copyInviteCode() {
|
||||
uni.setClipboardData({
|
||||
data: this.inviteCode,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '邀请码已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
async regenerateInviteCode() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '重新生成邀请码后,旧的邀请码将失效,确定要重新生成吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
this.regenerating = true
|
||||
try {
|
||||
const res = await this.$api.family.regenerateInviteCode.post()
|
||||
if (res.code === 200) {
|
||||
this.inviteCode = res.data.invite_code
|
||||
uni.showToast({
|
||||
title: '邀请码已更新',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重新生成邀请码失败', error)
|
||||
uni.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.regenerating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
removeMember(member) {
|
||||
uni.showModal({
|
||||
title: '确认移除',
|
||||
content: `确定要将 ${member.name || member.username} 移出家庭吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await this.$api.family.removeMember.post({
|
||||
user_id: member.id
|
||||
})
|
||||
if (result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '移除成功',
|
||||
icon: 'success'
|
||||
})
|
||||
this.loadFamilyData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('移除成员失败', error)
|
||||
uni.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
transferOwner() {
|
||||
// TODO: 实现转让家主功能
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
confirmLeaveFamily() {
|
||||
uni.showModal({
|
||||
title: '确认退出',
|
||||
content: '退出家庭后,您将无法查看和记录家庭账单,确定要退出吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await this.$store.dispatch('family/leaveFamily')
|
||||
if (result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '已退出家庭',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退出家庭失败', error)
|
||||
uni.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
goToCreate() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/family/create'
|
||||
})
|
||||
},
|
||||
goToJoin() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/family/join'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 30rpx;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.no-family-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
|
||||
.no-family-icon {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
text {
|
||||
font-size: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.no-family-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.no-family-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
width: 100%;
|
||||
padding: 0 40rpx;
|
||||
|
||||
button {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.family-container {
|
||||
.family-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.family-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.family-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
|
||||
text {
|
||||
font-size: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.family-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.family-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.family-role {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.invite-section {
|
||||
border-top: 2rpx solid #f5f5f5;
|
||||
padding-top: 30rpx;
|
||||
|
||||
.invite-info {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.invite-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.invite-code-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.invite-code {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10rpx 20rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50rpx;
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regenerate-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
font-size: 26rpx;
|
||||
background: #f8f8f8;
|
||||
color: #666;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.members-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.member-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.member-list {
|
||||
.member-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 2rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.member-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.member-role {
|
||||
font-size: 22rpx;
|
||||
color: #667eea;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
border-radius: 50rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
uni-icons {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background: #f8f8f8;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
uni-icons {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.exit-btn {
|
||||
background: #fff;
|
||||
color: #FF6B6B;
|
||||
border: 2rpx solid #FF6B6B;
|
||||
}
|
||||
</style>
|
||||
180
resources/mobile/pages/family/join.vue
Normal file
180
resources/mobile/pages/family/join.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="加入家庭"
|
||||
:show-back="true"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view class="form-section">
|
||||
<view class="section-title">家庭邀请码</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="inviteCode"
|
||||
placeholder="请输入家庭邀请码"
|
||||
placeholder-style="color: #ccc"
|
||||
maxlength="10"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips-section">
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">邀请码由家庭家主提供</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">每个用户只能加入一个家庭</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="18" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">加入家庭后,可以查看和记录家庭账单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn" @tap="handleJoin" :loading="loading">
|
||||
加入家庭
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
inviteCode: '',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleJoin() {
|
||||
if (!this.inviteCode.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入邀请码',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.inviteCode.trim().length !== 10) {
|
||||
uni.showToast({
|
||||
title: '邀请码格式不正确',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const res = await this.$store.dispatch('family/joinFamily', this.inviteCode.trim())
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: '加入成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message || '加入失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加入家庭失败', error)
|
||||
uni.showToast({
|
||||
title: '加入失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding: 0;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: rgba(102, 126, 234, 0.05);
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
uni-icons {
|
||||
margin-right: 12rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin-top: 60rpx;
|
||||
|
||||
.submit-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,30 +7,112 @@
|
||||
@tab-change="handleTabChange"
|
||||
>
|
||||
<view class="page-content">
|
||||
<view class="welcome-section">
|
||||
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
|
||||
<text class="welcome-title">欢迎使用家庭记账</text>
|
||||
<text class="welcome-desc">简单好用的家庭记账助手</text>
|
||||
<!-- 用户信息头部 -->
|
||||
<view class="user-header">
|
||||
<view class="user-avatar">
|
||||
<text>{{ userName ? userName.charAt(0) : 'U' }}</text>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="greeting">你好,{{ userName || '用户' }}</text>
|
||||
<text class="date-text">{{ currentDate }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收支概览卡片 -->
|
||||
<view class="overview-card">
|
||||
<view class="overview-header">
|
||||
<text class="overview-title">本月概览</text>
|
||||
<text class="overview-month">{{ currentMonth }}</text>
|
||||
</view>
|
||||
<view class="overview-body">
|
||||
<view class="overview-item income">
|
||||
<view class="overview-icon">
|
||||
<text class="icon-text">收</text>
|
||||
</view>
|
||||
<view class="overview-data">
|
||||
<text class="overview-label">收入</text>
|
||||
<text class="overview-amount">¥0.00</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="overview-divider"></view>
|
||||
<view class="overview-item expense">
|
||||
<view class="overview-icon">
|
||||
<text class="icon-text">支</text>
|
||||
</view>
|
||||
<view class="overview-data">
|
||||
<text class="overview-label">支出</text>
|
||||
<text class="overview-amount">¥0.00</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷操作 -->
|
||||
<view class="quick-actions">
|
||||
<view class="action-card" @tap="navigateTo('/pages/account/bill/add')">
|
||||
<uni-icons type="plus" size="40" color="#667eea"></uni-icons>
|
||||
<view class="action-icon-wrapper">
|
||||
<uni-icons type="plus" size="36" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="action-title">记一笔</text>
|
||||
<text class="action-desc">快速记录</text>
|
||||
</view>
|
||||
<view class="action-card" @tap="navigateTo('/pages/account/bill/index')">
|
||||
<uni-icons type="list" size="40" color="#f093fb"></uni-icons>
|
||||
<view class="action-icon-wrapper">
|
||||
<uni-icons type="list" size="36" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="action-title">账单</text>
|
||||
<text class="action-desc">查看明细</text>
|
||||
</view>
|
||||
<view class="action-card" @tap="navigateTo('/pages/account/statistics/index')">
|
||||
<view class="action-icon-wrapper">
|
||||
<uni-icons type="color-filled" size="36" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="action-title">统计</text>
|
||||
<text class="action-desc">数据分析</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近记录 -->
|
||||
<view class="recent-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">最近记录</text>
|
||||
<text class="section-more" @tap="navigateTo('/pages/account/bill/index')">查看更多 ></text>
|
||||
<view class="section-more" @tap="navigateTo('/pages/account/bill/index')">
|
||||
<text>全部</text>
|
||||
<uni-icons type="right" size="14" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-list">
|
||||
<view class="empty-icon">
|
||||
<uni-icons type="list" size="80" color="#ddd"></uni-icons>
|
||||
</view>
|
||||
<text class="empty-text">暂无记录</text>
|
||||
<button class="quick-add-btn" @tap="navigateTo('/pages/account/bill/add')">
|
||||
<uni-icons type="plus" size="16" color="#fff"></uni-icons>
|
||||
<text>记一笔</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 家庭管理 -->
|
||||
<view class="family-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">家庭管理</text>
|
||||
<view class="section-more" @tap="navigateTo('/pages/family/index')">
|
||||
<text>管理</text>
|
||||
<uni-icons type="right" size="14" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="family-card" @tap="navigateTo('/pages/family/index')">
|
||||
<view class="family-icon-wrapper">
|
||||
<text class="family-icon-text">🏠</text>
|
||||
</view>
|
||||
<view class="family-info">
|
||||
<text class="family-status">{{ hasFamily ? '已加入家庭' : '未加入家庭' }}</text>
|
||||
<text class="family-desc">{{ hasFamily ? '点击查看家庭成员' : '点击创建或加入家庭' }}</text>
|
||||
</view>
|
||||
<view class="family-arrow">
|
||||
<uni-icons type="right" size="18" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -39,16 +121,49 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentDate: '',
|
||||
currentMonth: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFamily() {
|
||||
if (!this.$store || !this.$store.getters) {
|
||||
return false
|
||||
}
|
||||
return this.$store.getters['family/hasFamily'] || false
|
||||
},
|
||||
userName() {
|
||||
if (!this.$store || !this.$store.state || !this.$store.state.user || !this.$store.state.user.userInfo) {
|
||||
return ''
|
||||
}
|
||||
return this.$store.state.user.userInfo.name || this.$store.state.user.userInfo.username || ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.updateDate()
|
||||
},
|
||||
onShow() {
|
||||
this.updateDate()
|
||||
},
|
||||
methods: {
|
||||
updateDate() {
|
||||
const now = new Date()
|
||||
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
const month = now.getMonth() + 1
|
||||
const date = now.getDate()
|
||||
const day = weekDays[now.getDay()]
|
||||
|
||||
this.currentDate = `${month}月${date}日 ${day}`
|
||||
this.currentMonth = `${month}月`
|
||||
},
|
||||
handleTabChange(path) {
|
||||
console.log('Tab changed to:', path)
|
||||
// tabbar组件已经处理了跳转,这里不需要额外处理
|
||||
},
|
||||
navigateTo(url) {
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -57,95 +172,512 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 40rpx 30rpx;
|
||||
padding: 30rpx;
|
||||
padding-bottom: 150rpx;
|
||||
background: linear-gradient(180deg, #F5F7FA 0%, #FFFFFF 100%);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -100rpx;
|
||||
right: -100rpx;
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 200rpx;
|
||||
left: -100rpx;
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
background: radial-gradient(circle, rgba(118, 75, 162, 0.08) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
> view {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
.user-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 0;
|
||||
padding: 20rpx 0 40rpx;
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
|
||||
.logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 20rpx;
|
||||
.user-avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 28rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(102, 126, 234, 0.35);
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
right: -4rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: #4cd964;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid #fff;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 36rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
.user-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.greeting {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.welcome-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -30%;
|
||||
left: -30%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.05) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.overview-title {
|
||||
font-size: 30rpx;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.overview-month {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 24rpx;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.overview-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.overview-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&.income {
|
||||
.overview-icon {
|
||||
background: rgba(46, 204, 113, 0.25);
|
||||
box-shadow: 0 4rpx 12rpx rgba(46, 204, 113, 0.3);
|
||||
}
|
||||
|
||||
.overview-amount {
|
||||
background: linear-gradient(135deg, #98D8C8 0%, #7FDBDA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
&.expense {
|
||||
.overview-icon {
|
||||
background: rgba(255, 107, 107, 0.25);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
.overview-amount {
|
||||
background: linear-gradient(135deg, #FFEAA7 0%, #FD79A8 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
.icon-text {
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-data {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.overview-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.overview-amount {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overview-divider {
|
||||
width: 2rpx;
|
||||
height: 72rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
margin: 0 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 60rpx 0;
|
||||
margin-bottom: 40rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
.action-card {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 20rpx;
|
||||
margin: 0 10rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 16rpx;
|
||||
margin: 0 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4rpx;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(-4rpx) scale(0.98);
|
||||
box-shadow: 0 12rpx 28rpx rgba(0, 0, 0, 0.12);
|
||||
|
||||
.action-icon-wrapper {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
.action-icon-wrapper {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.action-icon-wrapper {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
.action-icon-wrapper {
|
||||
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(90deg, #4ECDC4 0%, #44A08D 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon-wrapper {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
margin-top: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recent-section,
|
||||
.family-section {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
animation: fadeInUp 0.6s ease-out both;
|
||||
}
|
||||
|
||||
.recent-section {
|
||||
margin-top: 40rpx;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
.family-section {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.section-more {
|
||||
font-size: 26rpx;
|
||||
color: #667eea;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(135deg, #333 0%, #666 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
background: #fff;
|
||||
.section-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 80rpx 40rpx;
|
||||
text-align: center;
|
||||
background: rgba(102, 126, 234, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
&:active {
|
||||
background: rgba(102, 126, 234, 0.15);
|
||||
}
|
||||
|
||||
text {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 0;
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 20rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.quick-add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
padding: 16rpx 36rpx;
|
||||
font-size: 26rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 6rpx 16rpx rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
uni-icons {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.family-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 0;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateX(4rpx);
|
||||
}
|
||||
|
||||
.family-icon-wrapper {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.family-icon-text {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.family-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.family-status {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.family-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.family-arrow {
|
||||
padding: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 动画关键帧
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,43 +9,135 @@
|
||||
<view class="page-content">
|
||||
<!-- 用户信息头部 -->
|
||||
<view class="user-header">
|
||||
<view class="header-bg"></view>
|
||||
<view class="user-avatar">
|
||||
<uni-icons type="person-filled" size="60" color="#fff"></uni-icons>
|
||||
<uni-icons type="person-filled" size="70" color="#fff"></uni-icons>
|
||||
<view class="vip-badge" v-if="isLogin">
|
||||
<text>VIP</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userInfo.nickname || '未登录' }}</text>
|
||||
<text class="user-desc">{{ userInfo.username || '点击登录' }}</text>
|
||||
<text class="user-name">{{ userInfo.nickname || userInfo.username || '未登录' }}</text>
|
||||
<text class="user-desc" v-if="isLogin">{{ userInfo.email || '暂无邮箱' }}</text>
|
||||
<text class="user-desc" v-else>点击登录体验更多功能</text>
|
||||
</view>
|
||||
<view class="edit-profile" @tap="handleEditProfile" v-if="isLogin">
|
||||
<uni-icons type="compose" size="18" color="rgba(255,255,255,0.8)"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计卡片 -->
|
||||
<view class="stats-section" v-if="isLogin">
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ stats.billCount || 0 }}</text>
|
||||
<text class="stat-label">账单数</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ stats.days || 0 }}</text>
|
||||
<text class="stat-label">记账天数</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ stats.familyMembers || 0 }}</text>
|
||||
<text class="stat-label">家庭成员</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷功能 -->
|
||||
<view class="quick-section">
|
||||
<view class="section-title">快捷功能</view>
|
||||
<view class="quick-grid">
|
||||
<view class="quick-item" @tap="navigateTo('/pages/account/bill/add')">
|
||||
<view class="quick-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="quick-text">记一笔</text>
|
||||
</view>
|
||||
<view class="quick-item" @tap="navigateTo('/pages/family/index')">
|
||||
<view class="quick-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<text class="emoji-icon">👥</text>
|
||||
</view>
|
||||
<text class="quick-text">家庭管理</text>
|
||||
</view>
|
||||
<view class="quick-item" @tap="navigateTo('/pages/account/statistics/index')">
|
||||
<view class="quick-icon" style="background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);">
|
||||
<uni-icons type="chart" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="quick-text">统计分析</text>
|
||||
</view>
|
||||
<view class="quick-item" @tap="handleExport">
|
||||
<view class="quick-icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
||||
<uni-icons type="download" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="quick-text">数据导出</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/profile')">
|
||||
<view class="menu-left">
|
||||
<uni-icons type="gear" size="20" color="#667eea"></uni-icons>
|
||||
<text class="menu-text">个人设置</text>
|
||||
<view class="section-title">设置</view>
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/profile')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="gear" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-text">个人设置</text>
|
||||
<text class="menu-desc">修改个人信息</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/help')">
|
||||
<view class="menu-left">
|
||||
<uni-icons type="help" size="20" color="#667eea"></uni-icons>
|
||||
<text class="menu-text">帮助中心</text>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/help')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="help" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-text">帮助中心</text>
|
||||
<text class="menu-desc">常见问题解答</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/about')">
|
||||
<view class="menu-left">
|
||||
<uni-icons type="info" size="20" color="#667eea"></uni-icons>
|
||||
<text class="menu-text">关于我们</text>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/about')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="info" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-text">关于我们</text>
|
||||
<text class="menu-desc">版本信息</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="handleClearCache">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="trash" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-text">清除缓存</text>
|
||||
<text class="menu-desc">释放存储空间</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="cache-size">{{ cacheSize }}</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class="logout-section" v-if="isLogin">
|
||||
<button class="logout-btn" @tap="handleLogout">退出登录</button>
|
||||
<!-- 登录/退出按钮 -->
|
||||
<view class="auth-section">
|
||||
<button class="auth-btn login-btn" @tap="handleLogin" v-if="!isLogin">
|
||||
立即登录
|
||||
</button>
|
||||
<button class="auth-btn logout-btn" @tap="handleLogout" v-else>
|
||||
退出登录
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
@@ -58,11 +150,18 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
userInfo: {},
|
||||
isLogin: false
|
||||
isLogin: false,
|
||||
stats: {
|
||||
billCount: 0,
|
||||
days: 0,
|
||||
familyMembers: 0
|
||||
},
|
||||
cacheSize: '0 MB'
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.checkLoginStatus()
|
||||
this.calculateCacheSize()
|
||||
},
|
||||
onShow() {
|
||||
this.checkLoginStatus()
|
||||
@@ -72,10 +171,58 @@ export default {
|
||||
checkLoginStatus() {
|
||||
this.isLogin = isLogin()
|
||||
this.userInfo = uni.getStorageSync('userInfo') || {}
|
||||
|
||||
if (this.isLogin) {
|
||||
this.loadUserStats()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户统计数据
|
||||
loadUserStats() {
|
||||
// 这里应该调用API获取实际数据
|
||||
// 暂时使用模拟数据
|
||||
this.stats = {
|
||||
billCount: 128,
|
||||
days: 45,
|
||||
familyMembers: 3
|
||||
}
|
||||
},
|
||||
|
||||
// 计算缓存大小
|
||||
calculateCacheSize() {
|
||||
try {
|
||||
const info = uni.getStorageInfoSync()
|
||||
const size = (info.currentSize / 1024).toFixed(2)
|
||||
this.cacheSize = `${size} MB`
|
||||
} catch (e) {
|
||||
this.cacheSize = '0 MB'
|
||||
}
|
||||
},
|
||||
|
||||
// 页面跳转
|
||||
navigateTo(url) {
|
||||
if (!this.isLogin && url !== '/pages/ucenter/login/index') {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/login/index'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 功能开发中提示
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑个人资料
|
||||
handleEditProfile() {
|
||||
this.navigateTo('/pages/ucenter/profile')
|
||||
},
|
||||
|
||||
// 数据导出
|
||||
handleExport() {
|
||||
if (!this.isLogin) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/login/index'
|
||||
@@ -90,6 +237,38 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// 清除缓存
|
||||
handleClearCache() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清除缓存吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.clearStorageSync()
|
||||
this.calculateCacheSize()
|
||||
uni.showToast({
|
||||
title: '缓存已清除',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '清除失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 登录
|
||||
handleLogin() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/login/index'
|
||||
})
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
@@ -98,6 +277,7 @@ export default {
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
logout()
|
||||
this.checkLoginStatus()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -113,92 +293,365 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
padding: 40rpx 30rpx;
|
||||
padding: 30rpx;
|
||||
padding-bottom: 150rpx;
|
||||
background: linear-gradient(180deg, #F5F7FA 0%, #FFFFFF 100%);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50rpx;
|
||||
right: -80rpx;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
background: radial-gradient(circle, rgba(102, 126, 234, 0.08) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
> view {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.user-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 20rpx;
|
||||
border-radius: 24rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
margin: 40rpx 0;
|
||||
margin: 20rpx 0 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 30rpx;
|
||||
margin-right: 28rpx;
|
||||
position: relative;
|
||||
backdrop-filter: blur(10rpx);
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
.vip-badge {
|
||||
position: absolute;
|
||||
bottom: -6rpx;
|
||||
right: -6rpx;
|
||||
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
|
||||
border-radius: 12rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border: 2rpx solid #fff;
|
||||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||
|
||||
text {
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-desc {
|
||||
font-size: 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-profile {
|
||||
padding: 16rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
.stats-section {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||
|
||||
.menu-item {
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 40rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.stat-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 32rpx;
|
||||
.stat-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background: #f0f0f0;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-section {
|
||||
margin-bottom: 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.quick-grid {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.quick-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(-4rpx);
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12);
|
||||
|
||||
.emoji-icon {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
margin-left: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
margin-top: 60rpx;
|
||||
.menu-section {
|
||||
margin-bottom: 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.3s both;
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
border-radius: 45rpx;
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background: #fff;
|
||||
color: #f56c6c;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.menu-icon-wrapper {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.menu-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.menu-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cache-size {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-section {
|
||||
margin-top: 40rpx;
|
||||
padding: 0 20rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.4s both;
|
||||
|
||||
.auth-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
border-radius: 48rpx;
|
||||
font-size: 32rpx;
|
||||
border: 2rpx solid #f56c6c;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: #fff;
|
||||
color: #f56c6c;
|
||||
border: 2rpx solid #f56c6c;
|
||||
box-shadow: 0 6rpx 20rpx rgba(245, 108, 108, 0.15);
|
||||
|
||||
&:active {
|
||||
background: #fef0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user