第一次上传
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<view class="app-page" :class="themeClass">
|
||||
<view class="surface-card page-hero">
|
||||
<text class="hero-kicker">ABOUT</text>
|
||||
<text class="hero-title">关于与隐私</text>
|
||||
<text class="hero-desc">查看应用定位、数据说明与发布前应补齐的正式信息。</text>
|
||||
<view class="hero-tags">
|
||||
<!-- <text class="hero-tag">版本 1.0.0</text> -->
|
||||
<text class="hero-tag soft">发布说明</text>
|
||||
</view>
|
||||
</view>
|
||||
<ad-custom unit-id="adunit-64707ea333329399"></ad-custom>
|
||||
<section-card title="关于应用" subtitle="面向日常收支记录、预算控制与月度复盘的轻量工具">
|
||||
<view class="about-card surface-strong">
|
||||
<text class="app-name">账单小管家</text>
|
||||
<!-- <text class="app-version">版本 1.0.0</text> -->
|
||||
<text class="about-text">定位为轻量、无广告的本地记账工具,适合学生、情侣、合租和个人日常记账场景。</text>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="隐私与数据" subtitle="">
|
||||
<view class="info-list">
|
||||
<view class="info-item surface-strong">
|
||||
<text class="info-title">本地存储</text>
|
||||
<text class="info-text">账单、预算、分类、账户和设置默认保存在当前设备本地。</text>
|
||||
</view>
|
||||
<view class="info-item surface-strong">
|
||||
<text class="info-title">本地昵称</text>
|
||||
<text class="info-text">昵称仅保存在当前设备,用于个人页展示和首字头像。</text>
|
||||
</view>
|
||||
<view class="info-item surface-strong">
|
||||
<text class="info-title">数据迁移</text>
|
||||
<text class="info-text">如需换机迁移,可在“备份与恢复”中导出 JSON 并在新设备恢复。</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import SectionCard from '../../../components/SectionCard.vue'
|
||||
import { useAppStore } from '../../../utils/store'
|
||||
|
||||
const store = useAppStore()
|
||||
|
||||
onShow(()=>{
|
||||
showIt()
|
||||
})
|
||||
|
||||
function showIt(){
|
||||
let interstitialAd = null;
|
||||
if (wx.createInterstitialAd) {
|
||||
interstitialAd = wx.createInterstitialAd({
|
||||
adUnitId: 'adunit-0abc32053b19a4e9'
|
||||
})
|
||||
interstitialAd.onLoad(() => {})
|
||||
interstitialAd.onError((err) => {
|
||||
console.error('插屏广告加载失败', err)
|
||||
})
|
||||
interstitialAd.onClose(() => {})
|
||||
}
|
||||
|
||||
setTimeout(()=>{
|
||||
if (interstitialAd) {
|
||||
interstitialAd.show().catch((err) => {
|
||||
console.error('插屏广告显示失败', err)
|
||||
})
|
||||
}
|
||||
}, 2280)
|
||||
}
|
||||
|
||||
const themeClass = computed(() => (store.state.settings.theme === 'dark' ? 'theme-dark' : ''))
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-hero {
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(145deg, rgba(16, 42, 67, 0.96) 0%, rgba(31, 111, 95, 0.88) 60%, rgba(212, 108, 67, 0.82) 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-kicker,
|
||||
.hero-desc,
|
||||
.hero-tag.soft {
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14rpx;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.about-card,
|
||||
.info-item,
|
||||
.tips-card {
|
||||
padding: 26rpx;
|
||||
border-radius: 28rpx;
|
||||
}
|
||||
|
||||
.app-name,
|
||||
.info-title {
|
||||
display: block;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.app-version {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 23rpx;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.about-text,
|
||||
.info-text,
|
||||
.tip-line {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.8;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tip-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16rpx;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.tip-index {
|
||||
width: 56rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: var(--brand);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<view class="app-page" :class="themeClass">
|
||||
<view class="surface-card page-hero">
|
||||
<text class="hero-kicker">BACKUP</text>
|
||||
<text class="hero-title">备份与恢复</text>
|
||||
<text class="hero-desc">导出本地 JSON 备份,恢复时覆盖当前设备数据,适合换机或手动留档。</text>
|
||||
<view class="hero-tags">
|
||||
<text class="hero-tag">本地文件</text>
|
||||
<text class="hero-tag soft">{{ store.state.settings.lastBackupAt ? '最近已备份' : '尚未备份' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<ad-custom unit-id="adunit-64707ea333329399"></ad-custom>
|
||||
<section-card title="导出备份" subtitle="将账单、预算和设置导出为 JSON 文件,便于迁移设备或手动保存">
|
||||
<view class="hero-panel surface-strong">
|
||||
<view>
|
||||
<text class="panel-title">安全备份当前数据</text>
|
||||
<text class="panel-desc">所有数据均为本地文件,不会自动上传服务器。</text>
|
||||
</view>
|
||||
<view class="status-chip">{{ store.state.settings.lastBackupAt ? '可继续备份' : '建议先备份' }}</view>
|
||||
</view>
|
||||
<view class="action-row single-row">
|
||||
<view class="primary-button" @click="exportBackupFile">导出备份</view>
|
||||
</view>
|
||||
<text class="tiny-text info-line" v-if="store.state.settings.lastBackupAt">最近备份:{{ store.state.settings.lastBackupAt }}</text>
|
||||
</section-card>
|
||||
|
||||
<section-card title="恢复备份" subtitle="粘贴之前导出的 JSON 内容,恢复后将覆盖当前设备数据">
|
||||
<view class="input-shell textarea-shell">
|
||||
<textarea v-model="importText" placeholder="请粘贴备份 JSON"></textarea>
|
||||
</view>
|
||||
<view class="action-row">
|
||||
<view class="ghost-button" @click="importText = ''">清空内容</view>
|
||||
<view class="primary-button" @click="restoreBackup">开始恢复</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="数据操作" subtitle="谨慎执行不可撤销的本地清理操作">
|
||||
<view class="menu-list">
|
||||
<view class="menu-item surface-strong" @click="copyBackupText">
|
||||
<view>
|
||||
<text class="menu-title">复制备份内容</text>
|
||||
</view>
|
||||
<text class="status-chip">复制</text>
|
||||
</view>
|
||||
<view class="menu-item surface-strong danger-shell" @click="clearCache">
|
||||
<view>
|
||||
<text class="menu-title negative">清空全部数据</text>
|
||||
</view>
|
||||
<text class="danger-text">执行</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import SectionCard from '../../../components/SectionCard.vue'
|
||||
import { useAppStore } from '../../../utils/store'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
const store = useAppStore()
|
||||
const importText = ref('')
|
||||
const themeClass = computed(() => (store.state.settings.theme === 'dark' ? 'theme-dark' : ''))
|
||||
|
||||
function writeBackupFile(content) {
|
||||
const timeLabel = new Date().toLocaleString()
|
||||
if (typeof wx !== 'undefined' && wx.getFileSystemManager) {
|
||||
const filePath = `${wx.env.USER_DATA_PATH}/bill-helper-backup.json`
|
||||
wx.getFileSystemManager().writeFile({
|
||||
filePath,
|
||||
data: content,
|
||||
encoding: 'utf8',
|
||||
success: () => {
|
||||
store.markBackup(timeLabel)
|
||||
uni.showModal({ title: '备份成功', content: `文件已生成:${filePath}`, showCancel: false })
|
||||
},
|
||||
fail: () => {
|
||||
uni.setClipboardData({ data: content })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
store.markBackup(timeLabel)
|
||||
uni.setClipboardData({ data: content })
|
||||
}
|
||||
|
||||
function exportBackupFile() {
|
||||
writeBackupFile(store.exportBackup())
|
||||
}
|
||||
|
||||
function restoreBackup() {
|
||||
if (!importText.value.trim()) {
|
||||
uni.showToast({ title: '请先粘贴备份内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
store.importBackup(importText.value)
|
||||
importText.value = ''
|
||||
uni.showToast({ title: '备份恢复成功', icon: 'none' })
|
||||
} catch (error) {
|
||||
uni.showToast({ title: '备份内容无效', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function copyBackupText() {
|
||||
uni.setClipboardData({ data: store.exportBackup() })
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
uni.showModal({
|
||||
title: '清空全部数据',
|
||||
content: '确认删除当前设备中的账单、预算和设置吗?此操作不可撤销。',
|
||||
success: ({ confirm }) => {
|
||||
if (confirm) {
|
||||
store.resetAll()
|
||||
uni.showToast({ title: '本地数据已清空', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShow(()=>{
|
||||
showIt()
|
||||
})
|
||||
|
||||
function showIt(){
|
||||
let interstitialAd = null;
|
||||
if (wx.createInterstitialAd) {
|
||||
interstitialAd = wx.createInterstitialAd({
|
||||
adUnitId: 'adunit-0abc32053b19a4e9'
|
||||
})
|
||||
interstitialAd.onLoad(() => {})
|
||||
interstitialAd.onError((err) => {
|
||||
console.error('插屏广告加载失败', err)
|
||||
})
|
||||
interstitialAd.onClose(() => {})
|
||||
}
|
||||
|
||||
setTimeout(()=>{
|
||||
if (interstitialAd) {
|
||||
interstitialAd.show().catch((err) => {
|
||||
console.error('插屏广告显示失败', err)
|
||||
})
|
||||
}
|
||||
}, 2280)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-hero {
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(145deg, rgba(16, 42, 67, 0.96) 0%, rgba(61, 102, 178, 0.92) 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-kicker,
|
||||
.hero-desc,
|
||||
.hero-tag.soft {
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14rpx;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.hero-tag,
|
||||
.status-chip {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.hero-panel,
|
||||
.menu-item,
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.hero-panel,
|
||||
.menu-item {
|
||||
padding: 26rpx;
|
||||
border-radius: 28rpx;
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.panel-title,
|
||||
.menu-title {
|
||||
display: block;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.panel-desc,
|
||||
|
||||
.status-chip {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.action-row {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.single-row .primary-button,
|
||||
.action-row .ghost-button,
|
||||
.action-row .primary-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-line {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.textarea-shell {
|
||||
align-items: flex-start;
|
||||
min-height: 280rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.textarea-shell textarea {
|
||||
min-height: 240rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.danger-shell {
|
||||
border: 1rpx solid rgba(210, 85, 67, 0.12);
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
font-size: 24rpx;
|
||||
color: var(--danger);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<view class="app-page" :class="themeClass">
|
||||
<view class="surface-card page-hero">
|
||||
<text class="hero-kicker">GUIDE</text>
|
||||
<text class="hero-title">使用帮助</text>
|
||||
<text class="hero-desc">通过上手步骤、常见问题和快捷入口,快速熟悉整个记账流程。</text>
|
||||
<view class="hero-tags">
|
||||
<text class="hero-tag">4 个步骤</text>
|
||||
<text class="hero-tag soft">FAQ 指南</text>
|
||||
</view>
|
||||
</view>
|
||||
<ad-custom unit-id="adunit-64707ea333329399"></ad-custom>
|
||||
<section-card title="快速上手" subtitle="初次使用建议先完成下面 4 个动作">
|
||||
<view class="step-list">
|
||||
<view v-for="item in quickSteps" :key="item.title" class="step-item surface-strong">
|
||||
<text class="step-index">{{ item.index }}</text>
|
||||
<view class="step-body">
|
||||
<text class="step-title">{{ item.title }}</text>
|
||||
<text class="step-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="常见问题" subtitle="">
|
||||
<view class="faq-list">
|
||||
<view v-for="item in faqList" :key="item.q" class="faq-item surface-strong">
|
||||
<text class="faq-question">{{ item.q }}</text>
|
||||
<text class="faq-answer">{{ item.a }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="功能入口" subtitle="需要操作时可直接跳转到对应模块">
|
||||
<view class="entry-grid">
|
||||
<view class="entry-item surface-strong" @click="go('/pages/home/index')">
|
||||
<text class="entry-title">首页记账</text>
|
||||
</view>
|
||||
<view class="entry-item surface-strong" @click="go('/pages/budget/index')">
|
||||
<text class="entry-title">预算设置</text>
|
||||
</view>
|
||||
<view class="entry-item surface-strong" @click="go('/pages/stats/index')">
|
||||
<text class="entry-title">查看报表</text>
|
||||
</view>
|
||||
<view class="entry-item surface-strong" @click="go('/pages/mine/backup/index')">
|
||||
<text class="entry-title">备份恢复</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import SectionCard from '../../../components/SectionCard.vue'
|
||||
import { useAppStore } from '../../../utils/store'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
|
||||
const store = useAppStore()
|
||||
const themeClass = computed(() => (store.state.settings.theme === 'dark' ? 'theme-dark' : ''))
|
||||
|
||||
const quickSteps = [
|
||||
{ index: '01', title: '先设置月预算', desc: '进入预算页设置总预算与分类预算,首页会同步显示剩余额度。' },
|
||||
{ index: '02', title: '用首页快捷记账', desc: '首页支持支出/收入快速录入,也能通过常用分类一键记账。' },
|
||||
{ index: '03', title: '到账单页做筛选', desc: '账单页支持按月份、账户、金额区间和关键词精确筛选。' },
|
||||
{ index: '04', title: '每月查看报表', desc: '报表页可导出 CSV,并查看支出结构、近 7 日趋势和月度对比。' }
|
||||
]
|
||||
|
||||
const faqList = [
|
||||
{ q: '账单数据保存在哪里?', a: '默认仅保存在当前设备的本地存储中,不会自动上传云端。' },
|
||||
{ q: '换手机后如何迁移?', a: '先进入“备份与恢复”导出 JSON 备份,再在新设备粘贴恢复。' },
|
||||
{ q: '为什么预算进度显示超出 100%?', a: '这代表本月支出已经超过预算,条形进度会封顶,但文字会继续显示真实比例。' },
|
||||
{ q: '昵称可以怎么修改?', a: '进入“我的-账户资料”后可直接修改本地昵称,留空时默认显示为“用户”。' }
|
||||
]
|
||||
|
||||
function go(url) {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
onShow(()=>{
|
||||
showIt()
|
||||
})
|
||||
|
||||
function showIt(){
|
||||
let interstitialAd = null;
|
||||
if (wx.createInterstitialAd) {
|
||||
interstitialAd = wx.createInterstitialAd({
|
||||
adUnitId: 'adunit-0abc32053b19a4e9'
|
||||
})
|
||||
interstitialAd.onLoad(() => {})
|
||||
interstitialAd.onError((err) => {
|
||||
console.error('插屏广告加载失败', err)
|
||||
})
|
||||
interstitialAd.onClose(() => {})
|
||||
}
|
||||
|
||||
setTimeout(()=>{
|
||||
if (interstitialAd) {
|
||||
interstitialAd.show().catch((err) => {
|
||||
console.error('插屏广告显示失败', err)
|
||||
})
|
||||
}
|
||||
}, 2280)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-hero {
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(145deg, rgba(16, 42, 67, 0.96) 0%, rgba(127, 86, 217, 0.9) 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-kicker,
|
||||
.hero-desc,
|
||||
.hero-tag.soft {
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14rpx;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.step-list,
|
||||
.faq-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.step-item,
|
||||
.faq-item {
|
||||
padding: 24rpx;
|
||||
border-radius: 28rpx;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.step-index {
|
||||
width: 74rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.step-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title,
|
||||
.faq-question,
|
||||
.entry-title {
|
||||
display: block;
|
||||
font-size: 29rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.step-desc,
|
||||
.faq-answer {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.75;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.entry-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.entry-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 28rpx 20rpx;
|
||||
border-radius: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="app-page" :class="themeClass">
|
||||
<section-card title="账户概览" subtitle="集中管理本地昵称与主题设置">
|
||||
<view class="profile-card surface-strong" @click="go('/pages/mine/profile/index')">
|
||||
<view class="avatar-shell">{{ avatarText }}</view>
|
||||
<view class="profile-body">
|
||||
<text class="profile-name">{{ profileName }}</text>
|
||||
<text class="section-subtitle"></text>
|
||||
</view>
|
||||
<text class="arrow-text">{{right}}</text>
|
||||
</view>
|
||||
<view class="theme-row">
|
||||
<view class="pill-button" :class="{ active: store.state.settings.theme === 'light' }" @click.stop="setTheme('light')">浅色</view>
|
||||
<view class="pill-button" :class="{ active: store.state.settings.theme === 'dark' }" @click.stop="setTheme('dark')">深色</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<ad-custom unit-id="adunit-74730c6c27c95a37"></ad-custom>
|
||||
|
||||
<section-card title="数据管理" subtitle="备份、恢复和清理等高频操作统一收口">
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @click="go('/pages/mine/backup/index')">
|
||||
<view>
|
||||
<text class="menu-title">备份与恢复</text>
|
||||
</view>
|
||||
<text class="arrow-text">{{right}}</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/mine/guide/index')">
|
||||
<view>
|
||||
<text class="menu-title">使用帮助</text>
|
||||
</view>
|
||||
<text class="arrow-text">{{right}}</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/mine/about/index')">
|
||||
<view>
|
||||
<text class="menu-title">关于与隐私</text>
|
||||
</view>
|
||||
<text class="arrow-text">{{right}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
|
||||
|
||||
<app-tab-bar current="mine" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed,ref } from 'vue'
|
||||
import SectionCard from '../../components/SectionCard.vue'
|
||||
import AppTabBar from '../../components/AppTabBar.vue'
|
||||
import { useAppStore } from '../../utils/store'
|
||||
|
||||
const store = useAppStore()
|
||||
const themeClass = computed(() => (store.state.settings.theme === 'dark' ? 'theme-dark' : ''))
|
||||
const profileName = computed(() => store.state.settings.profile.nickname || '用户')
|
||||
const avatarText = computed(() => (store.state.settings.profile.nickname || '用户').slice(0, 1))
|
||||
|
||||
const right = ref(">")
|
||||
|
||||
function setTheme(theme) {
|
||||
store.setTheme(theme)
|
||||
}
|
||||
|
||||
function go(url) {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.profile-card,
|
||||
.theme-row,
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
padding: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.avatar-shell {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-accent);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.profile-name,
|
||||
.menu-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.arrow-text {
|
||||
font-size: 32rpx;
|
||||
// color: var(--brand);
|
||||
|
||||
}
|
||||
|
||||
.theme-row {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.theme-row .pill-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-list,
|
||||
.action-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
justify-content: space-between;
|
||||
padding: 22rpx;
|
||||
border-radius: 24rpx;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<view class="app-page" :class="themeClass">
|
||||
<view class="surface-card page-hero">
|
||||
<text class="hero-kicker">ACCOUNT</text>
|
||||
<text class="hero-title">账户资料</text>
|
||||
<text class="hero-desc">管理本地昵称、显示资料与本机记账模式说明。</text>
|
||||
<view class="hero-tags">
|
||||
<text class="hero-tag">本地资料</text>
|
||||
<text class="hero-tag soft">本地存储</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<section-card title="昵称设置" subtitle="修改后仅用于个人页展示和首字头像,不参与账单计算">
|
||||
<view class="profile-card surface-strong">
|
||||
<view class="avatar-shell">{{ avatarText }}</view>
|
||||
<view class="profile-body">
|
||||
<text class="profile-name">{{ profileName }}</text>
|
||||
<text class="profile-meta">当前昵称仅保存在本地设备,可随时修改。</text>
|
||||
</view>
|
||||
<view class="status-badge">本地</view>
|
||||
</view>
|
||||
<view class="editor-block">
|
||||
<view class="input-shell">
|
||||
<input v-model="nicknameInput" maxlength="12" placeholder="请输入昵称" />
|
||||
</view>
|
||||
<text class="tiny-text editor-tip">留空时页面会统一显示“用户”。</text>
|
||||
</view>
|
||||
<view class="action-row">
|
||||
<view class="ghost-button" @click="clearNickname">清空昵称</view>
|
||||
<view class="primary-button" @click="saveNickname">保存昵称</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="显示与模式" subtitle="集中展示当前账户页的生效状态">
|
||||
<view class="info-list">
|
||||
<view class="info-item surface-strong">
|
||||
<view>
|
||||
<text class="info-title">昵称首字头像</text>
|
||||
<text class="info-desc">当前显示 {{ avatarText }},自动根据昵称生成</text>
|
||||
</view>
|
||||
<text class="info-mark">已启用</text>
|
||||
</view>
|
||||
<view class="info-item surface-strong">
|
||||
<view>
|
||||
<text class="info-title">本机记账模式</text>
|
||||
<text class="info-desc">账单与预算默认仅保存在当前设备本地</text>
|
||||
</view>
|
||||
<text class="info-mark">默认</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
|
||||
<section-card title="使用提示" subtitle="帮助用户理解昵称显示与数据边界">
|
||||
<view class="tips-card surface-strong">
|
||||
<view v-for="(tip, index) in tips" :key="tip" class="tip-row">
|
||||
<text class="tip-index">0{{ index + 1 }}</text>
|
||||
<text class="tip-line">{{ tip }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</section-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import SectionCard from '../../../components/SectionCard.vue'
|
||||
import { useAppStore } from '../../../utils/store'
|
||||
|
||||
const store = useAppStore()
|
||||
const themeClass = computed(() => (store.state.settings.theme === 'dark' ? 'theme-dark' : ''))
|
||||
const profileName = computed(() => store.state.settings.profile.nickname || '用户')
|
||||
const avatarText = computed(() => profileName.value.slice(0, 1))
|
||||
const nicknameInput = ref(store.state.settings.profile.nickname || '')
|
||||
const tips = [
|
||||
'昵称仅用于个人页展示和首字头像,不参与账单计算。',
|
||||
'账单、预算和设置默认不会自动上传云端。',
|
||||
'如需更换设备,请先在“备份与恢复”页面导出 JSON 备份。'
|
||||
]
|
||||
|
||||
watch(
|
||||
() => store.state.settings.profile.nickname,
|
||||
(value) => {
|
||||
nicknameInput.value = value || ''
|
||||
}
|
||||
)
|
||||
|
||||
function saveNickname() {
|
||||
const nextName = nicknameInput.value.trim()
|
||||
store.setProfile({
|
||||
authorized: false,
|
||||
nickname: nextName,
|
||||
avatarUrl: ''
|
||||
})
|
||||
uni.showToast({ title: '昵称已保存', icon: 'none' })
|
||||
}
|
||||
|
||||
function clearNickname() {
|
||||
nicknameInput.value = ''
|
||||
store.setProfile({
|
||||
authorized: false,
|
||||
nickname: '',
|
||||
avatarUrl: ''
|
||||
})
|
||||
uni.showToast({ title: '昵称已清空', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-hero {
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(145deg, rgba(16, 42, 67, 0.96) 0%, rgba(31, 111, 95, 0.92) 100%);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hero-kicker,
|
||||
.hero-desc,
|
||||
.hero-tag.soft {
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.hero-kicker {
|
||||
font-size: 20rpx;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14rpx;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.hero-tag {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.profile-card,
|
||||
.info-item,
|
||||
.action-row,
|
||||
.tip-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.profile-card,
|
||||
.tips-card {
|
||||
padding: 26rpx;
|
||||
border-radius: 28rpx;
|
||||
}
|
||||
|
||||
.avatar-shell {
|
||||
width: 108rpx;
|
||||
height: 108rpx;
|
||||
border-radius: 32rpx;
|
||||
background: var(--bg-accent);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 18rpx 32rpx rgba(16, 42, 67, 0.16);
|
||||
}
|
||||
|
||||
.profile-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.profile-name,
|
||||
.info-title {
|
||||
display: block;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.profile-meta,
|
||||
.info-desc,
|
||||
.tip-line {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.status-badge,
|
||||
.info-mark {
|
||||
padding: 10rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: var(--brand-soft);
|
||||
font-size: 22rpx;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.editor-block {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.editor-tip {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.action-row .ghost-button,
|
||||
.action-row .primary-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
border-radius: 26rpx;
|
||||
}
|
||||
|
||||
.tip-row {
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
}
|
||||
|
||||
.tip-index {
|
||||
width: 56rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: var(--brand);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user