Merge remote-tracking branch 'origin/main' into feature/zhoushengchen-reverse-logistics-quotes
This commit is contained in:
commit
d98aba6d77
@ -249,6 +249,63 @@
|
||||
color: var(--agreement-muted);
|
||||
}
|
||||
|
||||
.agreement-courier-search-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.agreement-courier-native-select--enhanced {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
clip-path: inset(50%);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.agreement-courier-options {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
left: 0;
|
||||
max-height: 16rem;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--agreement-border);
|
||||
border-radius: 8px;
|
||||
background: var(--agreement-surface);
|
||||
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.1);
|
||||
}
|
||||
|
||||
.agreement-courier-option {
|
||||
min-height: 34px;
|
||||
padding: 0.5rem 0.65rem;
|
||||
color: var(--agreement-ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.agreement-courier-option:hover,
|
||||
.agreement-courier-option.is-active {
|
||||
background: var(--agreement-accent-soft);
|
||||
color: var(--agreement-accent);
|
||||
}
|
||||
|
||||
.agreement-courier-options-status {
|
||||
padding: 0.5rem 0.65rem;
|
||||
border-top: 1px solid var(--agreement-border);
|
||||
color: var(--agreement-muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.agreement-courier-native-select--enhanced:focus-visible,
|
||||
.agreement-courier-search-wrap .agreement-control:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 3px rgba(133, 102, 50, 0.2);
|
||||
}
|
||||
|
||||
.agreement-search {
|
||||
position: relative;
|
||||
}
|
||||
@ -1368,15 +1425,21 @@
|
||||
<div class="agreement-form-grid">
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-courier">快递公司 <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
<select class="agreement-control" id="agreement-courier" name="courier_name" data-courier-select required>
|
||||
<option value="">请选择</option>
|
||||
@if ($agreementCourierNames->isEmpty())
|
||||
<option value="" disabled>暂无可选快递资料</option>
|
||||
@endif
|
||||
@foreach ($agreementCourierNames as $courierName)
|
||||
<option value="{{ $courierName }}" data-esign-account="{{ $agreementCourierAccounts[$courierName] ?? '' }}">{{ $courierName }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="agreement-courier-search-wrap" data-courier-control>
|
||||
<select class="agreement-control" id="agreement-courier" name="courier_name" data-courier-select required>
|
||||
<option value="">请选择</option>
|
||||
@if ($agreementCourierNames->isEmpty())
|
||||
<option value="" disabled>暂无可选快递资料</option>
|
||||
@endif
|
||||
@foreach ($agreementCourierNames as $courierName)
|
||||
<option value="{{ $courierName }}" data-esign-account="{{ $agreementCourierAccounts[$courierName] ?? '' }}">{{ $courierName }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="agreement-courier-search-wrap" data-courier-search-wrap hidden>
|
||||
<input class="agreement-control" id="agreement-courier-search" type="search" placeholder="输入快递名称搜索" autocomplete="off" role="combobox" aria-autocomplete="list" aria-haspopup="listbox" aria-expanded="false" aria-controls="agreement-courier-options" data-courier-search>
|
||||
<div class="agreement-courier-options" id="agreement-courier-options" role="listbox" aria-label="快递资料" hidden data-courier-options></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-document-type">文件类型 <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
@ -1920,6 +1983,18 @@
|
||||
var subjectNameInput = root.querySelector('[data-subject-name]');
|
||||
var subjectCodeInput = root.querySelector('[data-subject-code]');
|
||||
var courierSelect = root.querySelector('[data-courier-select]');
|
||||
var courierSearchWrap = root.querySelector('[data-courier-search-wrap]');
|
||||
var courierSearch = root.querySelector('[data-courier-search]');
|
||||
var courierOptionsList = root.querySelector('[data-courier-options]');
|
||||
var courierControl = root.querySelector('[data-courier-control]');
|
||||
var courierLabel = root.querySelector('label[for="agreement-courier"]');
|
||||
var courierOptions = courierSelect ? Array.prototype.filter.call(courierSelect.options, function (option) {
|
||||
return option.value !== '' && !option.disabled;
|
||||
}).map(function (option) {
|
||||
return { value: option.value, account: option.dataset.esignAccount || '' };
|
||||
}) : [];
|
||||
var visibleCourierIndexes = [];
|
||||
var activeCourierIndex = -1;
|
||||
var esignAccountInput = root.querySelector('input[name="esign_account"]');
|
||||
var accountSignerTypeField = root.querySelector('[data-account-signer-type-field]');
|
||||
var accountSignerTypeInputs = Array.prototype.slice.call(root.querySelectorAll('[data-account-signer-type]'));
|
||||
@ -2259,6 +2334,197 @@
|
||||
}
|
||||
}
|
||||
|
||||
function closeCourierOptions() {
|
||||
if (!courierSearch || !courierOptionsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
courierOptionsList.hidden = true;
|
||||
courierSearch.setAttribute('aria-expanded', 'false');
|
||||
courierSearch.removeAttribute('aria-activedescendant');
|
||||
activeCourierIndex = -1;
|
||||
}
|
||||
|
||||
function renderCourierOptions(query) {
|
||||
if (!courierSearch || !courierOptionsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedQuery = String(query || '').trim().toLocaleLowerCase();
|
||||
var matches = [];
|
||||
|
||||
courierOptions.forEach(function (option, index) {
|
||||
if (normalizedQuery === '' || option.value.toLocaleLowerCase().indexOf(normalizedQuery) !== -1) {
|
||||
matches.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
visibleCourierIndexes = matches.slice(0, 80);
|
||||
courierOptionsList.innerHTML = '';
|
||||
|
||||
if (visibleCourierIndexes.length === 0) {
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'agreement-courier-options-status';
|
||||
empty.setAttribute('role', 'status');
|
||||
empty.textContent = normalizedQuery === '' ? '暂无可选快递资料' : '没有匹配的快递名称';
|
||||
courierOptionsList.appendChild(empty);
|
||||
} else {
|
||||
visibleCourierIndexes.forEach(function (optionIndex) {
|
||||
var option = courierOptions[optionIndex];
|
||||
var item = document.createElement('div');
|
||||
item.className = 'agreement-courier-option';
|
||||
item.id = 'agreement-courier-option-' + optionIndex;
|
||||
item.setAttribute('role', 'option');
|
||||
item.setAttribute('aria-selected', courierSelect.value === option.value ? 'true' : 'false');
|
||||
item.dataset.courierOption = String(optionIndex);
|
||||
item.textContent = option.value;
|
||||
courierOptionsList.appendChild(item);
|
||||
});
|
||||
|
||||
if (matches.length > visibleCourierIndexes.length) {
|
||||
var status = document.createElement('div');
|
||||
status.className = 'agreement-courier-options-status';
|
||||
status.setAttribute('role', 'status');
|
||||
status.textContent = '匹配到 ' + matches.length + ' 项,当前显示前 80 项;继续输入可缩小范围';
|
||||
courierOptionsList.appendChild(status);
|
||||
}
|
||||
}
|
||||
|
||||
courierOptionsList.hidden = false;
|
||||
courierSearch.setAttribute('aria-expanded', 'true');
|
||||
activeCourierIndex = -1;
|
||||
courierSearch.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
|
||||
function setActiveCourierOption(position) {
|
||||
if (!courierOptionsList || visibleCourierIndexes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeCourierIndex = (position + visibleCourierIndexes.length) % visibleCourierIndexes.length;
|
||||
var optionIndex = visibleCourierIndexes[activeCourierIndex];
|
||||
var optionElement = courierOptionsList.querySelector('[data-courier-option="' + optionIndex + '"]');
|
||||
courierOptionsList.querySelectorAll('.agreement-courier-option').forEach(function (item) {
|
||||
item.classList.toggle('is-active', item === optionElement);
|
||||
});
|
||||
courierSearch.setAttribute('aria-activedescendant', 'agreement-courier-option-' + optionIndex);
|
||||
optionElement && optionElement.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function syncCourierSelection() {
|
||||
if (!courierSelect || !courierSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
var enteredName = courierSearch.value.trim();
|
||||
var option = courierOptions.find(function (item) {
|
||||
return item.value.toLocaleLowerCase() === enteredName.toLocaleLowerCase();
|
||||
});
|
||||
|
||||
if (option) {
|
||||
courierSearch.value = option.value;
|
||||
courierSelect.value = option.value;
|
||||
courierSearch.setCustomValidity('');
|
||||
} else {
|
||||
courierSelect.value = '';
|
||||
courierSearch.setCustomValidity(enteredName === '' ? '请选择快递公司' : '请从快递资料中选择快递公司');
|
||||
}
|
||||
}
|
||||
|
||||
function chooseCourierOption(index) {
|
||||
if (!courierSearch || !courierSelect || !courierOptions[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
courierSearch.value = courierOptions[index].value;
|
||||
syncCourierSelection();
|
||||
closeCourierOptions();
|
||||
syncEsignAccount();
|
||||
refreshPreview();
|
||||
}
|
||||
|
||||
function initializeCourierSearch() {
|
||||
if (!courierSelect || !courierSearchWrap || !courierSearch || !courierOptionsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
courierSearchWrap.hidden = false;
|
||||
courierSelect.classList.add('agreement-courier-native-select--enhanced');
|
||||
courierSelect.setAttribute('aria-hidden', 'true');
|
||||
courierSelect.tabIndex = -1;
|
||||
courierSelect.required = false;
|
||||
courierSearch.value = courierSelect.value;
|
||||
courierSearch.required = true;
|
||||
if (courierLabel) {
|
||||
courierLabel.htmlFor = courierSearch.id;
|
||||
}
|
||||
syncCourierSelection();
|
||||
|
||||
courierSearch.addEventListener('focus', function () {
|
||||
courierSearch.select();
|
||||
renderCourierOptions('');
|
||||
});
|
||||
courierSearch.addEventListener('input', function () {
|
||||
syncCourierSelection();
|
||||
renderCourierOptions(courierSearch.value);
|
||||
syncEsignAccount();
|
||||
refreshPreview();
|
||||
});
|
||||
courierSearch.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
if (courierOptionsList.hidden) {
|
||||
renderCourierOptions(courierSearch.value);
|
||||
}
|
||||
setActiveCourierOption(activeCourierIndex + 1);
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
if (courierOptionsList.hidden) {
|
||||
renderCourierOptions(courierSearch.value);
|
||||
}
|
||||
setActiveCourierOption(activeCourierIndex < 0 ? visibleCourierIndexes.length - 1 : activeCourierIndex - 1);
|
||||
} else if (event.key === 'Enter' && !courierOptionsList.hidden) {
|
||||
event.preventDefault();
|
||||
var selectedIndex = activeCourierIndex >= 0
|
||||
? visibleCourierIndexes[activeCourierIndex]
|
||||
: visibleCourierIndexes.find(function (optionIndex) {
|
||||
return courierOptions[optionIndex].value.toLocaleLowerCase() === courierSearch.value.trim().toLocaleLowerCase();
|
||||
});
|
||||
if (selectedIndex === undefined && visibleCourierIndexes.length === 1) {
|
||||
selectedIndex = visibleCourierIndexes[0];
|
||||
}
|
||||
if (selectedIndex !== undefined) {
|
||||
chooseCourierOption(selectedIndex);
|
||||
}
|
||||
} else if (event.key === 'Escape') {
|
||||
closeCourierOptions();
|
||||
}
|
||||
});
|
||||
courierSearch.addEventListener('blur', function () {
|
||||
window.setTimeout(function () {
|
||||
if (courierControl && !courierControl.contains(document.activeElement)) {
|
||||
closeCourierOptions();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
courierOptionsList.addEventListener('mousedown', function (event) {
|
||||
if (event.target.closest('[data-courier-option]')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
courierOptionsList.addEventListener('click', function (event) {
|
||||
var optionElement = event.target.closest('[data-courier-option]');
|
||||
if (optionElement) {
|
||||
chooseCourierOption(Number(optionElement.dataset.courierOption));
|
||||
}
|
||||
});
|
||||
root.addEventListener('click', function (event) {
|
||||
if (courierControl && !courierControl.contains(event.target)) {
|
||||
closeCourierOptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setText(selector, value) {
|
||||
root.querySelectorAll(selector).forEach(function (target) {
|
||||
target.textContent = value;
|
||||
@ -2718,6 +2984,10 @@
|
||||
|
||||
if (courierSelect) {
|
||||
courierSelect.addEventListener('change', function () {
|
||||
if (courierSearch) {
|
||||
courierSearch.value = courierSelect.value;
|
||||
syncCourierSelection();
|
||||
}
|
||||
syncEsignAccount();
|
||||
refreshPreview();
|
||||
});
|
||||
@ -2755,6 +3025,10 @@
|
||||
});
|
||||
if (courierOption) {
|
||||
courierSelect.value = option.dataset.carrier;
|
||||
if (courierSearch) {
|
||||
courierSearch.value = option.dataset.carrier;
|
||||
syncCourierSelection();
|
||||
}
|
||||
syncEsignAccount();
|
||||
}
|
||||
}
|
||||
@ -2829,6 +3103,7 @@
|
||||
|
||||
var initialUrl = new URL(window.location.href);
|
||||
var initialView = initialUrl.searchParams.get('agreement_view') || 'tasks';
|
||||
initializeCourierSearch();
|
||||
renderSubjectSelect();
|
||||
renderSubjectList();
|
||||
showTab(initialView, { updateUrl: false });
|
||||
|
||||
@ -63,6 +63,10 @@ class AgreementSigningPageTest extends FeatureTestCase
|
||||
->assertDontSee('data-agreement-step-panel', false)
|
||||
->assertSeeText('发起签章请求')
|
||||
->assertSeeText('快递公司')
|
||||
->assertSee('data-courier-search', false)
|
||||
->assertSee('placeholder="输入快递名称搜索"', false)
|
||||
->assertSee('aria-haspopup="listbox"', false)
|
||||
->assertSee('data-courier-options', false)
|
||||
->assertSeeText('文件类型')
|
||||
->assertSeeText('收款账户证明')
|
||||
->assertSeeText('TPL-ACCOUNT-CERT-V2')
|
||||
@ -118,6 +122,8 @@ class AgreementSigningPageTest extends FeatureTestCase
|
||||
'agreement_view' => 'create',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSee('data-courier-search', false)
|
||||
->assertSee('data-courier-select required', false)
|
||||
->assertSee('<option value="顺丰" data-esign-account="13800000001">顺丰</option>', false)
|
||||
->assertSee('<option value="韵达" data-esign-account="">韵达</option>', false)
|
||||
->assertDontSee('<option value="新北中通"', false);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user