添加广告管理
parent
32a95b96d7
commit
64ff120a9e
|
|
@ -78,10 +78,14 @@ class Components extends BaseRenderer {
|
|||
* 图片上传,带裁剪
|
||||
*/
|
||||
public function cropImageControl($name, $label, $aspectRatio = null){
|
||||
return amis()->ImageControl($name, $label)->joinValues(false)
|
||||
->crop([
|
||||
'aspectRatio' => $aspectRatio ?? 1
|
||||
])->autoUpload(true);
|
||||
$cropImage = amis()->ImageControl($name, $label)->joinValues(false);
|
||||
// if($aspectRatio){
|
||||
$cropImage->crop([
|
||||
'aspectRatio' => $aspectRatio ?? 1
|
||||
]);
|
||||
// }
|
||||
$cropImage->autoUpload(true);
|
||||
return $cropImage;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,7 +94,6 @@ class Components extends BaseRenderer {
|
|||
public function fileControl($name, $label, $accept = '.txt', $multiple = false){
|
||||
return amis()->FileControl($name, $label ?? __('admin.components.files'))
|
||||
->multiple($multiple)
|
||||
// ->receiver()
|
||||
->joinValues(false)
|
||||
->useChunk(false)
|
||||
->maxSize(20*1024*1024)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers;
|
||||
|
||||
use Slowlyo\OwlAdmin\Admin;
|
||||
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||
use Slowlyo\OwlAdmin\Controllers\AdminController;
|
||||
use Slowlyo\OwlAdmin\Renderers\Operation;
|
||||
use App\Services\Admin\AdService;
|
||||
use App\Admin\Components;
|
||||
use App\Models\Keyword;
|
||||
use App\Models\Ad;
|
||||
|
||||
/**
|
||||
* 广告管理
|
||||
*
|
||||
* @property AdService $service
|
||||
*/
|
||||
class AdController extends AdminController
|
||||
{
|
||||
protected string $serviceName = AdService::class;
|
||||
|
||||
public function list(): Page
|
||||
{
|
||||
$crud = $this->baseCRUD()->tableLayout('fixed')
|
||||
->headerToolbar([
|
||||
$this->createButton(),
|
||||
...$this->baseHeaderToolBar(),
|
||||
])
|
||||
->filter($this->baseFilter()->body())
|
||||
->columns([
|
||||
amis()->TableColumn('id', __('admin.ads.id'))->width('50px')->sortable(),
|
||||
amis()->TableColumn('address', __('admin.ads.address'))->type('mapping')
|
||||
->map(Keyword::allChildrenOfKey('banner_address')->pluck('name', 'key')->toArray())
|
||||
->itemSchema(amis()->Tag()->label('${item}')->color(Admin::setting()->get('system_theme_setting')['theme_color'] ?? '#1677ff')),
|
||||
amis()->TableColumn('sort', __('admin.ads.sort')),
|
||||
amis()->TableColumn('resource', __('admin.ads.resource'))->type('image')->height('50px')->width('150px')->enlargeAble(true),
|
||||
amis()->TableColumn('remark', __('admin.ads.remark')),
|
||||
amis()->TableColumn('published_at', __('admin.ads.published_at'))->remark(__('admin.ads.published_at_remark')),
|
||||
amis()->TableColumn('is_enable', __('admin.ads.is_enable'))->type('switch'),
|
||||
amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
|
||||
Operation::make()->label(__('admin.actions'))->buttons([
|
||||
$this->rowEditButton(),
|
||||
$this->rowDeleteButton(),
|
||||
])
|
||||
]);
|
||||
|
||||
return $this->baseList($crud);
|
||||
}
|
||||
|
||||
public function form($isEdit = false): Form
|
||||
{
|
||||
return $this->baseForm()->body([
|
||||
Components::make()->parentControl(admin_url('api/keywords/tree-list?parent_name=banner_address&has_owner=0'), 'address', __('admin.ads.address'), 'name', 'key')->required(true),
|
||||
Components::make()->imageControl('resource', __('admin.ads.resource'))->required(true),
|
||||
amis()->TextControl('remark', __('admin.ads.remark')),
|
||||
Components::make()->sortControl('sort', __('admin.ads.sort')),
|
||||
amis()->DateTimeControl('published_at', __('admin.ads.published_at'))->format('YYYY-MM-DD HH:mm:ss')->description(__('admin.ads.published_at_remark')),
|
||||
amis()->SwitchControl('is_enable', __('admin.ads.is_enable'))->value(false),
|
||||
amis()->RadiosControl('jump_type', __('admin.ads.jump_type'))->selectFirst(true)->options(Ad::jumpTypeMap())->required(true),
|
||||
amis()->TextControl('jump_config.web_link', __('admin.ads.jump_config_arr.web_link'))->visibleOn('this.jump_type == '.Ad::TYPE_WEB)->required(true),
|
||||
amis()->TextControl('jump_config.app_link', __('admin.ads.jump_config_arr.app_link'))->visibleOn('this.jump_type == '.Ad::TYPE_APP)->required(true),
|
||||
amis()->GroupControl()->body([
|
||||
amis()->TextControl('jump_config.mini_id', __('admin.ads.jump_config_arr.mini_id'))->visibleOn('this.jump_type == '.Ad::TYPE_MINI)->required(true),
|
||||
amis()->TextControl('jump_config.mini_link', __('admin.ads.jump_config_arr.mini_link'))->visibleOn('this.jump_type == '.Ad::TYPE_MINI)->required(true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function detail(): Form
|
||||
{
|
||||
return $this->baseDetail()->body([
|
||||
amis()->TextControl('id', 'ID')->static(),
|
||||
amis()->TextControl('created_at', __('admin.created_at'))->static(),
|
||||
amis()->TextControl('updated_at', __('admin.updated_at'))->static()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ class ArticleController extends AdminController
|
|||
...$this->baseHeaderToolBar(),
|
||||
])
|
||||
->filter($this->baseFilter()->body())
|
||||
->itemBadge([
|
||||
->itemBadge([//行角标
|
||||
'text' => __('admin.articles.is_recommend'),
|
||||
'mode' => 'ribbon',
|
||||
'position' => 'top-left',
|
||||
|
|
@ -32,7 +32,7 @@ class ArticleController extends AdminController
|
|||
'size' => 15
|
||||
])
|
||||
->columns([
|
||||
amis()->TableColumn('id', __('admin.id')),
|
||||
amis()->TableColumn('id', __('admin.id'))->sortable(true),
|
||||
amis()->TableColumn('title', __('admin.articles.title'))->width('300px'),
|
||||
amis()->TableColumn('category', __('admin.articles.category'))->type('mapping')
|
||||
->map(Keyword::allChildrenOfKey('article_category')->pluck('name', 'key')->toArray())
|
||||
|
|
@ -42,7 +42,7 @@ class ArticleController extends AdminController
|
|||
amis()->TableColumn('published_at', __('admin.articles.published_at'))->remark(__('admin.articles.published_at_remark')),
|
||||
amis()->TableColumn('is_enable', __('admin.articles.is_enable'))->type('switch'),
|
||||
amis()->TableColumn('is_recommend', __('admin.articles.is_recommend'))->type('switch'),
|
||||
amis()->TableColumn('created_at', __('admin.created_at')),
|
||||
amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
|
||||
amis()->Operation()->label(__('admin.actions'))->buttons([
|
||||
$this->rowEditButton(),
|
||||
$this->rowDeleteButton(),
|
||||
|
|
@ -65,7 +65,7 @@ class ArticleController extends AdminController
|
|||
amis()->DateTimeControl('published_at', __('admin.articles.published_at'))->format('YYYY-MM-DD HH:mm:ss')->description(__('admin.articles.published_at_remark')),
|
||||
amis()->SwitchControl('is_enable', __('admin.articles.is_enable'))->value(false),
|
||||
amis()->SwitchControl('is_recommend', __('admin.articles.is_recommend'))->value(false),
|
||||
Components::make()->fileControl('appendixes', __('admin.articles.appendixes'), '.xsl,.xlsx,.txt,.doc,.docx,.pdf,.pptx'),
|
||||
Components::make()->fileControl('appendixes', __('admin.articles.appendixes'), '.xsl,.xlsx,.txt,.doc,.docx,.pdf,.pptx', true),
|
||||
])->md(4),
|
||||
amis()->Wrapper()->body([
|
||||
Components::make()->fuEditorControl('content', __('admin.articles.content')),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ Route::group([
|
|||
|
||||
$router->resource('articles', \App\Admin\Controllers\ArticleController::class);
|
||||
|
||||
$router->resource('ads', \App\Admin\Controllers\AdController::class);
|
||||
|
||||
//修改上传
|
||||
$router->post('upload_file', [\App\Admin\Controllers\IndexController::class, 'uploadFile']);
|
||||
$router->post('upload_image', [\App\Admin\Controllers\IndexController::class, 'uploadImage']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use EloquentFilter\Filterable;
|
||||
use App\Casts\Storage;
|
||||
|
||||
class Ad extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Filterable;
|
||||
|
||||
public const TYPE_OFF = 0; //无跳转
|
||||
public const TYPE_WEB = 1; //网页跳转
|
||||
public const TYPE_APP = 2; //应用跳转
|
||||
public const TYPE_MINI = 3;//小程序跳转
|
||||
|
||||
protected function serializeDate(\DateTimeInterface $date)
|
||||
{
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'published_at' => 'datetime',
|
||||
'is_enable' => 'boolean',
|
||||
'resource' => Storage::class,
|
||||
'jump_config' => 'array',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'resource',
|
||||
'address',
|
||||
'remark',
|
||||
'published_at',
|
||||
'is_enable',
|
||||
'sort',
|
||||
'jump_type',
|
||||
'jump_config',
|
||||
];
|
||||
|
||||
public static function jumpTypeMap() :array
|
||||
{
|
||||
return [
|
||||
self::TYPE_WEB => '网页跳转',
|
||||
self::TYPE_APP => '应用跳转',
|
||||
self::TYPE_MINI => '小程序跳转',
|
||||
self::TYPE_OFF => '无跳转',
|
||||
];
|
||||
}
|
||||
|
||||
public static function jumpTypeMapLabel()
|
||||
{
|
||||
return [
|
||||
self::TYPE_OFF => "<span class='label'>无跳转</span>",
|
||||
self::TYPE_WEB => "<span class='label label-info'>网页跳转</span>",
|
||||
self::TYPE_APP => "<span class='label label-warning'>应用跳转</span>",
|
||||
self::TYPE_MINI => "<span class='label label-green'>小程序跳转</span>",
|
||||
'*'=>'其他:${jump_type}'
|
||||
];
|
||||
}
|
||||
|
||||
public static function typeMapLabel()
|
||||
{
|
||||
return [
|
||||
self::TYPE_IN => "<span class='label label-info'>入住缴费</span>",
|
||||
self::TYPE_CONTINUE => "<span class='label label-warning'>续住缴费</span>",
|
||||
self::TYPE_EXIT => "<span class='label label-danger'>离开结算</span>",
|
||||
'*'=>'其他:${live_in}'
|
||||
];
|
||||
}
|
||||
|
||||
public function scopeShow(){
|
||||
$q->where('is_enable', true)->where('published_at', '>=', now());
|
||||
}
|
||||
|
||||
public function scopeSort($q)
|
||||
{
|
||||
$q->orderBy('sort', 'asc')
|
||||
->orderBy('published_at', 'desc')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Filters;
|
||||
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class AdFilter extends ModelFilter
|
||||
{
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
public function name($name)
|
||||
{
|
||||
return $this->where('name','like', '%'.$name.'%');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Models\Ad;
|
||||
use App\Models\Filters\AdFilter;
|
||||
use App\Traits\UploadTrait;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
* @method Ad getModel()
|
||||
* @method Ad|\Illuminate\Database\Query\Builder query()
|
||||
*/
|
||||
class AdService extends BaseService
|
||||
{
|
||||
use UploadTrait;
|
||||
|
||||
protected string $modelName = Ad::class;
|
||||
|
||||
protected string $modelFilterName = AdFilter::class;
|
||||
|
||||
protected bool $modelSortAble = true;
|
||||
|
||||
public function store($data): bool
|
||||
{
|
||||
$columns = $this->getTableColumns();
|
||||
$model = $this->getModel();
|
||||
|
||||
$isEnable = Arr::get($data, 'is_enabled');
|
||||
$publishedAt = Arr::get($data, 'published_at');
|
||||
if ($isEnable && empty($publishedAt)) {
|
||||
$data['published_at'] = now();
|
||||
}
|
||||
|
||||
$data['resource'] = $this->saveImage('resource', 'ads/resource')[0];
|
||||
|
||||
//处理跳转配置
|
||||
$jumpType = Arr::get($data, 'jump_type');
|
||||
if($jumpType !== null){
|
||||
switch($jumpType){
|
||||
case Ad::TYPE_OFF:
|
||||
$data['jump_config'] = null;
|
||||
break;
|
||||
case Ad::TYPE_WEB:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], 'web_link');
|
||||
break;
|
||||
case Ad::TYPE_APP:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], 'app_link');;
|
||||
break;
|
||||
case Ad::TYPE_MINI:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], ['mini_id', 'mini_link']);;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, $columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$model->setAttribute($k, $v);
|
||||
}
|
||||
|
||||
return $model->save();
|
||||
}
|
||||
|
||||
public function update($primaryKey, $data): bool
|
||||
{
|
||||
$columns = $this->getTableColumns();
|
||||
$model = $this->query()->whereKey($primaryKey)->first();
|
||||
|
||||
$isEnable = Arr::get($data, 'is_enable');
|
||||
$publishedAt = Arr::get($data, 'published_at');
|
||||
|
||||
if ($isEnable && empty($publishedAt) && empty($model->published_at)) {
|
||||
$data['published_at'] = now();
|
||||
}
|
||||
|
||||
if(isset($data['resource'])){
|
||||
$data['resource'] = $this->saveImage('resource', 'ads/resource')[0];
|
||||
}
|
||||
|
||||
//处理跳转配置
|
||||
$jumpType = Arr::get($data, 'jump_type');
|
||||
if($jumpType !== null){
|
||||
switch($jumpType){
|
||||
case Ad::TYPE_OFF:
|
||||
$data['jump_config'] = null;
|
||||
break;
|
||||
case Ad::TYPE_WEB:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], 'web_link');
|
||||
break;
|
||||
case Ad::TYPE_APP:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], 'app_link');;
|
||||
break;
|
||||
case Ad::TYPE_MINI:
|
||||
$data['jump_config'] = Arr::only($data['jump_config'], ['mini_id', 'mini_link']);;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, $columns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$model->setAttribute($k, $v);
|
||||
}
|
||||
|
||||
return $model->save();
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +128,8 @@ class AdminUserService extends BaseService
|
|||
$query->where('username', 'like', "%{$keyword}%")->orWhere('name', 'like', "%{$keyword}%");
|
||||
});
|
||||
|
||||
$this->sortable($query);
|
||||
|
||||
$items = (clone $query)->paginate(request()->input('perPage', 20))->items();
|
||||
$total = (clone $query)->count();
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ class ArticleService extends BaseService
|
|||
|
||||
protected string $modelFilterName = ArticleFilter::class;
|
||||
|
||||
protected bool $modelSortAble = true;
|
||||
|
||||
public function store($data): bool
|
||||
{
|
||||
$columns = $this->getTableColumns();
|
||||
|
|
@ -55,9 +57,13 @@ class ArticleService extends BaseService
|
|||
if ($isEnable && empty($publishedAt) && empty($model->published_at)) {
|
||||
$data['published_at'] = now();
|
||||
}
|
||||
if(isset($data['cover'])){
|
||||
$data['cover'] = $this->saveImage('cover', 'articles/cover')[0];
|
||||
}
|
||||
|
||||
$data['cover'] = $this->saveImage('cover', 'articles/cover')[0];
|
||||
$data['appendixes'] = $this->saveFile('appendixes', 'articles/appendixes');
|
||||
if(isset($data['appendixes'])){
|
||||
$data['appendixes'] = $this->saveFile('appendixes', 'articles/appendixes');
|
||||
}
|
||||
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, $columns)) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ class BaseService extends AdminService
|
|||
|
||||
protected string $modelFilterName = '';
|
||||
|
||||
protected bool $modelSortAble = false;
|
||||
|
||||
public function getTree()
|
||||
{
|
||||
$list = $this->query()->orderByDesc('sort')->get();
|
||||
|
|
@ -40,7 +42,13 @@ class BaseService extends AdminService
|
|||
$query->filter(request()->input(), $filter);
|
||||
}
|
||||
|
||||
return $query->orderByDesc($model->getUpdatedAtColumn() ?? $model->getKeyName());
|
||||
if($this->modelSortAble){
|
||||
$query->sort();
|
||||
}
|
||||
|
||||
$this->sortable($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getDetail($id)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class KeywordService extends BaseService
|
|||
{
|
||||
$list = $this->query()->filter(request()->all(), $this->modelFilterName)->orderByDesc('sort')->get();
|
||||
$minNum = $list->min('parent_id');
|
||||
return array2tree($list->toArray(), $minNum);
|
||||
return !$list->isEmpty() ? array2tree($list->toArray(), $minNum) :[];
|
||||
}
|
||||
|
||||
public function parentIsChild($id, $pid): bool
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ads', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('address')->comment('位置');
|
||||
$table->string('resource')->comment('资源');
|
||||
$table->string('remark')->nullable()->comment('备注');
|
||||
$table->timestamp('published_at')->nullable()->comment('发布时间');
|
||||
$table->unsignedTinyInteger('is_enable')->default(1)->comment('显示开关');
|
||||
$table->unsignedInteger('sort')->default(0)->comment('排序');
|
||||
$table->unsignedTinyInteger('jump_type')->default(0)->comment('跳转,0:不跳转。1:网页跳转;2应用跳转;3微信小程序跳转');
|
||||
$table->text('jump_config')->nullable()->comment('跳转配置');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ads');
|
||||
}
|
||||
};
|
||||
|
|
@ -33,7 +33,7 @@ class AdminMenuSeeder extends Seeder
|
|||
['title' => 'web_content', 'icon' => 'ic:outline-collections-bookmark', 'url' => '', 'order'=>3,
|
||||
'children' =>[
|
||||
['title'=>'articles', 'icon'=>'ic:outline-article','url'=>'/articles', 'order'=>1],
|
||||
['title'=>'banners', 'icon'=>'lets-icons:img-box','url'=>'/banners', 'order'=>2],
|
||||
['title'=>'ads', 'icon'=>'lets-icons:img-box','url'=>'/ads', 'order'=>2],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ return [
|
|||
'cancel' => '取消',
|
||||
'please_login' => '请先登录',
|
||||
'unauthorized' => '无权访问',
|
||||
'id' => '主键',
|
||||
'id' => 'ID',
|
||||
|
||||
'components' => [
|
||||
'content' => '内容',
|
||||
|
|
@ -274,7 +274,7 @@ return [
|
|||
'parent_keyword' => '父级关键字',
|
||||
],
|
||||
'articles' => [
|
||||
'id' => '主键',
|
||||
'id' => 'ID',
|
||||
'title' => '标题',
|
||||
'content' => '内容',
|
||||
'cover' =>'封面',
|
||||
|
|
@ -288,5 +288,24 @@ return [
|
|||
'sort' => '排序',
|
||||
'appendixes' => '附件',
|
||||
'published_at_remark' => '*若未设置发布时间且操作设置为显示,则默认生成发布时间'
|
||||
],
|
||||
'ads' => [
|
||||
'id' => 'ID',
|
||||
'address' => '位置',
|
||||
'resource' =>'内容',
|
||||
'published_at' => '定时发布',
|
||||
'published_at_g' => '发布时间',
|
||||
'is_enable' => '显示',
|
||||
'remark' => '备注',
|
||||
'sort' => '排序',
|
||||
'published_at_remark' => '*若未设置发布时间且操作设置为显示,则默认生成发布时间',
|
||||
'jump_type' => '跳转类型',
|
||||
'jump_config'=>'跳转配置',
|
||||
'jump_config_arr'=>[
|
||||
'web_link' => '网页地址',
|
||||
'app_link' => '应用路径',
|
||||
'mini_id' => '小程序ID',
|
||||
'mini_link'=> '小程序路径'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@ return [
|
|||
'keywords' => '数据字典',
|
||||
'web_content' => '内容管理',
|
||||
'articles' => '文章管理',
|
||||
'banners' => '广告管理',
|
||||
'ads' => '广告管理',
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue