Dualside print update realisation + add backend versioning
This commit is contained in:
@ -55,17 +55,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.version {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 8px;
|
|
||||||
left: 8px;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
color: rgba(245, 247, 250, 0.35);
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{#if needRefresh}
|
{#if needRefresh}
|
||||||
@ -81,5 +70,3 @@
|
|||||||
<ScanPage onBack={handleBack} />
|
<ScanPage onBack={handleBack} />
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<span class="version">Версия 1.0.3</span>
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
/** @type {Array<{ id: 'print' | 'scan' | 'photo' | 'access', label: string, grad: string, glow: string, icon: string }>} */
|
/** @type {Array<{ id: 'print' | 'scan' | 'photo' | 'access', label: string, grad: string, glow: string, icon: string }>} */
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
@ -31,10 +33,30 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @param {'print' | 'scan' | 'photo' | 'access'} id */
|
/**
|
||||||
export let onAction = (id) => {
|
* @type {{ onAction?: (id: 'print' | 'scan' | 'photo' | 'access') => void }}
|
||||||
|
*/
|
||||||
|
let { onAction = (id) => {
|
||||||
console.log('Выбрано действие:', id);
|
console.log('Выбрано действие:', id);
|
||||||
};
|
} } = $props();
|
||||||
|
|
||||||
|
let backendVersion = $state('—');
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/version');
|
||||||
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
|
const text = await res.text();
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(text);
|
||||||
|
backendVersion = json.version ?? json.data ?? text;
|
||||||
|
} catch {
|
||||||
|
backendVersion = text;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
backendVersion = 'offline';
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
@ -64,8 +86,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Стили остаются здесь, но убираем глобальные сбросы body/html */
|
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@ -93,14 +113,12 @@
|
|||||||
color: #8b93a1;
|
color: #8b93a1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Сетка 2×2 */
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Карточка-кнопка в стиле мобильного UI */
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -128,12 +146,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Эффект нажатия, как в мобильных приложениях */
|
|
||||||
.card:active {
|
.card:active {
|
||||||
transform: scale(0.96);
|
transform: scale(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Иконка с градиентом и свечением */
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 58px;
|
width: 58px;
|
||||||
height: 58px;
|
height: 58px;
|
||||||
@ -152,7 +168,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* «Домашняя» полоска внизу, как на iPhone */
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding: 32px 0 8px;
|
padding: 32px 0 8px;
|
||||||
@ -166,4 +181,29 @@
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.22);
|
background: rgba(255, 255, 255, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 8px;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: rgba(245, 247, 250, 0.35);
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version span:first-child {
|
||||||
|
color: rgba(245, 247, 250, 0.25);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div class="version">
|
||||||
|
<span>v{backendVersion}</span>
|
||||||
|
<span>v1.0.4</span>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<!-- lib/pages/print/PrintProgress.svelte -->
|
<!-- lib/pages/print/PrintProgress.svelte -->
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const currentStatus = $derived.by(() => {
|
const currentStatus = $derived.by(() => {
|
||||||
if (!store.job) return 'idle';
|
if (!store.job) return 'idle';
|
||||||
const f = store.job.files.find(f => f.status === 'printing');
|
const f = store.job.files.find(f => f.status === 'printing');
|
||||||
@ -13,10 +12,9 @@
|
|||||||
|
|
||||||
const OFFICE_EXT = ['DOC', 'DOCX', 'ODT', 'XLS', 'XLSX', 'ODS', 'PPT', 'PPTX', 'ODP'];
|
const OFFICE_EXT = ['DOC', 'DOCX', 'ODT', 'XLS', 'XLSX', 'ODS', 'PPT', 'PPTX', 'ODP'];
|
||||||
const STATUS_LABEL = {
|
const STATUS_LABEL = {
|
||||||
queued: 'В очереди',
|
queued: 'Ожидает',
|
||||||
printing: 'Печатается…',
|
printing: 'Печатается…',
|
||||||
awaiting_flip: 'Переверните',
|
awaiting_flip: 'Переложите бумагу',
|
||||||
awaiting: 'Далее',
|
|
||||||
done: 'Готово',
|
done: 'Готово',
|
||||||
error: 'Ошибка',
|
error: 'Ошибка',
|
||||||
cancelled: 'Отменено'
|
cancelled: 'Отменено'
|
||||||
@ -79,7 +77,9 @@
|
|||||||
{#if store.job}
|
{#if store.job}
|
||||||
{#each store.job.files as f, i (i + '-' + f.name)}
|
{#each store.job.files as f, i (i + '-' + f.name)}
|
||||||
{@const b = badge(f.name)}
|
{@const b = badge(f.name)}
|
||||||
<section class="file-card" class:current={f.status === 'awaiting_flip' || f.status === 'awaiting' || f.status === 'printing'}>
|
{@const isLastDone = f.status === 'done' && !store.job.files.slice(i + 1).some(f => f.status === 'printing' || f.status === 'queued')}
|
||||||
|
{@const hasNextQueued = store.job.files.slice(i + 1).some(f => f.status === 'queued')}
|
||||||
|
<section class="file-card" class:current={f.status === 'awaiting_flip' || f.status === 'printing' || isLastDone}>
|
||||||
<div class="file-row">
|
<div class="file-row">
|
||||||
{#if b}<span class="type-badge">{b}</span>{/if}
|
{#if b}<span class="type-badge">{b}</span>{/if}
|
||||||
<span class="file-name" title={f.name}>{f.name}</span>
|
<span class="file-name" title={f.name}>{f.name}</span>
|
||||||
@ -89,15 +89,12 @@
|
|||||||
<!-- ── Инструкция для РУЧНОГО ДУПЛЕКСА (после 1-го прохода) ── -->
|
<!-- ── Инструкция для РУЧНОГО ДУПЛЕКСА (после 1-го прохода) ── -->
|
||||||
{#if f.status === 'awaiting_flip'}
|
{#if f.status === 'awaiting_flip'}
|
||||||
<ol class="pickup-hint flip-hint">
|
<ol class="pickup-hint flip-hint">
|
||||||
{#if f.remove_top_sheet}
|
|
||||||
<li>Уберите верхний листок</li>
|
|
||||||
{/if}
|
|
||||||
<li>Не переворачивая, положите остальные листы в слот ручной подачи</li>
|
<li>Не переворачивая, положите остальные листы в слот ручной подачи</li>
|
||||||
</ol>
|
</ol>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- ── Инструкция для обычного «забери документ» ── -->
|
<!-- ── Инструкция для обычного «забери документ» ── -->
|
||||||
{#if f.status === 'awaiting'}
|
{#if isLastDone && hasNextQueued}
|
||||||
<ol class="pickup-hint">
|
<ol class="pickup-hint">
|
||||||
<li>Возьмите готовый документ</li>
|
<li>Возьмите готовый документ</li>
|
||||||
<li>Нажмите далее</li>
|
<li>Нажмите далее</li>
|
||||||
@ -128,8 +125,8 @@
|
|||||||
<button class="next-btn next-btn-flip" onclick={store.advanceJob}>
|
<button class="next-btn next-btn-flip" onclick={store.advanceJob}>
|
||||||
Готово — печатать обратную сторону
|
Готово — печатать обратную сторону
|
||||||
</button>
|
</button>
|
||||||
{:else if store.job?.phase === 'awaiting_pickup'}
|
{:else if store.job?.phase === 'awaiting_pickup' && store.job.files.some(f => f.status === 'queued')}
|
||||||
<!-- Обычное: забрал документ → следующий -->
|
<!-- Обычное: забрал документ → следующий (только если есть следующий) -->
|
||||||
<button class="next-btn" onclick={store.advanceJob}>Далее</button>
|
<button class="next-btn" onclick={store.advanceJob}>Далее</button>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Кнопка видна, но серая и disabled пока сервер не допечатал -->
|
<!-- Кнопка видна, но серая и disabled пока сервер не допечатал -->
|
||||||
@ -182,9 +179,8 @@
|
|||||||
.file-name { flex: 1; min-width: 0; font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.file-name { flex: 1; min-width: 0; font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.file-status { flex-shrink: 0; font-size: 14px; font-weight: 700; }
|
.file-status { flex-shrink: 0; font-size: 14px; font-weight: 700; }
|
||||||
.st-done { color: #22c55e; }
|
.st-done { color: #22c55e; }
|
||||||
.st-awaiting { color: #fbbf24; }
|
|
||||||
.st-printing { color: #a5b4fc; }
|
|
||||||
.st-queued { color: #8b93a1; }
|
.st-queued { color: #8b93a1; }
|
||||||
|
.st-printing { color: #a5b4fc; }
|
||||||
.st-error { color: #ef4444; }
|
.st-error { color: #ef4444; }
|
||||||
.st-cancelled { color: #6b7280; }
|
.st-cancelled { color: #6b7280; }
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,6 @@ export default defineConfig({
|
|||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3000',
|
target: 'http://localhost:3000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user