admin 门店-店员管理

main
panliang 2024-03-26 13:03:50 +08:00
parent 3191976a75
commit 00ca851db4
11 changed files with 215 additions and 44 deletions

View File

@ -6,7 +6,7 @@ use App\Admin\Services\EmployeeService;
use App\Enums\EmployeeStatus;
use App\Models\Employee;
use Illuminate\Http\Request;
use Slowlyo\OwlAdmin\Controllers\AdminController;
use App\Admin\Controllers\AdminController;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
@ -33,18 +33,15 @@ class EmployeeController extends AdminController
amisMake()->TableColumn()->name('id')->label(__('employee.id')),
amisMake()->TableColumn()->name('name')->label(__('employee.name')),
amisMake()->TableColumn()->name('phone')->label(__('employee.phone')),
amisMake()->TableColumn()->name('employee_status_text')->label(__('employee.employee_status'))->set('type', 'tag')->set('color', '${employee_status_color}'),
amisMake()->TableColumn()->name('employee_status')->label(__('employee.employee_status'))
->type('switch')
->trueValue(EmployeeStatus::Online)
->falseValue(EmployeeStatus::Offline),
amisMake()->TableColumn()->name('created_at')->label(__('employee.created_at')),
$this->rowActions([
$this->rowShowButton(),
$this->rowEditButton(true),
$this->rowDeleteButton(),
amisMake()->AjaxAction()
->label(__('employee.leave'))
->level('link')
->icon('fa fa-sign-out')
->confirmText(__('employee.leave_confirm'))
->api('post:'.admin_url('hr/employees/${id}/leave')),
]),
]);
@ -57,15 +54,26 @@ class EmployeeController extends AdminController
amisMake()->TextControl()->name('name')->label(__('employee.name'))->required(),
amisMake()->TextControl()->name('phone')->label(__('employee.phone'))->required(),
amisMake()->SelectControl()->name('employee_status')->label(__('employee.employee_status'))->options(EmployeeStatus::options())->required(),
amisMake()->TagControl()->name('jobs')->label(__('employee.jobs'))
->source(admin_url('api/keywords/tree-list').'?parent_key='.Employee::JOB_KEY)
->labelField('name')
->valueField('key')
->joinValues(),
amisMake()->DateControl()->name('join_at')->label(__('employee.join_at'))->format('YYYY-MM-DD'),
amisMake()->TextControl()->name('username')->label(__('admin.username'))->value('${admin_user.username}')->required(! $edit),
amisMake()->TextControl()->name('password')->set('type', 'input-password')->label(__('admin.password'))->required(! $edit),
amisMake()->TextControl()->name('confirm_password')->set('type', 'input-password')->label(__('admin.confirm_password'))->required(! $edit),
amisMake()->ImageControl()->name('prize_images')->label(__('employee.prize_images'))
->multiple()
->receiver($this->uploadImagePath() . '?full-url=1')
->joinValues(false)
->extractValue(true),
amisMake()->ImageControl()->name('skill_images')->label(__('employee.skill_images'))
->multiple()
->receiver($this->uploadImagePath() . '?full-url=1')
->joinValues(false)
->extractValue(true),
amisMake()->TextControl()->name('username')->label(__('admin.username'))->value('${admin_user.username}')->visible(! $edit)->required(! $edit),
amisMake()->TextControl()->name('password')->set('type', 'input-password')->label(__('admin.password'))->visible(! $edit)->required(! $edit),
amisMake()->TextControl()->name('confirm_password')->set('type', 'input-password')->label(__('admin.confirm_password'))->visible(! $edit)->required(! $edit),
]);
}
@ -76,22 +84,12 @@ class EmployeeController extends AdminController
['label' => __('employee.phone'), 'content' => '${phone}'],
['label' => __('employee.jobs'), 'content' => amisMake()->Each()->name('jobs')->items(amisMake()->Tag()->label('${name}'))],
['label' => __('admin.username'), 'content' => '${admin_user.username}'],
['label' => __('employee.employee_status'), 'content' => amisMake()->Tag()->label('${employee_status_text}')->color('${employee_status_color}')],
['label' => __('employee.join_at'), 'content' => '${join_at}'],
['label' => __('employee.leave_at'), 'content' => '${leave_at}'],
['label' => __('admin.username'), 'content' => '${admin_user.username}', 'span' => 3],
['label' => __('employee.prize_images'), 'content' => amisMake()->Images()->source('${prize_images}')->enlargeAble(), 'span' => 3],
['label' => __('employee.skill_images'), 'content' => amisMake()->Images()->source('${skill_images}')->enlargeAble(), 'span' => 3],
]));
}
// 员工离职
public function leave($id, Request $request)
{
$user = Employee::findOrFail($id);
$user->update([
'leave_at' => $request->input('leave_at', now()),
'employee_status' => EmployeeStatus::Offline,
]);
return $this->response()->success(null, '操作成功');
}
}

