feat: 微客宝H5客户端 - 基础架构+核心页面
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Customer;
|
||||
use App\Models\Finance\AccountTransaction;
|
||||
use App\Models\Finance\CustomerAccount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
/**
|
||||
* 我的账户列表
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$accounts = CustomerAccount::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->get();
|
||||
|
||||
return $this->success($accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户流水记录(分页)
|
||||
*/
|
||||
public function transactions(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = AccountTransaction::withoutGlobalScope('store')
|
||||
->whereHas('account', fn ($q) => $q->where('customer_id', $customer->id));
|
||||
|
||||
if ($request->filled('account_id')) {
|
||||
$query->where('customer_account_id', (int) $request->input('account_id'));
|
||||
}
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('id')->paginate(20)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Customer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* 客户端登录(手机号 + 密码)
|
||||
*/
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$customer = Customer::withoutGlobalScope('store')
|
||||
->where('phone', $request->phone)
|
||||
->first();
|
||||
|
||||
if (!$customer || !$customer->password) {
|
||||
return $this->error('手机号或密码错误', 40100, 401);
|
||||
}
|
||||
|
||||
if (!\Hash::check($request->password, $customer->password)) {
|
||||
return $this->error('手机号或密码错误', 40100, 401);
|
||||
}
|
||||
|
||||
if ($customer->status !== 1) {
|
||||
return $this->error('账号已停用,请联系门店', 40101, 403);
|
||||
}
|
||||
|
||||
// 清除旧 client token
|
||||
$customer->tokens()->where('name', 'client')->delete();
|
||||
|
||||
$token = $customer->createToken('client')->plainTextToken;
|
||||
$customer->update(['last_login_at' => now()]);
|
||||
|
||||
return $this->success([
|
||||
'token' => $token,
|
||||
'customer' => $this->formatCustomer($customer),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
return $this->success(null, '退出成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前客户信息
|
||||
*/
|
||||
public function me(Request $request): JsonResponse
|
||||
{
|
||||
$customer = $request->user()->load(['families', 'contracts']);
|
||||
return $this->success($this->formatCustomer($customer));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
public function changePassword(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'old_password' => 'required|string',
|
||||
'new_password' => 'required|string|min:6',
|
||||
]);
|
||||
|
||||
$customer = $request->user();
|
||||
if (!\Hash::check($request->old_password, $customer->password)) {
|
||||
return $this->error('原密码不正确', 40001);
|
||||
}
|
||||
|
||||
$customer->update(['password' => $request->new_password]);
|
||||
return $this->success(null, '密码修改成功');
|
||||
}
|
||||
|
||||
private function formatCustomer(Customer $c): array
|
||||
{
|
||||
return [
|
||||
'id' => $c->id,
|
||||
'name' => $c->name,
|
||||
'phone' => $c->phone,
|
||||
'wechat' => $c->wechat,
|
||||
'store_id' => $c->store_id,
|
||||
'expected_date' => $c->expected_date?->toDateString(),
|
||||
'actual_date' => $c->actual_date?->toDateString(),
|
||||
'baby_count' => $c->baby_count,
|
||||
'status' => $c->status,
|
||||
'families' => $c->relationLoaded('families') ? $c->families : [],
|
||||
'contracts' => $c->relationLoaded('contracts') ? $c->contracts : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Care\CareProfile;
|
||||
use App\Models\Care\CareRecord;
|
||||
use App\Models\Care\HealthMetric;
|
||||
use App\Models\Crm\Customer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CareController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取当前客户护理档案
|
||||
*/
|
||||
public function profile(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$profile = CareProfile::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if ($profile) {
|
||||
$profile->load(['plans']);
|
||||
}
|
||||
|
||||
return $this->success($profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 护理记录列表(分页)
|
||||
*/
|
||||
public function records(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$profile = CareProfile::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$profile) {
|
||||
return $this->paginate(
|
||||
CareRecord::withoutGlobalScope('store')->whereRaw('0=1')->paginate(10)
|
||||
);
|
||||
}
|
||||
|
||||
$query = CareRecord::withoutGlobalScope('store')
|
||||
->where('care_profile_id', $profile->id)
|
||||
->with('nurse:id,name');
|
||||
|
||||
if ($request->filled('type')) {
|
||||
$query->where('type', (int) $request->input('type'));
|
||||
}
|
||||
|
||||
return $this->paginate($query->latest('recorded_at')->paginate(10));
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康指标列表(最近30条)
|
||||
*/
|
||||
public function metrics(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$profile = CareProfile::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$profile) {
|
||||
return $this->success([]);
|
||||
}
|
||||
|
||||
$query = HealthMetric::withoutGlobalScope('store')
|
||||
->where('care_profile_id', $profile->id);
|
||||
|
||||
if ($request->filled('metric_type')) {
|
||||
$query->where('metric_type', $request->input('metric_type'));
|
||||
}
|
||||
|
||||
$list = $query->orderByDesc('measured_at')->limit(30)->get();
|
||||
|
||||
return $this->success($list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Complaint;
|
||||
use App\Models\Crm\Customer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ComplaintController extends Controller
|
||||
{
|
||||
/**
|
||||
* 我的投诉列表(分页)
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = Complaint::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id);
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('id')->paginate(10)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交投诉
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'type' => 'required|string|max:50',
|
||||
'content' => 'required|string|max:1000',
|
||||
'images' => 'nullable|array',
|
||||
'images.*' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
$complaint = Complaint::create([
|
||||
'customer_id' => $customer->id,
|
||||
'store_id' => $customer->store_id,
|
||||
'type' => $validated['type'],
|
||||
'content' => $validated['content'],
|
||||
'images' => $validated['images'] ?? null,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
return $this->success($complaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Care\CareProfile;
|
||||
use App\Models\Care\CareRecord;
|
||||
use App\Models\Crm\Contract;
|
||||
use App\Models\Crm\Customer;
|
||||
use App\Models\Meal\DailyMealPlan;
|
||||
use App\Models\Nanny\NannyOrder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* 首页汇总数据
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
// 当前客户基本信息
|
||||
$customerInfo = [
|
||||
'name' => $customer->name,
|
||||
'phone' => $customer->phone,
|
||||
'expected_date' => $customer->expected_date,
|
||||
'actual_date' => $customer->actual_date,
|
||||
];
|
||||
|
||||
// 护理档案 → 最新护理记录3条
|
||||
$careProfile = CareProfile::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
$careRecords = [];
|
||||
if ($careProfile) {
|
||||
$careRecords = CareRecord::withoutGlobalScope('store')
|
||||
->where('care_profile_id', $careProfile->id)
|
||||
->with('nurse:id,name')
|
||||
->latest('recorded_at')
|
||||
->limit(3)
|
||||
->get();
|
||||
}
|
||||
|
||||
// 今日膳食
|
||||
$todayMeals = DailyMealPlan::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->whereDate('plan_date', today())
|
||||
->get();
|
||||
|
||||
// 当前合同(最新1条)
|
||||
$contract = Contract::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->latest()
|
||||
->first(['id', 'contract_no', 'status', 'start_date', 'end_date', 'total_amount']);
|
||||
|
||||
// 月嫂信息(进行中派单)
|
||||
$nannyOrder = NannyOrder::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->where('status', 2)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($nannyOrder) {
|
||||
$nannyOrder->load('nanny:id,name,level,phone');
|
||||
}
|
||||
|
||||
return $this->success(compact(
|
||||
'customerInfo',
|
||||
'careRecords',
|
||||
'todayMeals',
|
||||
'contract',
|
||||
'nannyOrder'
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Customer;
|
||||
use App\Models\Meal\DailyMealPlan;
|
||||
use App\Models\Meal\MealReview;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class MealController extends Controller
|
||||
{
|
||||
/**
|
||||
* 我的用膳列表(分页)
|
||||
*/
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = DailyMealPlan::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->with('review');
|
||||
|
||||
// 按指定日期筛选
|
||||
if ($request->filled('date')) {
|
||||
$query->whereDate('plan_date', $request->input('date'));
|
||||
}
|
||||
|
||||
// 最近N天
|
||||
if ($request->filled('days') && !$request->filled('date')) {
|
||||
$days = (int) $request->input('days', 7);
|
||||
$query->where('plan_date', '>=', Carbon::today()->subDays($days - 1));
|
||||
}
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('plan_date')->paginate(20)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日用膳
|
||||
*/
|
||||
public function today(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$list = DailyMealPlan::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->whereDate('plan_date', today())
|
||||
->get();
|
||||
|
||||
return $this->success($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交膳食点评
|
||||
*/
|
||||
public function review(Request $request, int $planId): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'rating' => 'required|integer|min:1|max:5',
|
||||
'content' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
// 确认该膳食计划属于当前客户
|
||||
$plan = DailyMealPlan::withoutGlobalScope('store')
|
||||
->where('id', $planId)
|
||||
->where('customer_id', $customer->id)
|
||||
->first();
|
||||
|
||||
if (!$plan) {
|
||||
return $this->error('膳食记录不存在或无权操作', 40401, 404);
|
||||
}
|
||||
|
||||
$review = MealReview::updateOrCreate(
|
||||
['daily_meal_plan_id' => $planId],
|
||||
[
|
||||
'score' => $validated['rating'],
|
||||
'content' => $validated['content'] ?? null,
|
||||
'customer_id' => $customer->id,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->success($review);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Customer;
|
||||
use App\Models\Nanny\Nanny;
|
||||
use App\Models\Nanny\NannyOrder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NannyController extends Controller
|
||||
{
|
||||
/**
|
||||
* 当前派单月嫂信息
|
||||
*/
|
||||
public function myNanny(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$order = NannyOrder::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->whereIn('status', [2, 3])
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if (!$order) {
|
||||
return $this->success(null);
|
||||
}
|
||||
|
||||
$order->load('nanny');
|
||||
|
||||
return $this->success([
|
||||
'order' => $order,
|
||||
'nanny' => $order->nanny,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 月嫂订单列表(分页)
|
||||
*/
|
||||
public function orders(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = NannyOrder::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->with('nanny:id,name,level,phone');
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('id')->paginate(15)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Crm\Customer;
|
||||
use App\Models\Service\ServiceExecution;
|
||||
use App\Models\Service\ServiceOrder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* 我的服务订单(分页)
|
||||
*/
|
||||
public function orders(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = ServiceOrder::withoutGlobalScope('store')
|
||||
->where('customer_id', $customer->id)
|
||||
->with('package:id,name', 'item:id,name');
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', (int) $request->input('status'));
|
||||
}
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('id')->paginate(15)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务执行记录(分页)
|
||||
*/
|
||||
public function executions(Request $request): JsonResponse
|
||||
{
|
||||
/** @var Customer $customer */
|
||||
$customer = $request->user();
|
||||
|
||||
$query = ServiceExecution::withoutGlobalScope('store')
|
||||
->whereHas('order', fn ($q) => $q->where('customer_id', $customer->id))
|
||||
->with('technician:id,name', 'order:id,status');
|
||||
|
||||
return $this->paginate(
|
||||
$query->orderByDesc('executed_at')->paginate(15)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user