You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.4 KiB
172 lines
6.4 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}快速录入{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="w-full">
|
|
<section class="mb-4 flex items-start justify-between gap-3 sm:mb-5">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<h2 class="page-title">快速录入</h2>
|
|
<span class="rounded-full bg-accent-100 px-3 py-1 text-xs font-medium text-accent-700 dark:bg-accent-900/40 dark:text-accent-300">当天礼金</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="mb-4 card sm:mb-5">
|
|
<div class="card-body">
|
|
<form action="{{ url_for('quick_entry.index') }}" method="get" class="space-y-3" id="quick-entry-search-form">
|
|
<label for="quick-entry-search" class="form-label">搜索关键词</label>
|
|
<div class="flex flex-col gap-3 sm:flex-row">
|
|
<input id="quick-entry-search" name="q" type="text" class="form-input flex-1" value="{{ search_term }}" placeholder="户主 / 成员 / 拼音 / 标签 / 备注" autofocus>
|
|
<div class="flex flex-wrap items-center justify-end gap-2 sm:flex-nowrap">
|
|
<label class="inline-flex shrink-0 items-center gap-2 rounded-full border border-neutral-200 bg-neutral-50 px-3 py-2 text-xs text-neutral-500 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-400">
|
|
<input
|
|
type="checkbox"
|
|
name="include_bride_side"
|
|
value="1"
|
|
class="h-3.5 w-3.5 rounded border-neutral-300 text-brand-600 focus:ring-brand-500 dark:border-neutral-600 dark:bg-neutral-900"
|
|
{% if include_bride_side %}checked{% endif %}
|
|
>
|
|
<span>包含女方</span>
|
|
</label>
|
|
<button type="submit" class="btn btn-primary flex-1 sm:flex-none">搜索</button>
|
|
<a class="btn btn-accent flex-1 sm:flex-none" href="{{ url_for('quick_entry.new_household', q=search_term, include_bride_side='1') if include_bride_side else (url_for('quick_entry.new_household', q=search_term) if search_term else url_for('quick_entry.new_household')) }}">新增</a>
|
|
<a class="btn btn-secondary flex-1 sm:flex-none" href="{{ url_for('quick_entry.index') }}">重置</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
{% include "quick_entry/_search_results.html" %}
|
|
|
|
<div id="quick-entry-edit-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4 py-6">
|
|
<div id="quick-entry-edit-modal-body" class="w-full max-w-xl"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const form = document.getElementById('quick-entry-search-form');
|
|
const resultsRegionId = 'quick-entry-search-results-region';
|
|
const modal = document.getElementById('quick-entry-edit-modal');
|
|
const modalBody = document.getElementById('quick-entry-edit-modal-body');
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
let debounceTimer = null;
|
|
let currentRequest = null;
|
|
|
|
function closeModal() {
|
|
if (!modal || !modalBody) {
|
|
return;
|
|
}
|
|
modal.classList.add('hidden');
|
|
modalBody.innerHTML = '';
|
|
}
|
|
|
|
async function openEditModal(url) {
|
|
if (!modal || !modalBody) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await window.fetch(url, {
|
|
headers: { 'X-HW-Partial': 'edit-modal' },
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`unexpected status ${response.status}`);
|
|
}
|
|
|
|
modalBody.innerHTML = await response.text();
|
|
modal.classList.remove('hidden');
|
|
const amountField = modalBody.querySelector('#total_gift_amount');
|
|
if (amountField) {
|
|
amountField.focus();
|
|
amountField.select();
|
|
}
|
|
} catch (error) {
|
|
window.console.error('Quick entry edit modal failed', error);
|
|
}
|
|
}
|
|
|
|
function scheduleRefresh() {
|
|
window.clearTimeout(debounceTimer);
|
|
debounceTimer = window.setTimeout(refreshResults, 300);
|
|
}
|
|
|
|
async function refreshResults() {
|
|
if (currentRequest) {
|
|
currentRequest.abort();
|
|
}
|
|
|
|
const controller = new AbortController();
|
|
currentRequest = controller;
|
|
const params = new URLSearchParams(new FormData(form));
|
|
params.set('partial', 'search-results');
|
|
|
|
try {
|
|
const response = await window.fetch(`${form.action}?${params.toString()}`, {
|
|
headers: { 'X-HW-Partial': 'search-results' },
|
|
signal: controller.signal,
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(`unexpected status ${response.status}`);
|
|
}
|
|
|
|
const html = await response.text();
|
|
const parser = new DOMParser();
|
|
const nextDocument = parser.parseFromString(html, 'text/html');
|
|
const nextRegion = nextDocument.getElementById(resultsRegionId);
|
|
const currentRegion = document.getElementById(resultsRegionId);
|
|
if (nextRegion && currentRegion) {
|
|
currentRegion.replaceWith(nextRegion);
|
|
}
|
|
window.history.replaceState({}, '', `${form.action}?${new URLSearchParams(new FormData(form)).toString()}`);
|
|
} catch (error) {
|
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
return;
|
|
}
|
|
window.console.error('Quick entry live refresh failed', error);
|
|
} finally {
|
|
if (currentRequest === controller) {
|
|
currentRequest = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
form.querySelectorAll('input').forEach(function (field) {
|
|
field.addEventListener('input', scheduleRefresh);
|
|
field.addEventListener('change', scheduleRefresh);
|
|
});
|
|
|
|
document.addEventListener('click', function (event) {
|
|
const editButton = event.target.closest('[data-quick-entry-edit-url]');
|
|
if (editButton) {
|
|
event.preventDefault();
|
|
openEditModal(editButton.getAttribute('data-quick-entry-edit-url'));
|
|
return;
|
|
}
|
|
|
|
const closeButton = event.target.closest('[data-quick-entry-modal-close]');
|
|
if (closeButton) {
|
|
event.preventDefault();
|
|
closeModal();
|
|
return;
|
|
}
|
|
|
|
if (modal && event.target === modal) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keydown', function (event) {
|
|
if (event.key === 'Escape' && modal && !modal.classList.contains('hidden')) {
|
|
closeModal();
|
|
}
|
|
});
|
|
}());
|
|
</script>
|
|
{% endblock %}
|
|
|