This commit is contained in:
f4ilji
2025-03-14 14:35:49 +05:00
parent 7a7ecf7b0d
commit 17518ceed2
21 changed files with 579 additions and 62 deletions
+4
View File
@@ -179,6 +179,10 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/spatie/robots-txt" />
<excludeFolder url="file://$MODULE_DIR$/vendor/spatie/temporary-directory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/predis/predis" />
<excludeFolder url="file://$MODULE_DIR$/vendor/yepsua/filament-range-field" />
<excludeFolder url="file://$MODULE_DIR$/vendor/tomatophp/console-helpers" />
<excludeFolder url="file://$MODULE_DIR$/vendor/tomatophp/filament-icons" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
Generated
+15
View File
@@ -190,6 +190,21 @@
<path value="$PROJECT_DIR$/vendor/spatie/laravel-sitemap" />
<path value="$PROJECT_DIR$/vendor/spatie/browsershot" />
<path value="$PROJECT_DIR$/vendor/spatie/crawler" />
<path value="$PROJECT_DIR$/vendor/spatie/laravel-signal-aware-command" />
<path value="$PROJECT_DIR$/vendor/spatie/db-dumper" />
<path value="$PROJECT_DIR$/vendor/spatie/laravel-backup" />
<path value="$PROJECT_DIR$/vendor/calebporzio/sushi" />
<path value="$PROJECT_DIR$/vendor/shuvroroy/filament-spatie-laravel-backup" />
<path value="$PROJECT_DIR$/vendor/askerakbar/checkpoint" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/filament/spatie-laravel-settings-plugin" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/spatie/laravel-settings" />
<path value="$PROJECT_DIR$/vendor/predis/predis" />
<path value="$PROJECT_DIR$/vendor/yepsua/filament-range-field" />
<path value="$PROJECT_DIR$/vendor/tomatophp/filament-icons" />
<path value="$PROJECT_DIR$/vendor/tomatophp/console-helpers" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.1" />
+214
View File
@@ -0,0 +1,214 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\SlideResource\Pages;
use App\Models\Event;
use App\Models\Page;
use App\Models\Post;
use App\Models\Slide;
use Filament\Forms;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\Carbon;
class SlideResource extends Resource
{
protected static ?string $model = Slide::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('Быстрая настройка слайда')->schema([
Forms\Components\Grid::make()->schema([
Forms\Components\Select::make('model_select')
->name('')
->label('Выбор типа данных')
->options([
'Post' => 'Новость',
'Page' => 'Страница',
'Event' => 'Мероприятие',
'Custom' => 'Кастомная ссылка',
])
->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set, Forms\Get $get) {
if ($get('model_select') === 'Custom') {
$set('model', null);
$set('title', null);
$set('content', null);
$set('link', null);
};
})->live(onBlur: true),
Forms\Components\Select::make('model')
->label('Поиск данных')
->name('')
->live(onBlur: true)
->searchable()
->live(onBlur: true)
->afterStateUpdated(function (string $operation, string|null $state, Forms\Set $set, Forms\Get $get) {
if ($get('model_select') === 'Post') {
$post = Post::find($state);
$set('title', $post->title);
$relativeUrl = parse_url(route('client.post.show', $post->slug), PHP_URL_PATH);
$set('link', $relativeUrl);
};
if ($get('model_select') === 'Page') {
$page = Page::find($state);
$set('title', $page->title);
$set('link', $page->path);
};
if ($get('model_select') === 'Event') {
$event = Event::find($state);
$set('title', $event->title);
$relativeUrl = parse_url(route('client.event.show', $event->slug), PHP_URL_PATH);
$set('link', $relativeUrl);
};
})
->options(function (Forms\Get $get) {
if ($get('model_select') === 'Post') {
return Post::where('status', '=', 'published')->pluck('title', 'id');
};
if ($get('model_select') === 'Page') {
return Page::where('title', '!=', null)->pluck('title', 'id');
};
if ($get('model_select') === 'Event') {
return Event::all()->pluck('title', 'id');
};
if ($get('model_select') === 'Custom') {
return [];
};
}),
]),
]),
Forms\Components\Section::make('Слайдер')->schema([
Forms\Components\Section::make('Информация слайда')->schema([
Forms\Components\TextInput::make('title')
->label('Заголовок слайда'),
Forms\Components\Textarea::make('content')
->label('Текст слайда'),
Forms\Components\Grid::make()->schema([
ColorPicker::make('color_theme')
->label('Цвет текста')
->default('#ffffff')
->required(),
Forms\Components\ToggleButtons::make('settings.text_position')
->options([
'left' => 'Текст слева',
'center' => 'Текст по середине',
'right' => 'Текст справа'
])
->inline()->default('left')->grouped()
->label('Позиция текста на слайде'),
]),
Forms\Components\Grid::make()->schema([
Toggle::make('active_button')
->label('Использовать кнопку для ссылки (Ссылка будет открываться при нажатии на слайд)')
->inline(false)
->default(true) // Проверяем, есть ли текст в link_text
->live()
->afterStateHydrated(function (Toggle $component, $state, $get) {
if ($state === null && !empty($get('settings.link_text'))) {
$component->state(true); // Устанавливаем значение по умолчанию
}
})
->dehydrated(false),
Forms\Components\TextInput::make('settings.link_text')
->default('Читать')
->label('Текст кнопки')
->disabled(fn (Forms\Get $get) => !$get('active_button'))
]),
]),
Forms\Components\Section::make('Изображение')->schema([
FileUpload::make('image.url')
->label('Изображение')
->image()
->optimize('webp')
->resize(50)
->disk('public')
->directory('images')
->imageEditor()
->required(),
ToggleButtons::make('image.shading')->inline()->grouped()->label('Уровень затемнения изображения')->options([
'1' => 'Без затемнения',
'0.7' => 'Слабое затемнение',
'0.5' => 'Среднее затемнение',
'0.3' => 'Сильное затемнение',
]),
]),
Forms\Components\Section::make('Общая часть')->schema([
Forms\Components\Grid::make()->schema([
DateTimePicker::make('start_time')
->label('Слайд начинается с')
->native()
->displayFormat('d/m/Y')
->default(Carbon::now())
->maxDate(Carbon::now()->addWeeks(2)),
DateTimePicker::make('end_time')
->label('Слайд действует до')
->native()
->displayFormat('d/m/Y')
->default(Carbon::now()->addWeeks(2))
->minDate(Carbon::now())
->maxDate(Carbon::now()->addMonth()),
]),
Forms\Components\TextInput::make('link')
->label('Ссылка кнопки')
->required(),
]),
Toggle::make('is_active')->default(true)->label('Активный слайдер')->inline(false),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->reorderable('sort')
->defaultSort('sort')
->columns([
Tables\Columns\TextColumn::make('title'),
Tables\Columns\ToggleColumn::make('is_active')
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSlides::route('/'),
'create' => Pages\CreateSlide::route('/create'),
'edit' => Pages\EditSlide::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SlideResource\Pages;
use App\Filament\Resources\SlideResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateSlide extends CreateRecord
{
protected static string $resource = SlideResource::class;
protected function mutateFormDataBeforeCreate(array $data): array
{
unset($data['model_select'], $data['model']);
return $data;
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Filament\Resources\SlideResource\Pages;
use App\Filament\Resources\SlideResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditSlide extends EditRecord
{
protected static string $resource = SlideResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
protected function mutateFormDataBeforeSave(array $data): array
{
unset($data['model_select'], $data['model']);
return $data;
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SlideResource\Pages;
use App\Filament\Resources\SlideResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListSlides extends ListRecords
{
protected static string $resource = SlideResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\SliderResource\Pages;
use App\Filament\Resources\SliderResource\RelationManagers;
use App\Models\Slider;
use Filament\Forms;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SliderResource extends Resource
{
protected static ?string $model = Slider::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make()->schema([
Forms\Components\TextInput::make('title')
->label('Заголовок слайдера'),
Toggle::make('is_active')->default(true)->label('Активный слайдер')->inline(false),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
//
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSliders::route('/'),
'create' => Pages\CreateSlider::route('/create'),
'edit' => Pages\EditSlider::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\SliderResource\Pages;
use App\Filament\Resources\SliderResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateSlider extends CreateRecord
{
protected static string $resource = SliderResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SliderResource\Pages;
use App\Filament\Resources\SliderResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditSlider extends EditRecord
{
protected static string $resource = SliderResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SliderResource\Pages;
use App\Filament\Resources\SliderResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListSliders extends ListRecords
{
protected static string $resource = SliderResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
+13
View File
@@ -4,8 +4,21 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Slide extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'settings' => 'array',
'image' => 'array',
];
public function slider(): BelongsTo
{
return $this->belongsTo(Slider::class);
}
}
+4 -6
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Slider extends Model
{
@@ -11,13 +12,10 @@ class Slider extends Model
protected $guarded = false;
protected $casts = [
'settings' => 'array',
'image' => 'array',
];
public function slidable()
public function slides(): HasMany
{
return $this->morphTo();
return $this->hasMany(Slide::class);
}
}
@@ -0,0 +1,29 @@
<?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('sliders', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->boolean('is_active');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sliders');
}
};
@@ -0,0 +1,36 @@
<?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('slides', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('image');
$table->string('link');
$table->string('link_text');
$table->string('color_theme');
$table->boolean('is_active')->default(true);
$table->integer('sort')->nullable();
$table->foreignId('slider_id')->references('id')->on('sliders')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('slides');
}
};
+18 -1
View File
@@ -26,7 +26,8 @@
"js-cookie": "^3.0.5",
"preline": "^1.9.0",
"slugify": "^1.6.6",
"vue3-yandex-smartcaptcha": "^1.0.0"
"vue3-yandex-smartcaptcha": "^1.0.0",
"vuex": "^4.1.0"
},
"devDependencies": {
"@inertiajs/vue3": "^1.0.0",
@@ -1037,6 +1038,11 @@
"@vue/shared": "3.5.11"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/reactivity": {
"version": "3.5.11",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz",
@@ -6218,6 +6224,17 @@
"vue": "^3.3.8"
}
},
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+2 -1
View File
@@ -45,7 +45,8 @@
"js-cookie": "^3.0.5",
"preline": "^1.9.0",
"slugify": "^1.6.6",
"vue3-yandex-smartcaptcha": "^1.0.0"
"vue3-yandex-smartcaptcha": "^1.0.0",
"vuex": "^4.1.0"
},
"name": "ntspi-reborn",
"description": "<p align=\"center\"><a href=\"https://laravel.com\" target=\"_blank\"><img src=\"https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg\" width=\"400\" alt=\"Laravel Logo\"></a></p>",
+13 -7
View File
@@ -69,6 +69,8 @@
</template>
<script>
import { mapActions } from "vuex";
export default {
name: 'slider',
props: {
@@ -82,7 +84,6 @@ export default {
currentIndex: 0,
intervalId: null,
slideDuration: 5000,
}
},
computed: {
@@ -90,12 +91,13 @@ export default {
return this.slidersCarousel && this.slidersCarousel.data ? this.slidersCarousel.data.length : 0;
},
progressBarStep() {
const slides = this.totalSlides
const step = 100 / slides
return (this.currentIndex + 1) * step
const slides = this.totalSlides;
const step = 100 / slides;
return (this.currentIndex + 1) * step;
}
},
methods: {
...mapActions(['updateLastSlider']),
next() {
if (this.totalSlides > 0) {
this.currentIndex = (this.currentIndex + 1) % this.totalSlides;
@@ -109,8 +111,8 @@ export default {
}
},
startTimer() {
this.stopTimer(); // Останавливаем предыдущий таймер, если он есть
this.intervalId = setInterval(this.next, this.slideDuration); // Запускаем новый таймер
this.stopTimer();
this.intervalId = setInterval(this.next, this.slideDuration);
},
stopTimer() {
clearInterval(this.intervalId);
@@ -121,7 +123,11 @@ export default {
}
},
mounted() {
this.$emit('slider-mounted', this.$refs.sliderRef);
// Передаем данные в Vuex после монтирования компонента
this.updateLastSlider({
url: this.$page.props.ziggy.location,
bottom: this.$refs.sliderRef.getBoundingClientRect().bottom, // Получаем актуальное значение bottom
});
this.startTimer();
},
beforeUnmount() {
+22 -37
View File
@@ -47,7 +47,6 @@
<MobileNavbar v-if="sections" :sections="sections" />
<SearchModal open_id="open-search-modal" />
</template>
@@ -55,14 +54,13 @@
<script>
import {Link} from "@inertiajs/vue3";
import axios from "axios";
import ClientGlobalSearch from "@/Components/ClientGlobalSearch.vue";
import * as isvek from "bvi"
import BaseIcon from "@/Components/BaseComponents/BaseIcon.vue";
import MobileNavbar from "@/Navbars/MobileNavbar.vue";
import SearchModal from "@/Components/Modals/SearchModal.vue";
import DesktopNavBar from "@/Navbars/DesktopNavBar.vue";
import {mapGetters} from "vuex";
export default {
@@ -87,7 +85,7 @@ export default {
return {
scrollPosition: 0,
headerFilter: false,
underSliderHeader: this.sliderRef,
underSliderHeader: true,
bvi: null,
isActiveBvi: null,
logos: {
@@ -96,36 +94,23 @@ export default {
},
}
},
methods: {
isSameRoute(route) {
if (route === this.$page.props.ziggy.location) {
return true;
}
const currentLocation = this.$page.props.ziggy.location;
const currentUrl = this.$page.props.ziggy.url + '/' + route;
if (currentLocation === currentUrl) {
return true;
}
return false;
return currentLocation === currentUrl;
},
hasActivePage(section) {
// Проверяем, есть ли активная страница в секции или подсекции
// if (!section || !section.pages) return false; // Проверка на наличие section и pages
if (section.pages) {
return section.pages.some(page => this.isSameRoute(page.path));
}
if (section.subSections) {
return section.subSections.some(subSection => this.hasActivePage(subSection));
}
},
iniBvi() {
if (this.getCookie('bvi_panelActive') === null) {
this.bvi = new isvek.Bvi({
@@ -138,43 +123,43 @@ export default {
});
}
},
handleScroll() {
if (typeof this.sliderRef === 'object') {
const mainSlider = this.sliderRef;
this.underSliderHeader = mainSlider.getBoundingClientRect().bottom < 50
this.scrollPosition = window.pageYOffset
this.headerFilter = this.scrollPosition > 90
if (this.lastSlider) {
const slider = this.lastSlider;
this.scrollPosition = window.pageYOffset;
if (this.isSameRoute(slider.url)) {
this.underSliderHeader = slider.bottom < this.scrollPosition;
}
this.headerFilter = this.scrollPosition > 90;
} else {
this.headerFilter = true
this.headerFilter = true;
}
},
},
watch: {
lastSlider(newVal) {
if (this.isSameRoute(newVal?.url)) {
this.underSliderHeader = newVal.bottom < 0; // Пример логики
}
},
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
window.addEventListener('scroll', this.handleScroll);
if (this.getCookie('bvi_panelActive') === null) {
this.iniBvi()
this.iniBvi();
}
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('scroll', this.handleScroll);
},
computed: {
...mapGetters(['lastSlider']),
currentLogo() {
return this.underSliderHeader ? this.logos.alternate : this.logos.default;
},
},
}
</script>
<style scoped>
+2 -10
View File
@@ -1,8 +1,8 @@
<template>
<AppHead :seo="seo" />
<MainPageNavBar :sections="$page.props.navigation" :slider-ref="sliderRef" />
<ClientMainSlider @slider-mounted="setSliderRef" :slidersCarousel="sliders" />
<MainPageNavBar :sections="$page.props.navigation" />
<ClientMainSlider :slidersCarousel="sliders" />
<section class="max-w-screen-xl w-full mx-auto px-4 py-3 pb-10">
<h2 class="text-brand-primary my-6 md:mb-[50px] md:mt-[80px] text-2xl font-semibold tracking-tight text-black lg:text-[32px] lg:leading-tight bvi-show">Последние новости</h2>
@@ -195,11 +195,6 @@ export default {
return LevelEducational
}
},
data() {
return {
sliderRef: null,
}
},
props: {
posts: {
@@ -240,9 +235,6 @@ export default {
methods: {
Cookies,
setSliderRef(ref) {
this.sliderRef = ref;
},
},
+3
View File
@@ -8,6 +8,8 @@ import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
import {linksReform} from "@/mixins/LinksReform.js";
import YSmartCaptcha from 'vue3-yandex-smartcaptcha'
import cookieMixin from "@/mixins/cookieMixin.js";
import store from '@/store/index.js';
// const appName = import.meta.env.VITE_APP_NAME || 'НТГСПИ';
@@ -20,6 +22,7 @@ createInertiaApp({
setup({ el, App, props, plugin }) {
return createSSRApp({ render: () => h(App, props) })
.use(plugin)
.use(store)
.mixin(linksReform)
.mixin(cookieMixin)
.use(ZiggyVue)
+20
View File
@@ -0,0 +1,20 @@
import { createStore } from 'vuex';
export default createStore({
state: {
lastSlider: null, // Состояние для отслеживания положения слайдера
},
mutations: {
setLastSlider(state, value) {
state.lastSlider = value; // Мутация для изменения состояния
},
},
actions: {
updateLastSlider({ commit }, value) {
commit('setLastSlider', value); // Действие для обновления состояния
},
},
getters: {
lastSlider: (state) => state.lastSlider, // Геттер для получения состояния
},
});