qr-code generator

master
Andrey 2024-09-02 09:30:56 +03:00
parent be0d57a6b9
commit f588583f73
7 changed files with 555 additions and 1 deletions

View File

@ -0,0 +1,24 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../environments/environment';
import {Observable} from "rxjs";
@Injectable({providedIn: 'root'})
export class AssetsService {
constructor(private http: HttpClient) {
}
upload(file: any, params?: any): Observable<any> {
let formData = new FormData();
formData.append('file', file);
for (let param in params) {
if (params.hasOwnProperty(param)) formData.append(param, params[param]);
}
return this.http.post<any>(`${environment.apiUrl}/api/assets`, formData, {reportProgress: true, observe: 'events'});
}
bookmark(id: string, registry: string, params?: any): Observable<any> {
return this.http.patch(`${environment.apiUrl}/api/assets/bookmark/${id}`, {registry: registry}, {params: params});
}
}

View File

@ -12,6 +12,7 @@
<a href="/development/">Разработка</a>
<a href="/projects/">Проекты</a>
<a href="/contacts/">Контакты</a>
<a href routerLink="qr-code">Создай свой Qr-code</a>
<a href="tel:+74994900465" class="right tel">
<svg>
<use href="assets/ico/call_24.svg#ico"></use>
@ -66,6 +67,7 @@
<a href routerLink="development">Разработка</a>
<a href routerLink="projects">Проекты</a>
<a href routerLink="contacts">Контакты</a>
<a href routerLink="qr-code">Создай свой Qr-code</a>
</div>
<div class="footer-info">
<h4>Контакты</h4>

View File

@ -12,6 +12,7 @@ import { Tk023Component } from './projects/tk023/tk023.component';
import { Tk465Component } from './projects/tk465/tk465.component';
import { ScvgComponent } from './projects/scvg/scvg.component';
import { pipe_prodComponent } from './projects/pipe_prod/pipe_prod.component';
import { QrCodeComponent } from './qr-code/qr-code.component';
import { NotFoundComponentComponent } from './not-found-component/not-found-component.component';
@ -29,5 +30,6 @@ export const routes: Routes = [
{path:'projects/tk465', component: Tk465Component},
{path:'projects/gazprom', component: ScvgComponent},
{path:'projects/pipe_prod', component: pipe_prodComponent},
{path: 'qr-code', component: QrCodeComponent},
{path: '404', component: NotFoundComponentComponent},
];

View File

@ -0,0 +1,200 @@
<div class="block">
<div class="container-fluid">
<div class="row inner">
<div class="col-md-7 col-lg-8 block-options">
<div class="accordion" [formGroup]="QrCodeForm">
<div class="pane" ng-class="{'active':editView==='content'}" style>
<div class="pane-header" ng-click="setEditView('content')">
<div class="icon"></div>
<h3 class="title">Введите текст или загрузите изображение</h3>
<div class="plus">
<i class="fa" aria-hidden="true"></i>
</div>
<div class="minus">
<i class="fa" aria-hidden="true"></i>
</div>
</div>
<div class="pane-content">
<div class="tab-content">
<div class="">
<div class="form-group">
<label for="qrcodeUrl">Ваш текст или картинка</label>
<textarea name="text" rows="3" class="form-control" formControlName="text" #text></textarea>
<small class="form-text text-mutted">Line breaks are allowed</small>
</div>
<div class="area" [class.hover]="dragOver">
<input #fileInput type="file" [id]="field.name" (change)="onFileInput($event)" />
<p>Перетащите сюда или <span (click)="fileInput.click()">выберите файл</span> в формате JPEG или PNG общим объемом не более 10 Мбайт.</p>
<div class="indicator" *ngIf="upload.file">
<div class="label">Загружается файл {{upload.file?.name}}</div>
<div class="progress"><div class="fill" [style.width]="upload.progress + '%'"></div></div>
</div>
</div>
<div class="values" *ngIf="asset">
<div class="item">
<div class="preview"><img [src]="asset.links?.thumb" alt="" /></div>
<button type="button" class="btn icon remove" (click)="clear()"></button>
</div>
</div>
</div>
<div class="values" *ngIf="asset">
<div class="item">
<div class="preview"><img [src]="asset.links?.thumb" alt="" /></div>
<button type="button" class="btn icon remove" (click)="clear()"></button>
</div>
</div>
</div>
</div>
</div>
<div class="pane" ng-class="{'active':editView==='colors'}" style>
<div class="pane-header" ng-click="setEditView('colors')">
<div class="icon"></div>
<h3 class="title">Выберите цвет</h3>
<div class="plus">
<i class="fa" aria-hidden="true"></i>
</div>
<div class="minus">
<i class="fa" aria-hidden="true"></i>
</div>
</div>
<div class="pane-content overflow-visible color-panel">
<label>Основной цвет</label>
<div class="color-group color-group-body">
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input ng-valid ng-not-empty ng dirty ng-valid-parse ng-touched" type="radio" name="color-type" value="solid" formControlName="color-type" checked>
Сплошной
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input ng-valid ng-not-empty ng dirty ng-valid-parse ng-touched" type="radio" name="color-type" value="gradient" formControlName="color-type">
Градиент
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input ng-valid ng-not-empty ng dirty ng-valid-parse ng-touched" type="checkbox" name="eyeMode" value=true style formControlName="custom-eye-color" [defaultValue]=false>
Цвет глаз
</label>
</div>
<div class="row">
<div class="col-sm-6 col-lg-4 form-group">
<input type="color" formControlName="foreground-color">
</div>
</div>
<div class="row">
<div class="col-sm-6 col-lg-4 form-group">
<input type="color" formControlName="gradient-start">
</div>
<div class="col-sm-6 col-lg-4 form-group">
<input type="color" formControlName="gradient-end">
</div>
<div class="col-sm-6 col-lg-4 form-group">
<div class="input-group">
<span>
<button>Цвета местами</button>
</span>
<select formControlName="gradient-type" id="gradient-type">
<option value="vertical">Вертикальный</option>
<option value="horizontal">Горизонтальный</option>
<option value="diagonal">Диагональ сверху вниз</option>
<option value="inverse_diagonal">Диагональ снизу вверх</option>
<option value="radial">Крулый</option>
</select>
</div>
</div>
</div>
<div>
<label>Цвет глаз</label>
<div class="row">
<div class="col-sm-6 col-lg-4 form-group">
<input type="color" formControlName="inner-eye-color">
</div>
<div class="col-sm-6 col-lg-4 form-group">
<input type="color" formControlName="outside-eye-color">
</div>
<div class="col-sm-6 col-lg-4 form-group">
<span>
<button>Цвета местами</button>
</span>
<span>
<button>Копировать основной цвет</button>
</span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-lg-4">
<label>Цвет фона</label>
<div class="form-group color-group">
<input type="color" formControlName="background-color">
</div>
</div>
</div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<div class="icon"></div>
<h3 class="title">Добавить изображение</h3>
<div class="plus">
<i class="fa" aria-hidden="true"></i>
</div>
<div class="minus">
<i class="fa" aria-hidden="true"></i>
</div>
</div>
<div class="pane-content">
<div class="area" [class.hover]="dragOver">
<input #fileInput type="file" [id]="field.name" (change)="onFileInput($event)" />
<p>Перетащите сюда или <span (click)="fileInput.click()">выберите файл</span> в формате JPEG или PNG общим объемом не более 10 Мбайт.</p>
<div class="indicator" *ngIf="upload.file">
<div class="label">Загружается файл {{upload.file?.name}}</div>
<div class="progress"><div class="fill" [style.width]="upload.progress + '%'"></div></div>
</div>
</div>
<div class="values" *ngIf="asset">
<div class="item">
<div class="preview"><img [src]="asset.links?.thumb" alt="" /></div>
<button type="button" class="btn icon remove" (click)="clear()"></button>
</div>
</div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<div class="icon"></div>
<h3 class="title">Ручной дизайн</h3>
<div class="plus">
<i class="fa" aria-hidden="true"></i>
</div>
<div class="minus">
<i class="fa" aria-hidden="true"></i>
</div>
</div>
<div class="pane-content">
<input type="radio"/>Обычный
<input type="radio"/>Скругленные края
</div>
</div>
</div>
</div>
<div class="col-md-5 col-lg-4 block-download">
<div class="preview">
<p>Созданный QR-CODE</p>
</div>
<button class="btn primary submit" (click)="onSubmit()" [disabled]="QrCodeForm.invalid" >
отправить
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,194 @@
*,::after,::before {
box-sizing: border-box
}
textarea {
overflow: auto;
resize: vertical
}
.block {
box-shadow: 0 0 20px rgba(0,0,0,.25);
position: relative;
margin: 0 auto;
border-radius: 32px;
}
.container-fluid {
width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto
}
.row {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px
}
.block .block-options {
padding: 32px
}
.block .accordion .pane {
margin-bottom: 4px
}
.block .accordion .pane .pane-header {
cursor: pointer;
-webkit-transition: all .3s;
transition: all .3s;
background: #fff;
font-size: 1.1rem;
position: relative;
font-weight: 500;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
height: 48px;
box-shadow: 0 0 6px rgba(0,0,0,.05)
}
.block .accordion .pane .pane-header .title {
padding: 0 16px;
text-transform: uppercase;
font-size: 1rem;
margin: 0;
-webkit-flex-shrink: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
white-space: nowrap;
line-height: 48px;
text-align: initial
}
.block .accordion .pane .pane-header .icon {
width: 48px;
text-align: center;
-webkit-flex-shrink: 0;
-webkit-flex-grow: 0;
flex-grow: 0;
flex-shrink: 0;
background: #f2f8fc;
line-height: 48px
}
.block .accordion .pane .pane-header .minus, .block .accordion .pane .pane-header .plus {
padding: 0 16px;
-webkit-flex-shrink: 0;
-webkit-flex-grow: 0;
flex-grow: 0;
flex-shrink: 0;
line-height: 48px;
color: rgba(111,120,127,.5);
font-size: .9rem
}
.block .accordion .pane .pane-header .minus {
display: none
}
.block .accordion .pane .pane-header:hover {
background-color: #f2f8fc;
box-shadow: none
}
.block.accordion .pane.active .pane-content {
padding: 1rem 0;
display: block;
opacity: 1;
visibility: visible;
max-height: 1400px;
overflow: visible;
-webkit-transition: all .5s ease-in-out 0s,opacity .25s ease-in-out .2s;
transition: all .5s ease-in-out 0s,opacity .25s ease-in-out .2s
}
.form-control {
display: block;
width: 100%;
padding: .375rem .75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out
}
.block .accordion .pane.active .pane-content {
padding: 1rem 0;
display: block;
opacity: 1;
visibility: visible;
max-height: 1400px;
overflow: visible;
-webkit-transition: all .5s ease-in-out 0s,opacity .25s ease-in-out .2s;
transition: all .5s ease-in-out 0s,opacity .25s ease-in-out .2s
}
.body .accordion .pane .pane-content .color-group label {
font-weight: 400
}
.block .accordion .pane .pane-content .color-group-body {
padding-bottom: 0
}
.form-check {
position: relative;
display: block;
padding-left: 1.25rem;
}
.form-check-inline {
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding-left: 0;
margin-right: .75rem;
}
.form-check-label {
cursor: pointer;
font-weight: 400;
}
.form-check-input {
vertical-align: middle;
}
.block .block-download {
background: #fff;
padding: 16px;
border-left: 1px solid rgba(0, 0, 0, .1);
border-radius: 0 5px 5px 0;
}
@media (min-width: 768px) {
.block .inner {
display:flex;
align-items: stretch
}
}
.fa {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QrCodeComponent } from './qr-code.component';
describe('QrCodeComponent', () => {
let component: QrCodeComponent;
let fixture: ComponentFixture<QrCodeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [QrCodeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(QrCodeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,109 @@
import {Component, Input, OnInit} from '@angular/core';
import {Router} from "@angular/router";
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from "@angular/forms";
import {NgIf} from "@angular/common";
import {FormsService} from "../_services/forms.service";
import {HttpEventType, HttpResponse} from "@angular/common/http";
import {AssetsService} from "../_services/assets.service";
@Component({
selector: 'qr-code',
standalone: true,
imports: [
FormsModule,
NgIf,
ReactiveFormsModule
],
templateUrl: './qr-code.component.html',
styleUrl: './qr-code.component.scss'
})
export class QrCodeComponent implements OnInit{
@Input() field!: any;
public asset: any;
public QrCodeForm: FormGroup;
public dragOver = false;
public upload: any = {file: null, progress: 0};
constructor(private formBuilder:FormBuilder, private formsService: FormsService, private assetsService: AssetsService){}
ngOnInit() {
this.QrCodeForm = this.formBuilder.group({
'text' : [''],
'format' : ['svg'],
'size' : [350],
'foreground-color' : ['#000000'],
'background-color' : ['#ffffff'],
'gradient-start' : ['#ffffff'],
'gradient-end' : ['#00A0FF'],
'gradient-type' : ['vertical'],
'color-type' : ['solid'],
'custom-eye-color' : [false],
'outside-eye-color' : ['#000000'],
'inner-eye-color' : ['#000000'],
'image' : []
});
}
onSubmit(){
this.QrCodeForm.markAllAsTouched();
if (this.QrCodeForm.valid)
this.formsService.save('model', 'QrCode', null, this.QrCodeForm.value).subscribe(res => {
}, error => {
console.log(error);
});
}
get initialValue() {
return this.field.value?.data[0];
}
get control() {
return this.QrCodeForm.controls[this.field.name];
}
get value() {
return this.control?.value || [];
}
set value(asset: any) {
this.asset = asset;
this.control?.setValue(asset?.id);
}
setValue(value: any) {
this.control.setValue(value);
this.control.markAsTouched();
}
clear() {
this.control.markAsTouched();
this.value = null;
}
onFileInput(event: any) {
this.uploadFiles(event.target.files);
event.target.value = null;
}
uploadFiles(files: any) {
for (let file of files) {
this.uploadFile(file);
}
}
uploadFile(file: File) {
this.upload.file = file;
this.assetsService.upload(file).subscribe((result) => {
if (result.type === HttpEventType.UploadProgress) {
this.upload.progress = Math.round(100 * result.loaded / result.total);
} else if (result instanceof HttpResponse) {
this.upload = {file: null, progress: 0};
this.value = result.body.data;
this.control.markAsTouched();
}
}, error => {
this.upload = {file: null, progress: 0};
alert(error.error.message);
});
}
}