View File

@ -2,11 +2,18 @@
namespace App\Admin\Controllers\Store;
use App\Models\{Store, Employee};
use Illuminate\Http\Request;
use App\Enums\{BusinessStatus, StoreRole};
use App\Admin\Services\StoreService;
use Slowlyo\OwlAdmin\Controllers\AdminController;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
use Illuminate\Support\Facades\DB;
use App\Admin\Controllers\AdminController;
/**
* 门店管理
*/
class StoreController extends AdminController
{
protected string $serviceName = StoreService::class;
@ -22,7 +29,7 @@ class StoreController extends AdminController
->filter($this->baseFilter()->body([
amis()->GroupControl()->mode('horizontal')->body([
amisMake()->TextControl()->name('title')->label(__('store.title'))->columnRatio(3)->clearable(),
amisMake()->SelectControl()->name('category_id')->label(__('store.category_id'))->columnRatio(3)
amisMake()->TreeSelectControl()->name('category_id')->label(__('store.category_id'))->columnRatio(3)
->source(admin_url('api/keywords/tree-list?parent_key=store_category'))
->labelField('name')
->valueField('key')
@ -40,11 +47,11 @@ class StoreController extends AdminController
->labelField('name')
->valueField('key')
->clearable(),
amisMake()->InputCityControl()->name('region')->label(__('store.region'))->columnRatio(3)
->allowDistrict(false)
->extractValue(false)
->clearable(),
amisMake()->SelectControl()->name('business_status')->label(__('store.business_status'))->columnRatio(3)->clearable(),
// amisMake()->InputCityControl()->name('region')->label(__('store.region'))->columnRatio(3)
// ->allowDistrict(false)
// ->extractValue(false)
// ->clearable(),
amisMake()->SelectControl()->name('business_status')->label(__('store.business_status'))->options(BusinessStatus::options())->columnRatio(3)->clearable(),
]),
]))
->columns([
@ -54,7 +61,7 @@ class StoreController extends AdminController
amisMake()->TableColumn()->name('business.name')->label(__('store.business_id')),
amisMake()->TableColumn()->name('level.name')->label(__('store.level_id')),
amisMake()->TableColumn()->name('region')->label(__('store.region'))->set('type', 'tpl')->set('tpl', '${region.province}-${region.city}'),
amisMake()->TableColumn()->name('business_status_text')->label(__('store.business_status'))->set('type', 'tag')->set('color', '${business_status_color}'),
amisMake()->TableColumn()->name('business_status')->label(__('store.business_status'))->type('switch')->trueValue(BusinessStatus::Open)->falseValue(BusinessStatus::Close),
amisMake()->TableColumn()->name('created_at')->label(__('store.created_at')),
$this->rowActions([
$this->rowShowButton(),
@ -98,7 +105,7 @@ class StoreController extends AdminController
public function detail(): Form
{
return $this->baseDetail()->title('')->body(amisMake()->Property()->items([
$detail = amisMake()->Property()->items([
['label' => __('store.title'), 'content' => '${title}'],
['label' => __('store.master_id'), 'content' => '${master.name}'],
['label' => __('store.business_status'), 'content' => amisMake()->Tag()->label('${business_status_text}')->color('${business_status_color}')],
@ -106,7 +113,103 @@ class StoreController extends AdminController
['label' => __('store.business_id'), 'content' => '${business.name}'],
['label' => __('store.level_id'), 'content' => '${level.name}'],
['label' => __('store.region'), 'content' => '${region.province}-${region.city}'],
['label' => __('store.profit_ratio'), 'content' => '${profit_ratio}%'],
['label' => __('store.address'), 'content' => '${region.address}', 'span' => 2],
['label' => __('store.profit_ratio'), 'content' => '${profit_ratio}%', 'span' => 3],
]);
// 员工列表
$sales = amisMake()->Service()->id('store-employees-table')->api(admin_url('store/stores/${id}/employees'))->initFetch(false)->body(amisMake()->Table()->columns([
amisMake()->TableColumn()->name('name')->label(__('employee.name')),
amisMake()->TableColumn()->name('phone')->label(__('employee.phone')),
])->itemActions([
amisMake()->AjaxAction()
->label(__('admin.delete'))
->level('link')
->confirmText(__('admin.confirm_delete'))
->api('delete:'.admin_url('store/stores/${id}/employees').'?employees=${id}')
->reload('store-employees-table')
]));
return $this->baseDetail()->title('')->body([$detail, amisMake()->Divider()->title(__('store.employees')), $sales]);
}
public function show($id)
{
if ($this->actionOfGetData()) {
return $this->response()->success($this->service->getDetail($id));
}
$form = amisMake()->Form()
->labelWidth(0)
->api('post:' . admin_url('store/stores/'.$id.'/employees'))
->redirect('')
->onEvent([
'submitSucc' => [
'actions' => [
['actionType' => 'reload','componentId' => 'store-employees-table']
]
]
])
->body([
amisMake()->TransferControl()->name('employees')
->source(admin_url('store/stores/employee_options') . '?store_id='.$id)
->selectMode('table')
->resultListModeFollowSelect()
->joinValues(false)
->extractValue(true)
->columns([
amisMake()->Column()->name('label')->label(__('employee.name')),
amisMake()->Column()->name('phone')->label(__('employee.phone')),
]),
]);
$dialog = amisMake()->Dialog()->size('lg')->title('添加'.__('store.employees'))->body($form);
$detail = amis()
->Card()
->className('base-form')
->header(['title' => __('admin.detail')])
->body($this->detail())
->toolbar([
amisMake()->DialogAction()->label('添加'. __('store.employees'))->level('primary')->className('mr-1')->dialog($dialog),
$this->backButton(),
]);
$page = $this->basePage()->body($detail);
return $this->response()->success($page);
}
public function employees($id)
{
$store = Store::findOrFail($id);
$list = $store->employees()->wherePivot('role', StoreRole::Employee)->get();
return $this->response()->success($list);
}
public function employeeAdd($id, Request $request)
{
$employees = $request->input('employees');
$store = Store::findOrFail($id);
$service = $this->service;
if ($service->attachEmployee($store, $employees)) {
return $this->response()->success();
}
return $this->response()->fail($service->getError());
}
public function employeeDestroy($id, Request $request)
{
$employees = $request->input('employees');
$store = Store::findOrFail($id);
$service = $this->service;
if ($service->destroyEmployee($store, is_array($employees) ? $employees : explode(',', $employees))) {
return $this->response()->success();
}
return $this->response()->fail($service->getError());
}
public function employeeOptions()
{
$ignore = DB::table('store_employees')->pluck('employee_id');
$list = Employee::select(['name as label', 'id as value', 'phone'])->whereNotIn('id', $ignore)->get();
return $this->response()->success($list);
}
}

View File

@ -6,4 +6,28 @@ use EloquentFilter\ModelFilter;
class StoreFilter extends ModelFilter
{
public function title($name)
{
$this->whereLike('title', $name);
}
public function category($key)
{
$this->where('category_id', $key);
}
public function business($key)
{
$this->where('business_id', $key);
}
public function level($key)
{
$this->where('level_id', $key);
}
public function businessStatus($key)
{
$this->where('business_status', $key);
}
}

View File

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use Slowlyo\OwlAdmin\Models\AdminUser;
use Slowlyo\OwlAdmin\Services\AdminUserService;
@ -70,7 +71,6 @@ class EmployeeService extends BaseService
public function resloveData($data, $model = null)
{
// 管理员信息
$adminUserService = AdminUserService::make();
if ($model) {
// 修改管理员信息
@ -108,11 +108,19 @@ class EmployeeService extends BaseService
public function preDelete(array $ids)
{
// 店长关联
if (DB::table('store_employees')->whereIn('employee_id', $ids)->exists()) {
return '员工已关联门店, 请先从门店中删除';
}
// 删除管理员
$adminUserIds = Employee::whereIn('id', $ids)->pluck('admin_user_id')->implode(',');
$adminUserService = AdminUserService::make();
$adminUserService->delete($adminUserIds);
// 删除职位关联
DB::table('employee_jobs')->whereIn('employee_id', $ids)->delete();
return true;
}

View File

@ -2,11 +2,12 @@
namespace App\Admin\Services;
use App\Admin\Filters\StoreFilter;
use App\Enums\StoreRole;
use App\Models\Store;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Models\{Store, Employee};
use App\Admin\Filters\StoreFilter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class StoreService extends BaseService
{
@ -48,18 +49,44 @@ class StoreService extends BaseService
// 修改店长
if (isset($data['master_id']) && $data['master_id'] != $model->master_id) {
$store->employees()->detach($model->master_id);
$store->employees()->attach([$data['master_id'] => ['role' => StoreRole::Master]]);
$model->employees()->detach($model->master_id);
$model->employees()->attach([$data['master_id'] => ['role' => StoreRole::Master]]);
}
return $model->update($data);
}
// 添加员工
public function attachEmployee(Store $store, array $employeeIds)
{
$data = [];
$employees = Employee::whereIn('id', $employeeIds)->get();
// 每个员工只能有一个门店
foreach ($employees as $employee) {
if (DB::table('store_employees')->where('employee_id', $employee->id)->exists()) {
$this->setError($employee->name.' 已经是店员');
return false;
}
$data[$employee->id] = ['role' => StoreRole::Employee];
}
$store->employees()->attach($data);
return true;
}
// 移除员工
public function destroyEmployee(Store $store, array $employeeIds)
{
$store->employees()->detach($employeeIds);
return true;
}
public function resloveData($data, $model = null)
{
if (isset($data['location'])) {
$data['lon'] = data_get($data['location'], 'lng');
$data['lat'] = data_get($data['location'], 'lat');
$data['address'] = data_get($data['location'], 'address');
}
return $data;
@ -78,7 +105,7 @@ class StoreService extends BaseService
'lat' => ['required'],
];
$updateRules = [
'master_id' => [Rule::unique($model, 'master_id')],
'master_id' => [Rule::unique('stores', 'master_id')->ignore($model, 'master_id')],
];
$validator = Validator::make($data, $model ? $updateRules : $createRules, [
'master_id.unique' => '已经是店长了',

View File

@ -23,6 +23,10 @@ Route::group([
| 门店管理
|--------------------------------------------------------------------------
*/
$router->get('store/stores/{id}/employees', [\App\Admin\Controllers\Store\StoreController::class, 'employees']);
$router->get('store/stores/employee_options', [\App\Admin\Controllers\Store\StoreController::class, 'employeeOptions']);
$router->post('store/stores/{id}/employees', [\App\Admin\Controllers\Store\StoreController::class, 'employeeAdd']);
$router->delete('store/stores/{id}/employees', [\App\Admin\Controllers\Store\StoreController::class, 'employeeDestroy']);
$router->resource('store/stores', \App\Admin\Controllers\Store\StoreController::class);
/*
@ -31,7 +35,6 @@ Route::group([
|--------------------------------------------------------------------------
*/
$router->resource('hr/employees', \App\Admin\Controllers\Hr\EmployeeController::class);
$router->post('hr/employees/{id}/leave', [\App\Admin\Controllers\Hr\EmployeeController::class, 'leave']);
/*
|--------------------------------------------------------------------------

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Casts\StorageJson;
use App\Enums\EmployeeStatus;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;

View File

@ -15,7 +15,7 @@ class Store extends Model
{
use Filterable, HasDateTimeFormatter;
protected $fillable = ['title', 'master_id', 'category_id', 'business_id', 'level_id', 'region', 'lon', 'lat', 'profit_ratio', 'profit_money', 'business_status'];
protected $fillable = ['title', 'master_id', 'category_id', 'business_id', 'level_id', 'region', 'lon', 'lat', 'address', 'profit_ratio', 'profit_money', 'business_status'];
protected $casts = [
// 地区 {province: 四川省, city: 成都市}

View File

@ -62,6 +62,9 @@ trait UploadTrait
}
$path = $file->store(Admin::config('admin.upload.tem_directory.'.$type).'/'.date('Y-m-d'), Admin::config('admin.upload.disk'));
if (request()->has('full-url')) {
$path = Storage::disk(Admin::config('admin.upload.disk'))->url($path);
}
return $this->response()->success(['value' => $path]);
}

View File

@ -19,6 +19,7 @@ return new class extends Migration
$table->string('business_id')->comment('经营分类, keywords.store_business');
$table->string('level_id')->comment('门店等级, keywords.store_level');
$table->json('region')->comment('地区{province,city}');
$table->string('address')->nullable()->comment('详细地址');
$table->string('lon')->comment('精度');
$table->string('lat')->comment('纬度');
$table->unsignedInteger('profit_ratio')->default(0)->comment('佣金比例(0-100)');
@ -31,7 +32,7 @@ return new class extends Migration
Schema::create('store_employees', function (Blueprint $table) {
$table->foreignId('store_id');
$table->foreignId('employee_id');
$table->unsignedInteger('role')->default(1)->comment('身份(1: 店长, 2: 店员)');
$table->unsignedInteger('role')->default(2)->comment('身份(1: 店长, 2: 店员)');
$table->comment('门店-店员');
});
}

View File

@ -16,4 +16,7 @@ return [
'profit_ratio' => '佣金比例',
'profit_money' => '店长提成',
'business_status' => '状态',
'role' => '身份',
'address' => '详细地址',
'employees' => '店员',
];