ЛР4 беда на бэке
This commit is contained in:
parent
da683c642b
commit
49b8125146
46
Frontend/vue-project/src/components/CatalogGenres.vue
Normal file
46
Frontend/vue-project/src/components/CatalogGenres.vue
Normal 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>
|
23
Frontend/vue-project/src/components/Catalogs.vue
Normal file
23
Frontend/vue-project/src/components/Catalogs.vue
Normal 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>
|
70
Frontend/vue-project/src/components/DataTable.vue
Normal file
70
Frontend/vue-project/src/components/DataTable.vue
Normal 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>
|
@ -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>
|
||||||
|
63
Frontend/vue-project/src/components/Modal.vue
Normal file
63
Frontend/vue-project/src/components/Modal.vue
Normal 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>
|
9
Frontend/vue-project/src/components/Table.vue
Normal file
9
Frontend/vue-project/src/components/Table.vue
Normal 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>
|
46
Frontend/vue-project/src/components/ToolBar.vue
Normal file
46
Frontend/vue-project/src/components/ToolBar.vue
Normal 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>
|
@ -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({
|
||||||
|
102
Frontend/vue-project/src/mixins/CatalogMixins.js
Normal file
102
Frontend/vue-project/src/mixins/CatalogMixins.js
Normal 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;
|
26
Frontend/vue-project/src/models/Collection.js
Normal file
26
Frontend/vue-project/src/models/Collection.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
26
Frontend/vue-project/src/models/Film.js
Normal file
26
Frontend/vue-project/src/models/Film.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
21
Frontend/vue-project/src/models/Genre.js
Normal file
21
Frontend/vue-project/src/models/Genre.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
42
Frontend/vue-project/src/services/DataService.js
Normal file
42
Frontend/vue-project/src/services/DataService.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
BIN
data.mv.db
BIN
data.mv.db
Binary file not shown.
@ -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;
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user