add object-type, group, and fields without group

master
Andrey 2024-03-20 14:50:02 +03:00
parent 2e5fce6c23
commit 952fd67cba
18 changed files with 697 additions and 25 deletions

View File

@ -14,6 +14,10 @@ import {WidjetModule} from "@app/_modules/widjet/widjet.module";
import {AdministrateLicenceComponent} from "@app/_modules/administration/licence/administrate-licence.component";
import {AdministrateUsersComponent} from "@app/_modules/administration/users/administrate-users.component";
import {UsersModule} from "@app/_modules/users/users.module";
import { ObjectTypeComponent } from './object-type/object-type.component';
import { ObjectTypeListComponent } from '@app/_modules/administration/object-type/list/object-type-list.component';
import { ObjectTypeListItemComponent } from '@app/_modules/administration/object-type/list/item/object-type-list-item.component';
import {SortablejsModule} from "@dustfoundation/ngx-sortablejs";
type PathMatch = "full" | "prefix" | undefined;
const routes = [
@ -31,6 +35,7 @@ const routes = [
PagesModule,
WidjetModule,
UsersModule,
SortablejsModule,
],
declarations: [
AdministrationPageComponent,
@ -38,7 +43,10 @@ const routes = [
AdministrateCommitteeComponent,
AdministrateSitePagesComponent,
AdministrateLicenceComponent,
AdministrateUsersComponent
AdministrateUsersComponent,
ObjectTypeComponent,
ObjectTypeListComponent,
ObjectTypeListItemComponent
],
exports: [
RouterModule

View File

@ -0,0 +1,94 @@
<div class="item" [class.hidden]="isHidden" [class.home]="!parent" (click)="touched = true">
<div class="bar">
<div class="left">
<drop-down ico="chevron_right_24" [angle]="[0,90]" *ngIf="hasChildren" (toggle)="active=$event"></drop-down>
</div>
<div class="mid" [class.deleted]="isDeleted">
<div class="info">
<div class="logo" *ngIf="parent">
<!-- <ico ico="web_page_24" color="#FFF"></ico>-->
</div>
<div class="logo" *ngIf="!parent">
<!-- <ico ico="home_24" color="#FFF"></ico>-->
</div>
<div class="name">
<div>
<a [routerLink]="" target="_blank">{{objectType.title}}</a>
</div>
<div *ngIf="!isDeleted" title="Добавить группу" (click)="addGroup()">
<ico ico="webpage_plus_24" class="page-control"></ico>
</div>
<div *ngFor="let group of groups">
<p>Группа объектов: <a [routerLink]="" target="_blank">{{group.name}}</a></p>
<div class="right">
<div *ngIf="!isDeleted" title="Редактировать группу" (click)="editGroup()">
<ico ico="edit_24" class="page-control"></ico>
</div>
<div *ngIf="!isDeleted" title="Удалить группу" (click)="deleteGroup()">
<ico ico="visibility_off_24" class="page-control"></ico>
</div>
</div>
<table>
<tr>
<th style="border: #1F1F1F solid 2px">title</th>
<th style="border: #1F1F1F solid 2px">name</th>
<th style="border: #1F1F1F solid 2px">type</th>
<th style="border: #1F1F1F solid 2px">required</th>
<th style="border: #1F1F1F solid 2px">multiple</th>
<th style="border: #1F1F1F solid 2px">related</th>
<th>
<div *ngIf="!isDeleted" title="Добавить объект" (click)="addField()">
<ico ico="webpage_plus_24" class="page-control"></ico>
</div>
</th>
</tr>
<tr *ngFor="let field of fields">
<td style="border: #1F1F1F solid 1px">{{field.title}}</td>
<td style="border: #1F1F1F solid 1px">{{field.name}}</td>
<td style="border: #1F1F1F solid 1px">{{field.type}}</td>
<td style="border: #1F1F1F solid 1px">{{field.required}}</td>
<td style="border: #1F1F1F solid 1px">{{field.multiple}}</td>
<td style="border: #1F1F1F solid 1px">{{field.related}}</td>
<td>
<div *ngIf="!isDeleted" title="Редактировать объект" (click)="editField()">
<ico ico="edit_24" class="page-control"></ico>
</div>
</td>
<td>
<div *ngIf="!isDeleted" title="Удалить объект" (click)="deleteField()">
<ico ico="visibility_off_24" class="page-control"></ico>
</div>
</td>
</tr>
</table>
</div>
<!-- <a [routerLink]="" target="_blank" *ngIf="page.h1">{{page.h1}}</a>-->
</div>
</div>
</div>
<div class="right">
<div *ngIf="parent && !isDeleted" title="Копировать" (click)="clone()">
<ico ico="copy_24" class="page-control"></ico>
</div>
<div *ngIf="!isDeleted" title="Добавить типовой объект" (click)="add()">
<ico ico="webpage_plus_24" class="page-control"></ico>
</div>
<div *ngIf="!isDeleted" title="Редактировать типовой объект" (click)="edit()">
<ico ico="edit_24" class="page-control"></ico>
</div>
<div *ngIf="isDeleted" title="Восстановить типой объект" (click)="restore()">
<ico ico="visibility_on_24" class="page-control"></ico>
</div>
<div *ngIf="isDeleted" title="Удалить безвозвратно" (click)="delete()">
<ico ico="delete_24" class="page-control"></ico>
</div>
<div *ngIf="!isDeleted" title="Удалить типовой объект" (click)="delete()">
<ico ico="visibility_off_24" class="page-control"></ico>
</div>
</div>
</div>
<div class="items" *ngIf="active">
<object-type-list [parent]="objectType"></object-type-list>
</div>
</div>

View File

@ -0,0 +1,186 @@
import {Component, Input} from '@angular/core';
import {concat, Subscription} from "rxjs";
import {FormsService, ListsService} from "@app/_services";
import {DialogService} from "@app/_services/dialog.service";
import {ObjectTypesService} from "@app/_services/object-types.service";
import {group} from "@angular/animations";
import {concatAll} from "rxjs/operators";
@Component({
selector: 'object-type-list-item',
templateUrl: './object-type-list-item.component.html',
styleUrls: ['./object-type-list-item.component.scss']
})
export class ObjectTypeListItemComponent {
@Input() objectType: any;
@Input() FieldsGroup: any;
@Input() parent: any;
public active = false;
public touched = false;
public subscription: Subscription;
constructor(
public objectTypesService: ObjectTypesService,
private formsService: FormsService,
private listsService: ListsService,
private dialog: DialogService) {
}
ngOnInit() {
this.subscription = this.listsService.controls(this.listId).subscribe(res => {
if (this.touched) this.fetch();
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
get listId() {
return this.objectType.id;
}
get parentListId() {
return this.parent?.id || 'object-type-root';
}
get logo() {
return this.objectType?.logo?.data.links?.full;
}
get noLogoLetters() {
return this.objectType.name.replace(' ', '').slice(0, 2);
}
get children() {
return this.objectType.children?.data;
}
get groups() {
return this.objectType.groups?.data;
}
get fields() {
let allFields = [];
this.groups.forEach(function (groups){
groups.fields.data.forEach(function (fields){
allFields.push(fields);
})
});
return allFields;
}
get isDeleted() {
return !!this.objectType.deletedAt;
}
get isHidden() {
return this.isDeleted && !this.showDeleted;
}
get showDeleted() {
return this.objectTypesService.showDeleted;
}
get hasChildren() {
return this.showDeleted ? this.children?.length : this.children?.filter(objectType => {return !objectType.deletedAt}).length;
}
fetch() {
let include = ['children.children', 'groups'];
this.objectTypesService.show(this.objectType.id, {include: include.join(','), withTrashed: true}).subscribe(res => {
this.objectType = res.data;
});
}
clone() {
this.dialog.confirm(`Копировать страницу ${this.objectType.name}?`).subscribe(
resp=>{
if (resp) {
this.dialog.waiting('Выполняется копирование данных. Подождите, пожалуйста')
this.objectTypesService.clone(this.objectType.id, {recursive: true}).subscribe(res => {
this.dialog.waiting(null);
this.listsService.refresh(this.parentListId);
this.refresh();
});
}
}
)
}
add() {
this.formsService.createModel('objectType', {extraProps: {parent: this.objectType.id}}, this.listId);
this.active = true;
this.refresh()
}
edit() {
this.formsService.editModel('objectType', this.objectType.id, null, this.listId);
}
delete() {
this.dialog.confirm(`Удалить страницу ${this.objectType.name}?`).subscribe(
resp=>{
if (resp) this.objectTypesService.delete(this.objectType.id).subscribe(res => {
this.listsService.refresh(this.parentListId);
this.refresh();
});
}
)
}
restore() {
this.dialog.confirm(`Восстановить страницу ${this.objectType.name}?`).subscribe(
resp=>{
if (resp) this.objectTypesService.restore(this.objectType.id, {}).subscribe(res => {
this.listsService.refresh(this.parentListId);
this.refresh();
});
}
)
}
private refresh(){
this.objectTypesService.root({include:'children'}).subscribe(
res => { this.objectTypesService.rootObjectTypes = res }
)
}
toggle() {
this.active = !this.active;
}
addGroup() {
this.formsService.createModel('fieldsGroup',{extraProps: {object_type: this.objectType.id}});
this.active = true;
this.refresh()
}
editGroup() {
this.formsService.editModel('fieldsGroup', this.objectType.groups.id, null, this.listId);
}
deleteGroup() {
this.dialog.confirm(`Удалить страницу ${this.objectType.groups.name}?`).subscribe(
resp=>{
if (resp) this.objectTypesService.delete(this.objectType.groups.id).subscribe(res => {
this.listsService.refresh(this.parentListId);
this.refresh();
});
}
)
}
addField() {
this.formsService.createModel('field'/*,{extraProps: {object_type: this.objectType.id}}*/);
this.refresh()
}
editField() {
}
deleteField() {
}
}

View File

@ -0,0 +1,3 @@
<div class="items" [sortablejs]="objectTypes" [sortablejsOptions]="parent ? optionsObjectTypes : optionsLocales">
<object-type-list-item [id]="objectType.id" [objectType]="objectType" [parent]="parent" *ngFor="let objectType of objectTypes"></object-type-list-item>
</div>

View File

@ -0,0 +1,86 @@
import {Component, Input} from '@angular/core';
import {Subscription} from "rxjs";
import {SortableOptions} from "sortablejs";
import {Router} from "@angular/router";
import {ObjectTypesService} from "@app/_services/object-types.service";
import {FormsService, ListsService} from "@app/_services";
@Component({
selector: 'object-type-list',
templateUrl: './object-type-list.component.html',
styleUrls: ['./object-type-list.component.scss']
})
export class ObjectTypeListComponent {
@Input() parent: any;
public objectTypes = <any>[];
subscription: Subscription;
public optionsObjectTypes: SortableOptions = {
group: 'object-types',
handle: '.logo',
onUpdate: (event: any) => {this.move(event)},
onAdd: (event: any) => {this.move(event)}
};
public optionsLocales: SortableOptions = {
group: 'site-locales',
handle: '.logo',
onUpdate: (event: any) => {this.move(event)},
onAdd: (event: any) => {this.move(event)}
};
constructor(private router: Router, private objectTypesService: ObjectTypesService, private listsService: ListsService, private formsService: FormsService) {
}
get listId() {
return this.parent?.id || 'object-types-root';
}
ngOnInit() {
this.subscription = this.listsService.controls(this.listId).subscribe(res => {
this.fetch();
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
fetch() {
this.parent ? this.fetchSubObjectTypes() : this.fetchRootObjectTypes();
}
fetchRootObjectTypes() {
let include = [
'children.children',
'groups',
'groups.fields',
'children.groups.fields'
];
this.objectTypesService.root({include: include.join(','), withTrashed: true}).subscribe(res => {
this.objectTypes = res.data;
});
}
fetchSubObjectTypes() {
let include = ['children.children', 'groups', 'children.groups.fields'];
this.objectTypesService.show(this.parent.id, {include: include.join(','), withTrashed: true}).subscribe(res => {
this.objectTypes = res.data?.children?.data;
});
}
add() {
this.formsService.createModel('ObjectType', null, this.listId);
}
move(event: any) {
this.objectTypesService.move(event.item.id, {parent: this.parent?.id, ord: event.newIndex}).subscribe(res => {
this.listsService.refresh(this.parent?.id);
this.objectTypesService.root({include:'children'}).subscribe(
res => { this.objectTypesService.rootObjectTypes = res }
)
});
}
}

View File

@ -0,0 +1,19 @@
<ng-container *ngIf="licenceService.isActive">
<div class="site-admin-control">
<div class="site-admin-control-toggle" (click)="toggle()">
Показать скрытые
<switch [val]="showDeleted"></switch>
</div>
<div class="site-admin-page-block">
<div class="site-admin-company">
<!-- <ico ico="cloud_24" class="page-lable" color="#FFF"></ico>-->
<span>Типовые объекты</span>
</div>
<ico ico="home_plus_24" class="page-control" (click)="addLocale()"></ico>
</div>
</div>
<object-type-list></object-type-list>
</ng-container>
<ng-container *ngIf="!licenceService.isActive">
<p>Лицензия не активна. Перейдите в раздел <a routerLink="/administrate/licence">Данные о лицензии</a></p>
</ng-container>

View File

@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import {ObjectTypesService} from "@app/_services/object-types.service";
import {FormsService} from "@app/_services";
import {LicenceService} from "@app/_services/licence.service";
import {DialogService} from "@app/_services/dialog.service";
@Component({
selector: 'object-type',
templateUrl: './object-type.component.html',
styleUrls: ['./object-type.component.scss']
})
export class ObjectTypeComponent {
constructor(private objectTypesService: ObjectTypesService, private formsService: FormsService, public licenceService: LicenceService, private dialog: DialogService) {
}
get isLicenceActive() {
return this.licenceService.isActive;
}
get isMultilang() {
return this.isLicenceActive && this.licenceService.hasOption('multilang');
}
get showDeleted() {
return this.objectTypesService.showDeleted;
}
ngOnInit() {
}
ngOnDestroy() {
this.objectTypesService.showDeleted = false;
}
addLocale() {
if (this.isMultilang) this.formsService.createModel('objectType', null, 'object-types-root');
else this.dialog.alert('Данная опция не входит в Вашу лицензию. Для включения опции обратитесь в службу поддержки НИР (+7 499 490 04 65, help@nirgroup.ru)');
}
toggle() {
this.objectTypesService.showDeleted = !this.objectTypesService.showDeleted;
}
}

View File

@ -6,7 +6,7 @@
<div class="tabs default">
<div *ngFor="let tab of tabs" >
<button type="button"[class.active]="tab.active" *ngIf="showTab(tab)" routerLink="/administrate/{{tab.name}}">{{tab.title}}</button>
</div>
</div>
</div>
<div [ngSwitch]="tab.name">
<administrate-company *ngSwitchCase="'company'" [companyId]="'main'"></administrate-company>
@ -14,6 +14,7 @@
<administrate-site-pages *ngSwitchCase="'site-pages'"></administrate-site-pages>
<administrate-licence *ngSwitchCase="'licence'"></administrate-licence>
<administrate-users *ngSwitchCase="'users'"></administrate-users>
<object-type *ngSwitchCase="'object-type'"></object-type>
<p *ngSwitchDefault>Страница не найдена</p>
</div>
</div>

View File

@ -36,9 +36,10 @@ export class AdministrationPageComponent {
//this.tabs = [{name: 'company', title: 'Структура ФАУ «ФЦС»'}, {name: 'committee', title: 'Структура ТК 465'}];
//if (this.authService.isSuperAdmin) this.tabs.push({name: 'site-pages', title: 'Структура сайта'});
this.tabs = [
{name: 'site-pages', title: 'Структура сайта', access: ["admin", "editor"]},
{name: 'users', title: 'Пользователи', access: ["admin"]},
{name: 'licence', title: 'Данные о лицензии', access: ["admin"]}];
{name: 'site-pages', title: 'Структура сайта', access: ["admin", "editor"]},
{name: 'users', title: 'Пользователи', access: ["admin"]},
{name: 'licence', title: 'Данные о лицензии', access: ["admin"]},
{name: 'object-type', title: 'Объекты сайта', access: ["admin"]}];
this.switchTab(this.route.snapshot.paramMap.get('tab'));
}

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FieldsGroupService {
constructor() { }
}

View File

@ -0,0 +1,108 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '@environments/environment';
import {Observable, BehaviorSubject} from "rxjs";
import {LicenceService} from "@app/_services/licence.service";
import { DialogService } from '@app/_services/dialog.service';
@Injectable({providedIn: 'root'})
export class ObjectTypesService {
public currentObjectTypesSubject = new BehaviorSubject<any>(null);
public rootObjectTypesSubject = new BehaviorSubject<any>(null);
public metaSubject = new BehaviorSubject<any>({title: '', description: '', keywords: ''});
public editModeSubject = new BehaviorSubject<boolean>(false);
public showDeletedSubject = new BehaviorSubject<boolean>(false);
public menuSelectedLink: string;
public rootPage:any;
constructor(private http: HttpClient, private licenceService: LicenceService, private dialog: DialogService) {
//this.find('/').subscribe(res => {this.rootPages = res.data});
}
get currentObjectType() {
return this.currentObjectTypesSubject.value;
}
set currentObjectType(val: any) {
this.currentObjectTypesSubject.next(val);
this.setMetaFromPage(val);
}
// get rootObjectTypes() {
// return this.rootObjectTypesSubject.value;
// }
set rootObjectTypes(val: any) {
this.rootObjectTypesSubject.next(val);
}
// get isRtl() {
// return ['ar'].indexOf(this.rootPage?.slug) !== -1;
// }
get editMode() {
return this.editModeSubject.value;
}
set editMode(value: boolean) {
if (value) {
let error = this.licenceService.checkEditAvailability(this.currentObjectType);
if (error) {
this.dialog.alert(error);
return;
}
}
this.editModeSubject.next(value);
}
get showDeleted() {
return this.showDeletedSubject.value;
}
set showDeleted(value: boolean) {
this.showDeletedSubject.next(value);
}
root(params?: any): Observable<any> {
return this.http.get(`${environment.apiUrl}/api/object-types`, {params: params});
}
find(url: string, params?: any): Observable<any> {
if (!params) params = {};
params.url = url;
return this.http.get(`${environment.apiUrl}/api/object-types/find`, {params: params});
}
list(params?: any): Observable<any> {
return this.http.get(`${environment.apiUrl}/api/object-types`, {params: params});
}
show(id: string, params?: any): Observable<any> {
return this.http.get<any>(`${environment.apiUrl}/api/object-types/${id}`, {params: params});
}
delete(id: string): Observable<any> {
return this.http.delete(`${environment.apiUrl}/api/object-types/${id}`);
}
restore(id: string, data: any): Observable<any> {
return this.http.patch(`${environment.apiUrl}/api/pages/restore/${id}`, data);
}
deleteBackground(id: string): Observable<any> {
return this.http.delete(`${environment.apiUrl}/api/pages/background/${id}`);
}
move(id: string, data: any): Observable<any> {
return this.http.put(`${environment.apiUrl}/api/pages/move/${id}`, data);
}
clone(id: string, data: any): Observable<any> {
return this.http.put(`${environment.apiUrl}/api/pages/clone/${id}`, data);
}
setMetaFromPage(page: any) {
this.setMeta({title: page?.title || page?.name, description: page?.description || '', keywords: page?.keywords || ''});
}
setMeta(meta: any) {
this.metaSubject.next(meta);
}
}

View File

@ -5,8 +5,8 @@
export const environment = {
production: false,
apiUrl: 'http://api.nircms.lc',
clientId: 4,
clientSecret: 'KmGnhqVbEi3wlzkEyXi1JeNg9FtswdOdKQHpOcAu',
clientId: 2,
clientSecret: 'iyTS47vCZHgMxokKToa1HhOgfvFrwlOu7WkmJ3cQ',
project: null,
licence: 'POUFLO4YW7SU',
defaultLocale: 'ru'

View File

@ -5,8 +5,8 @@
export const environment = {
production: false,
apiUrl: 'http://api.nircms.lc',
clientId: 4,
clientSecret: 'KmGnhqVbEi3wlzkEyXi1JeNg9FtswdOdKQHpOcAu',
clientId: 2,
clientSecret: 'iyTS47vCZHgMxokKToa1HhOgfvFrwlOu7WkmJ3cQ',
project: 'vniigaz-v2',
licence: 'POUFLO4YW7SU',
defaultLocale: 'ru'

View File

@ -10,14 +10,13 @@
<a href="https://vniigaz.gazprom.ru/" target="_blank">Учреждено в 1998</a>
</h4>
</div>
</div>
</div>
<div class="space jumbotron-edit-field" *ngIf="editable" dir="ltr">
<div class="layout-corral jumbotron-edit-field-row">
<div class="switch-host" (click)="toggleEditMode()">
Режим редактирования
<switch [val]="editMode"></switch>
</div>
<switch [val]="editMode"></switch>
</div>
</div>
</div>

View File

@ -37,7 +37,7 @@
}
.site-admin-company{
font-weight: 700;
}
}
}
ico.page-lable{
background-color: var(--prime);
@ -68,9 +68,9 @@ administrate-site-pages{
}
}
}
ico.page-control{
cursor: pointer;
cursor: pointer;
svg{
color:var(--second);
&:hover{
@ -79,7 +79,7 @@ ico.page-control{
}
}
pages-tree{
.item:not(.home){
.bar{
padding-left: 50px;
@ -175,15 +175,130 @@ pages-tree{
}
}
}
}
object-type-list{
.item:not(.home){
.bar{
padding-left: 50px;
}
.item .bar{
padding-left: 100px;
}
.item .item .bar{
padding-left: 150px;
}
.item .item .item .bar{
padding-left: 200px;
}
.item .item .item .item .bar{
padding-left: 250px;
}
.item .item .item .item .item .bar{
padding-left: 300px;
}
}
.item {
flex-direction: column;
align-items: stretch;
.bar {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 16px 0;
border-bottom: #E0E0E0 solid 1px;
.left {
flex-shrink: 0;
width: 40px;
height: 24px;
cursor: pointer;
}
.mid {
flex-grow: 1;
padding: 0 16px;
.info {
display: flex;
flex-direction: row;
align-items: center;
.logo {
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-shrink: 0;
width: 40px;
height: 40px;
margin-right: 16px;
border-radius: 100px;
background-color: var(--prime);
color: #ffffff;
cursor: move;
&:hover{
background-color: var(--prime-act);
}
span{
display: none;
}
}
.name {
p {
margin: 0;
a {
color: var(--grey-7);
}
&.sub {
font-size: 0.875rem;
color: #7f7f7f;
}
}
}
}
}
.right {
display: flex;
flex-direction: row;
align-items: flex-start;
flex-shrink: 1;
gap: 8px;
}
}
}
@media screen and (max-width: 1330px) {
.item {
.bar {
.mid {
padding: 0 12px;
.info .logo {
display: none;
}
}
.right {
display: none;
}
}
.items {
padding-left: 16px;
}
&.company {
.items {
padding-left: 40px;
}
}
}
}
}
administrate-licence{
font-family: PT Sans;
@ -193,7 +308,7 @@ administrate-licence{
line-height: 24px;
color: var(--dark);
.default{
.caption{
color: var(--second);
}
@ -242,7 +357,7 @@ users-list{
flex-shrink: 0;
}
}
users-list-item {
display: flex;
align-items: center;
@ -316,4 +431,4 @@ users-list{
}
}
}
}