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:
@@ -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 = 'Сохранение владельца…';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user