feat: add all equipment view with sorting, print functionality, and UI improvements

- Add "Всё оборудование" menu item with equipment sorted by inventory number
- Add row numbering in all equipment view
- Add print functionality for auditory view with "Проверено" column
- Hide unnecessary columns (quantity, type, owner) when printing
- Make cards full-width and responsive (container-fluid)
- Consolidate CSS styles from static/css/index.css to frontend/styles.css
- Fix text wrapping in "Расположение" column
- Add sort_by_inv parameter to /oboruds/ API endpoint

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Danamir
2026-01-22 22:48:27 +01:00
parent e2ff0f9a05
commit 35bd29c223
4 changed files with 280 additions and 36 deletions

View File

@@ -3,6 +3,7 @@ const { createApp } = Vue;
const api = {
auds: "/auditories/",
oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`,
allOboruds: "/oboruds/?sort_by_inv=true",
owners: "/owners/",
};
@@ -19,8 +20,10 @@ createApp({
auditories: [],
selectedAudId: '',
oboruds: [],
allOboruds: [],
status: '',
error: '',
printTitle: '',
// auth/user management
token: '',
role: '',
@@ -98,6 +101,34 @@ createApp({
this.status = '';
}
},
async loadAllOboruds() {
this.status = 'Загрузка всего оборудования…';
this.error = '';
try {
this.allOboruds = await fetchJSON(api.allOboruds);
this.status = `Загружено ${this.allOboruds.length} записей`;
} catch (e) {
console.error(e);
this.error = 'Не удалось загрузить оборудование';
this.status = '';
}
},
async showAllEquipment() {
this.view = 'allEquipment';
await this.loadAllOboruds();
},
getAuditoryName(audId) {
if (!audId) return '';
const aud = this.auditories.find(a => a.id === audId);
return aud ? aud.audnazvanie : '';
},
printPage() {
const aud = this.auditories.find(a => a.id === this.selectedAudId);
this.printTitle = 'Аудитория № ' + (aud ? aud.audnazvanie : '');
this.$nextTick(() => {
window.print();
});
},
async saveOwner(item) {
try {
this.status = 'Сохранение владельца…';

View File

@@ -5,10 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>АСУ Инвентаризация</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
<link rel="stylesheet" href="/static/css/index.css" />
<link rel="stylesheet" href="/app/styles.css" />
</head>
<body>
<header>
<header class="no-print">
<h1>
<a href="/app/">АСУ Инвентаризация</a>
</h1>
@@ -26,6 +26,7 @@
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="#" @click.prevent="view='byAud'">По аудитории</a></li>
<li class="nav-item"><a class="nav-link" href="#" @click.prevent="showAllEquipment">Всё оборудование</a></li>
<li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='users'">Пользователи</a></li>
<li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='audManage'">Аудитории</a></li>
<li class="nav-item"><a class="nav-link" href="/docs" target="_blank">API Docs</a></li>
@@ -39,20 +40,22 @@
</nav>
</div>
<div class="container">
<div class="container-fluid px-4">
<div v-if="view==='byAud'" class="row">
<div class="card col-md-10 col-10">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Оборудование по аудитории</h3>
<div class="mb-2 d-flex align-items-center gap-2">
<h3 class="card-title no-print">Оборудование по аудитории</h3>
<h2 class="print-only print-title">{{ printTitle }}</h2>
<div class="mb-2 d-flex align-items-center gap-2 no-print">
<label for="aud-select" class="me-2">Аудитория:</label>
<select id="aud-select" class="form-select w-auto" v-model="selectedAudId">
<option value="">— выберите аудиторию —</option>
<option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option>
</select>
<button class="btn btn-primary" @click="loadOboruds">Показать</button>
<button class="btn btn-secondary" @click="printPage" :disabled="!oboruds.length">Печать</button>
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<div class="status no-print" :class="{error: !!error}">{{ status }}</div>
<div class="table-responsive">
<table class="table datatable">
<thead>
@@ -61,9 +64,10 @@
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Расположение</th>
<th scope="col">Кол-во</th>
<th scope="col">Тип</th>
<th scope="col">Владелец</th>
<th scope="col" class="no-print">Кол-во</th>
<th scope="col" class="no-print">Тип</th>
<th scope="col" class="no-print">Владелец</th>
<th scope="col" class="print-only">Проверено</th>
</tr>
</thead>
<tbody>
@@ -72,9 +76,9 @@
<td class="inv">{{ it.invNumber ?? '' }}</td>
<td>{{ it.nazvanie ?? '' }}</td>
<td class="rasp">{{ it.raspologenie ?? '' }}</td>
<td>{{ it.kolichestvo ?? '' }}</td>
<td>{{ it.type?.name ?? '' }}</td>
<td>
<td class="no-print">{{ it.kolichestvo ?? '' }}</td>
<td class="no-print">{{ it.type?.name ?? '' }}</td>
<td class="no-print">
<template v-if="canEdit">
<select class="form-select form-select-sm d-inline w-auto" v-model="it.selectedOwnerId">
<option value="">— нет —</option>
@@ -86,6 +90,42 @@
{{ it.owner?.name ?? '' }}
</template>
</td>
<td class="print-only"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div v-if="view==='allEquipment'" class="row">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Всё оборудование (по инв. номеру)</h3>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Аудитория</th>
<th scope="col">Расположение</th>
<th scope="col">Кол-во</th>
<th scope="col">Владелец</th>
</tr>
</thead>
<tbody>
<tr v-for="(it, index) in allOboruds" :key="it.id">
<td>{{ index + 1 }}</td>
<td class="inv">{{ it.invNumber ?? '' }}</td>
<td>{{ it.nazvanie ?? '' }}</td>
<td>{{ getAuditoryName(it.aud_id) }}</td>
<td class="rasp">{{ it.raspologenie ?? '' }}</td>
<td>{{ it.kolichestvo ?? '' }}</td>
<td>{{ it.owner?.name ?? '' }}</td>
</tr>
</tbody>
</table>
@@ -95,7 +135,7 @@
</div>
<div v-if="view==='users'" class="row">
<div class="card col-md-10 col-10">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Администрирование пользователей</h3>
<div v-if="role!=='admin'" class="alert alert-warning">Недостаточно прав. Войдите как администратор.</div>
@@ -142,7 +182,7 @@
</div>
<div v-if="view==='audManage'" class="row">
<div class="card col-md-10 col-10">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Управление аудиториями</h3>
<div v-if="role!=='admin'" class="alert alert-warning">Недостаточно прав. Войдите как администратор.</div>
@@ -183,7 +223,7 @@
</div>
<div v-if="view==='owners'" class="row">
<div class="card col-md-10 col-10">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Управление владельцами</h3>
<div v-if="!canEdit" class="alert alert-warning">Недостаточно прав. Войдите как администратор или редактор.</div>

View File

@@ -1,20 +1,191 @@
:root { --bg: #0f172a; --fg: #e2e8f0; --muted: #94a3b8; --accent: #38bdf8; --err: #ef4444; --warn: #f59e0b; }
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--fg); }
header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #1f2937; }
header h1 { margin: 0; font-size: 20px; }
header nav a { color: var(--accent); text-decoration: none; }
main { padding: 20px; max-width: 1000px; margin: 0 auto; }
.panel { background: #0b1220; border: 1px solid #1f2937; border-radius: 8px; padding: 16px; }
.controls { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
select, button { padding: 8px 10px; border-radius: 6px; border: 1px solid #1f2937; background: #0a0f1a; color: var(--fg); }
button { cursor: pointer; }
button:hover { border-color: var(--accent); }
.status { min-height: 20px; color: var(--muted); margin-bottom: 8px; }
.status.error { color: var(--err); }
.status.warn { color: var(--warn); }
table { width: 100%; border-collapse: collapse; }
th, td { border-bottom: 1px solid #1f2937; padding: 8px; text-align: left; }
th { color: var(--muted); font-weight: 600; }
td.rasp { max-width: 200px; word-wrap: break-word; white-space: normal; }
* {
margin: 0;
padding: 0;
}
body {
background-color: #E2F3FD;
min-width: 580px;
}
.row {
text-align: center;
display: flex;
justify-content: center;
border: 1px;
}
header {
text-align: center;
background-color: #6A90B6;
padding: 2px;
}
a:hover {
text-decoration: none;
color: #041322;
}
a {
color: #041322;
}
.container {
margin: 10px;
}
button {
background-color: #E07D54;
margin-top: 5px;
margin-bottom: 5px;
}
.card {
margin: 10px;
border-radius: 15px;
border-color: #E07D54;
}
h5 {
text-align: center;
}
.hidden-column {
display: none;
}
nav {
width: 100%;
}
table {
word-break: break-all;
border-collapse: separate !important;
}
.table td {
font-size: 14px;
padding: 0;
max-width: 10rem;
word-break: break-all;
border-collapse: separate !important;
}
.aud {
width: 110px;
}
.inv {
width: 400px;
}
.rasp {
width: 250px;
max-width: 250px;
word-break: break-word;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal !important;
}
#modal_matcenn {
margin-left: 20px;
}
.datatable {
background-color: whitesmoke;
}
.print-only {
display: none;
}
.print-title {
text-align: center;
margin-bottom: 20px;
}
.datatable th:nth-child(7) {
width: 200px;
}
@media print {
* {
font-family: "Times New Roman", Times, serif;
}
body {
margin: 0;
padding: 0;
background-color: transparent;
}
a {
text-decoration: none;
border: none;
}
@page {
size: A4;
margin: 10mm;
}
@page :first {
margin-top: 10mm;
}
html, body {
height: 100%;
}
title {
display: none;
}
.card {
border: none;
width: 100%;
}
.no-print {
display: none !important;
}
.print-only {
display: table-cell !important;
}
h2.print-only {
display: block !important;
}
.table {
border: 1px solid #000000;
margin-top: 20px;
font-size: 12pt;
}
.table th, .table td {
border: 1px solid #000000;
padding: 5px;
font-size: 12pt;
}
table.rs-table-bordered {
border: 1px solid #000000;
margin-top: 20px;
font-size: 14pt;
}
table.rs-table-bordered > thead > tr > th {
border: 1px solid #000000;
padding: 2px;
font-size: 14pt;
}
table.rs-table-bordered > tbody > tr > td {
border: 1px solid #000000;
padding: 10px;
font-size: 14pt;
}
}