diff --git a/app/Admin/Controllers/Hr/EmployeeController.php b/app/Admin/Controllers/Hr/EmployeeController.php index 016df9e..3542fff 100644 --- a/app/Admin/Controllers/Hr/EmployeeController.php +++ b/app/Admin/Controllers/Hr/EmployeeController.php @@ -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, '操作成功'); - } } diff --git a/app/Admin/Controllers/Store/StoreController.php b/app/Admin/Controllers/Store/StoreController.php index fe2bce7..76aacce 100644 --- a/app/Admin/Controllers/Store/StoreController.php +++ b/app/Admin/Controllers/Store/StoreController.php @@ -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); } } diff --git a/app/Admin/Filters/StoreFilter.php b/app/Admin/Filters/StoreFilter.php index ab3e77d..8e2e943 100644 --- a/app/Admin/Filters/StoreFilter.php +++ b/app/Admin/Filters/StoreFilter.php @@ -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); + } } diff --git a/app/Admin/Services/EmployeeService.php b/app/Admin/Services/EmployeeService.php index e9b29f4..4a64987 100644 --- a/app/Admin/Services/EmployeeService.php +++ b/app/Admin/Services/EmployeeService.php @@ -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; } diff --git a/app/Admin/Services/StoreService.php b/app/Admin/Services/StoreService.php index 8d20143..04216e2 100644 --- a/app/Admin/Services/StoreService.php +++ b/app/Admin/Services/StoreService.php @@ -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' => '已经是店长了', diff --git a/app/Admin/routes.php b/app/Admin/routes.php index acd8661..0c391d5 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -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']); /* |-------------------------------------------------------------------------- diff --git a/app/Models/Employee.php b/app/Models/Employee.php index 366099a..43027b5 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Casts\StorageJson; use App\Enums\EmployeeStatus; use App\Traits\HasDateTimeFormatter; use EloquentFilter\Filterable; diff --git a/app/Models/Store.php b/app/Models/Store.php index 675517e..512e503 100644 --- a/app/Models/Store.php +++ b/app/Models/Store.php @@ -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: 成都市} diff --git a/app/Traits/UploadTrait.php b/app/Traits/UploadTrait.php index 21874ee..c94856b 100644 --- a/app/Traits/UploadTrait.php +++ b/app/Traits/UploadTrait.php @@ -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]); } diff --git a/database/migrations/2024_03_23_095309_create_stores_table.php b/database/migrations/2024_03_23_095309_create_stores_table.php index f53706d..ce90dfb 100644 --- a/database/migrations/2024_03_23_095309_create_stores_table.php +++ b/database/migrations/2024_03_23_095309_create_stores_table.php @@ -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('门店-店员'); }); } diff --git a/lang/zh_CN/store.php b/lang/zh_CN/store.php index 5553023..799192c 100644 --- a/lang/zh_CN/store.php +++ b/lang/zh_CN/store.php @@ -16,4 +16,7 @@ return [ 'profit_ratio' => '佣金比例', 'profit_money' => '店长提成', 'business_status' => '状态', + 'role' => '身份', + 'address' => '详细地址', + 'employees' => '店员', ];