generated from liutk/owl-admin-base
Compare commits
2 Commits
ea174e3b88
...
250f365281
| Author | SHA1 | Date |
|---|---|---|
|
|
250f365281 | |
|
|
26dc3dfa6f |
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Admin\Controllers;
|
||||||
|
|
||||||
|
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||||
|
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||||
|
use Slowlyo\OwlAdmin\Controllers\AdminController;
|
||||||
|
use App\Services\Admin\ContactService;
|
||||||
|
use App\Traits\CustomActionTrait;
|
||||||
|
use App\Admin\Components;
|
||||||
|
use App\Models\{Keyword};
|
||||||
|
use Slowlyo\OwlAdmin\Admin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线询价
|
||||||
|
*
|
||||||
|
* @property ContactService $service
|
||||||
|
*/
|
||||||
|
class ContactController extends AdminController
|
||||||
|
{
|
||||||
|
use CustomActionTrait;
|
||||||
|
protected string $serviceName = ContactService::class;
|
||||||
|
|
||||||
|
public function list(): Page
|
||||||
|
{
|
||||||
|
$crud = $this->baseCRUD()->tableLayout('fixed')
|
||||||
|
->headerToolbar([
|
||||||
|
// $this->createTypeButton('drawer', 'md'),
|
||||||
|
...$this->baseHeaderToolBar(),
|
||||||
|
])
|
||||||
|
->filter($this->baseFilter()->body([
|
||||||
|
amis()->GroupControl()->mode('horizontal')->body([
|
||||||
|
amis()->TextControl('name', __('admin.contacts.name'))->columnRatio(3)
|
||||||
|
->placeholder(__('admin.contacts.name')),
|
||||||
|
amis()->TextControl('phone', __('admin.contacts.phone'))->columnRatio(3)
|
||||||
|
->placeholder(__('admin.contacts.phone')),
|
||||||
|
amis()->selectControl('type', __('admin.contacts.type'))->options(Keyword::allChildrenOfKey('contact_types')->pluck('name', 'id'))
|
||||||
|
->placeholder(__('admin.contacts.type'))->columnRatio(3),
|
||||||
|
]),
|
||||||
|
]))
|
||||||
|
->columns([
|
||||||
|
// amis()->TableColumn('id', __('admin.id'))->sortable(true)->width('50px'),
|
||||||
|
amis()->TableColumn('name', __('admin.contacts.name'))->width('300px'),
|
||||||
|
amis()->TableColumn('phone', __('admin.contacts.phone'))->width('300px'),
|
||||||
|
amis()->TableColumn('type', __('admin.contacts.type'))->type('mapping')
|
||||||
|
->map(Keyword::allChildrenOfKey('contact_types')->pluck('name', 'id')->toArray())
|
||||||
|
->itemSchema(amis()->Tag()->label('${item}')->color(Admin::setting()->get('system_theme_setting')['theme_color'] ?? '#1677ff')),
|
||||||
|
amis()->TableColumn('phone', __('admin.contacts.phone'))->width('300px'),
|
||||||
|
amis()->TableColumn('status', __('admin.contacts.status'))->type('switch'),
|
||||||
|
amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
|
||||||
|
amis()->TableColumn('updated_at', __('admin.updated_at'))->type('datetime')->sortable(true),
|
||||||
|
amis()->Operation()->label(__('admin.actions'))->buttons([
|
||||||
|
$this->rowShowTypeButton('drawer', 'md'),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->baseList($crud);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form($isEdit = false): Form
|
||||||
|
{
|
||||||
|
return $this->baseForm()->panelClassName('px-0')->body([
|
||||||
|
amis()->Grid()->columns([
|
||||||
|
amis()->Wrapper()->body([
|
||||||
|
amis()->TextControl('name', __('admin.project_articles.name'))->required(true),
|
||||||
|
amis()->TextareaControl('description', __('admin.project_articles.description')),
|
||||||
|
// amis()->selectControl('cate', __('admin.project_articles.cate'))->options(ProjectCate::get()->pluck('title', 'id'))->required(true),
|
||||||
|
Components::make()->keywordsTagControl('t_ids', __('admin.articles.tags'), 'case_study_tag')->required(true),
|
||||||
|
Components::make()->cropImageControl('cover', __('admin.project_articles.cover'), 0.775)->required(true),
|
||||||
|
Components::make()->sortControl('sort', __('admin.project_articles.sort')),
|
||||||
|
// amis()->DateTimeControl('published_at', __('admin.project_articles.published_at'))->format('YYYY-MM-DD HH:mm:ss')->description(__('admin.project_articles.published_at_remark')),
|
||||||
|
amis()->SwitchControl('is_enable', __('admin.project_articles.is_enable'))->value(false),
|
||||||
|
// amis()->SwitchControl('is_recommend', __('admin.project_articles.is_recommend'))->value(false),
|
||||||
|
// Components::make()->fileControl('appendixes', __('admin.articles.appendixes'), '.xsl,.xlsx,.txt,.doc,.docx,.pdf,.pptx'),
|
||||||
|
])->md(4),
|
||||||
|
amis()->Wrapper()->body([
|
||||||
|
Components::make()->fuEditorControl('content', __('admin.project_articles.content'))->required(true),
|
||||||
|
])->md(8)
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detail(): Form
|
||||||
|
{
|
||||||
|
return $this->baseDetail()->panelClassName('px-0')->body([
|
||||||
|
amis()->Grid()->columns([
|
||||||
|
amis()->Wrapper()->body([
|
||||||
|
amis()->TextControl('name', __('admin.contacts.name'))->disabled(true),
|
||||||
|
amis()->TextControl('phone', __('admin.contacts.phone'))->disabled(true),
|
||||||
|
amis()->TextControl('company', __('admin.contacts.company'))->disabled(true),
|
||||||
|
amis()->selectControl('type', __('admin.contacts.type'))->options(Keyword::allChildrenOfKey('contact_types')->pluck('name', 'id'))->disabled(true),
|
||||||
|
amis()->TextareaControl('content', __('admin.contacts.content'))->disabled(true),
|
||||||
|
amis()->DateTimeControl('created_at', __('admin.contacts.created_at'))->format('YYYY-MM-DD HH:mm:ss')->disabled(true),
|
||||||
|
amis()->SwitchControl('status', __('admin.contacts.status'))->disabled(true),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ Route::group([
|
||||||
$router->resource('friend_links', \App\Admin\Controllers\FriendLinkController::class);
|
$router->resource('friend_links', \App\Admin\Controllers\FriendLinkController::class);
|
||||||
$router->resource('honors', \App\Admin\Controllers\HonorController::class);
|
$router->resource('honors', \App\Admin\Controllers\HonorController::class);
|
||||||
$router->resource('timelines', \App\Admin\Controllers\TimelineController::class);
|
$router->resource('timelines', \App\Admin\Controllers\TimelineController::class);
|
||||||
|
$router->resource('contacts', \App\Admin\Controllers\ContactController::class);
|
||||||
|
|
||||||
//修改上传
|
//修改上传
|
||||||
$router->post('upload_file', [\App\Admin\Controllers\IndexController::class, 'uploadFile']);
|
$router->post('upload_file', [\App\Admin\Controllers\IndexController::class, 'uploadFile']);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ContactRequest;
|
||||||
|
use App\Models\{Contact,Keyword};
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Resources\{KeywordResource};
|
||||||
|
|
||||||
|
class ContactController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function store(ContactRequest $request)
|
||||||
|
{
|
||||||
|
$inputParams = $request->input();
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
Contact::create($inputParams);
|
||||||
|
DB::commit();
|
||||||
|
} catch(\Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
report($th);
|
||||||
|
|
||||||
|
return $this->error('提交失败,请稍后再试');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success('提交成功!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签列表
|
||||||
|
*/
|
||||||
|
public function types(Request $request){
|
||||||
|
$query = Keyword::allChildrenOfKey('contact_types');
|
||||||
|
$list = $query->sort()->get();
|
||||||
|
return $this->json(KeywordResource::collection($list));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\Validator;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||||
|
use App\Rules\CaptchaRule;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ContactRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|string|max:20',
|
||||||
|
'phone' => 'required|string|regex:/^1[3-9]\d{9}$/|unique:contacts,phone',
|
||||||
|
'captcha' => ['required', 'string', new CaptchaRule(request()->input('key'))],
|
||||||
|
'company' => 'required|string|max:50',
|
||||||
|
'type' => ['required', 'integer', Rule::exists('keywords', 'id')->where('parent_key', 'contact_types')],
|
||||||
|
'content' => 'required|string|max:255',
|
||||||
|
'key' => 'required|string|max:255',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages()
|
||||||
|
{
|
||||||
|
$messages = [
|
||||||
|
'name.required' => '姓名不能为空',
|
||||||
|
'name.max' => '姓名最大不能超过20长度',
|
||||||
|
'phone.required' => '手机号不能为空',
|
||||||
|
'phone.regex' => '手机号格式不正确',
|
||||||
|
'phone.unique' => '该手机号已提交过询价,请耐心等待工作人员联系',
|
||||||
|
'company.required' => '公司不能为空',
|
||||||
|
'company.max' => '公司名称最大不能超过50长度',
|
||||||
|
'content.required' => '需求描述不能为空',
|
||||||
|
'content.max' => '需求描述最大不能超过200长度',
|
||||||
|
'captcha.required' => '请输入验证码',
|
||||||
|
'type.required' => '请选择业务需求',
|
||||||
|
'type.exists' => '业务需求类型不存在',
|
||||||
|
'key.required' => '非法请求',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function failedValidation(Validator $validator)
|
||||||
|
{
|
||||||
|
$error = $validator->errors()->all();
|
||||||
|
throw new HttpResponseException(response()->json(['data' => [], 'code' => 400, 'message' => $error[0]]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,35 @@ namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use EloquentFilter\Filterable;
|
||||||
|
|
||||||
class Contact extends Model
|
class Contact extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use Filterable;
|
||||||
|
|
||||||
|
protected function serializeDate(\DateTimeInterface $date)
|
||||||
|
{
|
||||||
|
return $date->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||||
|
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||||
|
'status' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'phone',
|
||||||
|
'company',
|
||||||
|
'type',
|
||||||
|
'content',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function scopeSort($q)
|
||||||
|
{
|
||||||
|
$q->orderBy('created_at', 'desc');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Filters;
|
||||||
|
|
||||||
|
use EloquentFilter\ModelFilter;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ContactFilter extends ModelFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
public function id($id)
|
||||||
|
{
|
||||||
|
return $this->where('id', $id);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
public function name($name)
|
||||||
|
{
|
||||||
|
return $this->where('name','like', $name.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
public function phone($phone)
|
||||||
|
{
|
||||||
|
return $this->where('phone','like', $phone.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分类下的文章
|
||||||
|
*/
|
||||||
|
public function type($type)
|
||||||
|
{
|
||||||
|
return $this->where('type', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status($status){
|
||||||
|
return $this->where('status', $status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use App\Services\CaptchaService;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
class CaptchaRule implements ValidationRule
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string 验证码类型键名
|
||||||
|
*/
|
||||||
|
protected string $key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool 验证后是否销毁
|
||||||
|
*/
|
||||||
|
protected bool $destroy;
|
||||||
|
|
||||||
|
public function __construct(string $key = 'default', bool $destroy = true)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the validation rule.
|
||||||
|
*
|
||||||
|
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// 从容器获取 Service(便于测试和依赖注入)
|
||||||
|
$captchaService = App::make(CaptchaService::class);
|
||||||
|
|
||||||
|
if (!$captchaService->testPhrase($this->key, $value)) {
|
||||||
|
$fail('验证码错误,请重新输入');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Admin;
|
||||||
|
|
||||||
|
use App\Models\Filters\ContactFilter;
|
||||||
|
use App\Models\Contact;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Article getModel()
|
||||||
|
* @method Article|\Illuminate\Database\Query\Builder query()
|
||||||
|
*/
|
||||||
|
class ContactService extends BaseService
|
||||||
|
{
|
||||||
|
|
||||||
|
protected string $modelName = Contact::class;
|
||||||
|
|
||||||
|
protected string $modelFilterName = ContactFilter::class;
|
||||||
|
|
||||||
|
public function update($primaryKey, $data): bool
|
||||||
|
{
|
||||||
|
$columns = $this->getTableColumns();
|
||||||
|
$model = $this->query()->whereKey($primaryKey)->first();
|
||||||
|
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
if (!in_array($k, $columns)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model->setAttribute($k, $v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $model->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ class CaptchaService
|
||||||
public function validatePhrase(string $key, string $phrase): void
|
public function validatePhrase(string $key, string $phrase): void
|
||||||
{
|
{
|
||||||
if (! $this->testPhrase($key, $phrase)) {
|
if (! $this->testPhrase($key, $phrase)) {
|
||||||
throw new BizException(__('Invalid captcha'));
|
throw new BizException(__('无效验证码'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ class KeywordSeeder extends Seeder
|
||||||
]],
|
]],
|
||||||
['key' => 'honors', 'name' => '资质荣誉', 'list' => [
|
['key' => 'honors', 'name' => '资质荣誉', 'list' => [
|
||||||
'honor1'=>'核心资质认证', 'honor2'=>'管理体系认证','honor3'=>'重要荣誉奖项'
|
'honor1'=>'核心资质认证', 'honor2'=>'管理体系认证','honor3'=>'重要荣誉奖项'
|
||||||
|
]],
|
||||||
|
['key' => 'contact_types', 'name' => '业务需求类别', 'list' => [
|
||||||
|
|
||||||
]],
|
]],
|
||||||
['key' => 'case_study_tag', 'name' => '服务案例标签', 'list' => [//标签value填写色号,指定标签颜色
|
['key' => 'case_study_tag', 'name' => '服务案例标签', 'list' => [//标签value填写色号,指定标签颜色
|
||||||
'case_study_tag1'=>'全项目保洁',
|
'case_study_tag1'=>'全项目保洁',
|
||||||
|
|
|
||||||
|
|
@ -375,4 +375,15 @@ return [
|
||||||
'is_recommend' => '推荐',
|
'is_recommend' => '推荐',
|
||||||
'sort' => '排序',
|
'sort' => '排序',
|
||||||
],
|
],
|
||||||
|
'contacts' => [
|
||||||
|
'id' => '主键ID',
|
||||||
|
'name' => '姓名',
|
||||||
|
'phone'=> '手机号',
|
||||||
|
'company' =>'公司',
|
||||||
|
'type' => '业务需求类别',
|
||||||
|
'created_at' => '提交时间',
|
||||||
|
'updated_at' => '更新时间',
|
||||||
|
'status' => '处理状态',
|
||||||
|
'content' => '需求描述',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -45,4 +45,7 @@ Route::middleware('api')->group(function () {
|
||||||
Route::get('/honors', [HonorController::class, 'index']);
|
Route::get('/honors', [HonorController::class, 'index']);
|
||||||
//发展历程
|
//发展历程
|
||||||
Route::get('/timelines', [TimelineController::class, 'index']);
|
Route::get('/timelines', [TimelineController::class, 'index']);
|
||||||
|
//联系我们
|
||||||
|
Route::get('/contact_types', [ContactController::class, 'types']);
|
||||||
|
Route::post('/contacts', [ContactController::class, 'store']);
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue