抽奖活动
parent
c90a503775
commit
b5a1d28226
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Grid;
|
||||
|
||||
use App\Enums\DrawActivityStatus;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DrawActivityClose extends RowAction
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return '<i class="feather icon-x-circle grid-action-icon"></i> 关闭';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.close');
|
||||
}
|
||||
|
||||
public function confirm()
|
||||
{
|
||||
return '您确定要关闭选中的抽奖活动吗?';
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->getKey());
|
||||
|
||||
$drawActivity->update([
|
||||
'status' => DrawActivityStatus::Closed,
|
||||
]);
|
||||
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Grid;
|
||||
|
||||
use App\Admin\Forms\DrawActivityPrizeStockChange as DrawActivityPrizeStockChangeForm;
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Dcat\Admin\Widgets\Modal;
|
||||
|
||||
class DrawActivityPrizeStockChange extends RowAction
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return '<i class="feather icon-edit grid-action-icon"></i> 库存';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.prize_stock');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$form = DrawActivityPrizeStockChangeForm::make()->payload(['id'=>$this->getKey()]);
|
||||
|
||||
return Modal::make()
|
||||
->lg()
|
||||
->title($this->title())
|
||||
->body($form)
|
||||
->button($this->title());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Grid;
|
||||
|
||||
use App\Admin\Forms\DrawActivityPublish as DrawActivityPublishForm;
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Dcat\Admin\Widgets\Modal;
|
||||
|
||||
class DrawActivityPublish extends RowAction
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return '<i class="feather icon-navigation grid-action-icon"></i> 发布';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.publish');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$form = DrawActivityPublishForm::make()->payload(['id'=>$this->getKey()]);
|
||||
|
||||
return Modal::make()
|
||||
->lg()
|
||||
->title($this->title())
|
||||
->body($form)
|
||||
->button($this->title());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Grid;
|
||||
|
||||
use App\Enums\DrawLogStatus;
|
||||
use App\Models\DrawLog;
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DrawLogComplete extends RowAction
|
||||
{
|
||||
protected $title = '<i class="feather icon-navigation grid-action-icon"></i> 发放奖品';
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.log_complete');
|
||||
}
|
||||
|
||||
// 确认弹窗信息
|
||||
public function confirm()
|
||||
{
|
||||
return '您确定要发放中奖奖品吗?';
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$drawLog = DrawLog::findOrFail($this->getKey());
|
||||
|
||||
if (! $drawLog->isPending()) {
|
||||
return $this->response()->error('操作失败:中奖记录状态异常')->refresh();
|
||||
}
|
||||
|
||||
$drawLog->update([
|
||||
'status' => DrawLogStatus::Completed,
|
||||
]);
|
||||
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Show;
|
||||
|
||||
use App\Admin\Forms\DrawActivityPublish as DrawActivityPublishForm;
|
||||
use Dcat\Admin\Show\AbstractTool;
|
||||
use Dcat\Admin\Widgets\Modal;
|
||||
|
||||
class DrawActivityPublish extends AbstractTool
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected $title = '<i class="feather icon-navigation"></i> 发布';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $style = 'btn-danger';
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.publish');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$form = DrawActivityPublishForm::make()->payload(['id'=>$this->getKey()]);
|
||||
|
||||
return Modal::make()
|
||||
->lg()
|
||||
->title($this->title())
|
||||
->body($form)
|
||||
->button("<a href=\"javascript:void(0)\" class=\"btn btn-sm {$this->style}\">{$this->title}</a> ");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers;
|
||||
|
||||
use App\Admin\Actions\Grid\DrawActivityClose;
|
||||
use App\Admin\Actions\Grid\DrawActivityPublish;
|
||||
use App\Admin\Actions\Show\DrawActivityPublish as DrawActivityPublishShowAction;
|
||||
use App\Admin\Renderable\DrawActivityPrizeTable;
|
||||
use App\Admin\Renderable\DrawActivityTicketTable;
|
||||
use App\Admin\Renderable\DrawLogTable;
|
||||
use App\Admin\Repositories\DrawActivity as DrawActivityRepository;
|
||||
use App\Enums\DrawActivityStatus;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Dcat\Admin\Layout\Row;
|
||||
use Dcat\Admin\Show;
|
||||
use Dcat\Admin\Widgets\Tab;
|
||||
|
||||
class DrawActivityController extends AdminController
|
||||
{
|
||||
public function update($id)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($id);
|
||||
|
||||
if ($drawActivity->isClosed()) {
|
||||
throw new BizException('活动已结束');
|
||||
}
|
||||
|
||||
return parent::update($id);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($id);
|
||||
|
||||
if ($drawActivity->isPublished()) {
|
||||
throw new BizException('活动已发布');
|
||||
}
|
||||
|
||||
return Admin::json()
|
||||
->alert()
|
||||
->status(true)
|
||||
->message(trans('admin.delete_succeeded'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a grid builder.
|
||||
*
|
||||
* @return Grid
|
||||
*/
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(new DrawActivityRepository(), function (Grid $grid) {
|
||||
// 设置弹窗的宽和高
|
||||
$grid->setDialogFormDimensions('70%', '90%');
|
||||
|
||||
$grid->model()->orderBy('id', 'desc');
|
||||
|
||||
$grid->column('id')->sortable();
|
||||
$grid->column('name');
|
||||
$grid->column('start_at')->display(function ($v) {
|
||||
return $v?->toDateTimeString() ?: '-';
|
||||
});
|
||||
$grid->column('end_at')->display(function ($v) {
|
||||
return $v?->toDateTimeString() ?: '-';
|
||||
});
|
||||
$grid->column('published_at')->display(function ($v) {
|
||||
return $v?->toDateTimeString() ?: '-';
|
||||
})->sortable();
|
||||
$grid->column('real_status')->display(function ($status) {
|
||||
return $status->label();
|
||||
});
|
||||
$grid->column('bg_image')->image(null, 80, 80);
|
||||
$grid->column('bg_color')->display(function () {
|
||||
return $this->getBgColorLabel();
|
||||
});
|
||||
|
||||
if (Admin::user()->can('dcat.admin.draw_activities.create')) {
|
||||
$grid->enableDialogCreate();
|
||||
$grid->showCreateButton();
|
||||
}
|
||||
|
||||
$grid->actions(function (Grid\Displayers\Actions $actions) {
|
||||
if (! $actions->row->isClosed() && Admin::user()->can('dcat.admin.draw_activities.edit')) {
|
||||
$actions->quickEdit(true);
|
||||
}
|
||||
|
||||
if (Admin::user()->can('dcat.admin.draw_activities.show')) {
|
||||
$actions->append('<a style="cursor: pointer;" target="_blank" href="'.admin_route('draw_activities.show', ['draw_activity' => $actions->row]).'"><i class="feather icon-eye"></i> 显示</a>');
|
||||
}
|
||||
|
||||
if (! $actions->row->isClosed() && $actions->row->isPublished() && Admin::user()->can('dcat.admin.draw_activities.close')) {
|
||||
$actions->append(new DrawActivityClose());
|
||||
}
|
||||
|
||||
if (! $actions->row->isPublished() && Admin::user()->can('dcat.admin.draw_activities.publish')) {
|
||||
$actions->append(new DrawActivityPublish());
|
||||
}
|
||||
|
||||
if ($actions->row->isPublished() && Admin::user()->can('dcat.admin.draw_activities.ticket_list')) {
|
||||
$actions->append('<a style="cursor: pointer;" target="_blank" href="'.admin_route('draw_activities.ticket_list', ['draw_activity' => $actions->row]).'"><i class="feather icon-eye"></i> 抽奖次数</a>');
|
||||
}
|
||||
|
||||
if ($actions->row->isPublished() && Admin::user()->can('dcat.admin.draw_activities.log_list')) {
|
||||
$actions->append('<a style="cursor: pointer;" target="_blank" href="'.admin_route('draw_activities.log_list', ['draw_activity' => $actions->row]).'"><i class="feather icon-eye"></i> 中奖记录</a>');
|
||||
}
|
||||
|
||||
if (! $actions->row->isPublished() && Admin::user()->can('dcat.admin.draw_activities.destroy')) {
|
||||
$actions->disableDelete(false);
|
||||
}
|
||||
});
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) {
|
||||
$filter->panel(false);
|
||||
$filter->like('name')->width(3);
|
||||
$filter->where('status', function ($builder) {
|
||||
return match (DrawActivityStatus::tryFrom($this->input)) {
|
||||
DrawActivityStatus::Created => $builder->onlyCreated(),
|
||||
DrawActivityStatus::Publishing => $builder->onlyPublishing(),
|
||||
DrawActivityStatus::Unstart => $builder->onlyUnstart(),
|
||||
DrawActivityStatus::Running => $builder->onlyRunning(),
|
||||
DrawActivityStatus::Closed =>$builder->onlyClosed(),
|
||||
default => $builder,
|
||||
};
|
||||
})->select(DrawActivityStatus::options())->width(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($id);
|
||||
|
||||
$row = new Row();
|
||||
|
||||
$row->column(5, Show::make($id, $drawActivity, function (Show $show) {
|
||||
$show->field('name');
|
||||
$show->field('desc')->unescape();
|
||||
$show->field('start_at');
|
||||
$show->field('end_at');
|
||||
$show->field('real_status')->unescape()->as(function () {
|
||||
return $this->real_status->label();
|
||||
});
|
||||
$show->field('published_at');
|
||||
$show->field('bg_image')->image();
|
||||
$show->field('bg_color')->unescape()->as(function () {
|
||||
return $this->getBgColorLabel();
|
||||
});
|
||||
|
||||
$show->panel()
|
||||
->tools(function (Show\Tools $tools) use ($show) {
|
||||
$tools->disableEdit();
|
||||
$tools->disableDelete();
|
||||
|
||||
if (! $show->model()->isPublished() && Admin::user()->can('dcat.admin.draw_activities.publish')) {
|
||||
$tools->append(new DrawActivityPublishShowAction());
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
$row->column(7, tap(new Tab(), function (Tab $tab) use ($drawActivity) {
|
||||
if (request()->routeIs('dcat.admin.draw_activities.ticket_list')) {
|
||||
$tab->add('抽奖次数', new DrawActivityTicketTable(['draw_activity_id' => $drawActivity->id]), true);
|
||||
} elseif (request()->routeIs('dcat.admin.draw_activities.log_list')) {
|
||||
$tab->add('中奖记录', new DrawLogTable(['draw_activity_id' => $drawActivity->id]), true);
|
||||
} else {
|
||||
$tab->add('活动奖品', new DrawActivityPrizeTable(['draw_activity_id' => $drawActivity->id]), true);
|
||||
}
|
||||
|
||||
$tab->withCard();
|
||||
}));
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new DrawActivityRepository(), function (Form $form) {
|
||||
$form->text('name')
|
||||
->rules(['bail', 'required', 'string', 'max:255'])
|
||||
->setLabelClass(['asterisk'])
|
||||
->attribute('required', true);
|
||||
$form->editor('desc')
|
||||
->height('600');
|
||||
|
||||
if ($form->isEditing() && $form->model()->isPublished()) {
|
||||
$form->display('start_at');
|
||||
$form->display('end_at');
|
||||
} else {
|
||||
$form->datetime('start_at');
|
||||
$form->datetime('end_at');
|
||||
}
|
||||
|
||||
$form->image('bg_image')
|
||||
->move('draw/activities')
|
||||
->uniqueName()
|
||||
->saveFullUrl()
|
||||
->removable(false)
|
||||
->autoUpload()
|
||||
->retainable();
|
||||
$form->text('bg_color')->help('十六进制颜色码');
|
||||
|
||||
$form->submitted(function (Form $form) {
|
||||
if ($form->isEditing() && $form->model()->isPublished()) {
|
||||
$form->deleteInput(['start_at', 'end_at']);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers;
|
||||
|
||||
use App\Admin\Repositories\DrawActivityPrize as DrawActivityPrizeRepository;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\DrawPrize;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Layout\Content;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DrawActivityPrizeController extends Controller
|
||||
{
|
||||
protected function title()
|
||||
{
|
||||
if (property_exists($this, 'title')) {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
return admin_trans_label();
|
||||
}
|
||||
|
||||
protected function description()
|
||||
{
|
||||
if (property_exists($this, 'description')) {
|
||||
return $this->description;
|
||||
}
|
||||
}
|
||||
|
||||
protected function translation()
|
||||
{
|
||||
if (property_exists($this, 'translation')) {
|
||||
return $this->translation;
|
||||
}
|
||||
}
|
||||
|
||||
public function create($drawActivityId, Content $content)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($this->title())
|
||||
->description($this->description()['create'] ?? trans('admin.create'))
|
||||
->body($this->form($drawActivity));
|
||||
}
|
||||
|
||||
public function store($drawActivityId)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
if ($drawActivity->isPublished()) {
|
||||
throw new BizException('活动已发布');
|
||||
}
|
||||
|
||||
return $this->form($drawActivity)->store();
|
||||
}
|
||||
|
||||
public function edit($drawActivityId, $drawActivityPrizeId, Content $content)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($this->title())
|
||||
->description($this->description()['edit'] ?? trans('admin.edit'))
|
||||
->body($this->form($drawActivity)->edit($drawActivityPrizeId));
|
||||
}
|
||||
|
||||
public function update($drawActivityId, $drawActivityPrizeId)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
if ($drawActivity->isClosed()) {
|
||||
throw new BizException('活动已结束');
|
||||
}
|
||||
|
||||
$drawActivityPrize = $drawActivity->prizes()->findOrFail($drawActivityPrizeId);
|
||||
|
||||
return $this->form($drawActivity)->update($drawActivityPrize->id);
|
||||
}
|
||||
|
||||
public function destroy($drawActivityId, $drawActivityPrizeId)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
if ($drawActivity->isPublished()) {
|
||||
throw new BizException('活动已发布');
|
||||
}
|
||||
|
||||
$drawActivity->prizes()->where('id', $drawActivityPrizeId)->delete();
|
||||
|
||||
return Admin::json()
|
||||
->alert()
|
||||
->status(true)
|
||||
->message(trans('admin.delete_succeeded'))
|
||||
->refresh();
|
||||
}
|
||||
|
||||
protected function form(DrawActivity $drawActivity)
|
||||
{
|
||||
return Form::make(new DrawActivityPrizeRepository(), function (Form $form) use ($drawActivity) {
|
||||
if ($form->isCreating()) {
|
||||
$form->hidden('draw_activity_id')->default($drawActivity->id);
|
||||
}
|
||||
|
||||
if ($form->isCreating() || ! $drawActivity->isPublished()) {
|
||||
$form->radio('draw_prize_source', '来源')
|
||||
->options([
|
||||
1 => '自定义',
|
||||
2 => '奖品库',
|
||||
])
|
||||
->when(1, function (Form $form) {
|
||||
$form->text('name', '名称')
|
||||
->rules(['bail', 'required_if:draw_prize_source,1', 'nullable', 'string', 'max:255'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
|
||||
$form->image('icon', '图标')
|
||||
->move('draw/prizes')
|
||||
->uniqueName()
|
||||
->saveFullUrl()
|
||||
->removable(false)
|
||||
->autoUpload()
|
||||
->retainable()
|
||||
->rules(['bail', 'required_if:draw_prize_source,1'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
|
||||
$form->select('type', '类型')
|
||||
->options(DrawPrizeType::options())
|
||||
->rules(['bail', 'required_if:draw_prize_source,1', 'nullable', Rule::in(array_keys(DrawPrizeType::options()))], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
|
||||
$form->text('amount', '面值/数量')
|
||||
->rules(['bail', 'required_if:draw_prize_source,1', 'nullable', 'min:0', 'regex:/^([1-9]\d*|0)(\.\d{1,2})?$/'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk'])
|
||||
->help('精确到2位小数');
|
||||
})
|
||||
->when(2, function (Form $form) {
|
||||
$form->select('draw_prize_id', '奖品')
|
||||
->options(DrawPrize::pluck('name', 'id'))
|
||||
->rules(['bail', 'required_if:draw_prize_source,2'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
})
|
||||
->setLabelClass(['asterisk'])
|
||||
->value($form->isEditing() ? 1 : 2)
|
||||
->default(1);
|
||||
|
||||
$form->radio('limited', '是否限量')
|
||||
->options([
|
||||
0 => '否',
|
||||
1 => '是',
|
||||
])
|
||||
->when(1, function (Form $form) {
|
||||
$form->number('stock', '库存')
|
||||
->min(0)
|
||||
->rules(['required_if:limited,1', 'int', 'min:0'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
})
|
||||
->default(1)
|
||||
->customFormat(function ($v) {
|
||||
return $v ? 1 : 0;
|
||||
})
|
||||
->setLabelClass(['asterisk']);
|
||||
} else {
|
||||
$form->display('name', '名称')
|
||||
->setLabelClass(['asterisk']);
|
||||
$form->image('icon', '图标')
|
||||
->disable()
|
||||
->setLabelClass(['asterisk']);
|
||||
$form->display('type', '类型')
|
||||
->setLabelClass(['asterisk'])
|
||||
->with(fn () => $this->type->label());
|
||||
$form
|
||||
->display('amount', '面值/数量')
|
||||
->setLabelClass(['asterisk'])
|
||||
->with(fn ($value) => trim_trailing_zeros($value));
|
||||
}
|
||||
|
||||
$form->number('weight', '权重')
|
||||
->rules(['bail', 'required', 'int', 'min:0'])
|
||||
->min(0)
|
||||
->setLabelClass(['asterisk'])
|
||||
->attribute('required', true);
|
||||
|
||||
$form->number('sort', '排序')->default(0)->rules(['required', 'int']);
|
||||
|
||||
$form->saving(function (Form $form) use ($drawActivity) {
|
||||
if (! $form->isCreating()) {
|
||||
$form->deleteInput('draw_activity_id');
|
||||
}
|
||||
|
||||
if ($form->isCreating() || ! $drawActivity->isPublished()) {
|
||||
if ($form->input('draw_prize_source') != 1) {
|
||||
$drawPrize = DrawPrize::findOrFail($form->input('draw_prize_id'));
|
||||
|
||||
$form->input('name', $drawPrize->name);
|
||||
$form->input('icon', $drawPrize->icon);
|
||||
$form->input('type', $drawPrize->type);
|
||||
$form->input('amount', $drawPrize->amount);
|
||||
}
|
||||
|
||||
$form->deleteInput(['draw_prize_source', 'draw_prize_id']);
|
||||
} else {
|
||||
$form->deleteInput(['name', 'icon', 'type', 'amount', 'limited', 'stock']);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers;
|
||||
|
||||
use App\Admin\Repositories\DrawLog as DrawLogRepository;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Layout\Content;
|
||||
|
||||
class DrawLogController extends Controller
|
||||
{
|
||||
protected function title()
|
||||
{
|
||||
if (property_exists($this, 'title')) {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
return admin_trans_label();
|
||||
}
|
||||
|
||||
protected function description()
|
||||
{
|
||||
if (property_exists($this, 'description')) {
|
||||
return $this->description;
|
||||
}
|
||||
}
|
||||
|
||||
protected function translation()
|
||||
{
|
||||
if (property_exists($this, 'translation')) {
|
||||
return $this->translation;
|
||||
}
|
||||
}
|
||||
|
||||
public function edit($drawActivityId, $drawLogId, Content $content)
|
||||
{
|
||||
DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
return $content
|
||||
->translation($this->translation())
|
||||
->title($this->title())
|
||||
->description($this->description()['edit'] ?? trans('admin.edit'))
|
||||
->body($this->form()->edit($drawLogId));
|
||||
}
|
||||
|
||||
public function update($drawActivityId, $drawLogId)
|
||||
{
|
||||
DrawActivity::findOrFail($drawActivityId);
|
||||
|
||||
return $this->form()->update($drawLogId);
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new DrawLogRepository(), function (Form $form) {
|
||||
if ($form->isEditing()) {
|
||||
if ($form->model()->isPending()) {
|
||||
$form->text('consignee_name', '收件人')
|
||||
->rules(['bail', 'nullable', 'string', 'max:255']);
|
||||
|
||||
$form->text('consignee_phone', '联系方式')
|
||||
->rules(['bail', 'nullable', 'string', 'max:255']);
|
||||
|
||||
$form->text('consignee_address', '收货地址')
|
||||
->rules(['bail', 'nullable', 'string', 'max:255']);
|
||||
}
|
||||
|
||||
$form->textarea('remark', '备注')
|
||||
->rules(['bail', 'nullable', 'string', 'max:255']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers;
|
||||
|
||||
use App\Admin\Repositories\DrawPrize as DrawPrizeRepository;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DrawPrizeController extends AdminController
|
||||
{
|
||||
/**
|
||||
* Make a grid builder.
|
||||
*
|
||||
* @return Grid
|
||||
*/
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(new DrawPrizeRepository(), function (Grid $grid) {
|
||||
$grid->column('id')->sortable();
|
||||
$grid->column('name');
|
||||
$grid->column('icon')->image(null, 80, 80);
|
||||
$grid->column('type', '类型')->display(function ($type) {
|
||||
return $type->label();
|
||||
});
|
||||
$grid->column('amount')->display(function ($v) {
|
||||
return trim_trailing_zeros($v);
|
||||
});
|
||||
$grid->column('created_at');
|
||||
$grid->column('updated_at');
|
||||
|
||||
$grid->model()->orderBy('id', 'asc');
|
||||
|
||||
if (Admin::user()->can('dcat.admin.draw_prizes.create')) {
|
||||
$grid->enableDialogCreate();
|
||||
$grid->showCreateButton();
|
||||
}
|
||||
|
||||
if (Admin::user()->can('dcat.admin.draw_prizes.edit')) {
|
||||
$grid->showQuickEditButton();
|
||||
}
|
||||
|
||||
if (Admin::user()->can('dcat.admin.draw_prizes.destroy')) {
|
||||
$grid->showDeleteButton();
|
||||
}
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) {
|
||||
$filter->panel(false);
|
||||
$filter->like('name')->width(3);
|
||||
$filter->equal('type')->select(DrawPrizeType::options())->width(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a form builder.
|
||||
*
|
||||
* @return Form
|
||||
*/
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new DrawPrizeRepository(), function (Form $form) {
|
||||
$form->display('id');
|
||||
|
||||
$form->text('name')
|
||||
->rules(['bail', 'required', 'string', 'max:255'])
|
||||
->setLabelClass(['asterisk'])
|
||||
->attribute('required', true);
|
||||
|
||||
$form->image('icon')
|
||||
->move('draw/prizes')
|
||||
->uniqueName()
|
||||
->saveFullUrl()
|
||||
->removable(false)
|
||||
->autoUpload()
|
||||
->retainable()
|
||||
->setLabelClass(['asterisk'])
|
||||
->rules(['bail', 'required'])
|
||||
->attribute('required', true);
|
||||
|
||||
$form->select('type')
|
||||
->options(DrawPrizeType::options())
|
||||
->rules(['bail', 'required', Rule::in(array_keys(DrawPrizeType::options()))])
|
||||
->setLabelClass(['asterisk'])
|
||||
->attribute('required', true);
|
||||
|
||||
$form->text('amount')
|
||||
->rules(['bail', 'required', 'min:0', 'regex:/^([1-9]\d*|0)(\.\d{1,2})?$/'])
|
||||
->setLabelClass(['asterisk'])
|
||||
->attribute('required', true)
|
||||
->help('最多保留2位小数');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Forms;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivityPrize;
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Illuminate\Database\QueryException;
|
||||
|
||||
class DrawActivityPrizeStockChange extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public const TYPE_ADD = 1;
|
||||
public const TYPE_SUB = 2;
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.prize_stock');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the form request.
|
||||
*
|
||||
* @param array $input
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(array $input)
|
||||
{
|
||||
$drawActivityPrize = DrawActivityPrize::findOrFail($this->payload['id']);
|
||||
|
||||
if ($drawActivityPrize->activity->isClosed()) {
|
||||
throw new BizException('活动已结束');
|
||||
}
|
||||
|
||||
$limited = (bool) $input['limited'];
|
||||
|
||||
try {
|
||||
if ($limited) {
|
||||
$stock = (int) $input['stock'];
|
||||
|
||||
// 如果限量
|
||||
if ($drawActivityPrize->limited) {
|
||||
if ($stock === 0) {
|
||||
throw new BizException('库存不能为0');
|
||||
}
|
||||
|
||||
$drawActivityPrize->increment('stock', $stock);
|
||||
} else {
|
||||
$drawActivityPrize->update([
|
||||
'limited' => $limited,
|
||||
'stock' => $stock,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$drawActivityPrize->update([
|
||||
'limited' => $limited,
|
||||
'stock' => 0,
|
||||
]);
|
||||
}
|
||||
} catch (QueryException $e) {
|
||||
if (strpos($e->getMessage(), 'Numeric value out of range') !== false) {
|
||||
$e = new BizException('奖品库存不足');
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->response()
|
||||
->success(__('admin.update_succeeded'))
|
||||
->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a form here.
|
||||
*/
|
||||
public function form()
|
||||
{
|
||||
$drawActivityPrize = DrawActivityPrize::findOrFail($this->payload['id']);
|
||||
|
||||
$this->radio('limited', '是否限量')
|
||||
->options([
|
||||
0 => '否',
|
||||
1 => '是',
|
||||
])
|
||||
->when(1, function () use ($drawActivityPrize) {
|
||||
if ($drawActivityPrize->limited) {
|
||||
$this->number('stock', '库存')
|
||||
->rules(['required_if:limited,1', 'int'], ['required_if' => '不能为空'])
|
||||
->help('库存大于0时,增加库存;小于0,减少库存')
|
||||
->setLabelClass(['asterisk']);
|
||||
} else {
|
||||
$this->number('stock', '初始库存')
|
||||
->min(0)
|
||||
->rules(['required_if:limited,1', 'int', 'min:0'], ['required_if' => '不能为空'])
|
||||
->setLabelClass(['asterisk']);
|
||||
}
|
||||
})
|
||||
->value($drawActivityPrize->limited)
|
||||
->customFormat(function ($v) {
|
||||
return $v ? 1 : 0;
|
||||
})
|
||||
->setLabelClass(['asterisk']);
|
||||
}
|
||||
|
||||
public function default()
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Forms;
|
||||
|
||||
use App\Enums\DrawActivityStatus;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
|
||||
class DrawActivityPublish extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public const METHOD_NONE = 1; // 暂不发布
|
||||
public const METHOD_QUICK = 2; // 立即发布
|
||||
public const METHOD_TIMER = 3; // 定时发布
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.publish');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the form request.
|
||||
*
|
||||
* @param array $input
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(array $input)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['id']);
|
||||
|
||||
if ($drawActivity->isPublished()) {
|
||||
throw new BizException('活动已发布');
|
||||
}
|
||||
|
||||
$method = (int) $input['method'];
|
||||
|
||||
if ($method === static::METHOD_NONE) {
|
||||
$drawActivity->update([
|
||||
'status' => DrawActivityStatus::Created,
|
||||
'published_at' => null,
|
||||
]);
|
||||
} else {
|
||||
$prizes = $drawActivity->prizes()->get();
|
||||
|
||||
if ($prizes->isEmpty()) {
|
||||
throw new BizException('活动未设置奖品');
|
||||
}
|
||||
|
||||
if ($prizes->sum('weight') == 0) {
|
||||
throw new BizException('活动奖品总权重值不能为0');
|
||||
}
|
||||
|
||||
$drawActivity->update([
|
||||
'status' => DrawActivityStatus::Running,
|
||||
'published_at' => match ($method) {
|
||||
static::METHOD_TIMER => $input['published_at'],
|
||||
default => now(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response()
|
||||
->success(__('admin.update_succeeded'))
|
||||
->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a form here.
|
||||
*/
|
||||
public function form()
|
||||
{
|
||||
$this->radio('method', '发布方式')
|
||||
->options([
|
||||
static::METHOD_NONE => '暂不发布',
|
||||
static::METHOD_QUICK => '立即发布',
|
||||
static::METHOD_TIMER => '定时发布',
|
||||
])
|
||||
->when(static::METHOD_TIMER, function () {
|
||||
$this->datetime('published_at')
|
||||
->rules(['bail', 'required_if:method,'.static::METHOD_TIMER], ['required_if' => '不能为空']);
|
||||
});
|
||||
}
|
||||
|
||||
public function default()
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['id']);
|
||||
|
||||
return [
|
||||
'method' => match ($drawActivity->real_status) {
|
||||
DrawActivityStatus::Publishing => static::METHOD_TIMER,
|
||||
default => static::METHOD_QUICK,
|
||||
},
|
||||
'published_at' => $drawActivity->published_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Forms;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\User;
|
||||
use App\Services\DrawTicketService;
|
||||
use Dcat\Admin\Contracts\LazyRenderable;
|
||||
use Dcat\Admin\Traits\LazyWidget;
|
||||
use Dcat\Admin\Widgets\Form;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
class DrawActivityTicketChange extends Form implements LazyRenderable
|
||||
{
|
||||
use LazyWidget;
|
||||
|
||||
public const TYPE_ADD = 1;
|
||||
public const TYPE_SUB = 2;
|
||||
|
||||
/**
|
||||
* @param Model|Authenticatable|HasPermissions|null $user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function authorize($user): bool
|
||||
{
|
||||
return $user->can('dcat.admin.draw_activities.change_tickets');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the form request.
|
||||
*
|
||||
* @param array $input
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(array $input)
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['id']);
|
||||
|
||||
if ($drawActivity->isClosed()) {
|
||||
throw new BizException('活动已结束');
|
||||
}
|
||||
|
||||
$user = User::findOrFail($input['user_id']);
|
||||
|
||||
switch ($input['type']) {
|
||||
case static::TYPE_SUB:
|
||||
$number = $input['number'] * -1;
|
||||
$remark = $input['remark'] ?? '系统扣除';
|
||||
break;
|
||||
|
||||
default:
|
||||
$number = $input['number'];
|
||||
$remark = $input['remark'] ?? '系统增加';
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
(new DrawTicketService())->change($user, $drawActivity, $number, $remark);
|
||||
|
||||
DB::commit();
|
||||
} catch (QueryException $e) {
|
||||
DB::rollBack();
|
||||
|
||||
if (strpos($e->getMessage(), 'Numeric value out of range') !== false) {
|
||||
throw new BizException('抽奖次数不足');
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->response()
|
||||
->success(__('admin.update_succeeded'))
|
||||
->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a form here.
|
||||
*/
|
||||
public function form()
|
||||
{
|
||||
$this->radio('type', '类型')
|
||||
->options([
|
||||
static::TYPE_ADD => '增加',
|
||||
static::TYPE_SUB => '扣除',
|
||||
])
|
||||
->required();
|
||||
|
||||
$this->select('user_id', '手机号')
|
||||
->ajax(admin_route('api.users'))
|
||||
->required();
|
||||
|
||||
$this->number('number', '次数')
|
||||
->min(0)
|
||||
->required();
|
||||
|
||||
$this->textarea('remark', '备注')
|
||||
->rules(['max:255']);
|
||||
}
|
||||
|
||||
public function default()
|
||||
{
|
||||
return [
|
||||
'type' => static::TYPE_ADD,
|
||||
'number' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Renderable;
|
||||
|
||||
use App\Admin\Actions\Grid\DrawActivityPrizeStockChange;
|
||||
use App\Admin\Repositories\DrawActivityPrize;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\LazyRenderable;
|
||||
|
||||
class DrawActivityPrizeTable extends LazyRenderable
|
||||
{
|
||||
public function grid(): Grid
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['draw_activity_id'] ?? 0);
|
||||
|
||||
return Grid::make(new DrawActivityPrize(), function (Grid $grid) use ($drawActivity) {
|
||||
$grid->setResource("draw-activities/{$drawActivity->id}/prizes");
|
||||
|
||||
$grid->model()->where('draw_activity_id', $drawActivity->id)->orderBy('sort', 'desc');
|
||||
|
||||
$grid->column('name', '名称');
|
||||
$grid->column('icon', '图标')->image(null, 80, 80);
|
||||
$grid->column('type', '类型')->display(function ($type) {
|
||||
return $type->label();
|
||||
});
|
||||
$grid->column('amount', '数量/面值')->display(function ($v) {
|
||||
return trim_trailing_zeros($v);
|
||||
});
|
||||
$grid->column('weight', '权重');
|
||||
$grid->column('stock', '库存')->display(function ($stock) {
|
||||
if ($this->limited) {
|
||||
return $stock;
|
||||
}
|
||||
|
||||
return '不限';
|
||||
});
|
||||
$grid->column('winnings', '中奖数量');
|
||||
$grid->column('sort', '排序');
|
||||
|
||||
$grid->disableRefreshButton();
|
||||
|
||||
if (! $drawActivity->isPublished() && Admin::user()->can('dcat.admin.draw_activities.prize_create')) {
|
||||
$grid->enableDialogCreate();
|
||||
$grid->showCreateButton();
|
||||
}
|
||||
|
||||
$grid->actions(function (Grid\Displayers\Actions $actions) use ($drawActivity) {
|
||||
if (! $drawActivity->isClosed() && Admin::user()->can('dcat.admin.draw_activities.prize_edit')) {
|
||||
$actions->quickEdit(true);
|
||||
}
|
||||
|
||||
if (! $drawActivity->isPublished() && Admin::user()->can('dcat.admin.draw_activities.prize_delete')) {
|
||||
$actions->disableDelete(false);
|
||||
}
|
||||
if (! $drawActivity->isClosed() && $drawActivity->isPublished() && Admin::user()->can('dcat.admin.draw_activities.prize_stock')) {
|
||||
$actions->append(new DrawActivityPrizeStockChange());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Renderable;
|
||||
|
||||
use App\Admin\Extensions\Grid\Tools\DrawActivityTicketChange;
|
||||
use App\Admin\Repositories\DrawTicket;
|
||||
use App\Models\DrawActivity;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\LazyRenderable;
|
||||
|
||||
class DrawActivityTicketTable extends LazyRenderable
|
||||
{
|
||||
public function grid(): Grid
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['draw_activity_id'] ?? 0);
|
||||
|
||||
return Grid::make(DrawTicket::with(['user', 'userInfo']), function (Grid $grid) use ($drawActivity) {
|
||||
$grid->model()->where('draw_activity_id', $drawActivity->id)->orderBy('id', 'desc');
|
||||
|
||||
$grid->column('user.phone', '手机号');
|
||||
$grid->column('userInfo.nickname', '昵称');
|
||||
$grid->column('number', '次数')->sortable();
|
||||
|
||||
$grid->disableRefreshButton();
|
||||
$grid->disableActions();
|
||||
|
||||
|
||||
if (! $drawActivity->isClosed() && Admin::user()->can('dcat.admin.draw_activities.change_tickets')) {
|
||||
$grid->tools(new DrawActivityTicketChange($drawActivity));
|
||||
}
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) {
|
||||
$filter->panel();
|
||||
$filter->like('user.phone', '手机号')->width(3);
|
||||
$filter->like('userInfo.nickname', '昵称')->width(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Renderable;
|
||||
|
||||
use App\Admin\Actions\Grid\DrawLogComplete;
|
||||
use App\Enums\DrawLogStatus;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\DrawActivityPrize;
|
||||
use App\Models\DrawLog;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\LazyRenderable;
|
||||
|
||||
class DrawLogTable extends LazyRenderable
|
||||
{
|
||||
public function grid(): Grid
|
||||
{
|
||||
$drawActivity = DrawActivity::findOrFail($this->payload['draw_activity_id'] ?? 0);
|
||||
|
||||
return Grid::make(DrawLog::with(['prize', 'user']), function (Grid $grid) use ($drawActivity) {
|
||||
$grid->setResource("draw-activities/{$drawActivity->id}/logs");
|
||||
|
||||
$grid->model()->where('draw_activity_id', $drawActivity->id)->orderBy('id', 'desc');
|
||||
|
||||
$grid->column('prize.name', '奖品名称');
|
||||
$grid->column('prize.icon', '奖品图标')->image(null, 80, 80);
|
||||
$grid->column('prize.type', '奖品类型')->display(function () {
|
||||
return $this->prize->type->label();
|
||||
});
|
||||
$grid->column('user.phone', '中奖人手机号')->display(function () {
|
||||
$href = admin_route('users.show', ['user' => $this->user_id]);
|
||||
|
||||
return "<a href=\"{$href}\" target=\"_blank\">{$this->user->phone}</a>";
|
||||
});
|
||||
$grid->column('created_at', '中奖时间')->sortable();
|
||||
$grid->column('consignee_name', '收件人');
|
||||
$grid->column('consignee_phone', '联系方式');
|
||||
$grid->column('consignee_address', '收货地址');
|
||||
$grid->column('remark', '备注');
|
||||
$grid->column('status', '状态')->display(function () {
|
||||
return $this->status->label();
|
||||
});
|
||||
|
||||
$grid->actions(function (Grid\Displayers\Actions $actions) {
|
||||
if (Admin::user()->can('dcat.admin.draw_activities.log_edit')) {
|
||||
$actions->quickEdit(true);
|
||||
}
|
||||
|
||||
if ($actions->row->isPending() && Admin::user()->can('dcat.admin.draw_activities.log_complete')) {
|
||||
$actions->append(new DrawLogComplete());
|
||||
}
|
||||
});
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) use ($drawActivity) {
|
||||
$filter->panel(false);
|
||||
$filter->like('user.phone', '手机号')->width(6);
|
||||
$filter->equal('draw_activity_prize_id', '奖品名称')->select(DrawActivityPrize::where('draw_activity_id', $drawActivity->id)->pluck('name', 'id'))->width(6);
|
||||
$filter->equal('prize.type', '奖品类型')->select(DrawPrizeType::options())->width(6);
|
||||
$filter->equal('status', '状态')->select(DrawLogStatus::options())->width(6);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Repositories;
|
||||
|
||||
use App\Models\DrawActivity as Model;
|
||||
use Dcat\Admin\Repositories\EloquentRepository;
|
||||
|
||||
class DrawActivity extends EloquentRepository
|
||||
{
|
||||
/**
|
||||
* Model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eloquentClass = Model::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Repositories;
|
||||
|
||||
use App\Models\DrawActivityPrize as Model;
|
||||
use Dcat\Admin\Repositories\EloquentRepository;
|
||||
|
||||
class DrawActivityPrize extends EloquentRepository
|
||||
{
|
||||
/**
|
||||
* Model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eloquentClass = Model::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Repositories;
|
||||
|
||||
use App\Models\DrawLog as Model;
|
||||
use Dcat\Admin\Repositories\EloquentRepository;
|
||||
|
||||
class DrawLog extends EloquentRepository
|
||||
{
|
||||
/**
|
||||
* Model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eloquentClass = Model::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Repositories;
|
||||
|
||||
use App\Models\DrawPrize as Model;
|
||||
use Dcat\Admin\Repositories\EloquentRepository;
|
||||
|
||||
class DrawPrize extends EloquentRepository
|
||||
{
|
||||
/**
|
||||
* Model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eloquentClass = Model::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Repositories;
|
||||
|
||||
use App\Models\DrawTicket as Model;
|
||||
use Dcat\Admin\Repositories\EloquentRepository;
|
||||
|
||||
class DrawTicket extends EloquentRepository
|
||||
{
|
||||
/**
|
||||
* Model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eloquentClass = Model::class;
|
||||
}
|
||||
|
|
@ -169,6 +169,19 @@ Route::group([
|
|||
$router->get('api/product-by-store', 'Store\ProductController@listByStore')->name('api.store_product');
|
||||
$router->get('api/store', 'Store\StoreController@list')->name('api.store');
|
||||
|
||||
// 抽奖管理
|
||||
$router->resource('draw-prizes', 'DrawPrizeController')->names('draw_prizes');
|
||||
$router->resource('draw-activities', 'DrawActivityController')->names('draw_activities');
|
||||
$router->get('draw-activities/{draw_activity}/prizes/create', 'DrawActivityPrizeController@create')->name('draw_activities.prize_create');
|
||||
$router->post('draw-activities/{draw_activity}/prizes', 'DrawActivityPrizeController@store')->name('draw_activities.prize_store');
|
||||
$router->get('draw-activities/{draw_activity}/prizes/{prize}/edit', 'DrawActivityPrizeController@edit')->name('draw_activities.prize_edit');
|
||||
$router->put('draw-activities/{draw_activity}/prizes/{prize}', 'DrawActivityPrizeController@update')->name('draw_activities.prize_update');
|
||||
$router->delete('draw-activities/{draw_activity}/prizes/{prize}', 'DrawActivityPrizeController@destroy')->name('draw_activities.prize_delete');
|
||||
$router->get('draw-activities/{draw_activity}/tickets', 'DrawActivityController@show')->name('draw_activities.ticket_list');
|
||||
$router->get('draw-activities/{draw_activity}/logs', 'DrawActivityController@show')->name('draw_activities.log_list');
|
||||
$router->put('draw-activities/{draw_activity}/logs/{log}', 'DrawLogController@update')->name('draw_activities.log_update');
|
||||
$router->get('draw-activities/{draw_activity}/logs/{log}/edit', 'DrawLogController@edit')->name('draw_activities.log_edit');
|
||||
|
||||
/** 调试接口 **/
|
||||
// $router->get('test', 'HomeController@test');
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Controllers;
|
||||
|
||||
use App\Endpoint\Api\Http\Resources\DrawActivityResource;
|
||||
use App\Endpoint\Api\Http\Resources\DrawLogResource;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\DrawLog;
|
||||
use App\Models\DrawTicket;
|
||||
use App\Services\DrawActivityService;
|
||||
use Illuminate\Contracts\Cache\LockTimeoutException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
class DrawActivityController extends Controller
|
||||
{
|
||||
public function show($id, Request $request)
|
||||
{
|
||||
$drawActivity = DrawActivity::with(['prizes' => function ($builder) {
|
||||
$builder->latest('sort');
|
||||
}])->onlyPublished()->findOrFail($id);
|
||||
|
||||
$drawTicket = null;
|
||||
$drawLog = null;
|
||||
|
||||
if ($user = $request->user()) {
|
||||
// 当前用户的抽奖机会
|
||||
$drawTicket = DrawTicket::where('draw_activity_id', $drawActivity->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
// 当前用户未填写收货地址的实物奖品
|
||||
$drawLog = DrawLog::whereRelation('prize', 'type', DrawPrizeType::Goods->value)
|
||||
->where('draw_activity_id', $drawActivity->id)
|
||||
->where('user_id', $user->id)
|
||||
->whereNull('consignee_name')
|
||||
->first();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'draw_activity' => DrawActivityResource::make($drawActivity),
|
||||
'draw_log_id' => $drawLog?->id,
|
||||
'draw_tickets_number' => (int) $drawTicket?->number,
|
||||
]);
|
||||
}
|
||||
|
||||
public function draw($id, Request $request, DrawActivityService $drawActivityService)
|
||||
{
|
||||
$drawActivity = DrawActivity::onlyPublished()->findOrFail($id);
|
||||
|
||||
$lock = Cache::lock("draw_activity_{$drawActivity->id}", 5);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
try {
|
||||
$lock->block(3);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
$drawLog = $drawActivityService->draw($drawActivity, $user);
|
||||
|
||||
DB::commit();
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
|
||||
if ($e instanceof LockTimeoutException) {
|
||||
$e = new BizException('抽奖人数较多,请稍后');
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
$lock?->release();
|
||||
}
|
||||
|
||||
return DrawLogResource::make($drawLog);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Controllers;
|
||||
|
||||
use App\Endpoint\Api\Http\Resources\DrawLogResource;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawLog;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DrawLogController extends Controller
|
||||
{
|
||||
public function index($drawActivityId, Request $request)
|
||||
{
|
||||
$drawLogs = DrawLog::with(['userInfo', 'prize'])
|
||||
->where('draw_activity_id', $drawActivityId)
|
||||
->latest('id')
|
||||
->simplePaginate($request->input('per_page'));
|
||||
|
||||
return DrawLogResource::collection($drawLogs);
|
||||
}
|
||||
|
||||
public function update($drawActivityId, $drawLogId, Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'consignee_name' => ['bail', 'required', 'string', 'max:255'],
|
||||
'consignee_phone' => ['bail', 'required', 'string', 'max:255'],
|
||||
'consignee_address' => ['bail', 'required', 'string', 'max:255'],
|
||||
], [], [
|
||||
'consignee_name' => '收件人',
|
||||
'consignee_phone' => '联系方式',
|
||||
'consignee_address' => '收货地址',
|
||||
]);
|
||||
|
||||
$drawLog = DrawLog::where([
|
||||
'user_id' => $request->user()->id,
|
||||
'draw_activity_id' => $drawActivityId,
|
||||
])->findOrFail($drawLogId);
|
||||
|
||||
if ($drawLog->prize->type !== DrawPrizeType::Goods) {
|
||||
throw new BizException('奖品不是实物');
|
||||
}
|
||||
|
||||
if (filled($drawLog->consignee_name)) {
|
||||
throw new BizException('请联系客服修改收件人信息');
|
||||
}
|
||||
|
||||
$drawLog->update($request->only([
|
||||
'consignee_name',
|
||||
'consignee_phone',
|
||||
'consignee_address',
|
||||
]));
|
||||
|
||||
return DrawLogResource::make($drawLog);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DrawActivityPrizeResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'icon' => $this->icon,
|
||||
'type' => $this->type,
|
||||
'amount' => trim_trailing_zeros($this->amount),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DrawActivityResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'desc' => $this->desc,
|
||||
'prizes' => DrawActivityPrizeResource::collection($this->whenLoaded('prizes')),
|
||||
'start_at' => $this->start_at?->toDateTimeString(),
|
||||
'end_at' => $this->end_at?->toDateTimeString(),
|
||||
'bg_image' => $this->bg_image,
|
||||
'bg_color' => $this->bg_color,
|
||||
'status' => $this->real_status,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DrawLogResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'user' => UserInfoSimpleResource::make($this->whenLoaded('userInfo')),
|
||||
'prize' => DrawActivityPrizeResource::make($this->whenLoaded('prize')),
|
||||
'created_at' => $this->created_at?->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +90,9 @@ Route::group([
|
|||
Route::get('bargain-order/sku/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'barginaOrderBySku']);
|
||||
Route::get('bargain-order/order/{order}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargainOrderById']);
|
||||
|
||||
// 抽奖活动信息
|
||||
Route::get('draw-activities/{draw_activity}', [App\Endpoint\Api\Http\Controllers\DrawActivityController::class, 'show']);
|
||||
Route::get('draw-activities/{draw_activity}/logs', [App\Endpoint\Api\Http\Controllers\DrawLogController::class, 'index']);
|
||||
|
||||
//三方登录聚合
|
||||
Route::group([
|
||||
|
|
@ -207,6 +210,10 @@ Route::group([
|
|||
|
||||
// 佣金
|
||||
Route::apiResource('profit', \App\Endpoint\Api\Http\Controllers\ProfitController::class)->only(['index', 'show']);
|
||||
|
||||
// 抽奖活动抽奖
|
||||
Route::post('draw-activities/{draw_activity}/draw', [App\Endpoint\Api\Http\Controllers\DrawActivityController::class, 'draw']);
|
||||
Route::put('draw-activities/{draw_activity}/logs/{log}', [App\Endpoint\Api\Http\Controllers\DrawLogController::class, 'update']);
|
||||
});
|
||||
|
||||
// 微信小程序
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
|
||||
enum DrawActivityStatus: int {
|
||||
case Created = 0; // 已创建
|
||||
case Publishing = 1; // 发布中
|
||||
case Unstart = 2; // 未开始
|
||||
case Running = 3; // 进行中
|
||||
case Closed = 4; // 已结束
|
||||
|
||||
public function label()
|
||||
{
|
||||
$color = match ($this) {
|
||||
static::Created => 'primary',
|
||||
static::Publishing => 'blue',
|
||||
static::Unstart => 'danger',
|
||||
static::Running => 'success',
|
||||
static::Closed => 'gray',
|
||||
};
|
||||
|
||||
$background = Admin::color()->get($color, $color);
|
||||
|
||||
$name = static::options()[$this->value] ?? 'Unknown';
|
||||
|
||||
return "<span class='label' style='background: $background;'>{$name}</span>";
|
||||
}
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return [
|
||||
static::Created->value => '已创建',
|
||||
static::Publishing->value => '发布中',
|
||||
static::Unstart->value => '未开始',
|
||||
static::Running->value => '进行中',
|
||||
static::Closed->value => '已结束',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
|
||||
enum DrawLogStatus: int {
|
||||
case Pending = 0;
|
||||
case Queuing = 1;
|
||||
case Completed = 5;
|
||||
|
||||
public function label()
|
||||
{
|
||||
$color = match ($this) {
|
||||
static::Pending => 'primary',
|
||||
static::Queuing => 'pink',
|
||||
static::Completed => 'success',
|
||||
};
|
||||
|
||||
$background = Admin::color()->get($color, $color);
|
||||
|
||||
$name = static::options()[$this->value] ?? '其它';
|
||||
|
||||
return "<span class='label' style='background: $background;'>{$name}</span>";
|
||||
}
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return [
|
||||
static::Pending->value => '待处理',
|
||||
static::Queuing->value => '发放中',
|
||||
static::Completed->value => '已完成',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
|
||||
enum DrawPrizeType: int {
|
||||
case None = 0;
|
||||
case Goods = 1;
|
||||
case Wallet = 2;
|
||||
case Balance = 3;
|
||||
case Point = 4;
|
||||
case Money = 5;
|
||||
|
||||
public function label()
|
||||
{
|
||||
$color = match ($this) {
|
||||
static::None => 'warning',
|
||||
static::Goods => 'primary',
|
||||
static::Wallet => 'blue1',
|
||||
static::Balance => 'pink',
|
||||
static::Point => 'success',
|
||||
static::Money => 'danger',
|
||||
};
|
||||
|
||||
$background = Admin::color()->get($color, $color);
|
||||
|
||||
$name = static::options()[$this->value] ?? '其它';
|
||||
|
||||
return "<span class='label' style='background: $background;'>{$name}</span>";
|
||||
}
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return [
|
||||
self::None->value => '谢谢参与',
|
||||
self::Goods->value => '实物',
|
||||
self::Wallet->value => '可提',
|
||||
self::Balance->value => '余额',
|
||||
self::Point->value => '积分',
|
||||
self::Money->value => '现金',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\DrawActivityStatus;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawActivity extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'status' => DrawActivityStatus::Created,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'start_at' => 'datetime',
|
||||
'end_at' => 'datetime',
|
||||
'published_at' => 'datetime',
|
||||
'status' => DrawActivityStatus::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'cover',
|
||||
'desc',
|
||||
'start_at',
|
||||
'end_at',
|
||||
'published_at',
|
||||
'bg_image',
|
||||
'bg_color',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* 仅查询已发布的活动
|
||||
*/
|
||||
public function scopeOnlyPublished(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where('status', '!=', DrawActivityStatus::Created)->where('published_at', '<=', now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅查询已创建的活动
|
||||
*/
|
||||
public function scopeOnlyCreated(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where('status', DrawActivityStatus::Created);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅查询发布中的活动
|
||||
*/
|
||||
public function scopeOnlyPublishing(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where('status', DrawActivityStatus::Running)->where('published_at', '>', now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅查询未开始的活动
|
||||
*/
|
||||
public function scopeOnlyUnstart(Builder $builder): Builder
|
||||
{
|
||||
$tz = now();
|
||||
|
||||
return $builder->where('status', DrawActivityStatus::Running)
|
||||
->where('published_at', '<=', $tz)
|
||||
->whereNotNull('start_at')
|
||||
->where('start_at', '>', $tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅查询进行中的活动
|
||||
*/
|
||||
public function scopeOnlyRunning(Builder $builder): Builder
|
||||
{
|
||||
$tz = now();
|
||||
|
||||
return $builder->where('status', DrawActivityStatus::Running)
|
||||
->where('published_at', '<=', $tz)
|
||||
->where(function ($builder) use ($tz) {
|
||||
$builder->where(function ($builder) use ($tz) {
|
||||
$builder->whereNull('start_at')->orWhere(function ($builder) use ($tz) {
|
||||
$builder->whereNotNull('start_at')->where('start_at', '<=', $tz);
|
||||
});
|
||||
})->where(function ($builder) use ($tz) {
|
||||
$builder->whereNull('end_at')->orWhere(function ($builder) use ($tz) {
|
||||
$builder->whereNotNull('end_at')->where('end_at', '>', $tz);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅查询已结束的活动
|
||||
*/
|
||||
public function scopeOnlyClosed(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where('status', DrawActivityStatus::Closed)
|
||||
->orWhere(function ($builder) {
|
||||
$tz = now();
|
||||
|
||||
return $builder->where('status', '!=', DrawActivityStatus::Created)
|
||||
->where('published_at', '<=', $tz)
|
||||
->whereNotNull('end_at')
|
||||
->where('end_at', '<=', $tz);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 属于此活动的奖品
|
||||
*/
|
||||
public function prizes()
|
||||
{
|
||||
return $this->hasMany(DrawActivityPrize::class, 'draw_activity_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认此活动是否已发布
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPublished(): bool
|
||||
{
|
||||
return in_array($this->real_status, [
|
||||
DrawActivityStatus::Unstart,
|
||||
DrawActivityStatus::Running,
|
||||
DrawActivityStatus::Closed,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认此活动是否未开始
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUnstart(): bool
|
||||
{
|
||||
return $this->real_status === DrawActivityStatus::Unstart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认此活动是否已结束
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClosed(): bool
|
||||
{
|
||||
return $this->real_status === DrawActivityStatus::Closed;
|
||||
}
|
||||
|
||||
public function getBgColorLabel()
|
||||
{
|
||||
if ($this->bg_color) {
|
||||
return "<span class='label shadow-100' style='background: {$this->bg_color};'>{$this->bg_color}</span>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此活动的真实状态
|
||||
*
|
||||
* @return DrawActivityStatus
|
||||
*/
|
||||
public function getRealStatusAttribute(): DrawActivityStatus
|
||||
{
|
||||
if ($this->status === DrawActivityStatus::Running) {
|
||||
if (now()->lt($this->published_at)) {
|
||||
return DrawActivityStatus::Publishing;
|
||||
}
|
||||
|
||||
if ($this->start_at && now()->lt($this->start_at)) {
|
||||
return DrawActivityStatus::Unstart;
|
||||
}
|
||||
|
||||
if ($this->end_at && now()->gte($this->end_at)) {
|
||||
return DrawActivityStatus::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Exceptions\BizException;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawActivityPrize extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'type' => DrawPrizeType::None,
|
||||
'weight' => 0,
|
||||
'limited' => true,
|
||||
'sort' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'type' => DrawPrizeType::class,
|
||||
'limited' => 'bool',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'draw_activity_id',
|
||||
'name',
|
||||
'icon',
|
||||
'type',
|
||||
'amount',
|
||||
'weight',
|
||||
'limited',
|
||||
'stock',
|
||||
'winnings',
|
||||
'sort',
|
||||
];
|
||||
|
||||
public static function booted()
|
||||
{
|
||||
static::updating(function ($drawActivityPrize) {
|
||||
if ($drawActivityPrize->weight == 0 && $drawActivityPrize->activity->isPublished()) {
|
||||
$weight = $drawActivityPrize->activity->prizes()->where('id', '!=', $drawActivityPrize->id)->sum('weight');
|
||||
|
||||
if ($weight == 0) {
|
||||
throw new BizException('活动奖品总权重值不能为0');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function activity()
|
||||
{
|
||||
return $this->belongsTo(DrawActivity::class, 'draw_activity_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\DrawLogStatus;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawLog extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'status' => DrawLogStatus::Pending,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'status' => DrawLogStatus::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'draw_activity_id',
|
||||
'draw_activity_prize_id',
|
||||
'consignee_name',
|
||||
'consignee_phone',
|
||||
'consignee_address',
|
||||
'remark',
|
||||
'status',
|
||||
];
|
||||
|
||||
public function scopeQueuing(Builder $builder): Builder
|
||||
{
|
||||
return $builder->where('status', DrawLogStatus::Queuing);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function userInfo()
|
||||
{
|
||||
return $this->belongsTo(UserInfo::class, 'user_id', 'user_id');
|
||||
}
|
||||
|
||||
public function activity()
|
||||
{
|
||||
return $this->belongsTo(DrawActivity::class, 'draw_activity_id');
|
||||
}
|
||||
|
||||
public function prize()
|
||||
{
|
||||
return $this->belongsTo(DrawActivityPrize::class, 'draw_activity_prize_id');
|
||||
}
|
||||
|
||||
public function isPending()
|
||||
{
|
||||
return $this->status === DrawLogStatus::Pending;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\DrawPrizeType;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawPrize extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'type' => DrawPrizeType::None,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'type' => DrawPrizeType::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'icon',
|
||||
'type',
|
||||
'amount',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawTicket extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'number' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'draw_activity_id',
|
||||
'user_id',
|
||||
'number',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function userInfo()
|
||||
{
|
||||
return $this->belongsTo(UserInfo::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DrawTicketLog extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'number' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'draw_activity_id',
|
||||
'user_id',
|
||||
'number',
|
||||
'remark',
|
||||
];
|
||||
}
|
||||
|
|
@ -56,6 +56,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
'balance_log' => \App\Models\BalanceLog::class,
|
||||
'admin_users' => \App\Models\Admin\Administrator::class,
|
||||
'user_vip' => \App\Models\UserVip::class,
|
||||
'draw_log' => \App\Models\DrawLog::class,
|
||||
]);
|
||||
|
||||
JsonResource::withoutWrapping();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\DrawLogStatus;
|
||||
use App\Enums\DrawPrizeType;
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\DrawActivityPrize;
|
||||
use App\Models\DrawLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DrawActivityService
|
||||
{
|
||||
/**
|
||||
* 活动抽奖
|
||||
*
|
||||
* @param \App\Models\DrawActivity $drawActivity
|
||||
* @param \App\Models\User $user
|
||||
* @return \App\Models\DrawLog
|
||||
*/
|
||||
public function draw(DrawActivity $drawActivity, User $user): DrawLog
|
||||
{
|
||||
if (! $drawActivity->isPublished()) {
|
||||
throw new BizException('抽奖活动未发布');
|
||||
}
|
||||
|
||||
if ($drawActivity->isUnstart()) {
|
||||
throw new BizException('抽奖活动未开始');
|
||||
}
|
||||
|
||||
if ($drawActivity->isClosed()) {
|
||||
throw new BizException('抽奖活动已结束');
|
||||
}
|
||||
|
||||
(new DrawTicketService())->change($user, $drawActivity, -1, '活动抽奖');
|
||||
|
||||
$prize = $this->getDrawActivityPrize($drawActivity);
|
||||
|
||||
// 如果限制了奖品数量,则需扣除奖品库存
|
||||
if ($prize->limited) {
|
||||
$prize->update([
|
||||
'stock' => DB::raw('stock-1'),
|
||||
'winnings' => DB::raw('winnings+1'),
|
||||
]);
|
||||
} else {
|
||||
$prize->increment('winnings', 1);
|
||||
}
|
||||
|
||||
$drawLog = DrawLog::create([
|
||||
'user_id' => $user->id,
|
||||
'draw_activity_id' => $drawActivity->id,
|
||||
'draw_activity_prize_id' => $prize->id,
|
||||
'status' => match ($prize->type) {
|
||||
DrawPrizeType::None => DrawLogStatus::Completed,
|
||||
DrawPrizeType::Wallet, DrawPrizeType::Balance, DrawPrizeType::Point => DrawLogStatus::Queuing,
|
||||
default => DrawLogStatus::Pending,
|
||||
},
|
||||
]);
|
||||
|
||||
return $drawLog->setRelation('prize', $prize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\Models\DrawActivity $drawActivity
|
||||
* @return \App\Models\DrawActivityPrize
|
||||
*/
|
||||
protected function getDrawActivityPrize(DrawActivity $drawActivity): DrawActivityPrize
|
||||
{
|
||||
$drawActivityPrizes = $drawActivity->prizes()->get();
|
||||
|
||||
// 过滤权重为0或库存不足的奖品
|
||||
$prizes = $drawActivityPrizes->filter(function ($item) {
|
||||
if ($item->weight === 0 || ($item->limited && $item->stock === 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$max = $prizes->sum('weight');
|
||||
|
||||
if ($max > 0) {
|
||||
$rand = mt_rand($min = 1, $max);
|
||||
|
||||
foreach ($prizes as $prize) {
|
||||
if ($rand <= $prize->weight + $min) {
|
||||
return $prize;
|
||||
}
|
||||
|
||||
$min += $prize->weight;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BizException('抽奖活动异常');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\DrawActivity;
|
||||
use App\Models\DrawTicket;
|
||||
use App\Models\DrawTicketLog;
|
||||
use App\Models\User;
|
||||
|
||||
class DrawTicketService
|
||||
{
|
||||
/**
|
||||
* 增加/扣除 用户在给定活动中的抽奖机会
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param \App\Models\DrawActivity $drawActivity
|
||||
* @param int $number
|
||||
* @param string|null $remark
|
||||
* @return void
|
||||
*
|
||||
* @throws \App\Exceptions\BizException
|
||||
*/
|
||||
public function change(User $user, DrawActivity $drawActivity, int $number, ?string $remark = null): void
|
||||
{
|
||||
if ($number === 0) {
|
||||
throw new BizException('抽奖次数不能为 0');
|
||||
}
|
||||
|
||||
if ($number < 0) {
|
||||
$drawTicket = DrawTicket::where([
|
||||
'draw_activity_id' => $drawActivity->id,
|
||||
'user_id' => $user->id,
|
||||
])->first();
|
||||
|
||||
if ($drawTicket === null || $drawTicket->number + $number < 0) {
|
||||
throw new BizException('抽奖次数不足');
|
||||
}
|
||||
} else {
|
||||
$drawTicket = DrawTicket::firstOrCreate([
|
||||
'draw_activity_id' => $drawActivity->id,
|
||||
'user_id' => $user->id,
|
||||
], [
|
||||
'number' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
$drawTicket->increment('number', $number);
|
||||
|
||||
DrawTicketLog::create([
|
||||
'draw_activity_id' => $drawActivity->id,
|
||||
'user_id' => $user->id,
|
||||
'number' => $number,
|
||||
'remark' => $remark,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawActivitiesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_activities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->comment('名称');
|
||||
$table->string('cover')->nullable()->comment('封面图');
|
||||
$table->text('desc')->nullable()->comment('描述');
|
||||
$table->timestamp('start_at')->nullable()->comment('开始时间');
|
||||
$table->timestamp('end_at')->nullable()->comment('结束时间');
|
||||
$table->timestamp('published_at')->nullable()->comment('发布时间');
|
||||
$table->string('bg_image')->nullable()->comment('背景图片');
|
||||
$table->string('bg_color')->nullable()->comment('背景颜色');
|
||||
$table->tinyInteger('status')->default(0)->comment('状态');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_activities');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawTicketsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_tickets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('draw_activity_id')->comment('抽奖活动ID');
|
||||
$table->unsignedBigInteger('user_id')->comment('用户ID');
|
||||
$table->unsignedInteger('number')->default(0)->comment('次数');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['draw_activity_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_tickets');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawPrizesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_prizes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->comment('名称');
|
||||
$table->string('icon')->nullable()->comment('图标');
|
||||
$table->tinyInteger('type')->comment('类型');
|
||||
$table->unsignedDecimal('amount')->default(0)->comment('面值/数量');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_prizes');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawActivityPrizesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_activity_prizes', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('draw_activity_id')->comment('抽奖活动ID');
|
||||
$table->string('name')->comment('名称');
|
||||
$table->string('icon')->nullable()->comment('图标');
|
||||
$table->tinyInteger('type')->comment('类型');
|
||||
$table->unsignedDecimal('amount')->default(0)->comment('面值/数量');
|
||||
$table->unsignedInteger('weight')->default(0)->comment('权重');
|
||||
$table->boolean('limited')->default(true)->comment('是否限量');
|
||||
$table->unsignedInteger('stock')->default(0)->comment('奖品库存');
|
||||
$table->unsignedInteger('winnings')->default(0)->comment('中奖数量');
|
||||
$table->integer('sort')->default(0)->comment('排序');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_activity_prizes');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawTicketLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_ticket_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('draw_activity_id')->comment('抽奖活动ID');
|
||||
$table->unsignedBigInteger('user_id')->comment('用户ID');
|
||||
$table->integer('number')->default(0)->comment('次数');
|
||||
$table->string('remark')->nullable()->comment('备注');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_ticket_logs');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateDrawLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('draw_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id')->comment('用户ID');
|
||||
$table->unsignedBigInteger('draw_activity_id')->comment('活动ID');
|
||||
$table->unsignedBigInteger('draw_activity_prize_id')->comment('活动奖品ID');
|
||||
$table->string('consignee_name')->nullable()->comment('收件人-姓名');
|
||||
$table->string('consignee_phone')->nullable()->comment('收件人-联系方式');
|
||||
$table->string('consignee_address')->nullable()->comment('收件人-地址');
|
||||
$table->string('remark')->nullable()->comment('备注');
|
||||
$table->tinyInteger('status')->default(0)->comment('状态');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('draw_logs');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue