ЛР4 беда на бэке

This commit is contained in:
ityurner02@mail.ru 2023-04-25 13:06:29 +04:00
parent da683c642b
commit 49b8125146
16 changed files with 485 additions and 4 deletions

View File

@ -0,0 +1,46 @@
<script>
import axios from 'axios';
import CatalogMixins from '../mixins/CatalogMixins.js';
import Genre from "../models/Genre";
import DataService from '../services/DataService';
export default {
mixins: [
CatalogMixins
],
data() {
return {
getAllUrl: 'genre/',
dataUrl: 'genre',
transformer: (data) => new Genre(data),
headers: [
{ name: 'name', label: 'Жанр' }
]
}
}
}
</script>
<template>
<ToolBar
@add="showAddModal"
@edit="showEditModal"
@remove="removeSelectedItems">
</ToolBar>
<DataTable
:headers="this.headers"
:items="this.items"
:selectedItems="this.selectedItems"
@dblclick="showEditModalDblClick">
</DataTable>
<Modal
:header="this.modal.header"
:confirm="this.modal.confirm"
v-model:visible="this.modalShow"
@done="saveItem">
<div class="mb-3">
<label for="genre" class="form-label">Жанр</label>
<input type="text" class="form-control" id="name" required v-model="data.name">
</div>
</Modal>
</template>

View File

@ -0,0 +1,23 @@
<script>
export default {
data() {
return {
catalogs: [
{ name: 'collections', label: 'Сборники' },
{ name: 'films', label: 'Фильмы' },
{ name: 'genres', label: 'Жанры' }
]
}
}
}
</script>
<template>
<div class="list-group">
<router-link v-for="catalog in this.catalogs"
:to="'/catalogs/' + catalog.name"
class="list-group-item list-group-item-action">
{{ catalog.label }}
</router-link>
</div>
</template>

View File

@ -0,0 +1,70 @@
<script>
export default {
props: {
headers: Array,
items: Array,
selectedItems: Array
},
emits: {
dblclick: null
},
methods: {
rowClick(id) {
if (this.isSelected(id)) {
var index = this.selectedItems.indexOf(id);
if (index !== -1) {
this.selectedItems.splice(index, 1);
}
} else {
this.selectedItems.push(id);
}
},
rowDblClick(id) {
this.$emit('dblclick', id);
},
isSelected(id) {
return this.selectedItems.includes(id);
}
}
}
</script>
<template>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th v-for="header in this.headers"
:id="header.name"
scope="col">{{ header.label }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in this.items"
@click="rowClick(item.id)"
@dblclick="rowDblClick(item.id)"
:class="{selected: isSelected(item.id)}">
<th scope="row">{{ index + 1 }}</th>
<td v-for="header in this.headers">
{{ item[header.name] }}
</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
tbody tr:hover {
cursor: pointer;
}
tr.selected {
background-color: #07fa44;
opacity: 80%;
}
tbody tr {
user-select: none;
}
*{
color: white;
}
</style>

View File

@ -23,6 +23,9 @@
<li class="nav-item"> <li class="nav-item">
<router-link to="/form" class="nav-link">Связь</router-link> <router-link to="/form" class="nav-link">Связь</router-link>
</li> </li>
<li class="nav-item">
<router-link to="/table" class="nav-link">Таблица</router-link>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -0,0 +1,63 @@
<script>
export default {
props: {
header: String,
confirm: String,
visible: Boolean
},
emits: {
done: null,
'update:visible': (value) => {
if (typeof value !== 'boolean') {
throw 'Value is not a boolean';
}
return true;
}
},
methods: {
hide() {
this.$emit('update:visible', false);
},
done() {
if (this.$refs.form.checkValidity()) {
this.$emit('done');
this.hide();
} else {
this.$refs.form.reportValidity();
}
}
}
}
</script>
<template>
<div class="modal fade" tabindex="-1" aria-hidden="true"
:class="{ 'modal-show': this.visible, 'show': this.visible }">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">{{ header }}</h1>
<button type="button" class="btn-close" aria-label="Close"
@click.prevent="hide"></button>
</div>
<div class="modal-body">
<form @submit.prevent="done" ref="form">
<slot></slot>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
@click.prevent="hide">Закрыть</button>
<button type="button" class="btn btn-primary"
@click.prevent="done">{{ confirm }}</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.modal-show {
display: block;
}
</style>

View File

@ -0,0 +1,9 @@
<template>
<router-link to="/catalogs" class="nav-link"><button class="btn btn-success my-1" type="submit">Каталоги</button></router-link>
</template>
<style scoped>
.btn{
min-width: 120px;
}
</style>

View File

@ -0,0 +1,46 @@
<script>
export default {
props: {
},
emits: {
add: null,
edit: null,
remove: null
},
methods: {
add() {
this.$emit('add');
},
edit() {
this.$emit('edit');
},
remove() {
this.$emit('remove');
}
}
}
</script>
<template>
<div class="btn-group mt-2" role="group">
<button type="button" class="btn btn-success"
@click.prevent="add">
Добавить
</button>
<button type="button" class="btn btn-warning"
@click.prevent="edit">
Изменить
</button>
<button type="button" class="btn btn-danger"
@click.prevent="remove">
Удалить
</button>
</div>
</template>
<style scoped>
.btn {
min-width: 140px;
}
</style>

View File

@ -6,15 +6,21 @@ import Player from './components/Player.vue'
import Films from './components/Films.vue' import Films from './components/Films.vue'
import About_us from './components/About_us.vue' import About_us from './components/About_us.vue'
import Form from './components/Form.vue' import Form from './components/Form.vue'
import Table from './components/Table.vue'
import Catalogs from './components/Catalogs.vue'
import CatalogGenres from './components/CatalogGenres.vue'
const routes = [ const routes = [
{ path: '/', redirect: '/index' }, { path: '/', redirect: '/index' },
{ path: '/index', component: Index }, { path: '/index', component: Index},
{ path: '/player', component: Player}, { path: '/player', component: Player},
{ path: '/films', component: Films}, { path: '/films', component: Films},
{ path: '/about_us', component: About_us}, { path: '/about_us', component: About_us},
{ path: '/form', component: Form} { path: '/form', component: Form},
{ path: '/table', component: Table},
{ path: '/catalogs', component: Catalogs},
{ path: '/catalogs/genres', component: CatalogGenres}
] ]
const router = createRouter({ const router = createRouter({

View File

@ -0,0 +1,102 @@
import ToolBar from '../components/ToolBar.vue';
import DataTable from '../components/DataTable.vue';
import Modal from '../components/Modal.vue';
import DataService from '../services/DataService';
const CatalogMixin = {
components: {
ToolBar, DataTable, Modal
},
data() {
return {
getAllUrl: undefined,
dataUrl: undefined,
transformer: undefined,
headers: [],
items: [],
selectedItems: [],
modal: {
header: undefined,
confirm: undefined,
},
modalShow: false,
data: undefined,
isEdit: false
}
},
created() {
this.loadItems();
},
methods: {
loadItems() {
this.getItems();
this.data = this.transformer();
},
getItems() {
DataService.readAll(this.getAllUrl, this.transformer)
.then(data => {
this.items = data;
});
},
showAddModal() {
this.isEdit = false;
this.data = this.transformer();
this.modal.header = 'Добавление элемента';
this.modal.confirm = 'Добавить';
this.modalShow = true;
},
showEditModal() {
if (this.selectedItems.length === 0) {
return;
}
this.showEditModalDblClick(this.selectedItems[0]);
},
showEditModalDblClick(editId) {
DataService.read(this.dataUrl + "/" + editId, this.transformer)
.then(data => {
this.data = data;
this.isEdit = true;
this.modal.header = 'Редактирование элемента';
this.modal.confirm = 'Сохранить';
this.modalShow = true;
});
},
saveItem() {
if (!this.isEdit) {
DataService.create(this.dataUrl + "/", this.data)
.then(() => {
this.getItems();
});
} else {
DataService.update(this.dataUrl + "/" + this.data.id, this.data)
.then(() => {
this.getItems();
});
}
},
removeSelectedItems() {
if (this.selectedItems.length === 0) {
return;
}
if (confirm('Удалить выбранные элементы?')) {
const promises = [];
const self = this;
this.selectedItems.forEach(item => {
promises.push(DataService.delete(this.dataUrl + item));
});
Promise.all(promises).then((results) => {
results.forEach(function (id) {
const index = self.selectedItems.indexOf(id);
if (index === - 1) {
return;
}
self.selectedItems.splice(index, 1);
});
this.getItems();
});
}
}
}
}
export default CatalogMixin;

View File

@ -0,0 +1,26 @@
export default class Collection {
constructor(data) {
this._id = data?.id;
this._name = data?.name;
this._filmIds = data?.filmIds;
}
get id() {
return this._id;
}
get name() {
return this._name;
}
set name(value) {
if (typeof value !== 'string' || value === null || value.length == 0) {
throw 'New name value ' + value + ' is not a string or empty';
}
this._name = value;
}
get filmIds() {
return this.filmIds;
}
}

View File

@ -0,0 +1,26 @@
export default class Film {
constructor(data) {
this._id = data?.id;
this._name = data?.name;
this._genreIds = data?.genreIds;
}
get id() {
return this._id;
}
get name() {
return this._name;
}
set name(value) {
if (typeof value !== 'string' || value === null || value.length == 0) {
throw 'New name value ' + value + ' is not a string or empty';
}
this._name = value;
}
get genreIds() {
return this._genreIds;
}
}

View File

@ -0,0 +1,21 @@
export default class Genre {
constructor(data) {
this._id = data?.id;
this._name = data?.name;
}
get id() {
return this._id;
}
get name() {
return this._name;
}
set name(value) {
if (typeof value !== 'string' || value === null || value.length == 0) {
throw 'New name value ' + value + ' is not a string or empty';
}
this._name = value;
}
}

View File

@ -0,0 +1,42 @@
import axios from 'axios';
function toJSON(data) {
const jsonObj = {};
const fields = Object.getOwnPropertyNames(data);
for (const field of fields) {
if (data[field] === undefined) {
continue;
}
jsonObj[field.substring(1)] = data[field];
}
return jsonObj;
}
export default class DataService {
static dataUrlPrefix = 'http://localhost:8080/';
static async readAll(url, transformer) {
const response = await axios.get(this.dataUrlPrefix + url);
return response.data.map(item => transformer(item));
}
static async read(url, transformer) {
const response = await axios.get(this.dataUrlPrefix + url);
return transformer(response.data);
}
static async create(url, data) {
const response = await axios.post(this.dataUrlPrefix + url, toJSON(data));
return true;
}
static async update(url, data) {
const response = await axios.put(this.dataUrlPrefix + url, toJSON(data));
return true;
}
static async delete(url) {
const response = await axios.delete(this.dataUrlPrefix + url);
return response.data.id;
}
}

Binary file not shown.

View File

@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.is.lab1.DataBase.model.Genre;
import ru.ulstu.is.lab1.DataBase.service.GenreService; import ru.ulstu.is.lab1.DataBase.service.GenreService;
import java.util.List; import java.util.List;

View File

@ -3,7 +3,6 @@ package ru.ulstu.is.lab1.DataBase.service;
import ru.ulstu.is.lab1.DataBase.model.Film; import ru.ulstu.is.lab1.DataBase.model.Film;
import ru.ulstu.is.lab1.DataBase.model.Collection; import ru.ulstu.is.lab1.DataBase.model.Collection;
import ru.ulstu.is.lab1.DataBase.Repository.ICollectionRepository; import ru.ulstu.is.lab1.DataBase.Repository.ICollectionRepository;
import ru.ulstu.is.lab1.DataBase.model.Genre;
import ru.ulstu.is.lab1.util.validation.ValidatorUtil; import ru.ulstu.is.lab1.util.validation.ValidatorUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;