完善版本
This commit is contained in:
@@ -89,4 +89,19 @@ class StatisticsController extends BaseController
|
||||
|
||||
return response()->json($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人中心统计数据
|
||||
*/
|
||||
public function dashboard(Request $request)
|
||||
{
|
||||
try {
|
||||
$this->data['data'] = $this->statisticsService->getDashboardStats($request);
|
||||
} catch (\Throwable $th) {
|
||||
$this->data['code'] = 0;
|
||||
$this->data['message'] = $th->getMessage();
|
||||
}
|
||||
|
||||
return response()->json($this->data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,21 +21,22 @@ class BillService
|
||||
$month = $request->input('month');
|
||||
$startDate = $request->input('start_date');
|
||||
$endDate = $request->input('end_date');
|
||||
$familyId = $request->input('family_id');
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
$query = Bill::with(['user:uid,nickname,username', 'family:id,name']);
|
||||
|
||||
// 如果用户加入了家庭,且没有指定family_id,则显示家庭账单
|
||||
if ($userFamilyId && !$familyId) {
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} elseif ($familyId) {
|
||||
// 如果指定了family_id,则显示该家庭的账单
|
||||
$query->where('family_id', $familyId);
|
||||
} else {
|
||||
// 否则显示个人账单
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
@@ -66,13 +67,13 @@ class BillService
|
||||
$monthExpense = Bill::query();
|
||||
$monthIncome = Bill::query();
|
||||
|
||||
// 同样的家庭查询逻辑
|
||||
if ($userFamilyId && !$familyId) {
|
||||
// 同样的数据类型查询逻辑
|
||||
if ($dataType === 'personal') {
|
||||
$monthExpense->where('user_id', $userId);
|
||||
$monthIncome->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
$monthExpense->where('family_id', $userFamilyId);
|
||||
$monthIncome->where('family_id', $userFamilyId);
|
||||
} elseif ($familyId) {
|
||||
$monthExpense->where('family_id', $familyId);
|
||||
$monthIncome->where('family_id', $familyId);
|
||||
} else {
|
||||
$monthExpense->where('user_id', $userId);
|
||||
$monthIncome->where('user_id', $userId);
|
||||
@@ -175,8 +176,14 @@ class BillService
|
||||
$data['category'] = $categoryMap[$data['category_id']];
|
||||
$data['bill_date'] = $data['date'];
|
||||
|
||||
// 检查家庭权限
|
||||
if (!empty($data['family_id'])) {
|
||||
// 如果前端没有指定family_id,自动获取用户所在的家庭
|
||||
if (empty($data['family_id'])) {
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
if ($userFamilyId) {
|
||||
$data['family_id'] = $userFamilyId;
|
||||
}
|
||||
} else {
|
||||
// 检查家庭权限
|
||||
$this->checkFamilyAccess($userId, $data['family_id']);
|
||||
}
|
||||
|
||||
@@ -233,17 +240,29 @@ class BillService
|
||||
$data = $request->validate([
|
||||
'type' => 'required|in:income,expense',
|
||||
'amount' => 'required|numeric|min:0.01',
|
||||
'category' => 'required|string|max:50',
|
||||
'category_id' => 'required|integer',
|
||||
'remark' => 'nullable|string|max:255',
|
||||
'bill_date' => 'required|date',
|
||||
'date' => 'required|date',
|
||||
'family_id' => 'nullable|integer|exists:account_families,id'
|
||||
]);
|
||||
|
||||
// 将category_id转换为category字符串
|
||||
$categoryMap = $this->getCategoryMap($data['type']);
|
||||
if (!isset($categoryMap[$data['category_id']])) {
|
||||
throw new \Exception('分类不存在');
|
||||
}
|
||||
|
||||
$data['category'] = $categoryMap[$data['category_id']];
|
||||
$data['bill_date'] = $data['date'];
|
||||
|
||||
// 检查家庭权限
|
||||
if (!empty($data['family_id'])) {
|
||||
$this->checkFamilyAccess($userId, $data['family_id']);
|
||||
}
|
||||
|
||||
// 移除前端字段
|
||||
unset($data['category_id'], $data['date']);
|
||||
|
||||
$bill->update($data);
|
||||
|
||||
return $bill;
|
||||
@@ -278,12 +297,25 @@ class BillService
|
||||
$bill = Bill::with(['user:uid,nickname,username', 'family:id,name,owner_id'])
|
||||
->findOrFail($id);
|
||||
|
||||
// 验证权限
|
||||
if ($bill->user_id != $userId && !in_array($userId, $bill->family->members->pluck('uid')->toArray())) {
|
||||
// 验证权限:只有账单创建者才能查看和编辑
|
||||
if ($bill->user_id != $userId) {
|
||||
throw new \Exception('无权查看此账单');
|
||||
}
|
||||
|
||||
return $bill;
|
||||
// 转换数据格式以匹配前端
|
||||
return [
|
||||
'id' => $bill->id,
|
||||
'type' => $bill->type,
|
||||
'amount' => (float)$bill->amount,
|
||||
'category' => $bill->category,
|
||||
'category_id' => $this->getCategoryId($bill->category, $bill->type),
|
||||
'remark' => $bill->remark,
|
||||
'date' => $bill->bill_date,
|
||||
'bill_date' => $bill->bill_date,
|
||||
'created_at' => $bill->created_at->format('Y-m-d H:i:s'),
|
||||
'user' => $bill->user,
|
||||
'family' => $bill->family
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,9 +17,10 @@ class StatisticsService
|
||||
$userId = auth('api')->user()['uid'];
|
||||
$year = $request->input('year');
|
||||
$month = $request->input('month');
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$familyId = $this->getUserFamilyId($userId);
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
// 查询当月的账单
|
||||
$query = Bill::query();
|
||||
@@ -32,9 +33,15 @@ class StatisticsService
|
||||
->whereYear('bill_date', date('Y'));
|
||||
}
|
||||
|
||||
if ($familyId) {
|
||||
$query->where('family_id', $familyId);
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
@@ -59,9 +66,10 @@ class StatisticsService
|
||||
$year = $request->input('year');
|
||||
$month = $request->input('month');
|
||||
$days = 7; // 最近7天
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$familyId = $this->getUserFamilyId($userId);
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
$data = [];
|
||||
for ($i = $days - 1; $i >= 0; $i--) {
|
||||
@@ -69,14 +77,19 @@ class StatisticsService
|
||||
|
||||
$query = Bill::whereDate('bill_date', $date);
|
||||
|
||||
if ($familyId) {
|
||||
$query->where('family_id', $familyId);
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$bills = $query->get();
|
||||
|
||||
$data[] = [
|
||||
'date' => $date,
|
||||
'income' => (float) $bills->where('type', 'income')->sum('amount'),
|
||||
@@ -96,9 +109,10 @@ class StatisticsService
|
||||
$type = $request->input('type', 'expense');
|
||||
$year = $request->input('year');
|
||||
$month = $request->input('month');
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$familyId = $this->getUserFamilyId($userId);
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
$query = Bill::where('type', $type);
|
||||
|
||||
@@ -110,16 +124,22 @@ class StatisticsService
|
||||
->whereYear('bill_date', date('Y'));
|
||||
}
|
||||
|
||||
if ($familyId) {
|
||||
$query->where('family_id', $familyId);
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$bills = $query->get();
|
||||
|
||||
// 按分类汇总
|
||||
$data = $bills->groupBy('category')->map(function ($items) {
|
||||
$data = $bills->groupBy('category')->map(function ($items) use ($type) {
|
||||
return [
|
||||
'category_id' => $this->getCategoryId($items->first()->category, $type),
|
||||
'amount' => (float) $items->sum('amount')
|
||||
@@ -153,18 +173,25 @@ class StatisticsService
|
||||
{
|
||||
$userId = auth('api')->user()['uid'];
|
||||
$year = $request->input('year', date('Y'));
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$familyId = $this->getUserFamilyId($userId);
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
$data = [];
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$query = Bill::whereMonth('bill_date', $month)
|
||||
->whereYear('bill_date', $year);
|
||||
|
||||
if ($familyId) {
|
||||
$query->where('family_id', $familyId);
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
@@ -191,9 +218,10 @@ class StatisticsService
|
||||
{
|
||||
$userId = auth('api')->user()['uid'];
|
||||
$years = $request->input('years', 5);
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$familyId = $this->getUserFamilyId($userId);
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
$data = [];
|
||||
for ($i = $years - 1; $i >= 0; $i--) {
|
||||
@@ -201,9 +229,15 @@ class StatisticsService
|
||||
|
||||
$query = Bill::whereYear('bill_date', $year);
|
||||
|
||||
if ($familyId) {
|
||||
$query->where('family_id', $familyId);
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只显示当前用户的账单
|
||||
$query->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:显示用户所在家庭的账单
|
||||
$query->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认显示个人数据
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
@@ -228,4 +262,55 @@ class StatisticsService
|
||||
$familyMember = FamilyMember::where('user_id', $userId)->first();
|
||||
return $familyMember ? $familyMember->family_id : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人中心统计数据
|
||||
*/
|
||||
public function getDashboardStats(Request $request)
|
||||
{
|
||||
$userId = auth('api')->user()['uid'];
|
||||
$dataType = $request->input('data_type', 'family'); // 默认为家庭数据
|
||||
|
||||
// 获取用户所在的家庭ID
|
||||
$userFamilyId = $this->getUserFamilyId($userId);
|
||||
|
||||
// 统计账单数
|
||||
$billQuery = Bill::query();
|
||||
|
||||
// 根据data_type参数判断数据类型
|
||||
if ($dataType === 'personal') {
|
||||
// 个人数据:只统计当前用户的账单
|
||||
$billQuery->where('user_id', $userId);
|
||||
} elseif ($dataType === 'family' && $userFamilyId) {
|
||||
// 家庭数据:统计用户所在家庭的账单
|
||||
$billQuery->where('family_id', $userFamilyId);
|
||||
} else {
|
||||
// 默认统计个人数据
|
||||
$billQuery->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$billCount = $billQuery->count();
|
||||
|
||||
// 统计记账天数(计算从第一个账单到现在的天数)
|
||||
$days = 0;
|
||||
$firstBill = $billQuery->orderBy('bill_date', 'asc')->first();
|
||||
if ($firstBill) {
|
||||
$firstDate = new \DateTime($firstBill->bill_date);
|
||||
$now = new \DateTime();
|
||||
$interval = $firstDate->diff($now);
|
||||
$days = $interval->days + 1; // +1 包含当天
|
||||
}
|
||||
|
||||
// 统计家庭成员数
|
||||
$familyMembers = 0;
|
||||
if ($userFamilyId) {
|
||||
$familyMembers = FamilyMember::where('family_id', $userFamilyId)->count();
|
||||
}
|
||||
|
||||
return [
|
||||
'bill_count' => $billCount,
|
||||
'days' => $days,
|
||||
'family_members' => $familyMembers
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,5 +44,6 @@ Route::middleware(['auth:api'])->group(function () {
|
||||
Route::get('category', [StatisticsController::class, 'category']);
|
||||
Route::get('monthly', [StatisticsController::class, 'monthly']);
|
||||
Route::get('yearly', [StatisticsController::class, 'yearly']);
|
||||
Route::get('dashboard', [StatisticsController::class, 'dashboard']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,5 +28,12 @@ export default {
|
||||
get: async function(params) {
|
||||
return request.get(this.url, {params: params})
|
||||
}
|
||||
},
|
||||
update: {
|
||||
url: '/api/member/update',
|
||||
name: '更新用户信息',
|
||||
post: async function(params) {
|
||||
return request.post(this.url, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
sendCode: {
|
||||
url: '/api/member/sms/send',
|
||||
name: '发送验证码',
|
||||
upload: {
|
||||
url: '/api/system/upload',
|
||||
name: '图片上传',
|
||||
post: async function(params) {
|
||||
return request.post(this.url, params)
|
||||
}
|
||||
18
resources/mobile/api/modules/member.js
Normal file
18
resources/mobile/api/modules/member.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
edit: {
|
||||
url: '/api/member/member/edit',
|
||||
name: '用户修改',
|
||||
post: async function(params) {
|
||||
return request.put(this.url, params)
|
||||
}
|
||||
},
|
||||
editpasswd: {
|
||||
url: '/api/member/member/editpasswd',
|
||||
name: '密码修改',
|
||||
post: async function(params) {
|
||||
return request.put(this.url, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,13 @@ export default {
|
||||
get: async function(params) {
|
||||
return request.get(this.url, {params: params})
|
||||
}
|
||||
},
|
||||
// 获取个人中心统计数据
|
||||
dashboard: {
|
||||
url: '/api/account/statistics/dashboard',
|
||||
name: '个人中心统计',
|
||||
get: async function(params) {
|
||||
return request.get(this.url, {params: params})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,26 @@
|
||||
{
|
||||
"path": "pages/ucenter/register/index"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/profile/index"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/profile/edit"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/profile/password"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/agreement/user"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/agreement/privacy"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/about/index"
|
||||
},
|
||||
{
|
||||
"path": "pages/ucenter/help/index"
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="记一笔"
|
||||
:nav-bar-title="pageTitle"
|
||||
:show-back="true"
|
||||
>
|
||||
<view class="page-content">
|
||||
<!-- 家庭提示 -->
|
||||
<view v-if="familyInfo" class="family-tip">
|
||||
<uni-icons type="home" size="16" color="#667eea"></uni-icons>
|
||||
<text class="tip-text">记录到家庭: {{ familyInfo.name }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 收支类型切换 -->
|
||||
<view class="type-switch">
|
||||
<view
|
||||
@@ -97,6 +103,10 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
billId: null,
|
||||
isEdit: false,
|
||||
pageTitle: '记一笔',
|
||||
familyInfo: null,
|
||||
form: {
|
||||
type: 'expense',
|
||||
amount: '',
|
||||
@@ -128,7 +138,76 @@ export default {
|
||||
return this.form.type === 'expense' ? this.expenseCategories : this.incomeCategories
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 检查是否为编辑模式
|
||||
if (options.id) {
|
||||
this.billId = parseInt(options.id)
|
||||
this.isEdit = true
|
||||
this.pageTitle = '编辑账单'
|
||||
this.loadBillDetail()
|
||||
}
|
||||
|
||||
this.loadFamilyInfo()
|
||||
},
|
||||
methods: {
|
||||
async loadFamilyInfo() {
|
||||
try {
|
||||
const res = await this.$store.dispatch('getFamilyInfo')
|
||||
if (res && res.code === 1 && res.data) {
|
||||
this.familyInfo = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取家庭信息失败', error)
|
||||
}
|
||||
},
|
||||
async loadBillDetail() {
|
||||
if (!this.billId) return
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await this.$api.bill.detail.get({ id: this.billId })
|
||||
if (res && res.code === 1 && res.data) {
|
||||
const bill = res.data
|
||||
this.form.type = bill.type
|
||||
this.form.amount = bill.amount.toString()
|
||||
this.form.categoryId = this.getCategoryIdByCategoryName(bill.category, bill.type)
|
||||
this.form.remark = bill.remark || ''
|
||||
this.form.date = bill.bill_date
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载账单详情失败', error)
|
||||
uni.showToast({
|
||||
title: error?.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
getCategoryIdByCategoryName(categoryName, type) {
|
||||
if (type === 'income') {
|
||||
const map = {
|
||||
'工资': 101,
|
||||
'奖金': 102,
|
||||
'投资': 103,
|
||||
'兼职': 104,
|
||||
'其他': 105
|
||||
}
|
||||
return map[categoryName] || 105
|
||||
} else {
|
||||
const map = {
|
||||
'餐饮': 1,
|
||||
'交通': 2,
|
||||
'购物': 3,
|
||||
'娱乐': 4,
|
||||
'医疗': 5,
|
||||
'教育': 6,
|
||||
'居住': 7,
|
||||
'其他': 8
|
||||
}
|
||||
return map[categoryName] || 8
|
||||
}
|
||||
},
|
||||
getTodayDate() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
@@ -185,13 +264,22 @@ export default {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const res = await this.$api.bill.add.post({
|
||||
// 根据模式调用不同的接口
|
||||
const apiUrl = this.isEdit ? 'edit' : 'add'
|
||||
const params = {
|
||||
type: this.form.type,
|
||||
amount: parseFloat(this.form.amount),
|
||||
category_id: this.form.categoryId,
|
||||
remark: this.form.remark || undefined,
|
||||
date: this.form.date
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑时需要传递ID
|
||||
if (this.isEdit) {
|
||||
params.id = this.billId
|
||||
}
|
||||
|
||||
const res = await this.$api.bill[apiUrl].post(params)
|
||||
|
||||
if (res && res.code === 1) {
|
||||
uni.showToast({
|
||||
@@ -226,6 +314,23 @@ export default {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.family-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%);
|
||||
border: 2rpx solid rgba(102, 126, 234, 0.18);
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.tip-text {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.type-switch {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
|
||||
@@ -7,6 +7,26 @@
|
||||
@tab-change="handleTabChange"
|
||||
>
|
||||
<view class="page-content">
|
||||
<!-- 数据类型切换 -->
|
||||
<view v-if="hasFamily" class="data-type-switch">
|
||||
<view
|
||||
class="switch-item"
|
||||
:class="{active: dataType === 'family'}"
|
||||
@tap="switchDataType('family')"
|
||||
>
|
||||
<uni-icons type="home" size="16" :color="dataType === 'family' ? '#fff' : '#999'"></uni-icons>
|
||||
<text>家庭</text>
|
||||
</view>
|
||||
<view
|
||||
class="switch-item"
|
||||
:class="{active: dataType === 'personal'}"
|
||||
@tap="switchDataType('personal')"
|
||||
>
|
||||
<uni-icons type="person" size="16" :color="dataType === 'personal' ? '#fff' : '#999'"></uni-icons>
|
||||
<text>个人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<view class="stat-item">
|
||||
@@ -32,40 +52,57 @@
|
||||
</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">{{ group.formattedDate }}</text>
|
||||
<text class="group-amount">支出: ¥{{ group.expense.toFixed(2) }} 收入: ¥{{ group.income.toFixed(2) }}</text>
|
||||
<scroll-view
|
||||
class="bill-scroll"
|
||||
scroll-y
|
||||
@scrolltolower="loadMore"
|
||||
lower-threshold="100"
|
||||
>
|
||||
<view class="bill-list-content">
|
||||
<view v-if="loading && billList.length === 0" class="loading-state">
|
||||
<uni-load-more status="loading"></uni-load-more>
|
||||
</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 v-else-if="billList.length === 0 && !loading" 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>
|
||||
<view class="bill-group" v-for="(group, date) in groupedBills" :key="date">
|
||||
<view class="group-header">
|
||||
<text class="group-date">{{ group.formattedDate }}</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 v-if="billList.length > 0" class="load-more-state">
|
||||
<uni-load-more
|
||||
:status="loadMoreStatus"
|
||||
:content-text="loadMoreText"
|
||||
></uni-load-more>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view class="fab-button" @tap="addBill">
|
||||
@@ -86,6 +123,17 @@ export default {
|
||||
billList: [],
|
||||
monthExpense: 0,
|
||||
monthIncome: 0,
|
||||
dataType: 'family', // family 或 personal
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
hasMore: true,
|
||||
loadMoreStatus: 'more', // more, loading, noMore
|
||||
loadMoreText: {
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
},
|
||||
categoryMap: {
|
||||
// 支出分类
|
||||
1: { name: '餐饮', icon: '🍜', color: '#FF6B6B' },
|
||||
@@ -106,6 +154,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFamily() {
|
||||
return this.$store.getters.hasFamily
|
||||
},
|
||||
groupedBills() {
|
||||
const groups = {}
|
||||
this.billList.forEach(bill => {
|
||||
@@ -129,10 +180,11 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.initDataType()
|
||||
this.loadBillList()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.loadBillList().then(() => {
|
||||
this.loadBillList(true).then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
@@ -140,6 +192,14 @@ export default {
|
||||
handleTabChange(path) {
|
||||
console.log('Tab changed to:', path)
|
||||
},
|
||||
initDataType() {
|
||||
// 如果用户加入了家庭,默认显示家庭数据
|
||||
this.dataType = this.hasFamily ? 'family' : 'personal'
|
||||
},
|
||||
switchDataType(type) {
|
||||
this.dataType = type
|
||||
this.loadBillList(true) // 切换数据类型时刷新
|
||||
},
|
||||
getCurrentMonth() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
@@ -148,21 +208,64 @@ export default {
|
||||
},
|
||||
onMonthChange(e) {
|
||||
this.currentMonth = e.detail.value
|
||||
this.loadBillList()
|
||||
this.loadBillList(true) // 切换月份时刷新
|
||||
},
|
||||
async loadBillList() {
|
||||
async loadBillList(refresh = false) {
|
||||
// 如果是刷新,重置页码
|
||||
if (refresh) {
|
||||
this.currentPage = 1
|
||||
this.hasMore = true
|
||||
this.loadMoreStatus = 'more'
|
||||
}
|
||||
|
||||
// 如果正在加载或没有更多数据,则不执行
|
||||
if (this.loading || !this.hasMore) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
if (!refresh) {
|
||||
this.loadMoreStatus = 'loading'
|
||||
}
|
||||
|
||||
try {
|
||||
const [year, month] = this.currentMonth.split('-')
|
||||
const res = await this.$api.bill.list.get({
|
||||
const params = {
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
month: parseInt(month),
|
||||
page: this.currentPage,
|
||||
limit: this.pageSize,
|
||||
data_type: this.dataType
|
||||
}
|
||||
|
||||
const res = await this.$api.bill.list.get(params)
|
||||
|
||||
if (res && res.code === 1) {
|
||||
this.billList = res.data?.list || []
|
||||
const list = res.data?.list || []
|
||||
const pagination = res.data?.pagination || {}
|
||||
|
||||
if (refresh) {
|
||||
// 刷新:替换数据
|
||||
this.billList = list
|
||||
} else {
|
||||
// 加载更多:追加数据
|
||||
this.billList = [...this.billList, ...list]
|
||||
}
|
||||
|
||||
this.monthExpense = res.data?.month_expense || 0
|
||||
this.monthIncome = res.data?.month_income || 0
|
||||
this.total = pagination.total || 0
|
||||
|
||||
// 判断是否还有更多数据
|
||||
const totalPages = pagination.last_page || 1
|
||||
this.hasMore = this.currentPage < totalPages
|
||||
|
||||
if (this.hasMore) {
|
||||
this.currentPage++
|
||||
this.loadMoreStatus = 'more'
|
||||
} else {
|
||||
this.loadMoreStatus = 'noMore'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载账单列表失败', error)
|
||||
@@ -170,20 +273,23 @@ export default {
|
||||
title: error?.message || '加载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
this.loadMoreStatus = 'more'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
loadMore() {
|
||||
this.loadBillList(false)
|
||||
},
|
||||
addBill() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/account/bill/add'
|
||||
})
|
||||
},
|
||||
editBill(bill) {
|
||||
// TODO: 实现编辑功能
|
||||
uni.showToast({
|
||||
title: '编辑功能开发中',
|
||||
icon: 'none'
|
||||
// 跳转到编辑页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/account/bill/add?id=${bill.id}`
|
||||
})
|
||||
},
|
||||
getCategoryName(categoryId) {
|
||||
@@ -204,10 +310,58 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30rpx;
|
||||
padding-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.bill-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.bill-list-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.data-type-switch {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.switch-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx 0;
|
||||
border-radius: 50rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
uni-icons {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
display: flex;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
@@ -272,45 +426,48 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.bill-list {
|
||||
.loading-state {
|
||||
padding: 60rpx 0;
|
||||
.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;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
.add-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
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);
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin: 30rpx 0 40rpx;
|
||||
uni-icons {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
&::after {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more-state {
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bill-group {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
|
||||
@@ -7,6 +7,26 @@
|
||||
@tab-change="handleTabChange"
|
||||
>
|
||||
<view class="page-content">
|
||||
<!-- 数据类型切换 -->
|
||||
<view v-if="hasFamily" class="data-type-switch">
|
||||
<view
|
||||
class="switch-item"
|
||||
:class="{active: dataType === 'family'}"
|
||||
@tap="switchDataType('family')"
|
||||
>
|
||||
<uni-icons type="home" size="16" :color="dataType === 'family' ? '#fff' : '#999'"></uni-icons>
|
||||
<text>家庭</text>
|
||||
</view>
|
||||
<view
|
||||
class="switch-item"
|
||||
:class="{active: dataType === 'personal'}"
|
||||
@tap="switchDataType('personal')"
|
||||
>
|
||||
<uni-icons type="person" size="16" :color="dataType === 'personal' ? '#fff' : '#999'"></uni-icons>
|
||||
<text>个人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 月份选择 -->
|
||||
<view class="month-selector">
|
||||
<picker mode="date" fields="month" :value="currentMonth" @change="onMonthChange">
|
||||
@@ -95,11 +115,13 @@
|
||||
<view class="bar-wrapper">
|
||||
<view
|
||||
class="bar income-bar"
|
||||
:style="{height: item.incomeHeight + '%'}"
|
||||
:style="{height: (item.incomeHeight || 0) + '%'}"
|
||||
:title="'收入: ' + item.income"
|
||||
></view>
|
||||
<view
|
||||
class="bar expense-bar"
|
||||
:style="{height: item.expenseHeight + '%'}"
|
||||
:style="{height: (item.expenseHeight || 0) + '%'}"
|
||||
:title="'支出: ' + item.expense"
|
||||
></view>
|
||||
</view>
|
||||
<text class="bar-label">{{ item.dateLabel }}</text>
|
||||
@@ -129,6 +151,7 @@ export default {
|
||||
return {
|
||||
loading: false,
|
||||
currentMonth: this.getCurrentMonth(),
|
||||
dataType: 'family', // family 或 personal
|
||||
overview: {
|
||||
income: 0,
|
||||
expense: 0,
|
||||
@@ -149,7 +172,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFamily() {
|
||||
return this.$store.getters.hasFamily
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.initDataType()
|
||||
this.loadStatistics()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
@@ -161,6 +190,14 @@ export default {
|
||||
handleTabChange(path) {
|
||||
console.log('Tab changed to:', path)
|
||||
},
|
||||
initDataType() {
|
||||
// 如果用户加入了家庭,默认显示家庭数据
|
||||
this.dataType = this.hasFamily ? 'family' : 'personal'
|
||||
},
|
||||
switchDataType(type) {
|
||||
this.dataType = type
|
||||
this.loadStatistics()
|
||||
},
|
||||
getCurrentMonth() {
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
@@ -177,11 +214,14 @@ export default {
|
||||
const [year, month] = this.currentMonth.split('-')
|
||||
console.log('加载统计数据 - 年:', year, '月:', month)
|
||||
|
||||
// 获取统计概览
|
||||
const overviewRes = await this.$api.statistics.overview.get({
|
||||
const baseParams = {
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
month: parseInt(month),
|
||||
data_type: this.dataType // 使用data_type参数:personal 或 family
|
||||
}
|
||||
|
||||
// 获取统计概览
|
||||
const overviewRes = await this.$api.statistics.overview.get(baseParams)
|
||||
console.log('概览接口返回:', overviewRes)
|
||||
if (overviewRes && overviewRes.code === 1) {
|
||||
this.overview = overviewRes.data || { income: 0, expense: 0, balance: 0 }
|
||||
@@ -191,10 +231,7 @@ export default {
|
||||
}
|
||||
|
||||
// 获取分类统计
|
||||
const categoryRes = await this.$api.statistics.category.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
const categoryRes = await this.$api.statistics.category.get(baseParams)
|
||||
console.log('分类接口返回:', categoryRes)
|
||||
if (categoryRes && categoryRes.code === 1) {
|
||||
this.processCategoryData(categoryRes.data || [])
|
||||
@@ -204,10 +241,7 @@ export default {
|
||||
}
|
||||
|
||||
// 获取收支趋势
|
||||
const trendRes = await this.$api.statistics.trend.get({
|
||||
year: parseInt(year),
|
||||
month: parseInt(month)
|
||||
})
|
||||
const trendRes = await this.$api.statistics.trend.get(baseParams)
|
||||
console.log('趋势接口返回:', trendRes)
|
||||
if (trendRes && trendRes.code === 1) {
|
||||
this.processTrendData(trendRes.data || [])
|
||||
@@ -246,6 +280,11 @@ export default {
|
||||
},
|
||||
processTrendData(data) {
|
||||
console.log('处理趋势数据:', data)
|
||||
if (!data || data.length === 0) {
|
||||
this.trendList = []
|
||||
return
|
||||
}
|
||||
|
||||
// 找出最大值用于计算高度百分比
|
||||
let maxIncome = 0
|
||||
let maxExpense = 0
|
||||
@@ -255,16 +294,36 @@ export default {
|
||||
maxExpense = Math.max(maxExpense, item.expense)
|
||||
})
|
||||
|
||||
console.log('最大收入:', maxIncome, '最大支出:', maxExpense)
|
||||
|
||||
this.trendList = data.map(item => {
|
||||
const dateLabel = this.formatDate(item.date, 'MM/dd')
|
||||
|
||||
// 确保高度至少为1,避免柱状图完全不可见
|
||||
let incomeHeight = 0
|
||||
let expenseHeight = 0
|
||||
|
||||
if (maxIncome > 0) {
|
||||
incomeHeight = (item.income / maxIncome) * 100
|
||||
if (item.income > 0 && incomeHeight < 1) {
|
||||
incomeHeight = 1
|
||||
}
|
||||
}
|
||||
|
||||
if (maxExpense > 0) {
|
||||
expenseHeight = (item.expense / maxExpense) * 100
|
||||
if (item.expense > 0 && expenseHeight < 1) {
|
||||
expenseHeight = 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
incomeHeight,
|
||||
expenseHeight
|
||||
}
|
||||
})
|
||||
console.log('处理后的趋势列表:', this.trendList)
|
||||
@@ -282,6 +341,42 @@ export default {
|
||||
padding-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.data-type-switch {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50rpx;
|
||||
padding: 8rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.switch-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx 0;
|
||||
border-radius: 50rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
uni-icons {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.month-selector {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -497,43 +592,59 @@ export default {
|
||||
.trend-chart {
|
||||
.chart-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
align-items: flex-end;
|
||||
height: 300rpx;
|
||||
padding: 20rpx 0;
|
||||
margin-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
|
||||
.chart-bar {
|
||||
flex: 1;
|
||||
flex: 0 0 auto;
|
||||
width: 48rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
|
||||
.bar-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 4rpx;
|
||||
gap: 6rpx;
|
||||
margin-bottom: 12rpx;
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
position: relative;
|
||||
|
||||
.bar {
|
||||
width: 24rpx;
|
||||
min-height: 4rpx;
|
||||
border-radius: 4rpx;
|
||||
transition: height 0.3s;
|
||||
flex: 0 0 auto;
|
||||
width: 20rpx;
|
||||
min-height: 2rpx;
|
||||
border-radius: 4rpx 4rpx 0 0;
|
||||
transition: height 0.3s ease;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
&.income-bar {
|
||||
background: #2ECC71;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.expense-bar {
|
||||
background: #FF6B6B;
|
||||
left: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-size: 22rpx;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
391
resources/mobile/pages/ucenter/about/index.vue
Normal file
391
resources/mobile/pages/ucenter/about/index.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="关于我们"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
>
|
||||
<view class="about-container">
|
||||
<!-- Logo和应用信息 -->
|
||||
<view class="app-info">
|
||||
<view class="app-logo">
|
||||
<text class="logo-icon">📊</text>
|
||||
</view>
|
||||
<text class="app-name">家庭记账</text>
|
||||
<text class="app-version">版本 1.0.0</text>
|
||||
</view>
|
||||
|
||||
<!-- 功能介绍 -->
|
||||
<view class="feature-section">
|
||||
<view class="section-title">产品介绍</view>
|
||||
<view class="feature-card">
|
||||
<text class="feature-text">
|
||||
家庭记账是一款简单易用的个人及家庭财务管理应用。我们致力于帮助用户轻松记录每一笔收支,合理规划财务,实现财富增长。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心功能 -->
|
||||
<view class="feature-section">
|
||||
<view class="section-title">核心功能</view>
|
||||
<view class="feature-grid">
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon-wrapper" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<uni-icons type="compose" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="feature-name">快速记账</text>
|
||||
<text class="feature-desc">简单快捷记录每一笔收支</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon-wrapper" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<uni-icons type="person" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="feature-name">家庭共享</text>
|
||||
<text class="feature-desc">与家人共享财务数据</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon-wrapper" style="background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);">
|
||||
<uni-icons type="chart" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="feature-name">统计分析</text>
|
||||
<text class="feature-desc">多维度数据可视化分析</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon-wrapper" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
||||
<uni-icons type="download" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="feature-name">数据导出</text>
|
||||
<text class="feature-desc">支持导出多种格式报表</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系我们 -->
|
||||
<view class="contact-section">
|
||||
<view class="section-title">联系我们</view>
|
||||
<view class="contact-list">
|
||||
<view class="contact-item">
|
||||
<view class="contact-left">
|
||||
<uni-icons type="email" size="20" color="#667eea"></uni-icons>
|
||||
<text class="contact-label">官方邮箱</text>
|
||||
</view>
|
||||
<text class="contact-value">support@familyaccount.com</text>
|
||||
</view>
|
||||
<view class="contact-item">
|
||||
<view class="contact-left">
|
||||
<uni-icons type="phone" size="20" color="#667eea"></uni-icons>
|
||||
<text class="contact-label">客服电话</text>
|
||||
</view>
|
||||
<text class="contact-value">400-XXX-XXXX</text>
|
||||
</view>
|
||||
<view class="contact-item">
|
||||
<view class="contact-left">
|
||||
<uni-icons type="chatbubble" size="20" color="#667eea"></uni-icons>
|
||||
<text class="contact-label">微信公众号</text>
|
||||
</view>
|
||||
<text class="contact-value">家庭记账助手</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 法律条款 -->
|
||||
<view class="legal-section">
|
||||
<view class="legal-item" @tap="navigateTo('/pages/ucenter/agreement/user')">
|
||||
<text class="legal-text">用户协议</text>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="legal-item" @tap="navigateTo('/pages/ucenter/agreement/privacy')">
|
||||
<text class="legal-text">隐私政策</text>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部版权信息 -->
|
||||
<view class="footer-section">
|
||||
<text class="copyright">© 2024 家庭记账 版权所有</text>
|
||||
<text class="icp">ICP备案号:京ICP备XXXXXXXX号</text>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
appName: '家庭记账',
|
||||
version: '1.0.0'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 获取应用版本信息(如果有)
|
||||
this.getAppInfo()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 获取应用信息
|
||||
getAppInfo() {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) => {
|
||||
this.version = widgetInfo.version
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 页面跳转
|
||||
navigateTo(url) {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.about-container {
|
||||
min-height: 100vh;
|
||||
background: #F8F8F8;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.app-info {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 80rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
|
||||
.app-logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30rpx;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(10rpx);
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
.logo-icon {
|
||||
font-size: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 16rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.app-version {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-section {
|
||||
margin: 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.feature-text {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.feature-item {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-section {
|
||||
margin: 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.contact-list {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.contact-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-left: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.legal-section {
|
||||
margin: 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
animation: fadeInUp 0.6s ease-out 0.3s both;
|
||||
|
||||
.legal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
transition: background 0.3s ease;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.legal-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
margin: 40rpx 30rpx 20rpx;
|
||||
text-align: center;
|
||||
animation: fadeInUp 0.6s ease-out 0.4s both;
|
||||
|
||||
.copyright {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.icp {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
||||
@@ -1,229 +1,227 @@
|
||||
<template>
|
||||
<view class="agreement-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">隐私政策</view>
|
||||
<view class="nav-placeholder"></view>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="隐私政策"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
>
|
||||
<view class="agreement-container">
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="agreement-content">
|
||||
<view class="agreement-title">家庭记账隐私政策</view>
|
||||
|
||||
<view class="agreement-intro">
|
||||
<text class="intro-text">
|
||||
我们非常重视您的隐私保护。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息。使用我们的服务即表示您同意本隐私政策的条款。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">1. 信息收集</text>
|
||||
<text class="section-text">
|
||||
1.1 我们收集的信息包括:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)账户信息:您在注册时提供的用户名、密码、昵称等;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)使用信息:您使用应用的功能、频率、偏好等使用数据;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)设备信息:设备型号、操作系统版本、唯一设备标识符等;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)日志信息:访问时间、页面浏览记录、错误日志等。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
1.2 我们仅在为您提供服务和改进产品时收集必要的信息,绝不会收集与提供服务无关的信息。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">2. 信息使用</text>
|
||||
<text class="section-text">
|
||||
2.1 我们可能使用收集的信息用于:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)提供、维护和改进我们的服务;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)处理您的请求和交易;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)向您发送与服务相关的通知和更新;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)分析使用情况,优化用户体验;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)防止欺诈、滥用和安全威胁;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(6)遵守法律法规要求。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">3. 信息共享</text>
|
||||
<text class="section-text">
|
||||
3.1 除以下情况外,我们不会与第三方共享您的个人信息:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)获得您的明确同意;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)履行法律义务或响应法律要求;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)保护我们的权利、财产或安全;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)与提供服务的合作伙伴共享,但仅限于必要范围;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)业务转让或合并时,涉及用户信息转移。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.2 所有共享的信息都会受到严格的保密协议和隐私保护。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">4. 信息存储与安全</text>
|
||||
<text class="section-text">
|
||||
4.1 我们采取合理的技术措施保护您的信息安全,包括但不限于:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)数据加密传输和存储;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)访问权限控制;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)定期安全审计和风险评估;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)员工保密培训和责任制度。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.2 我们在中华人民共和国境内存储您的个人信息。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.3 如发生信息泄露,我们将及时采取补救措施并通知您。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">5. Cookie和类似技术</text>
|
||||
<text class="section-text">
|
||||
5.1 我们可能使用Cookie和类似技术来收集和存储信息,以改善用户体验。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
5.2 您可以通过浏览器设置管理Cookie,但这可能影响某些功能的使用。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">6. 您的权利</text>
|
||||
<text class="section-text">
|
||||
6.1 您对个人信息享有以下权利:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)访问、更正、删除您的个人信息;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)撤回对信息处理的同意;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)获取个人信息副本;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)限制信息处理;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)数据可携带权;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(6)反对信息处理;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(7)向监管机构投诉。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
6.2 您可以通过应用内设置或联系我们来行使这些权利。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">7. 信息保留</text>
|
||||
<text class="section-text">
|
||||
7.1 我们仅在实现收集目的所必需的期间内保留您的个人信息。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
7.2 当账户注销或收集目的实现后,我们将删除或匿名化处理您的信息,法律法规另有规定的除外。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">8. 未成年人保护</text>
|
||||
<text class="section-text">
|
||||
8.1 我们的服务主要面向成年人。如果您未满18周岁,请在监护人的陪同下使用本服务。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
8.2 如发现未经未成年人监护人同意收集未成年人信息的情况,请立即联系我们,我们将及时删除相关信息。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">9. 隐私政策的变更</text>
|
||||
<text class="section-text">
|
||||
9.1 我们可能不时更新本隐私政策。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.2 更新后的政策将在应用内公布,重大变更时我们会通过适当方式通知您。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.3 继续使用服务即表示您接受更新后的隐私政策。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">10. 联系我们</text>
|
||||
<text class="section-text">
|
||||
如对本隐私政策有任何疑问、意见或请求,请通过以下方式联系我们:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
邮箱:privacy@familyaccount.com
|
||||
</text>
|
||||
<text class="section-text">
|
||||
电话:400-XXX-XXXX
|
||||
</text>
|
||||
<text class="section-text">
|
||||
地址:中国XX省XX市XX区XX路XX号
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">11. 适用法律</text>
|
||||
<text class="section-text">
|
||||
11.1 本隐私政策的订立、执行和解释均适用中华人民共和国法律。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
11.2 我们严格遵守《中华人民共和国个人信息保护法》、《网络安全法》等相关法律法规的规定。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-footer">
|
||||
<text class="footer-text">更新日期:2024年1月</text>
|
||||
<text class="footer-text">生效日期:2024年1月1日</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="agreement-content">
|
||||
<view class="agreement-title">家庭记账隐私政策</view>
|
||||
|
||||
<view class="agreement-intro">
|
||||
<text class="intro-text">
|
||||
我们非常重视您的隐私保护。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息。使用我们的服务即表示您同意本隐私政策的条款。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">1. 信息收集</text>
|
||||
<text class="section-text">
|
||||
1.1 我们收集的信息包括:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)账户信息:您在注册时提供的用户名、密码、昵称等;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)使用信息:您使用应用的功能、频率、偏好等使用数据;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)设备信息:设备型号、操作系统版本、唯一设备标识符等;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)日志信息:访问时间、页面浏览记录、错误日志等。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
1.2 我们仅在为您提供服务和改进产品时收集必要的信息,绝不会收集与提供服务无关的信息。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">2. 信息使用</text>
|
||||
<text class="section-text">
|
||||
2.1 我们可能使用收集的信息用于:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)提供、维护和改进我们的服务;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)处理您的请求和交易;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)向您发送与服务相关的通知和更新;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)分析使用情况,优化用户体验;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)防止欺诈、滥用和安全威胁;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(6)遵守法律法规要求。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">3. 信息共享</text>
|
||||
<text class="section-text">
|
||||
3.1 除以下情况外,我们不会与第三方共享您的个人信息:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)获得您的明确同意;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)履行法律义务或响应法律要求;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)保护我们的权利、财产或安全;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)与提供服务的合作伙伴共享,但仅限于必要范围;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)业务转让或合并时,涉及用户信息转移。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.2 所有共享的信息都会受到严格的保密协议和隐私保护。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">4. 信息存储与安全</text>
|
||||
<text class="section-text">
|
||||
4.1 我们采取合理的技术措施保护您的信息安全,包括但不限于:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)数据加密传输和存储;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)访问权限控制;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)定期安全审计和风险评估;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)员工保密培训和责任制度。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.2 我们在中华人民共和国境内存储您的个人信息。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.3 如发生信息泄露,我们将及时采取补救措施并通知您。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">5. Cookie和类似技术</text>
|
||||
<text class="section-text">
|
||||
5.1 我们可能使用Cookie和类似技术来收集和存储信息,以改善用户体验。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
5.2 您可以通过浏览器设置管理Cookie,但这可能影响某些功能的使用。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">6. 您的权利</text>
|
||||
<text class="section-text">
|
||||
6.1 您对个人信息享有以下权利:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)访问、更正、删除您的个人信息;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)撤回对信息处理的同意;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)获取个人信息副本;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)限制信息处理;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(5)数据可携带权;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(6)反对信息处理;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(7)向监管机构投诉。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
6.2 您可以通过应用内设置或联系我们来行使这些权利。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">7. 信息保留</text>
|
||||
<text class="section-text">
|
||||
7.1 我们仅在实现收集目的所必需的期间内保留您的个人信息。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
7.2 当账户注销或收集目的实现后,我们将删除或匿名化处理您的信息,法律法规另有规定的除外。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">8. 未成年人保护</text>
|
||||
<text class="section-text">
|
||||
8.1 我们的服务主要面向成年人。如果您未满18周岁,请在监护人的陪同下使用本服务。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
8.2 如发现未经未成年人监护人同意收集未成年人信息的情况,请立即联系我们,我们将及时删除相关信息。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">9. 隐私政策的变更</text>
|
||||
<text class="section-text">
|
||||
9.1 我们可能不时更新本隐私政策。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.2 更新后的政策将在应用内公布,重大变更时我们会通过适当方式通知您。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.3 继续使用服务即表示您接受更新后的隐私政策。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">10. 联系我们</text>
|
||||
<text class="section-text">
|
||||
如对本隐私政策有任何疑问、意见或请求,请通过以下方式联系我们:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
邮箱:privacy@familyaccount.com
|
||||
</text>
|
||||
<text class="section-text">
|
||||
电话:400-XXX-XXXX
|
||||
</text>
|
||||
<text class="section-text">
|
||||
地址:中国XX省XX市XX区XX路XX号
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">11. 适用法律</text>
|
||||
<text class="section-text">
|
||||
11.1 本隐私政策的订立、执行和解释均适用中华人民共和国法律。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
11.2 我们严格遵守《中华人民共和国个人信息保护法》、《网络安全法》等相关法律法规的规定。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-footer">
|
||||
<text class="footer-text">更新日期:2024年1月</text>
|
||||
<text class="footer-text">生效日期:2024年1月1日</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -233,12 +231,6 @@ export default {
|
||||
// 不需要登录验证
|
||||
needLogin: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -251,40 +243,9 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: calc(100vh - 88rpx);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.agreement-content {
|
||||
|
||||
@@ -1,146 +1,144 @@
|
||||
<template>
|
||||
<view class="agreement-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<uni-icons type="left" size="24" color="#333"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">用户协议</view>
|
||||
<view class="nav-placeholder"></view>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="用户协议"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
>
|
||||
<view class="agreement-container">
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="agreement-content">
|
||||
<view class="agreement-title">家庭记账用户协议</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">1. 服务条款的接受</text>
|
||||
<text class="section-text">
|
||||
欢迎使用家庭记账应用。通过下载、安装或使用本应用,即表示您同意遵守本用户协议的所有条款和条件。如果您不同意这些条款,请不要使用本应用。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">2. 服务说明</text>
|
||||
<text class="section-text">
|
||||
2.1 家庭记账是一款为用户提供记账、账单管理等服务的移动应用程序。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
2.2 我们保留随时修改或中断服务而不需通知用户的权利。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
2.3 用户需自行承担使用本服务所产生的风险,包括但不限于因使用本服务而导致的计算机系统损坏或数据丢失。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">3. 用户账号</text>
|
||||
<text class="section-text">
|
||||
3.1 用户注册成功后,将获得一个账号和密码。用户应妥善保管账号和密码,并对使用该账号和密码进行的所有活动承担责任。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.2 用户不得将账号转让、出借或以其他方式提供给他人使用。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.3 如发现任何非法使用账号的情况,应立即通知我们。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">4. 用户行为规范</text>
|
||||
<text class="section-text">
|
||||
4.1 用户在使用本服务时,必须遵守相关法律法规,不得利用本服务从事任何违法或不当行为。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.2 禁止用户:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)发布或传输任何违法、有害、威胁、辱骂、骚扰、诽谤、侵犯隐私的信息;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)侵犯他人的知识产权、商业秘密或其他合法权利;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)干扰或破坏本服务的正常运行;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)传播病毒、木马或其他恶意代码。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">5. 隐私保护</text>
|
||||
<text class="section-text">
|
||||
我们重视用户的隐私保护。关于个人信息收集、使用和保护的具体内容,请参阅我们的《隐私政策》。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">6. 知识产权</text>
|
||||
<text class="section-text">
|
||||
6.1 本应用的所有内容,包括但不限于文字、图片、音频、视频、软件、程序、版面设计等,均受知识产权法保护。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
6.2 用户在使用本服务过程中产生的内容,用户保留其知识产权,但授权我们在服务范围内使用该内容。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">7. 免责声明</text>
|
||||
<text class="section-text">
|
||||
7.1 本服务按"现状"提供,不提供任何明示或暗示的保证。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
7.2 我们不对以下情况承担责任:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)因用户使用本服务而产生的任何直接或间接损失;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)因不可抗力、网络故障、系统维护等原因导致的服务中断;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)第三方提供的内容或服务。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">8. 协议的修改</text>
|
||||
<text class="section-text">
|
||||
我们有权随时修改本协议的条款。修改后的协议一旦公布即代替原协议。用户如不同意修改后的协议,可以停止使用本服务。继续使用本服务即视为用户接受修改后的协议。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">9. 法律适用与争议解决</text>
|
||||
<text class="section-text">
|
||||
9.1 本协议的订立、执行和解释均适用中华人民共和国法律。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.2 如发生争议,双方应友好协商解决;协商不成的,任何一方均可向被告所在地人民法院提起诉讼。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">10. 联系我们</text>
|
||||
<text class="section-text">
|
||||
如对本协议有任何疑问或建议,请通过以下方式联系我们:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
邮箱:support@familyaccount.com
|
||||
</text>
|
||||
<text class="section-text">
|
||||
电话:400-XXX-XXXX
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-footer">
|
||||
<text class="footer-text">更新日期:2024年1月</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="agreement-content">
|
||||
<view class="agreement-title">家庭记账用户协议</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">1. 服务条款的接受</text>
|
||||
<text class="section-text">
|
||||
欢迎使用家庭记账应用。通过下载、安装或使用本应用,即表示您同意遵守本用户协议的所有条款和条件。如果您不同意这些条款,请不要使用本应用。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">2. 服务说明</text>
|
||||
<text class="section-text">
|
||||
2.1 家庭记账是一款为用户提供记账、账单管理等服务的移动应用程序。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
2.2 我们保留随时修改或中断服务而不需通知用户的权利。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
2.3 用户需自行承担使用本服务所产生的风险,包括但不限于因使用本服务而导致的计算机系统损坏或数据丢失。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">3. 用户账号</text>
|
||||
<text class="section-text">
|
||||
3.1 用户注册成功后,将获得一个账号和密码。用户应妥善保管账号和密码,并对使用该账号和密码进行的所有活动承担责任。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.2 用户不得将账号转让、出借或以其他方式提供给他人使用。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
3.3 如发现任何非法使用账号的情况,应立即通知我们。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">4. 用户行为规范</text>
|
||||
<text class="section-text">
|
||||
4.1 用户在使用本服务时,必须遵守相关法律法规,不得利用本服务从事任何违法或不当行为。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
4.2 禁止用户:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)发布或传输任何违法、有害、威胁、辱骂、骚扰、诽谤、侵犯隐私的信息;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)侵犯他人的知识产权、商业秘密或其他合法权利;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)干扰或破坏本服务的正常运行;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(4)传播病毒、木马或其他恶意代码。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">5. 隐私保护</text>
|
||||
<text class="section-text">
|
||||
我们重视用户的隐私保护。关于个人信息收集、使用和保护的具体内容,请参阅我们的《隐私政策》。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">6. 知识产权</text>
|
||||
<text class="section-text">
|
||||
6.1 本应用的所有内容,包括但不限于文字、图片、音频、视频、软件、程序、版面设计等,均受知识产权法保护。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
6.2 用户在使用本服务过程中产生的内容,用户保留其知识产权,但授权我们在服务范围内使用该内容。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">7. 免责声明</text>
|
||||
<text class="section-text">
|
||||
7.1 本服务按"现状"提供,不提供任何明示或暗示的保证。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
7.2 我们不对以下情况承担责任:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(1)因用户使用本服务而产生的任何直接或间接损失;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(2)因不可抗力、网络故障、系统维护等原因导致的服务中断;
|
||||
</text>
|
||||
<text class="section-text">
|
||||
(3)第三方提供的内容或服务。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">8. 协议的修改</text>
|
||||
<text class="section-text">
|
||||
我们有权随时修改本协议的条款。修改后的协议一旦公布即代替原协议。用户如不同意修改后的协议,可以停止使用本服务。继续使用本服务即视为用户接受修改后的协议。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">9. 法律适用与争议解决</text>
|
||||
<text class="section-text">
|
||||
9.1 本协议的订立、执行和解释均适用中华人民共和国法律。
|
||||
</text>
|
||||
<text class="section-text">
|
||||
9.2 如发生争议,双方应友好协商解决;协商不成的,任何一方均可向被告所在地人民法院提起诉讼。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-section">
|
||||
<text class="section-title">10. 联系我们</text>
|
||||
<text class="section-text">
|
||||
如对本协议有任何疑问或建议,请通过以下方式联系我们:
|
||||
</text>
|
||||
<text class="section-text">
|
||||
邮箱:support@familyaccount.com
|
||||
</text>
|
||||
<text class="section-text">
|
||||
电话:400-XXX-XXXX
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="agreement-footer">
|
||||
<text class="footer-text">更新日期:2024年1月</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -150,12 +148,6 @@ export default {
|
||||
// 不需要登录验证
|
||||
needLogin: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -168,40 +160,9 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: calc(100vh - 88rpx);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.agreement-content {
|
||||
|
||||
545
resources/mobile/pages/ucenter/help/index.vue
Normal file
545
resources/mobile/pages/ucenter/help/index.vue
Normal file
@@ -0,0 +1,545 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="帮助中心"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
>
|
||||
<view class="help-container">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-section">
|
||||
<view class="search-box">
|
||||
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
||||
<input
|
||||
class="search-input"
|
||||
type="text"
|
||||
placeholder="搜索问题关键词"
|
||||
v-model="searchKeyword"
|
||||
@input="handleSearch"
|
||||
placeholder-style="color: #999; font-size: 28rpx;"
|
||||
/>
|
||||
<view v-if="searchKeyword" class="clear-btn" @tap="clearSearch">
|
||||
<uni-icons type="clear" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<view class="category-section" v-if="!searchKeyword">
|
||||
<scroll-view scroll-x class="category-scroll" show-scrollbar="false">
|
||||
<view class="category-list">
|
||||
<view
|
||||
class="category-item"
|
||||
:class="{ active: activeCategory === item.value }"
|
||||
v-for="item in categoryList"
|
||||
:key="item.value"
|
||||
@tap="selectCategory(item.value)"
|
||||
>
|
||||
<text class="category-text">{{ item.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 问题列表 -->
|
||||
<view class="faq-section">
|
||||
<view class="faq-list" v-if="filteredQuestions.length > 0">
|
||||
<view
|
||||
class="faq-item"
|
||||
v-for="(item, index) in filteredQuestions"
|
||||
:key="index"
|
||||
animation="fadeInUp"
|
||||
:style="{ 'animation-delay': index * 0.1 + 's' }"
|
||||
>
|
||||
<view class="faq-question" @tap="toggleAnswer(index)">
|
||||
<text class="question-text">{{ item.question }}</text>
|
||||
<uni-icons
|
||||
:type="item.expanded ? 'up' : 'down'"
|
||||
size="16"
|
||||
:color="item.expanded ? '#667eea' : '#ccc'"
|
||||
class="arrow-icon"
|
||||
></uni-icons>
|
||||
</view>
|
||||
<view class="faq-answer" :class="{ expanded: item.expanded }">
|
||||
<text class="answer-text">{{ item.answer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-state" v-else>
|
||||
<text class="empty-icon">🔍</text>
|
||||
<text class="empty-text">暂无相关问题</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部反馈入口 -->
|
||||
<view class="feedback-section">
|
||||
<view class="feedback-card" @tap="goToFeedback">
|
||||
<view class="feedback-left">
|
||||
<view class="feedback-icon-wrapper">
|
||||
<uni-icons type="chat" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="feedback-info">
|
||||
<text class="feedback-title">问题反馈</text>
|
||||
<text class="feedback-desc">遇到问题?告诉我们</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="18" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<view class="contact-section">
|
||||
<view class="contact-item">
|
||||
<uni-icons type="email" size="20" color="#667eea"></uni-icons>
|
||||
<text class="contact-label">客服邮箱</text>
|
||||
<text class="contact-value">support@familyaccount.com</text>
|
||||
</view>
|
||||
<view class="contact-item">
|
||||
<uni-icons type="phone" size="20" color="#667eea"></uni-icons>
|
||||
<text class="contact-label">客服电话</text>
|
||||
<text class="contact-value">400-XXX-XXXX</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchKeyword: '',
|
||||
activeCategory: 'all',
|
||||
categoryList: [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '账户相关', value: 'account' },
|
||||
{ label: '功能使用', value: 'function' },
|
||||
{ label: '家庭共享', value: 'family' },
|
||||
{ label: '数据统计', value: 'statistics' },
|
||||
{ label: '问题反馈', value: 'feedback' }
|
||||
],
|
||||
questions: [
|
||||
{
|
||||
question: '如何注册账号?',
|
||||
answer: '点击首页的"注册"按钮,输入用户名、密码和确认密码,即可快速完成注册。注册后可使用手机号或邮箱登录。',
|
||||
category: 'account',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '忘记密码怎么办?',
|
||||
answer: '在登录页面点击"忘记密码"按钮,通过注册时的手机号或邮箱验证后,可设置新密码。',
|
||||
category: 'account',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何添加一笔账单?',
|
||||
answer: '点击首页底部的"+"按钮,选择收入或支出,填写金额、分类、日期和备注,点击保存即可完成记账。',
|
||||
category: 'function',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何修改或删除账单?',
|
||||
answer: '在账单列表中点击需要操作的账单,进入详情页面后可以进行编辑或删除操作。已删除的账单无法恢复。',
|
||||
category: 'function',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何创建家庭?',
|
||||
answer: '进入"家庭"页面,点击"创建家庭",设置家庭名称后即可创建。创建者自动成为家主,可以邀请家庭成员。',
|
||||
category: 'family',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何加入家庭?',
|
||||
answer: '向家主索要家庭邀请码,在"家庭"页面点击"加入家庭",输入邀请码即可加入。一个人只能加入一个家庭。',
|
||||
category: 'family',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '家主如何管理家庭成员?',
|
||||
answer: '家主可以在家庭页面查看所有成员,点击成员后的管理按钮可以移除成员。被移除的成员将无法查看该家庭的账单数据。',
|
||||
category: 'family',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何查看收支统计?',
|
||||
answer: '进入"统计"页面,可以选择查看"个人"或"家庭"的收支数据,支持按日、周、月、年进行统计,并可查看分类统计和趋势图表。',
|
||||
category: 'statistics',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何切换查看个人和家庭数据?',
|
||||
answer: '在账单列表和统计页面,顶部有"个人"和"家庭"切换按钮,点击即可在个人数据和家庭数据之间切换。',
|
||||
category: 'function',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何导出数据?',
|
||||
answer: '在统计页面点击"导出"按钮,可以选择导出Excel或CSV格式的报表,支持导出指定时间范围内的账单数据。',
|
||||
category: 'function',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何修改个人资料?',
|
||||
answer: '进入"我的"页面,点击个人设置,在资料编辑页面可以修改头像、昵称、邮箱等信息。用户名注册后不可修改。',
|
||||
category: 'account',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '数据会被安全保存吗?',
|
||||
answer: '是的,您的所有数据都经过加密存储,并定期备份。我们严格遵守隐私政策,不会泄露您的个人财务信息。',
|
||||
category: 'account',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何提交问题反馈?',
|
||||
answer: '在帮助中心底部点击"问题反馈"卡片,详细描述您遇到的问题,我们会尽快回复并处理您的反馈。',
|
||||
category: 'feedback',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何联系我们?',
|
||||
answer: '您可以通过客服邮箱 support@familyaccount.com 或客服电话 400-XXX-XXXX 联系我们,我们的工作时间为周一至周五 9:00-18:00。',
|
||||
category: 'feedback',
|
||||
expanded: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredQuestions() {
|
||||
let result = this.questions
|
||||
|
||||
// 按分类筛选
|
||||
if (this.activeCategory !== 'all') {
|
||||
result = result.filter(item => item.category === this.activeCategory)
|
||||
}
|
||||
|
||||
// 按关键词搜索
|
||||
if (this.searchKeyword) {
|
||||
const keyword = this.searchKeyword.toLowerCase()
|
||||
result = result.filter(item =>
|
||||
item.question.toLowerCase().includes(keyword) ||
|
||||
item.answer.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 选择分类
|
||||
selectCategory(value) {
|
||||
this.activeCategory = value
|
||||
},
|
||||
|
||||
// 搜索处理
|
||||
handleSearch() {
|
||||
// 搜索时自动显示所有分类的结果
|
||||
},
|
||||
|
||||
// 清空搜索
|
||||
clearSearch() {
|
||||
this.searchKeyword = ''
|
||||
},
|
||||
|
||||
// 展开/收起答案
|
||||
toggleAnswer(index) {
|
||||
this.filteredQuestions[index].expanded = !this.filteredQuestions[index].expanded
|
||||
},
|
||||
|
||||
// 跳转到反馈页面
|
||||
goToFeedback() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/feedback/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-container {
|
||||
min-height: 100vh;
|
||||
background: #F8F8F8;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 30rpx;
|
||||
background: #fff;
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F5F5F5;
|
||||
border-radius: 48rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
margin-right: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-section {
|
||||
padding: 20rpx 30rpx 0;
|
||||
animation: fadeInDown 0.6s ease-out 0.1s both;
|
||||
|
||||
.category-scroll {
|
||||
white-space: nowrap;
|
||||
|
||||
.category-list {
|
||||
display: inline-flex;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.category-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16rpx 32rpx;
|
||||
margin-right: 16rpx;
|
||||
background: #fff;
|
||||
border-radius: 48rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 6rpx 20rpx rgba(102, 126, 234, 0.35);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.category-text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.faq-section {
|
||||
margin: 20rpx 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
.faq-list {
|
||||
.faq-item {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 30rpx;
|
||||
transition: background 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s ease, padding 0.4s ease;
|
||||
background: #FAFAFA;
|
||||
|
||||
.answer-text {
|
||||
display: block;
|
||||
padding: 0 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
opacity: 0;
|
||||
transform: translateY(-10rpx);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
max-height: 500rpx;
|
||||
padding: 20rpx 0 32rpx;
|
||||
|
||||
.answer-text {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 120rpx 40rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.empty-icon {
|
||||
display: block;
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-section {
|
||||
margin: 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.3s both;
|
||||
|
||||
.feedback-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.feedback-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.feedback-icon-wrapper {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.feedback-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.feedback-title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.feedback-desc {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-section {
|
||||
margin: 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
animation: fadeInUp 0.6s ease-out 0.4s both;
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-left: 16rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
||||
@@ -11,14 +11,12 @@
|
||||
<view class="user-header">
|
||||
<view class="header-bg"></view>
|
||||
<view class="user-avatar">
|
||||
<uni-icons type="person-filled" size="70" color="#fff"></uni-icons>
|
||||
<view class="vip-badge" v-if="isLogin">
|
||||
<text>VIP</text>
|
||||
</view>
|
||||
<image v-if="userInfo.avatar" :src="userInfo.avatar" class="avatar-image" mode="aspectFill"></image>
|
||||
<uni-icons v-else type="person-filled" size="70" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userInfo.nickname || userInfo.username || '未登录' }}</text>
|
||||
<text class="user-desc" v-if="isLogin">{{ userInfo.email || '暂无邮箱' }}</text>
|
||||
<text class="user-name">{{ userInfo.username || '未登录' }}</text>
|
||||
<text class="user-nickname" v-if="isLogin && userInfo.nickname">{{ userInfo.nickname }}</text>
|
||||
<text class="user-desc" v-else>点击登录体验更多功能</text>
|
||||
</view>
|
||||
<view class="edit-profile" @tap="handleEditProfile" v-if="isLogin">
|
||||
@@ -62,7 +60,7 @@
|
||||
</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>
|
||||
<uni-icons type="color-filled" size="24" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<text class="quick-text">统计分析</text>
|
||||
</view>
|
||||
@@ -79,7 +77,7 @@
|
||||
<view class="menu-section">
|
||||
<view class="section-title">设置</view>
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/profile')">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/profile/index')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="gear" size="20" color="#667eea"></uni-icons>
|
||||
@@ -91,7 +89,7 @@
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/help')">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/help/index')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="help" size="20" color="#667eea"></uni-icons>
|
||||
@@ -103,7 +101,7 @@
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/about')">
|
||||
<view class="menu-item" @tap="navigateTo('/pages/ucenter/about/index')">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="info" size="20" color="#667eea"></uni-icons>
|
||||
@@ -115,7 +113,7 @@
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
<view class="menu-item" @tap="handleClearCache">
|
||||
<view class="menu-item" @tap="handleClearCache" v-if="false">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrapper">
|
||||
<uni-icons type="trash" size="20" color="#667eea"></uni-icons>
|
||||
@@ -168,46 +166,48 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
async checkLoginStatus() {
|
||||
this.isLogin = isLogin()
|
||||
this.userInfo = uni.getStorageSync('userInfo') || {}
|
||||
|
||||
if (this.isLogin) {
|
||||
await this.loadUserInfo()
|
||||
this.loadUserStats()
|
||||
} else {
|
||||
this.userInfo = {}
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户信息
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
const result = await this.$api.auth.info.get()
|
||||
if (result && result.code === 1) {
|
||||
this.userInfo = result.data || {}
|
||||
// 更新本地存储
|
||||
uni.setStorageSync('userInfo', this.userInfo)
|
||||
// 更新 Vuex store
|
||||
this.$store.commit('setUserInfo', this.userInfo)
|
||||
} else {
|
||||
// 如果接口失败,使用本地存储的数据
|
||||
this.userInfo = uni.getStorageSync('userInfo') || {}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error)
|
||||
// 如果接口失败,使用本地存储的数据
|
||||
this.userInfo = uni.getStorageSync('userInfo') || {}
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户统计数据
|
||||
async loadUserStats() {
|
||||
try {
|
||||
// 获取账单总数
|
||||
const billRes = await this.$api.bill.list.get({
|
||||
page: 1,
|
||||
limit: 1
|
||||
const result = await this.$api.statistics.dashboard.get({
|
||||
data_type: 'family'
|
||||
})
|
||||
if (billRes && billRes.code === 1) {
|
||||
this.stats.billCount = billRes.data?.total || 0
|
||||
}
|
||||
|
||||
// 获取家庭成员数
|
||||
const membersRes = await this.$api.family.members.get()
|
||||
if (membersRes && membersRes.code === 1) {
|
||||
this.stats.familyMembers = membersRes.data?.length || 0
|
||||
}
|
||||
|
||||
// 获取记账天数(计算从第一个账单到现在的天数)
|
||||
const firstBillRes = await this.$api.bill.list.get({
|
||||
page: 1,
|
||||
limit: 1,
|
||||
order: 'asc'
|
||||
})
|
||||
if (firstBillRes && firstBillRes.code === 1 && firstBillRes.data?.list?.length > 0) {
|
||||
const firstBill = firstBillRes.data.list[0]
|
||||
const firstDate = new Date(firstBill.date)
|
||||
const now = new Date()
|
||||
const diffTime = Math.abs(now - firstDate)
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
this.stats.days = diffDays
|
||||
if (result && result.code === 1) {
|
||||
this.stats.billCount = result.data?.bill_count || 0
|
||||
this.stats.days = result.data?.days || 0
|
||||
this.stats.familyMembers = result.data?.family_members || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败', error)
|
||||
@@ -243,10 +243,8 @@ export default {
|
||||
|
||||
// 编辑个人资料
|
||||
handleEditProfile() {
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/profile/edit'
|
||||
})
|
||||
},
|
||||
|
||||
@@ -381,21 +379,11 @@ export default {
|
||||
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;
|
||||
}
|
||||
.avatar-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +402,13 @@ export default {
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-nickname {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 500;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.user-desc {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
|
||||
412
resources/mobile/pages/ucenter/profile/edit.vue
Normal file
412
resources/mobile/pages/ucenter/profile/edit.vue
Normal file
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="编辑资料"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
:nav-bar-right-text="loading ? '保存中...' : '保存'"
|
||||
@nav-bar-right-click="handleSave"
|
||||
>
|
||||
<view class="edit-profile-container">
|
||||
<!-- 头像上传 -->
|
||||
<view class="avatar-section">
|
||||
<view class="avatar-wrapper" @tap="chooseAvatar">
|
||||
<image v-if="formData.avatar" :src="formData.avatar" class="avatar-image" mode="aspectFill"></image>
|
||||
<uni-icons v-else type="person-filled" size="60" color="#ccc"></uni-icons>
|
||||
<view class="avatar-overlay">
|
||||
<uni-icons type="camera" size="20" color="#fff"></uni-icons>
|
||||
<text class="avatar-tip">更换头像</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<text class="form-label">用户名</text>
|
||||
<input class="form-input" type="text" v-model="formData.username" placeholder="请输入用户名" disabled />
|
||||
<text class="form-tip">用户名不可修改</text>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">昵称</text>
|
||||
<input class="form-input" type="text" v-model="formData.nickname" placeholder="请输入昵称" maxlength="20" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">邮箱</text>
|
||||
<input class="form-input" type="text" v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn" @tap="handleSave" :disabled="loading">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import memberApi from '@/api/modules/member'
|
||||
import authApi from '@/api/modules/auth'
|
||||
import commonApi from '@/api/modules/common'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
formData: {
|
||||
username: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
avatar: ''
|
||||
},
|
||||
originalData: {}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载用户信息
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
// 从本地存储获取用户信息
|
||||
const userInfo = uni.getStorageSync('userInfo') || {}
|
||||
this.formData = {
|
||||
username: userInfo.username || '',
|
||||
nickname: userInfo.nickname || '',
|
||||
email: userInfo.email || '',
|
||||
avatar: userInfo.avatar || ''
|
||||
}
|
||||
// 保存原始数据用于对比
|
||||
this.originalData = { ...this.formData }
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 选择头像
|
||||
chooseAvatar() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0]
|
||||
this.uploadAvatar(tempFilePath)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 上传头像
|
||||
async uploadAvatar(filePath) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '上传中...'
|
||||
})
|
||||
|
||||
// 调用上传接口
|
||||
uni.uploadFile({
|
||||
url: commonApi.upload.url,
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + uni.getStorageSync('token')
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
uni.hideLoading()
|
||||
try {
|
||||
const data = JSON.parse(uploadRes.data)
|
||||
if (data.code === 1) {
|
||||
this.formData.avatar = data.data.url || data.data
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: data.message || '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析上传结果失败:', e)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
uni.hideLoading()
|
||||
console.error('上传失败:', error)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('上传头像失败:', error)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 保存修改
|
||||
async handleSave() {
|
||||
// 检查是否有修改
|
||||
const hasChanges = Object.keys(this.formData).some(key => {
|
||||
if (key === 'username') return false // 用户名不可修改
|
||||
return this.formData[key] !== this.originalData[key]
|
||||
})
|
||||
|
||||
if (!hasChanges) {
|
||||
uni.showToast({
|
||||
title: '未做任何修改',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
if (!this.validateForm()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
})
|
||||
|
||||
// 调用更新用户信息接口
|
||||
const result = await memberApi.edit.put({
|
||||
nickname: this.formData.nickname,
|
||||
email: this.formData.email,
|
||||
avatar: this.formData.avatar
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (result && result.code === 1) {
|
||||
// 重新获取用户信息
|
||||
const userInfoRes = await authApi.info.get()
|
||||
if (userInfoRes && userInfoRes.code === 1) {
|
||||
const updatedUserInfo = userInfoRes.data || {}
|
||||
uni.setStorageSync('userInfo', updatedUserInfo)
|
||||
this.$store.commit('setUserInfo', updatedUserInfo)
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.message || '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('保存失败:', error)
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 表单验证
|
||||
validateForm() {
|
||||
const { nickname, email } = this.formData
|
||||
|
||||
if (nickname && nickname.length < 2) {
|
||||
uni.showToast({
|
||||
title: '昵称至少2个字符',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (email) {
|
||||
const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailReg.test(email)) {
|
||||
uni.showToast({
|
||||
title: '邮箱格式不正确',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit-profile-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
background: #fff;
|
||||
padding: 60rpx 0;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
.avatar-tip {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&:hover .avatar-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 140rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
height: 44rpx;
|
||||
line-height: 44rpx;
|
||||
padding-right: 60rpx;
|
||||
|
||||
&[disabled] {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin: 40rpx 30rpx;
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
278
resources/mobile/pages/ucenter/profile/index.vue
Normal file
278
resources/mobile/pages/ucenter/profile/index.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="个人设置"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
>
|
||||
<view class="profile-container">
|
||||
<!-- 设置选项 -->
|
||||
<view class="settings-section">
|
||||
<view class="section-title">账户设置</view>
|
||||
<view class="settings-list">
|
||||
<view class="setting-item" @tap="navigateToEdit">
|
||||
<view class="setting-left">
|
||||
<view class="setting-icon">
|
||||
<uni-icons type="person" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="setting-content">
|
||||
<text class="setting-title">个人资料</text>
|
||||
<text class="setting-desc">修改头像、昵称、邮箱</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view class="setting-item" @tap="navigateToPassword">
|
||||
<view class="setting-left">
|
||||
<view class="setting-icon">
|
||||
<uni-icons type="locked" size="20" color="#667eea"></uni-icons>
|
||||
</view>
|
||||
<view class="setting-content">
|
||||
<text class="setting-title">修改密码</text>
|
||||
<text class="setting-desc">定期更换密码保护账户安全</text>
|
||||
</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 安全提示 -->
|
||||
<view class="security-tips">
|
||||
<view class="tip-header">
|
||||
<uni-icons type="info-filled" size="18" color="#ffa500"></uni-icons>
|
||||
<text class="tip-title">安全提示</text>
|
||||
</view>
|
||||
<view class="tip-content">
|
||||
<text class="tip-text">1. 请勿将密码告知他人</text>
|
||||
<text class="tip-text">2. 定期修改密码以确保账户安全</text>
|
||||
<text class="tip-text">3. 如遇异常请及时联系客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
userInfo: {}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载用户信息
|
||||
loadUserInfo() {
|
||||
// 从本地存储获取用户信息
|
||||
this.userInfo = this.$store.state.user.userInfo || {}
|
||||
},
|
||||
|
||||
// 跳转到编辑资料页面
|
||||
navigateToEdit() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/profile/edit'
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到修改密码页面
|
||||
navigateToPassword() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ucenter/profile/password'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.profile-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
animation: fadeInDown 0.6s ease-out;
|
||||
|
||||
.user-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 30rpx;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(10rpx);
|
||||
|
||||
.avatar-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-nickname {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin: 20rpx 30rpx;
|
||||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.settings-list {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.setting-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;
|
||||
}
|
||||
|
||||
.setting-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.setting-icon {
|
||||
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;
|
||||
}
|
||||
|
||||
.setting-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.setting-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.security-tips {
|
||||
margin: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||
|
||||
.tip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.tip-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tip-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 8rpx;
|
||||
padding-left: 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>
|
||||
328
resources/mobile/pages/ucenter/profile/password.vue
Normal file
328
resources/mobile/pages/ucenter/profile/password.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<un-pages
|
||||
:show-nav-bar="true"
|
||||
nav-bar-title="修改密码"
|
||||
:show-back="true"
|
||||
:show-tab-bar="false"
|
||||
:nav-bar-right-text="loading ? '保存中...' : '保存'"
|
||||
@nav-bar-right-click="handleSave"
|
||||
>
|
||||
<view class="password-container">
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<text class="form-label">当前密码</text>
|
||||
<input
|
||||
class="form-input"
|
||||
:type="showOldPassword ? 'text' : 'password'"
|
||||
v-model="formData.oldPassword"
|
||||
placeholder="请输入当前密码"
|
||||
/>
|
||||
<uni-icons
|
||||
:type="showOldPassword ? 'eye-slash' : 'eye'"
|
||||
size="20"
|
||||
color="#999"
|
||||
@tap="toggleOldPassword"
|
||||
></uni-icons>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">新密码</text>
|
||||
<input
|
||||
class="form-input"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
v-model="formData.newPassword"
|
||||
placeholder="请输入新密码(6-20位)"
|
||||
maxlength="20"
|
||||
/>
|
||||
<uni-icons
|
||||
:type="showNewPassword ? 'eye-slash' : 'eye'"
|
||||
size="20"
|
||||
color="#999"
|
||||
@tap="toggleNewPassword"
|
||||
></uni-icons>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">确认密码</text>
|
||||
<input
|
||||
class="form-input"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
v-model="formData.confirmPassword"
|
||||
placeholder="请再次输入新密码"
|
||||
maxlength="20"
|
||||
/>
|
||||
<uni-icons
|
||||
:type="showConfirmPassword ? 'eye-slash' : 'eye'"
|
||||
size="20"
|
||||
color="#999"
|
||||
@tap="toggleConfirmPassword"
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="tips-section">
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="16" color="#666"></uni-icons>
|
||||
<text class="tip-text">密码长度为6-20个字符</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="info" size="16" color="#666"></uni-icons>
|
||||
<text class="tip-text">建议使用字母、数字和符号的组合</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn" @tap="handleSave" :disabled="loading">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</un-pages>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import memberApi from '@/api/modules/member'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
formData: {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
showOldPassword: false,
|
||||
showNewPassword: false,
|
||||
showConfirmPassword: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 切换当前密码显示/隐藏
|
||||
toggleOldPassword() {
|
||||
this.showOldPassword = !this.showOldPassword
|
||||
},
|
||||
|
||||
// 切换新密码显示/隐藏
|
||||
toggleNewPassword() {
|
||||
this.showNewPassword = !this.showNewPassword
|
||||
},
|
||||
|
||||
// 切换确认密码显示/隐藏
|
||||
toggleConfirmPassword() {
|
||||
this.showConfirmPassword = !this.showConfirmPassword
|
||||
},
|
||||
|
||||
// 保存修改
|
||||
async handleSave() {
|
||||
// 表单验证
|
||||
if (!this.validateForm()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
})
|
||||
|
||||
// 调用修改密码接口
|
||||
const result = await memberApi.editpasswd.put({
|
||||
old_password: this.formData.oldPassword,
|
||||
new_password: this.formData.newPassword
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (result && result.code === 1) {
|
||||
uni.showToast({
|
||||
title: '密码修改成功,请重新登录',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 延迟退出登录,跳转到登录页
|
||||
setTimeout(() => {
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('userInfo')
|
||||
this.$store.commit('setUserLogout')
|
||||
uni.reLaunch({
|
||||
url: '/pages/ucenter/login/index'
|
||||
})
|
||||
}, 2000)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.message || '密码修改失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('修改密码失败:', error)
|
||||
uni.showToast({
|
||||
title: '修改失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 表单验证
|
||||
validateForm() {
|
||||
const { oldPassword, newPassword, confirmPassword } = this.formData
|
||||
|
||||
if (!oldPassword) {
|
||||
uni.showToast({
|
||||
title: '请输入当前密码',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (!newPassword) {
|
||||
uni.showToast({
|
||||
title: '请输入新密码',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (newPassword.length < 6 || newPassword.length > 20) {
|
||||
uni.showToast({
|
||||
title: '密码长度为6-20个字符',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (!confirmPassword) {
|
||||
uni.showToast({
|
||||
title: '请再次输入新密码',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
uni.showToast({
|
||||
title: '两次输入的密码不一致',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (oldPassword === newPassword) {
|
||||
uni.showToast({
|
||||
title: '新密码不能与当前密码相同',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.password-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 140rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
height: 44rpx;
|
||||
line-height: 44rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
margin: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-left: 12rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
margin: 40rpx 30rpx;
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.35);
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user