小程序登录优化

This commit is contained in:
liu
2026-08-10 08:55:22 +08:00
parent c6f69c5c55
commit df7631c7a9
25 changed files with 954 additions and 549 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
if (!function_exists('generate_unique_code')) {
function generate_unique_code($model, $column = 'code', $length = 6): string
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$max = strlen($characters) - 1;
do {
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $characters[random_int(0, $max)];
}
} while ($model::where($column, $code)->exists());
return $code;
}
}
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Customer;
use App\Exceptions\RepositoryException;
use App\Http\Requests\Customer\MiniUserBindRequest;
use App\Models\StoreModel;
use App\Models\SupplierModel;
use App\Models\UserModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -21,9 +20,7 @@ use Modules\Common\Http\Controllers\BaseController;
class MiniUserController extends BaseController
{
protected array $searchField = [
'type' => '=',
'store_id' => '=',
'supplier_id' => '=',
'status' => '=',
];
@@ -35,7 +32,7 @@ class MiniUserController extends BaseController
{
$params = $request->all();
$pageSize = $params['pageSize'] ?? 10;
$data = $this->buildSearch($params, UserModel::query()->with('store:id,name', 'supplier:id,name'))
$data = $this->buildSearch($params, UserModel::query()->with('store:id,name'))
->orderBy('id', 'desc')
->paginate($pageSize)
->toArray();
@@ -43,7 +40,7 @@ class MiniUserController extends BaseController
}
/**
* 绑定门店/供应商(一个门店可绑多个账号,一个账号只绑一个主体
* 绑定门店(一个门店可绑多个账号,一个账号只绑一个门店
*/
#[PutRoute(route: '/{id}/bind', authorize: 'bind', where: ['id' => '[0-9]+'])]
public function bind(int $id, MiniUserBindRequest $request): JsonResponse
@@ -53,29 +50,16 @@ class MiniUserController extends BaseController
if (empty($user)) {
throw new RepositoryException('用户不存在');
}
if ((int) $validated['type'] === UserModel::TYPE_STORE) {
$store = StoreModel::find((int) $validated['store_id']);
if (empty($store)) {
throw new RepositoryException('门店不存在');
}
$user->type = UserModel::TYPE_STORE;
$user->store_id = $store->id;
$user->supplier_id = 0;
} else {
$supplier = SupplierModel::find((int) $validated['supplier_id']);
if (empty($supplier)) {
throw new RepositoryException('供应商不存在');
}
$user->type = UserModel::TYPE_SUPPLIER;
$user->supplier_id = $supplier->id;
$user->store_id = 0;
$store = StoreModel::find((int) $validated['store_id']);
if (empty($store)) {
throw new RepositoryException('门店不存在');
}
$user->store_id = $store->id;
$user->save();
return $this->success();
}
/** 启用/停用(停用后登录时检查 status 拒绝,token 鉴权拦截) */
/** 启用/停用 */
#[PutRoute(route: '/{id}/status', authorize: 'update', where: ['id' => '[0-9]+'])]
public function status(int $id, Request $request): JsonResponse
{
@@ -46,7 +46,9 @@ class StoreController extends BaseController
#[PostRoute(authorize: 'create')]
public function create(StoreFormRequest $request): JsonResponse
{
StoreModel::create($request->validated());
$validated = $request->validated();
$validated['code'] = generate_unique_code(StoreModel::class);
StoreModel::create($validated);
return $this->success();
}
+73 -82
View File
@@ -3,134 +3,125 @@
namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Http\Requests\UserUpdateInfoRequest;
use App\Models\StoreModel;
use App\Models\SupplierModel;
use App\Models\UserModel;
use App\Services\WechatService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
/**
* 小程序认证(微信登录 / 手机号绑定 / 当前用户信息)
*
* authGuard: usersprovider 指向 UserModel,与后台 sys_users 天然隔离);
* authorize: true 仅要求登录(sanctum + authGuard:users),不做细粒度权限点;
* token abilities ['mini'] 作来源标记。
* 小程序认证
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class AuthController extends BaseMiniController
{
/** 小程序登录wx.login 的 code → openid → 自动注册/登录 → 签发 token */
/** 小程序登录 */
#[PostRoute('/auth/login', authorize: false)]
public function login(Request $request): JsonResponse
{
$data = $request->validate([
'code' => 'required|string',
$validated = $request->validate([
'code' => 'required|string'
], [
'code.required' => '缺少登录凭证 code',
'code.required' => '登录参数格式错误',
'code.string' => '登录参数格式错误',
]);
$session = app(WechatService::class)->code2Session($data['code']);
$session = app(WechatService::class)->code2Session($validated['code']);
$user = UserModel::firstOrNew(['openid' => $session['openid']]);
$isNew = ! $user->exists;
$user = UserModel::where('openid', $session['openid'])->first();
if ($user->status === UserModel::STATUS_DISABLED) {
throw new RepositoryException('账号已被停用,请联系客服');
if ($user === null || $user->status === UserModel::STATUS_DISABLED) {
return $this->error('账号不存在或已被停用');
}
if ($isNew) {
$user->type = UserModel::TYPE_PENDING;
$user->status = UserModel::STATUS_NORMAL;
}
if (! empty($session['unionid'])) {
$user->unionid = $session['unionid'];
}
$user->last_login_at = now();
$user->last_login_at = date('Y-m-d H:i:s');
$user->save();
$token = $user->createToken('mini', ['mini'])->plainTextToken;
$token = $user->createToken($user->openid)->toArray();
return $this->success([
'token' => $token,
'user' => $this->formatUser($user->fresh(['store.level:id,name', 'supplier:id,name'])),
], $isNew ? '注册成功' : '登录成功');
'token' => $token['plainTextToken'],
'user' => $user->toArray(),
], __('user.login_success'));
}
/**
* 绑定手机号:phoneCode 换手机号 → 按手机号自动匹配门店/供应商
* (命中门店 → type=1+store_id;命中供应商 → type=2+supplier_id;都不命中 → 保持待绑定,后台人工处理)
*/
#[PostRoute('/auth/phone', authorize: true)]
public function phone(Request $request): JsonResponse
/** 小程序注册 */
#[PostRoute('/auth/register', authorize: false)]
public function register(Request $request): JsonResponse
{
$data = $request->validate([
$validated = $request->validate([
'code' => 'required|string',
'phoneCode' => 'required|string',
'storeCode' => 'required|string'
], [
'phoneCode.required' => '缺少手机号授权凭证 phoneCode',
'code.required' => '注册参数格式错误',
'code.string' => '注册参数格式错误',
'phoneCode.required' => '注册参数格式错误',
'phoneCode.string' => '注册参数格式错误',
'storeCode.required' => '门店编码必须填写',
'storeCode.string' => '注册参数格式错误',
]);
$phone = app(WechatService::class)->getPhone($data['phoneCode']);
$user = $this->currentUser($request);
$user->phone = $phone;
if (! $user->isBound()) {
$store = StoreModel::where('phone', $phone)
->where('status', StoreModel::STATUS_NORMAL)
->first();
if ($store !== null) {
$user->type = UserModel::TYPE_STORE;
$user->store_id = $store->id;
$user->supplier_id = 0;
} else {
$supplier = SupplierModel::where('phone', $phone)
->where('status', SupplierModel::STATUS_NORMAL)
->first();
if ($supplier !== null) {
$user->type = UserModel::TYPE_SUPPLIER;
$user->supplier_id = $supplier->id;
$user->store_id = 0;
}
}
$store = StoreModel::where('code', $validated['storeCode'])->first();
if (!$store) {
return $this->error('门店不存在!');
}
$user->save();
// 通过 code 换取 openid、session_key、unionid
$session = app(WechatService::class)->code2Session($validated['code']);
$user = UserModel::where('openid', $session['openid'])->first();
if ($user) {
return $this->error('你的微信已经注册,请直接登录!');
}
$userData = [
'openid' => $session['openid'],
'unionid' => $session['unionid'] ?? '',
'username' => 'wx_'.uniqid(),
'nickname' => '微信用户',
'store_id' => $store->id,
'avatar' => '',
'password' => '',
'last_login_at' => date('Y-m-d H:i:s'),
];
$phone = app(WechatService::class)->getPhone($validated['phoneCode']);
$userData['phone'] = $phone ?? '';
$user = UserModel::create($userData);
$token = $user->createToken($user->username)->toArray();
return $this->success([
'user' => $this->formatUser($user->fresh(['store.level:id,name', 'supplier:id,name'])),
]);
'token' => $token['plainTextToken'],
'user' => $user->toArray(),
], __('user.login_success'));
}
/** 当前用户信息(含门店客户等级 —— 全局价格体系依据 / 供应商信息) */
#[GetRoute('/auth/info', authorize: true)]
/** 当前用户信息 */
#[GetRoute('/auth/info')]
public function info(Request $request): JsonResponse
{
$user = UserModel::with(['store.level:id,name', 'supplier:id,name'])
$user = UserModel::with(['store.level:id,name'])
->find($request->user()->id);
if ($user === null) {
throw new RepositoryException('账号不存在');
}
return $this->success(['user' => $this->formatUser($user)]);
return $this->success($user->toArray());
}
/**
* 小程序端用户信息输出结构
*
* @return array<string, mixed>
*/
private function formatUser(UserModel $user): array
#[PutRoute('auth/info')]
public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse
{
return [
'id' => $user->id,
'nickname' => $user->nickname,
'avatar' => $user->avatar,
'phone' => $user->phone,
'type' => $user->type,
'store' => $user->store,
'supplier' => $user->supplier,
];
UserModel::where('user_id', auth('user')->id())->update($request->validated());
return $this->error('更新成功');
}
}
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Models\StoreModel;
use App\Models\SupplierModel;
use App\Models\UserModel;
use Illuminate\Http\Request;
use Modules\Common\Http\Controllers\BaseController;
@@ -13,7 +12,7 @@ use Modules\Common\Http\Controllers\BaseController;
* 小程序端控制器基类
*
* 无 #[RequestAttribute],不会被 AnnoRoute 注册为路由。
* 提供当前用户获取与门店/供应商绑定前置校验。
* 提供当前用户获取与门店绑定前置校验。
*/
abstract class BaseMiniController extends BaseController
{
@@ -26,7 +25,6 @@ abstract class BaseMiniController extends BaseController
if ($user === null || $user->status === UserModel::STATUS_DISABLED) {
throw new RepositoryException('账号不存在或已被停用');
}
return $user;
}
@@ -35,7 +33,7 @@ abstract class BaseMiniController extends BaseController
*/
protected function ensureStoreBound(UserModel $user): StoreModel
{
if ($user->type !== UserModel::TYPE_STORE || $user->store_id <= 0) {
if ($user->store_id <= 0) {
throw new RepositoryException('尚未绑定门店,请联系客服处理');
}
$store = StoreModel::find($user->store_id);
@@ -47,18 +45,20 @@ abstract class BaseMiniController extends BaseController
}
/**
* 供应商端前置校验:type=供应商 且 supplier_id>0 且供应商正常
* 用户当前绑定的正常门店(未绑定/已停用返回 null,不抛错)
*
* 供商品浏览等「弱前置」场景使用:未绑定门店仍可浏览商品,仅价格不可见。
*/
protected function ensureSupplierBound(UserModel $user): SupplierModel
protected function boundStore(UserModel $user): ?StoreModel
{
if ($user->type !== UserModel::TYPE_SUPPLIER || $user->supplier_id <= 0) {
throw new RepositoryException('尚未绑定供应商,请联系客服处理');
if ($user->store_id <= 0) {
return null;
}
$supplier = SupplierModel::find($user->supplier_id);
if ($supplier === null || $supplier->status !== SupplierModel::STATUS_NORMAL) {
throw new RepositoryException('供应商不存在或已停用,请联系客服处理');
$store = StoreModel::find($user->store_id);
if ($store === null || $store->status !== StoreModel::STATUS_NORMAL) {
return null;
}
return $supplier;
return $store;
}
}
+10 -11
View File
@@ -16,34 +16,34 @@ use Modules\AnnoRoute\Attribute\PostRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Modules\SystemTool\Models\SysFileModel;
use Throwable;
/**
* 小程序购物车(门店订货车:加购 / 列表 / 改数量 / 删项 / 清空)
* 提交订货单复用 POST /mini/order,购物车仅作前置编辑容器
* 小程序购物车
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class CartController extends BaseMiniController
{
/** decimal(10,2) 上限 */
private const MAX_QUANTITY = '99999999.99';
private const string MAX_QUANTITY = '99999999.99';
/**
* 加购:商品上架 + 门店有等级价(与下单一致 fail-fast),同商品合并累加
* 加购
* @throws Throwable
*/
#[PostRoute('/cart', authorize: true)]
#[PostRoute('/cart')]
public function store(MiniCartRequest $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
if ($store->level_id <= 0) {
throw new RepositoryException('门店未设置客户等级,无法加购,请联系客服');
return $this->error('门店未设置客户等级,无法加购,请联系客服');
}
$productId = (int) $request->validated('product_id');
// Eloquent 查询自带 SoftDeletes 全局作用域:软删除/下架一并在内
$product = ProductModel::where('status', ProductModel::STATUS_ON)->find($productId);
if ($product === null) {
throw new RepositoryException('商品不存在或已下架,请刷新后重试');
return $this->error('商品不存在或已下架,请刷新后重试');
}
// 存在性校验与计价类型无关(百分比行 price 可能为 0 也能加购)
$hasPrice = ProductPriceModel::query()
@@ -51,7 +51,7 @@ class CartController extends BaseMiniController
->where('level_id', $store->level_id)
->exists();
if (! $hasPrice) {
throw new RepositoryException('商品「' . $product->name . '」未设置您所在等级的价格,无法加购');
return $this->error('商品「' . $product->name . '」价格未设置,无法加购');
}
$quantity = (string) $request->validated('quantity');
@@ -88,8 +88,7 @@ class CartController extends BaseMiniController
}
/**
* 购物车列表:当前用户全部项 + 实时等级价,逐项服务端 bcmul 算金额;
* status=1 可购 / 0 商品下架、缺失或未设等级价;汇总只统计可购项
* 购物车列表
*/
#[GetRoute('/cart', authorize: true)]
public function index(Request $request): JsonResponse
@@ -12,7 +12,10 @@ use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
/**
* 小程序商品(分类树 + 列表,价格 = 当前门店客户等级价
* 小程序商品(分类树 + 列表)
*
* 商品浏览仅需登录:未绑定门店/门店未设客户等级的用户也可查看商品,仅价格不可见(price=null);
* 加购、下单仍由购物车/订单前置校验拦截,要求绑定门店并已设客户等级。
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class ProductController extends BaseMiniController
@@ -53,22 +56,23 @@ class ProductController extends BaseMiniController
}
/**
* 商品列表:价格取当前门店客户等级价(未绑等级的门店报错)
* 商品列表:价格取当前门店客户等级价;
* 未绑定门店/门店未设客户等级 → 仍可浏览,price 为 null(不可见价格,加购/下单另由前置校验拦截);
* ?category_id=&keyword=&page=&pageSize=
*/
#[GetRoute('/product/list', authorize: true)]
public function products(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$store = $this->ensureStoreBound($user);
if ($store->level_id <= 0) {
throw new RepositoryException('门店未设置客户等级,无法展示价格,请联系客服');
}
$levelId = $this->boundStore($user)?->level_id ?? 0;
$query = ProductModel::query()
->where('status', ProductModel::STATUS_ON)
->with('category:id,name')
->with(['prices' => static fn ($q) => $q->where('level_id', $store->level_id)]);
->with('category:id,name');
if ($levelId > 0) {
$query->with(['prices' => static fn ($q) => $q->where('level_id', $levelId)]);
}
$categoryId = (int) $request->input('category_id', 0);
if ($categoryId > 0) {
@@ -88,7 +92,8 @@ class ProductController extends BaseMiniController
->paginate($pageSize)
->toArray();
// 扁平化价格:prices[0].actual_price → price(访问器经 toArray 自动输出换算后实际价;未设等级价为 null
// 扁平化价格:prices[0].actual_price → price(访问器经 toArray 自动输出换算后实际价;
// 未绑定门店或未设等级价为 null——未加载 prices 时 ?? null 兜底)
// unset prices 同时移除 price_type/percent,门店端无法反推成本;cost_price 已被 $hidden 过滤
foreach ($data['data'] as &$row) {
$row['price'] = $row['prices'][0]['actual_price'] ?? null;
@@ -1,104 +0,0 @@
<?php
namespace App\Http\Controllers\Mini;
use App\Exceptions\RepositoryException;
use App\Models\PurchaseOrderItemModel;
use App\Models\PurchaseOrderModel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\PutRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
/**
* 小程序供应商端(接收采购单 / 明细 / 确认接单)
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class SupplierController extends BaseMiniController
{
/** 收到的采购单:含本供应商 is_sent=1 明细的采购单(去重) */
#[GetRoute('/supplier/purchases', authorize: true)]
public function purchases(Request $request): JsonResponse
{
$user = $this->currentUser($request);
$supplier = $this->ensureSupplierBound($user);
$purchaseIds = PurchaseOrderItemModel::query()
->where('supplier_id', $supplier->id)
->where('is_sent', PurchaseOrderItemModel::SENT)
->distinct()
->pluck('purchase_id');
$data = PurchaseOrderModel::query()
->whereIn('id', $purchaseIds)
->orderBy('purchase_date', 'desc')
->orderBy('id', 'desc')
->paginate((int) $request->input('pageSize', 10))
->toArray();
return $this->success($data);
}
/** 采购单明细:仅本供应商且已发送的明细行 */
#[GetRoute('/supplier/purchases/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$supplier = $this->ensureSupplierBound($user);
$purchase = PurchaseOrderModel::find($id);
if ($purchase === null) {
throw new RepositoryException('采购单不存在');
}
$items = $purchase->items()
->where('supplier_id', $supplier->id)
->where('is_sent', PurchaseOrderItemModel::SENT)
->get();
if ($items->isEmpty()) {
throw new RepositoryException('该采购单无贵司的采购明细');
}
return $this->success([
'id' => $purchase->id,
'purchase_no' => $purchase->purchase_no,
'purchase_date' => $purchase->purchase_date?->toDateString(),
'remark' => $purchase->remark,
'items' => $items->toArray(),
]);
}
/** 确认接单:本供应商已发送明细批量记录 supplier_confirmed_at(幂等) */
#[PutRoute('/supplier/purchases/{id}/confirm', authorize: true, where: ['id' => '[0-9]+'])]
public function confirm(int $id, Request $request): JsonResponse
{
$user = $this->currentUser($request);
$supplier = $this->ensureSupplierBound($user);
$purchase = PurchaseOrderModel::find($id);
if ($purchase === null) {
throw new RepositoryException('采购单不存在');
}
$items = $purchase->items()
->where('supplier_id', $supplier->id)
->where('is_sent', PurchaseOrderItemModel::SENT)
->get();
if ($items->isEmpty()) {
throw new RepositoryException('该采购单无贵司的采购明细');
}
$now = now();
$confirmed = 0;
foreach ($items as $item) {
if ($item->supplier_confirmed_at === null) {
$item->supplier_confirmed_at = $now;
$item->save();
$confirmed++;
}
}
return $this->success(['confirmed' => $confirmed], '已确认接单');
}
}
@@ -146,7 +146,6 @@ class StoreOrderController extends BaseController
private function notifyStore(StoreOrderModel $order): void
{
$userIds = UserModel::query()
->where('type', UserModel::TYPE_STORE)
->where('store_id', $order->store_id)
->where('status', UserModel::STATUS_NORMAL)
->pluck('id');
@@ -257,7 +257,6 @@ class ProductController extends BaseController
// 受影响门店:客户等级在受影响等级范围内的正常门店,通知其绑定的正常用户
$userIds = UserModel::query()
->where('type', UserModel::TYPE_STORE)
->where('status', UserModel::STATUS_NORMAL)
->whereIn('store_id', function ($q) use ($percentLevelIds) {
$q->select('id')
@@ -5,7 +5,7 @@ namespace App\Http\Requests\Customer;
use Modules\Common\Http\Requests\BaseFormRequest;
/**
* 小程序用户绑定 验证(type=1 门店需 store_idtype=2 供应商需 supplier_id
* 小程序用户绑定 验证(绑定门店
*/
class MiniUserBindRequest extends BaseFormRequest
{
@@ -14,21 +14,15 @@ class MiniUserBindRequest extends BaseFormRequest
public function rules(): array
{
return [
'type' => 'required|integer|in:1,2',
'store_id' => 'required_if:type,1|nullable|integer|min:1',
'supplier_id' => 'required_if:type,2|nullable|integer|min:1',
'store_id' => 'required|integer|min:1',
];
}
public function messages(): array
{
return [
'type.required' => '请选择用户类型',
'type.in' => '用户类型只能是门店或供应商',
'store_id.required_if' => '绑定门店时必须选择门店',
'store_id.required' => '绑定门店时必须选择门店',
'store_id.min' => '门店ID不正确',
'supplier_id.required_if' => '绑定供应商时必须选择供应商',
'supplier_id.min' => '供应商ID不正确',
];
}
}
@@ -14,14 +14,8 @@ class StoreFormRequest extends BaseFormRequest
public function rules(): array
{
$unique = Rule::unique('store', 'code');
if ($this->isUpdate()) {
$unique = $unique->ignore($this->route('id'));
}
return [
'name' => 'required|string|max:100',
'code' => ['required', 'string', 'max:50', $unique],
'level_id' => 'required|integer|exists:customer_level,id',
'contact' => 'nullable|string|max:50',
'phone' => 'nullable|string|max:20',
@@ -37,8 +31,6 @@ class StoreFormRequest extends BaseFormRequest
return [
'name.required' => '门店名称不能为空',
'name.max' => '门店名称最长 100 个字符',
'code.required' => '门店编码不能为空',
'code.unique' => '门店编码已存在',
'level_id.required' => '请选择客户等级',
'level_id.exists' => '客户等级不存在',
'payment_cycle_days.integer' => '回款周期必须为整数',
+6 -34
View File
@@ -15,17 +15,10 @@ class UserModel extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/** 用户类型:待绑定(手机号未匹配到门店/供应商,需后台人工绑定) */
public const TYPE_PENDING = 0;
/** 用户类型:门店 */
public const TYPE_STORE = 1;
/** 用户类型:供应商 */
public const TYPE_SUPPLIER = 2;
/** 状态:停用 */
public const STATUS_DISABLED = 0;
public const int STATUS_DISABLED = 0;
/** 状态:正常 */
public const STATUS_NORMAL = 1;
public const int STATUS_NORMAL = 1;
protected $table = 'user';
@@ -45,38 +38,26 @@ class UserModel extends Authenticatable
'unionid',
'phone',
'avatar',
'type',
'store_id',
'supplier_id',
'status',
'last_login_at',
];
protected $casts = [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
'type' => 'integer',
'email_verified_at' => 'datetime:Y-m-d H:i:s',
'last_login_at' => 'datetime:Y-m-d H:i:s',
'created_at' => 'datetime:Y-m-d H:i:s',
'store_id' => 'integer',
'supplier_id' => 'integer',
'status' => 'integer',
];
/**
* 关联门店type=1 时有效)
* 关联门店
*/
public function store(): BelongsTo
{
return $this->belongsTo(StoreModel::class, 'store_id', 'id');
}
/**
* 关联供应商(type=2 时有效)
*/
public function supplier(): BelongsTo
{
return $this->belongsTo(SupplierModel::class, 'supplier_id', 'id');
}
/**
* 用户通知
*/
@@ -84,13 +65,4 @@ class UserModel extends Authenticatable
{
return $this->hasMany(NoticeModel::class, 'user_id', 'id');
}
/**
* 是否已绑定业务主体(门店或供应商)
*/
public function isBound(): bool
{
return $this->type === self::TYPE_STORE && $this->store_id > 0
|| $this->type === self::TYPE_SUPPLIER && $this->supplier_id > 0;
}
}