更新后端接口

This commit is contained in:
2026-01-19 14:52:08 +08:00
parent cc603cb158
commit 1d5930dc4c
8 changed files with 1448 additions and 3 deletions

View File

@@ -0,0 +1,101 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Controllers\BaseController;
use Modules\Account\Services\AdminBillService;
class Bill extends BaseController {
/**
* @title 账单列表
*
* @param Request $request
* @param BillService $service
* @return void
*/
public function index(Request $request, AdminBillService $service) {
try {
$this->data['data'] = $service->getDataList($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 添加账单
*
* @param Request $request
* @param BillService $service
* @return void
*/
public function add(Request $request, AdminBillService $service) {
try {
$this->data['data'] = $service->create($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 编辑账单
*
* @param Request $request
* @param BillService $service
* @return void
*/
public function edit(Request $request, AdminBillService $service) {
try {
$this->data['data'] = $service->update($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 删除账单
*
* @param Request $request
* @param BillService $service
* @return void
*/
public function delete(Request $request, AdminBillService $service) {
try {
$this->data['data'] = $service->delete($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 账单详情
*
* @param Request $request
* @param BillService $service
* @return void
*/
public function detail(Request $request, AdminBillService $service) {
try {
$this->data['data'] = $service->detail($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
}

View File

@@ -0,0 +1,152 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Controllers\BaseController;
use Modules\Account\Services\AdminFamilyService;
class Family extends BaseController {
/**
* @title 家庭列表
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function index(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->getDataList($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 添加家庭
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function add(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->create($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 编辑家庭
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function edit(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->update($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 删除家庭
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function delete(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->delete($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 家庭详情
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function detail(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->detail($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 家庭成员列表
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function members(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->getMembers($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 添加家庭成员
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function addMember(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->addMember($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 移除家庭成员
*
* @param Request $request
* @param AdminFamilyService $service
* @return void
*/
public function removeMember(Request $request, AdminFamilyService $service) {
try {
$this->data['data'] = $service->removeMember($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
}

View File

@@ -0,0 +1,101 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Controllers\BaseController;
use Modules\Account\Services\AdminStatisticsService;
class Statistics extends BaseController {
/**
* @title 概览统计
*
* @param Request $request
* @param AdminStatisticsService $service
* @return void
*/
public function overview(Request $request, AdminStatisticsService $service) {
try {
$this->data['data'] = $service->getOverview($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 趋势统计
*
* @param Request $request
* @param AdminStatisticsService $service
* @return void
*/
public function trend(Request $request, AdminStatisticsService $service) {
try {
$this->data['data'] = $service->getTrend($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 分类统计
*
* @param Request $request
* @param AdminStatisticsService $service
* @return void
*/
public function category(Request $request, AdminStatisticsService $service) {
try {
$this->data['data'] = $service->getCategoryStatistics($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 用户统计
*
* @param Request $request
* @param AdminStatisticsService $service
* @return void
*/
public function user(Request $request, AdminStatisticsService $service) {
try {
$this->data['data'] = $service->getUserStatistics($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
/**
* @title 家庭统计
*
* @param Request $request
* @param AdminStatisticsService $service
* @return void
*/
public function family(Request $request, AdminStatisticsService $service) {
try {
$this->data['data'] = $service->getFamilyStatistics($request);
} catch (\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return response()->json($this->data);
}
}

View File

@@ -0,0 +1,311 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Services;
use Illuminate\Http\Request;
use Modules\Account\Models\Bill;
class AdminBillService {
/**
* @title 获取账单列表
*
* @param Request $request
* @return void
*/
public function getDataList($request) {
$map = [];
// 用户筛选
if ($request->filled('user_id')) {
$map[] = ['user_id', '=', $request->input('user_id')];
}
// 家庭筛选
if ($request->filled('family_id')) {
$map[] = ['family_id', '=', $request->input('family_id')];
}
// 类型筛选
if ($request->filled('type') && in_array($request->input('type'), ['income', 'expense'])) {
$map[] = ['type', '=', $request->input('type')];
}
// 分类筛选
if ($request->filled('category')) {
$map[] = ['category', 'like', '%' . $request->input('category') . '%'];
}
// 支付方式筛选
if ($request->filled('payment_method')) {
$map[] = ['payment_method', '=', $request->input('payment_method')];
}
// 备注搜索
if ($request->filled('remark')) {
$map[] = ['remark', 'like', '%' . $request->input('remark') . '%'];
}
// 日期范围筛选
if ($request->filled('start_date')) {
$map[] = ['bill_date', '>=', $request->input('start_date')];
}
if ($request->filled('end_date')) {
$map[] = ['bill_date', '<=', $request->input('end_date')];
}
// 年月筛选
if ($request->filled('year') && $request->filled('month')) {
$startDate = $request->input('year') . '-' . str_pad($request->input('month'), 2, '0', STR_PAD_LEFT) . '-01';
$endDate = $request->input('year') . '-' . str_pad($request->input('month'), 2, '0', STR_PAD_LEFT) . '-31';
// 清除之前的日期条件
$map = array_filter($map, function($item) {
return !isset($item[0]) || !in_array($item[0], ['bill_date']);
});
$map[] = ['bill_date', '>=', $startDate];
$map[] = ['bill_date', '<=', $endDate];
}
$query = Bill::with(['user:uid,nickname,username', 'family:id,name']);
$query->where($map);
// 排序
$query->orderBy($request->input('order', 'bill_date'), $request->input('sort', 'desc'));
// 分页
if ($request->filled('page')) {
$data = [
'total' => $query->count(),
'page' => $request->input('page', 1),
'data' => $query->offset($request->input('offset', 0))
->limit($request->input('limit', 10))
->get(),
];
} else {
$data = $query->get();
}
// 计算统计数据
if ($request->filled('page')) {
$statsQuery = Bill::query()->where($map);
$totalExpense = (clone $statsQuery)->where('type', 'expense')->sum('amount');
$totalIncome = (clone $statsQuery)->where('type', 'income')->sum('amount');
$data['statistics'] = [
'total_expense' => (float)$totalExpense,
'total_income' => (float)$totalIncome,
'total_balance' => (float)($totalIncome - $totalExpense),
'total_count' => $data['total']
];
}
return $data;
}
/**
* @title 添加账单
*
* @param Request $request
* @return void
*/
public function create($request) {
$request->validate([
'user_id' => 'required|integer',
'type' => 'required|in:income,expense',
'amount' => 'required|numeric|min:0.01',
'category_id' => 'required|integer',
'payment_method' => 'nullable|string|in:微信,支付宝,银行卡,现金,其他',
'remark' => 'nullable|string|max:255',
'date' => 'required|date',
'family_id' => 'nullable|integer|exists:account_families,id'
], [
'user_id.required' => '请选择用户',
'type.required' => '请选择账单类型',
'amount.required' => '请输入金额',
'category_id.required' => '请选择分类',
'date.required' => '请选择日期'
]);
$data = $request->all();
// 将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'];
// 移除前端字段
unset($data['category_id'], $data['date']);
return Bill::create($data);
}
/**
* @title 更新账单
*
* @param Request $request
* @return void
*/
public function update($request) {
$request->validate([
'id' => 'required|integer',
'user_id' => 'required|integer',
'type' => 'required|in:income,expense',
'amount' => 'required|numeric|min:0.01',
'category_id' => 'required|integer',
'payment_method' => 'nullable|string|in:微信,支付宝,银行卡,现金,其他',
'remark' => 'nullable|string|max:255',
'date' => 'required|date',
'family_id' => 'nullable|integer|exists:account_families,id'
], [
'id.required' => '请提供账单ID',
'user_id.required' => '请选择用户',
'type.required' => '请选择账单类型',
'amount.required' => '请输入金额',
'category_id.required' => '请选择分类',
'date.required' => '请选择日期'
]);
$bill = Bill::findOrFail($request->input('id'));
$data = $request->all();
// 将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'];
// 移除前端字段
unset($data['category_id'], $data['date'], $data['id']);
$bill->update($data);
return $bill;
}
/**
* @title 删除账单
*
* @param Request $request
* @return void
*/
public function delete($request) {
if ($request->filled('id')) {
try {
$bill = Bill::findOrFail($request->input('id'));
$bill->delete();
} catch (\Throwable $th) {
throw new \Exception("账单不存在!", 1);
}
}
if ($request->filled('ids')) {
try {
$bill = Bill::whereIn('id', $request->input('ids'));
$bill->delete();
} catch (\Throwable $th) {
throw new \Exception($th->getMessage(), 1);
}
}
return $bill;
}
/**
* @title 账单详情
*
* @param Request $request
* @return void
*/
public function detail($request) {
$id = $request->input('id');
$bill = Bill::with(['user:uid,nickname,username', 'family:id,name,owner_id'])
->findOrFail($id);
return $bill;
}
/**
* @title 获取分类映射
*
* @param string $type
* @return array
*/
private function getCategoryMap($type) {
if ($type === 'income') {
return [
101 => '工资',
102 => '奖金',
103 => '投资',
104 => '兼职',
105 => '其他'
];
} else {
return [
1 => '餐饮',
2 => '交通',
3 => '购物',
4 => '娱乐',
5 => '医疗',
6 => '教育',
7 => '居住',
8 => '其他'
];
}
}
/**
* @title 获取分类列表
*
* @return array
*/
public function getCategoryList() {
return [
'expense' => [
['id' => 1, 'name' => '餐饮'],
['id' => 2, 'name' => '交通'],
['id' => 3, 'name' => '购物'],
['id' => 4, 'name' => '娱乐'],
['id' => 5, 'name' => '医疗'],
['id' => 6, 'name' => '教育'],
['id' => 7, 'name' => '居住'],
['id' => 8, 'name' => '其他']
],
'income' => [
['id' => 101, 'name' => '工资'],
['id' => 102, 'name' => '奖金'],
['id' => 103, 'name' => '投资'],
['id' => 104, 'name' => '兼职'],
['id' => 105, 'name' => '其他']
]
];
}
/**
* @title 获取支付方式列表
*
* @return array
*/
public function getPaymentMethods() {
return [
['value' => '微信', 'label' => '微信'],
['value' => '支付宝', 'label' => '支付宝'],
['value' => '银行卡', 'label' => '银行卡'],
['value' => '现金', 'label' => '现金'],
['value' => '其他', 'label' => '其他']
];
}
}

View File

@@ -0,0 +1,422 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Services;
use Illuminate\Http\Request;
use Modules\Account\Models\Family;
use Modules\Account\Models\FamilyMember;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AdminFamilyService {
/**
* @title 获取家庭列表
*
* @param Request $request
* @return void
*/
public function getDataList($request) {
$map = [];
// 名称搜索
if ($request->filled('name')) {
$map[] = ['name', 'like', '%' . $request->input('name') . '%'];
}
// 家主筛选
if ($request->filled('owner_id')) {
$map[] = ['owner_id', '=', $request->input('owner_id')];
}
// 邀请码筛选
if ($request->filled('invite_code')) {
$map[] = ['invite_code', '=', $request->input('invite_code')];
}
// 日期范围筛选
if ($request->filled('start_date')) {
$map[] = ['created_at', '>=', $request->input('start_date')];
}
if ($request->filled('end_date')) {
$map[] = ['created_at', '<=', $request->input('end_date')];
}
$query = Family::with(['owner:uid,nickname,username', 'members:uid,nickname,username']);
$query->where($map);
// 排序
$query->orderBy($request->input('order', 'created_at'), $request->input('sort', 'desc'));
// 分页
if ($request->filled('page')) {
$data = [
'total' => $query->count(),
'page' => $request->input('page', 1),
'data' => $query->offset($request->input('offset', 0))
->limit($request->input('limit', 10))
->get(),
];
} else {
$data = $query->get();
}
// 添加成员数量统计
if ($request->filled('page')) {
foreach ($data['data'] as $family) {
$family->member_count = $family->members->count();
}
}
return $data;
}
/**
* @title 创建家庭
*
* @param Request $request
* @return void
*/
public function create($request) {
$request->validate([
'name' => 'required|string|max:50',
'owner_id' => 'required|integer'
], [
'name.required' => '请输入家庭名称',
'owner_id.required' => '请选择家主'
]);
$data = $request->all();
// 生成10位邀请码
do {
$inviteCode = strtoupper(substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 10));
} while (Family::where('invite_code', $inviteCode)->exists());
$data['invite_code'] = $inviteCode;
DB::beginTransaction();
try {
$family = Family::create($data);
// 家主自动加入家庭
FamilyMember::create([
'family_id' => $family->id,
'user_id' => $data['owner_id']
]);
DB::commit();
return $family;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* @title 更新家庭
*
* @param Request $request
* @return void
*/
public function update($request) {
$request->validate([
'id' => 'required|integer',
'name' => 'required|string|max:50',
'owner_id' => 'required|integer'
], [
'id.required' => '请提供家庭ID',
'name.required' => '请输入家庭名称',
'owner_id.required' => '请选择家主'
]);
$family = Family::findOrFail($request->input('id'));
$data = $request->all();
unset($data['id']);
$family->update($data);
return $family;
}
/**
* @title 删除家庭
*
* @param Request $request
* @return void
*/
public function delete($request) {
if ($request->filled('id')) {
try {
$family = Family::findOrFail($request->input('id'));
DB::beginTransaction();
try {
// 删除家庭成员
FamilyMember::where('family_id', $family->id)->delete();
// 删除家庭
$family->delete();
DB::commit();
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
} catch (\Throwable $th) {
throw new \Exception("家庭不存在!", 1);
}
}
if ($request->filled('ids')) {
try {
$families = Family::whereIn('id', $request->input('ids'))->get();
DB::beginTransaction();
try {
foreach ($families as $family) {
// 删除家庭成员
FamilyMember::where('family_id', $family->id)->delete();
}
// 删除家庭
$families->each->delete();
DB::commit();
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
} catch (\Throwable $th) {
throw new \Exception($th->getMessage(), 1);
}
}
return $family;
}
/**
* @title 家庭详情
*
* @param Request $request
* @return void
*/
public function detail($request) {
$id = $request->input('id');
$family = Family::with(['owner:uid,nickname,username', 'members:uid,nickname,username,email'])
->findOrFail($id);
// 添加成员数量
$family->member_count = $family->members->count();
// 添加统计信息
$billCount = DB::table('account_bills')->where('family_id', $family->id)->count();
$totalExpense = DB::table('account_bills')
->where('family_id', $family->id)
->where('type', 'expense')
->sum('amount');
$totalIncome = DB::table('account_bills')
->where('family_id', $family->id)
->where('type', 'income')
->sum('amount');
$family->statistics = [
'bill_count' => $billCount,
'total_expense' => (float)$totalExpense,
'total_income' => (float)$totalIncome,
'total_balance' => (float)($totalIncome - $totalExpense)
];
return $family;
}
/**
* @title 获取家庭成员列表
*
* @param Request $request
* @return void
*/
public function getMembers($request) {
$familyId = $request->input('family_id');
if (!$familyId) {
throw new \Exception('请提供家庭ID');
}
$family = Family::findOrFail($familyId);
$members = FamilyMember::with('user:uid,nickname,username,email')
->where('family_id', $familyId)
->get();
return [
'family_id' => $familyId,
'family_name' => $family->name,
'owner_id' => $family->owner_id,
'members' => $members->map(function($member) use ($family) {
return [
'id' => $member->id,
'user_id' => $member->user_id,
'name' => $member->user->nickname ?? $member->user->username,
'username' => $member->user->username,
'email' => $member->user->email,
'is_owner' => $family->owner_id == $member->user_id,
'joined_at' => $member->created_at
];
})->toArray(),
'total' => $members->count()
];
}
/**
* @title 添加家庭成员
*
* @param Request $request
* @return void
*/
public function addMember($request) {
$request->validate([
'family_id' => 'required|integer',
'user_id' => 'required|integer'
], [
'family_id.required' => '请提供家庭ID',
'user_id.required' => '请选择用户'
]);
$familyId = $request->input('family_id');
$userId = $request->input('user_id');
// 检查家庭是否存在
$family = Family::findOrFail($familyId);
// 检查用户是否已在家庭中
$existingMember = FamilyMember::where('family_id', $familyId)
->where('user_id', $userId)
->first();
if ($existingMember) {
throw new \Exception('该用户已在家庭中');
}
// 检查用户是否已在其他家庭中
$userInOtherFamily = FamilyMember::where('user_id', $userId)->first();
if ($userInOtherFamily) {
throw new \Exception('该用户已在其他家庭中');
}
// 添加家庭成员
FamilyMember::create([
'family_id' => $familyId,
'user_id' => $userId
]);
return ['message' => '成员已添加'];
}
/**
* @title 移除家庭成员
*
* @param Request $request
* @return void
*/
public function removeMember($request) {
$request->validate([
'family_id' => 'required|integer',
'user_id' => 'required|integer'
], [
'family_id.required' => '请提供家庭ID',
'user_id.required' => '请提供用户ID'
]);
$familyId = $request->input('family_id');
$userId = $request->input('user_id');
// 检查家庭是否存在
$family = Family::findOrFail($familyId);
// 不能移除家主
if ($family->owner_id == $userId) {
throw new \Exception('不能移除家主,请先转让家主');
}
// 移除成员
$member = FamilyMember::where('family_id', $familyId)
->where('user_id', $userId)
->first();
if (!$member) {
throw new \Exception('该用户不在家庭中');
}
$member->delete();
return ['message' => '成员已移除'];
}
/**
* @title 重新生成邀请码
*
* @param Request $request
* @return void
*/
public function regenerateInviteCode($request) {
$familyId = $request->input('family_id');
if (!$familyId) {
throw new \Exception('请提供家庭ID');
}
$family = Family::findOrFail($familyId);
// 生成新邀请码
do {
$inviteCode = strtoupper(substr(str_shuffle('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 10));
} while (Family::where('invite_code', $inviteCode)->exists());
$family->update(['invite_code' => $inviteCode]);
return [
'invite_code' => $inviteCode,
'family_name' => $family->name
];
}
/**
* @title 转让家主
*
* @param Request $request
* @return void
*/
public function transferOwner($request) {
$request->validate([
'family_id' => 'required|integer',
'user_id' => 'required|integer'
], [
'family_id.required' => '请提供家庭ID',
'user_id.required' => '请提供用户ID'
]);
$familyId = $request->input('family_id');
$userId = $request->input('user_id');
$family = Family::findOrFail($familyId);
// 目标用户必须在家庭中
$targetMember = FamilyMember::where('family_id', $familyId)
->where('user_id', $userId)
->first();
if (!$targetMember) {
throw new \Exception('该用户不在家庭中');
}
// 转让家主
$family->update(['owner_id' => $userId]);
return ['message' => '家主已转让'];
}
}

View File

@@ -0,0 +1,303 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace Modules\Account\Services;
use Illuminate\Http\Request;
use Modules\Account\Models\Bill;
use Modules\Account\Models\Family;
use Modules\Account\Models\FamilyMember;
use Illuminate\Support\Facades\DB;
class AdminStatisticsService {
/**
* @title 获取概览统计
*
* @param Request $request
* @return array
*/
public function getOverview($request) {
$year = $request->input('year', date('Y'));
$month = $request->input('month', date('m'));
$startDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-01';
$endDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-31';
// 总账单数
$totalBills = Bill::count();
// 当月账单数
$monthBills = Bill::whereBetween('bill_date', [$startDate, $endDate])->count();
// 总收入
$totalIncome = (float)Bill::where('type', 'income')->sum('amount');
// 总支出
$totalExpense = (float)Bill::where('type', 'expense')->sum('amount');
// 当月收入
$monthIncome = (float)Bill::where('type', 'income')
->whereBetween('bill_date', [$startDate, $endDate])
->sum('amount');
// 当月支出
$monthExpense = (float)Bill::where('type', 'expense')
->whereBetween('bill_date', [$startDate, $endDate])
->sum('amount');
// 总用户数
$totalUsers = DB::table('auth_users')->count();
// 总家庭数
$totalFamilies = Family::count();
// 活跃用户数(本月有记账的用户)
$activeUsers = Bill::whereBetween('bill_date', [$startDate, $endDate])
->distinct('user_id')
->count('user_id');
// 活跃家庭数(本月有记账的家庭)
$activeFamilies = Bill::whereBetween('bill_date', [$startDate, $endDate])
->whereNotNull('family_id')
->distinct('family_id')
->count('family_id');
return [
'total_bills' => $totalBills,
'month_bills' => $monthBills,
'total_income' => $totalIncome,
'total_expense' => $totalExpense,
'total_balance' => $totalIncome - $totalExpense,
'month_income' => $monthIncome,
'month_expense' => $monthExpense,
'month_balance' => $monthIncome - $monthExpense,
'total_users' => $totalUsers,
'total_families' => $totalFamilies,
'active_users' => $activeUsers,
'active_families' => $activeFamilies
];
}
/**
* @title 获取趋势统计
*
* @param Request $request
* @return array
*/
public function getTrend($request) {
$type = $request->input('type', 'month'); // month 或 year
$year = $request->input('year', date('Y'));
$months = $request->input('months', 12);
$years = $request->input('years', 5);
if ($type === 'month') {
// 月度趋势
$data = [];
for ($i = $months - 1; $i >= 0; $i--) {
$date = date('Y-m', strtotime("-$i months"));
$startDate = $date . '-01';
$endDate = date('Y-m-t', strtotime($date));
$income = (float)Bill::where('type', 'income')
->whereBetween('bill_date', [$startDate, $endDate])
->sum('amount');
$expense = (float)Bill::where('type', 'expense')
->whereBetween('bill_date', [$startDate, $endDate])
->sum('amount');
$count = Bill::whereBetween('bill_date', [$startDate, $endDate])->count();
$data[] = [
'date' => $date,
'income' => $income,
'expense' => $expense,
'balance' => $income - $expense,
'count' => $count
];
}
} else {
// 年度趋势
$data = [];
for ($i = $years - 1; $i >= 0; $i--) {
$currentYear = date('Y') - $i;
$income = (float)Bill::where('type', 'income')
->whereYear('bill_date', $currentYear)
->sum('amount');
$expense = (float)Bill::where('type', 'expense')
->whereYear('bill_date', $currentYear)
->sum('amount');
$count = Bill::whereYear('bill_date', $currentYear)->count();
$data[] = [
'year' => $currentYear,
'income' => $income,
'expense' => $expense,
'balance' => $income - $expense,
'count' => $count
];
}
}
return $data;
}
/**
* @title 获取分类统计
*
* @param Request $request
* @return array
*/
public function getCategoryStatistics($request) {
$type = $request->input('type', 'expense'); // income 或 expense
$year = $request->input('year', date('Y'));
$month = $request->input('month');
$query = Bill::where('type', $type);
if ($year && $month) {
$startDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-01';
$endDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-31';
$query->whereBetween('bill_date', [$startDate, $endDate]);
} else {
$query->whereYear('bill_date', $year);
}
$bills = $query->get();
// 按分类汇总
$data = $bills->groupBy('category')->map(function ($items) {
return [
'category' => $items->first()->category,
'count' => $items->count(),
'amount' => (float)$items->sum('amount')
];
})->sortByDesc('amount')->values();
// 计算总金额和占比
$totalAmount = $data->sum('amount');
$data = $data->map(function ($item) use ($totalAmount) {
$item['percentage'] = $totalAmount > 0 ? round($item['amount'] / $totalAmount * 100, 2) : 0;
return $item;
});
return [
'type' => $type,
'total_amount' => (float)$totalAmount,
'total_count' => $data->sum('count'),
'categories' => $data
];
}
/**
* @title 获取用户统计
*
* @param Request $request
* @return array
*/
public function getUserStatistics($request) {
$limit = $request->input('limit', 20);
$type = $request->input('type', 'expense'); // income 或 expense
$year = $request->input('year', date('Y'));
$month = $request->input('month');
$query = Bill::select('user_id')
->selectRaw('COUNT(*) as bill_count')
->selectRaw('SUM(CASE WHEN type = "income" THEN amount ELSE 0 END) as total_income')
->selectRaw('SUM(CASE WHEN type = "expense" THEN amount ELSE 0 END) as total_expense')
->selectRaw('SUM(CASE WHEN type = "income" THEN amount ELSE 0 END) - SUM(CASE WHEN type = "expense" THEN amount ELSE 0 END) as balance')
->with('user:uid,nickname,username');
if ($year && $month) {
$startDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-01';
$endDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-31';
$query->whereBetween('bill_date', [$startDate, $endDate]);
} else {
$query->whereYear('bill_date', $year);
}
$users = $query->groupBy('user_id')
->orderBy($type === 'income' ? 'total_income' : 'total_expense', 'desc')
->limit($limit)
->get();
return [
'type' => $type,
'users' => $users->map(function ($user) {
return [
'user_id' => $user->user_id,
'name' => $user->user->nickname ?? $user->user->username,
'username' => $user->user->username,
'bill_count' => $user->bill_count,
'total_income' => (float)$user->total_income,
'total_expense' => (float)$user->total_expense,
'balance' => (float)$user->balance
];
})
];
}
/**
* @title 获取家庭统计
*
* @param Request $request
* @return array
*/
public function getFamilyStatistics($request) {
$limit = $request->input('limit', 20);
$type = $request->input('type', 'expense'); // income 或 expense
$year = $request->input('year', date('Y'));
$month = $request->input('month');
$query = Bill::select('family_id')
->selectRaw('COUNT(*) as bill_count')
->selectRaw('SUM(CASE WHEN type = "income" THEN amount ELSE 0 END) as total_income')
->selectRaw('SUM(CASE WHEN type = "expense" THEN amount ELSE 0 END) as total_expense')
->selectRaw('SUM(CASE WHEN type = "income" THEN amount ELSE 0 END) - SUM(CASE WHEN type = "expense" THEN amount ELSE 0 END) as balance')
->whereNotNull('family_id')
->with('family:id,name,owner_id');
if ($year && $month) {
$startDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-01';
$endDate = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-31';
$query->whereBetween('bill_date', [$startDate, $endDate]);
} else {
$query->whereYear('bill_date', $year);
}
$families = $query->groupBy('family_id')
->orderBy($type === 'income' ? 'total_income' : 'total_expense', 'desc')
->limit($limit)
->get();
// 获取成员数量
$familyIds = $families->pluck('family_id')->toArray();
$memberCounts = FamilyMember::whereIn('family_id', $familyIds)
->selectRaw('family_id, COUNT(*) as member_count')
->groupBy('family_id')
->pluck('member_count', 'family_id');
return [
'type' => $type,
'families' => $families->map(function ($family) use ($memberCounts) {
return [
'family_id' => $family->family_id,
'name' => $family->family->name,
'member_count' => $memberCounts[$family->family_id] ?? 0,
'bill_count' => $family->bill_count,
'total_income' => (float)$family->total_income,
'total_expense' => (float)$family->total_expense,
'balance' => (float)$family->balance
];
})
];
}
}

View File

@@ -9,6 +9,7 @@
namespace Modules\Account\Database\Seeders;
use Illuminate\Database\Seeder;
use App\Services\Auth\MenuService;
class AccountDatabaseSeeder extends Seeder
{
@@ -17,6 +18,36 @@ class AccountDatabaseSeeder extends Seeder
*/
public function run(): void
{
// $this->call([]);
$this->addAccountMenu();
}
/**
* @title 记账模块菜单导入
*
* @return void
*/
public function addAccountMenu(){
$menuService = app(MenuService::class);
$menuService->importMenu([
['title' => '账单', 'name' => 'account', 'path' => '/account', 'component' => 'account', 'type' => 'menu', 'children' => [
['title' => '账单管理', 'name' => 'account.lists', 'path' => '/account/lists', 'component' => 'account/lists', 'type' => 'menu', 'children' => [
['title' => '查看账单', 'name' => 'account.bill.view', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '添加账单', 'name' => 'account.bill.add', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '编辑账单', 'name' => 'account.bill.edit', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '删除账单', 'name' => 'account.bill.delete', 'path' => '', 'component' => '', 'type' => 'button'],
]
],
['title' => '家庭管理', 'name' => 'account.family', 'path' => '/account/family', 'component' => 'account/family', 'type' => 'menu', 'children' => [
['title' => '查看家庭', 'name' => 'account.family.view', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '添加家庭', 'name' => 'account.family.add', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '编辑家庭', 'name' => 'account.family.edit', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '删除家庭', 'name' => 'account.family.delete', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '管理成员', 'name' => 'account.family.manage', 'path' => '', 'component' => '', 'type' => 'button'],
]],
['title' => '统计报表', 'name' => 'account.statistics', 'path' => '/account/statistics', 'component' => 'account/statistics', 'type' => 'menu', 'children' => [
['title' => '查看统计', 'name' => 'account.statistics.view', 'path' => '', 'component' => '', 'type' => 'button'],
['title' => '导出报表', 'name' => 'account.statistics.export', 'path' => '', 'component' => '', 'type' => 'button'],
]],
]]], 0);
}
}

View File

@@ -7,8 +7,32 @@
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
use Illuminate\Support\Facades\Route;
use Modules\Account\Controllers\AccountController;
use Modules\Account\Controllers\Admin\Bill;
use Modules\Account\Controllers\Admin\Family;
use Modules\Account\Controllers\Admin\Statistics;
Route::middleware(['auth.check:admin'])->group(function () {
Route::apiResource('account', AccountController::class)->names('account');
// 账单管理
Route::get('account/bill', [Bill::class, 'index'])->name('account.bill.index');
Route::post('account/bill', [Bill::class, 'add'])->name('account.bill.add');
Route::put('account/bill', [Bill::class, 'edit'])->name('account.bill.edit');
Route::delete('account/bill', [Bill::class, 'delete'])->name('account.bill.delete');
Route::get('account/bill/detail', [Bill::class, 'detail'])->name('account.bill.detail');
// 家庭管理
Route::get('account/family', [Family::class, 'index'])->name('account.family.index');
Route::post('account/family', [Family::class, 'add'])->name('account.family.add');
Route::put('account/family', [Family::class, 'edit'])->name('account.family.edit');
Route::delete('account/family', [Family::class, 'delete'])->name('account.family.delete');
Route::get('account/family/detail', [Family::class, 'detail'])->name('account.family.detail');
Route::get('account/family/members', [Family::class, 'members'])->name('account.family.members');
Route::post('account/family/member', [Family::class, 'addMember'])->name('account.family.addMember');
Route::delete('account/family/member', [Family::class, 'removeMember'])->name('account.family.removeMember');
// 统计报表
Route::get('account/statistics/overview', [Statistics::class, 'overview'])->name('account.statistics.overview');
Route::get('account/statistics/trend', [Statistics::class, 'trend'])->name('account.statistics.trend');
Route::get('account/statistics/category', [Statistics::class, 'category'])->name('account.statistics.category');
Route::get('account/statistics/user', [Statistics::class, 'user'])->name('account.statistics.user');
Route::get('account/statistics/family', [Statistics::class, 'family'])->name('account.statistics.family');
});