Commit 33c4ab5c authored by 黄同智's avatar 黄同智

复刻项目

parent eacb3d4c
...@@ -155,6 +155,53 @@ ...@@ -155,6 +155,53 @@
padding-left: 4px; padding-left: 4px;
} }
/* 气泡框 */
.bubble {
position: absolute;
bottom: calc(100% + 14px);
left: -25px;
width: 150px;
padding: 10px 5px;
color: #000000;
border-radius: 4px;
border: 1px solid #62A7E3;
background-color: #EAF5FE;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease, visibility 0.2s ease;
z-index: 100;
pointer-events: none;
}
/* 显示气泡 */
.form-question:hover .bubble {
visibility: visible;
opacity: 1;
}
.bubble::before {
content: '';
position: absolute;
bottom: -12px;
left: 23px;
border-width: 12px 4px 0 10px;
border-style: solid;
border-color: #62A7E3 transparent transparent transparent;
z-index: 1;
}
/* 背景三角(内层,盖住部分边框) */
.bubble::after {
content: '';
position: absolute;
bottom: -9px;
left: 24px;
border-width: 9px 4px 0 8px;
border-style: solid;
border-color: #EAF5FE transparent transparent transparent;
z-index: 2;
}
.question1 { .question1 {
margin-left: 30px; margin-left: 30px;
} }
...@@ -194,10 +241,12 @@ ...@@ -194,10 +241,12 @@
bottom: 64px; bottom: 64px;
} }
.yy { .yy {
left: 264px; left: 264px;
bottom: 64px; bottom: 64px;
} }
.zz { .zz {
left: 390px; left: 390px;
bottom: 64px; bottom: 64px;
...@@ -285,12 +334,14 @@ ...@@ -285,12 +334,14 @@
<div class="form-row"> <div class="form-row">
<label class="form-label"><span class="required">*</span>起止日期:</label> <label class="form-label"><span class="required">*</span>起止日期:</label>
<input type="text" id="dateFrom" class="form-input small x-small" name="dateFrom"> <input type="text" id="dateFrom" class="form-input small x-small" name="dateFrom"
oninput="startDate = this.value" onblur="handleBlur('dateFrom')">
<div class="date-picker" id="sDate"> <div class="date-picker" id="sDate">
<img class="dateimg" src="./images/date.png" alt="" /> <img class="dateimg" src="./images/date.png" alt="" />
</div> </div>
<span>-</span> <span>-</span>
<input type="text" id="dateTo" class="form-input small x-small" name="dateTo"> <input type="text" id="dateTo" class="form-input small x-small" name="dateTo"
oninput="endDate = this.value" onblur="handleBlur('dateTo')">
<div class="date-picker" id="eDate"> <div class="date-picker" id="eDate">
<img class="dateimg" src="./images/date.png" alt="" /> <img class="dateimg" src="./images/date.png" alt="" />
<!-- <input class="date-input dateTo" type="date" /> --> <!-- <input class="date-input dateTo" type="date" /> -->
...@@ -300,7 +351,11 @@ ...@@ -300,7 +351,11 @@
<label><input type="radio" name="quickDate" value="180" /> 最近180天</label> <label><input type="radio" name="quickDate" value="180" /> 最近180天</label>
<label><input type="radio" name="quickDate" value="365" /> 最近365天</label> <label><input type="radio" name="quickDate" value="365" /> 最近365天</label>
</div> </div>
<span class="form-question question1"></span> <span class="form-question question1">
<span class="bubble">
请输入或选择最近10年内的日期;每次查询时间段不超过1年;日期输入标准格式20180101,表示2018年1月1日
</span>
</span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label class="form-label">交易金额:</label> <label class="form-label">交易金额:</label>
...@@ -318,7 +373,7 @@ ...@@ -318,7 +373,7 @@
<label class="form-label">对方账号:</label> <label class="form-label">对方账号:</label>
<input type="text" class="form-input wide" name="counterAccount"> <input type="text" class="form-input wide" name="counterAccount">
</div> </div>
<div class="form-row"> <div class="form-row">
<label class="form-label">交易摘要:</label> <label class="form-label">交易摘要:</label>
<!-- name="summary" --> <!-- name="summary" -->
...@@ -334,16 +389,21 @@ ...@@ -334,16 +389,21 @@
<input type="text" class="form-input small" name="detailFrom"> <input type="text" class="form-input small" name="detailFrom">
<span class="px-2">-</span> <span class="px-2">-</span>
<input type="text" class="form-input small" name="detailTo"> <input type="text" class="form-input small" name="detailTo">
<span class="form-question"></span> <span class="form-question">
<span class="bubble">
仅输入起始编号时,查询该编号(含)之后的明细;仅输入截止编号时,查询该编号(含)之前的明细;起止编号同时输入时,截止编号需大于或等于起始编号。
</span>
</span>
</div> </div>
<!-- 按钮组 --> <!-- 按钮组 -->
<div style="height: 90px;"></div> <div style="height: 90px;"></div>
<button class="buttonx xx" onclick="onAccount(1)" type="submit">确定</button> <button class="buttonx xx" onclick="onAccount(1)" type="submit">确定</button>
</form> </form>
<button class="buttonx yy" onclick="onAccount(2)">返回</button> <button class="buttonx yy" onclick="onAccount(2)">返回</button>
<button onclick="onAccount(3)" type="submit" class="buttonx zz" style="width: auto;padding: 0 20px;">异步下载查询</button> <button onclick="onAccount(3)" type="submit" class="buttonx zz"
style="width: auto;padding: 0 20px;">异步下载查询</button>
</div> </div>
<!-- 温馨提示 --> <!-- 温馨提示 -->
...@@ -357,25 +417,66 @@ ...@@ -357,25 +417,66 @@
</html> </html>
<script> <script>
/**
* 解析 YYYYMMDD 格式为 Date 对象
* @param {string} dateStr - 8位数字字符串,如 20250101
* @returns {Date}
*/
function parseYYYYMMDD(dateStr) {
const year = parseInt(dateStr.substring(0, 4));
const month = parseInt(dateStr.substring(4, 6)) - 1;
const day = parseInt(dateStr.substring(6, 8));
return new Date(year, month, day);
}
/**
* 检测 YYYYMMDD 格式的日期间隔是否大于365天
* @param {string} startStr - 开始日期,如 20250101
* @param {string} endStr - 结束日期,如 20260101
* @returns {boolean}
*/
function isOver365DaysYYYYMMDD(startStr, endStr) {
const start = parseYYYYMMDD(startStr);
const end = parseYYYYMMDD(endStr);
const diffDays = (end - start) / (1000 * 60 * 60 * 24);
return Math.abs(diffDays) > 365;
}
let startDate = '';
let endDate = '';
const handleBlur = (dateId) => {
console.log('handleBlur', startDate, endDate, isOver365DaysYYYYMMDD(startDate, endDate));
if (startDate && endDate && isOver365DaysYYYYMMDD(startDate, endDate)) {
alert('起止日期间隔不能超过365天');
if (dateId === 'dateFrom') {
startDate = '';
document.getElementById(dateId).value = '未选择';
} else {
endDate = '';
document.getElementById(dateId).value = '未选择';
}
const checkedQuickDate = document.querySelector('input[name="quickDate"]:checked');
checkedQuickDate && (checkedQuickDate.checked = false);
}
};
const pickerStart = new CalendarPlugin({ const pickerStart = new CalendarPlugin({
trigger: '#sDate', trigger: '#sDate',
onSelect: function (date) { onSelect: function (date) {
const result = date.replaceAll('-', ''); startDate = date.replaceAll('-', '');
document.getElementById('dateFrom').value = result || '未选择'; handleBlur('dateFrom');
} }
}); });
const pickerEnd = new CalendarPlugin({ const pickerEnd = new CalendarPlugin({
trigger: '#eDate', trigger: '#eDate',
onSelect: function (date) { onSelect: function (date) {
const result = date.replaceAll('-', ''); endDate = date.replaceAll('-', '');
document.getElementById('dateTo').value = result || '未选择'; handleBlur('dateTo');
} }
}); });
// 快速日期 // 快速日期
document.querySelectorAll('input[name=quickDate]').forEach(el => { document.querySelectorAll('input[name=quickDate]').forEach(el => {
...@@ -391,8 +492,10 @@ ...@@ -391,8 +492,10 @@
const today = new Date(formattedDate); const today = new Date(formattedDate);
const from = new Date(today.getTime() - days * 86400000); const from = new Date(today.getTime() - days * 86400000);
const fmt = d => d.getFullYear() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0'); const fmt = d => d.getFullYear() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0');
document.getElementById('dateFrom').value = fmt(from); startDate = fmt(from);
document.getElementById('dateTo').value = fmt(today); endDate = fmt(today);
document.getElementById('dateFrom').value = startDate;
document.getElementById('dateTo').value = endDate;
}); });
}); });
...@@ -401,7 +504,7 @@ ...@@ -401,7 +504,7 @@
function onAccount(number) { function onAccount(number) {
if( number == 1) { if (number == 1) {
form.addEventListener('submit', async (e) => { form.addEventListener('submit', async (e) => {
e.preventDefault(); // 阻止页面刷新 e.preventDefault(); // 阻止页面刷新
......
...@@ -157,15 +157,18 @@ ...@@ -157,15 +157,18 @@
transition: all 0.2s; transition: all 0.2s;
} }
/* 气泡框 - 完全参照截图风格:白底、圆角、箭头、阴影,左侧竖条装饰+列表排版 */ /* 气泡框 */
.bubble { .bubble {
position: absolute; position: absolute;
bottom: calc(100% + 14px); bottom: calc(100% + 14px);
left: 0; left: -25px;
width: 320px; width: 150px;
background: #ffffff; padding: 10px 5px;
border-radius: 16px; text-align: left;
box-shadow: 0 20px 35px -10px rgba(0, 0, 0, 0.2), 0 1px 3px rgba(0, 0, 0, 0.05); color: #000000;
border-radius: 4px;
border: 1px solid #62A7E3;
background-color: #EAF5FE;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
transition: opacity 0.2s ease, visibility 0.2s ease; transition: opacity 0.2s ease, visibility 0.2s ease;
...@@ -174,21 +177,32 @@ ...@@ -174,21 +177,32 @@
} }
/* 显示气泡 */ /* 显示气泡 */
.question:hover .bubble { .form-question:hover .bubble {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
/* 箭头:朝下(因为气泡在上方,箭头指向触发元素) */ .bubble::before {
content: '';
position: absolute;
bottom: -12px;
left: 23px;
border-width: 12px 4px 0 10px;
border-style: solid;
border-color: #62A7E3 transparent transparent transparent;
z-index: 1;
}
/* 背景三角(内层,盖住部分边框) */
.bubble::after { .bubble::after {
content: ''; content: '';
position: absolute; position: absolute;
bottom: -8px; bottom: -9px;
left: 24px; left: 24px;
border-width: 8px 8px 0 8px; border-width: 9px 4px 0 8px;
border-style: solid; border-style: solid;
border-color: #ffffff transparent transparent transparent; border-color: #EAF5FE transparent transparent transparent;
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); z-index: 2;
} }
.question1 { .question1 {
...@@ -328,12 +342,14 @@ ...@@ -328,12 +342,14 @@
<div class="form-row"> <div class="form-row">
<label class="form-label"><span class="required">*</span>起止日期:</label> <label class="form-label"><span class="required">*</span>起止日期:</label>
<input type="text" id="dateFrom" class="form-input small x-small" name="dateFrom"> <input type="text" id="dateFrom" class="form-input small x-small" name="dateFrom"
oninput="startDate = this.value" onblur="handleBlur('dateFrom')">
<div class="date-picker" id="sDate"> <div class="date-picker" id="sDate">
<img class="dateimg" src="./images/date.png" alt="" /> <img class="dateimg" src="./images/date.png" alt="" />
</div> </div>
<span>-</span> <span>-</span>
<input type="text" id="dateTo" class="form-input small x-small" name="dateTo"> <input type="text" id="dateTo" class="form-input small x-small" name="dateTo"
oninput="endDate = this.value" onblur="handleBlur('dateTo')">
<div class="date-picker" id="eDate"> <div class="date-picker" id="eDate">
<img class="dateimg" src="./images/date.png" alt="" /> <img class="dateimg" src="./images/date.png" alt="" />
<!-- <input class="date-input dateTo" type="date" /> --> <!-- <input class="date-input dateTo" type="date" /> -->
...@@ -345,7 +361,7 @@ ...@@ -345,7 +361,7 @@
</div> </div>
<span class="form-question question1"> <span class="form-question question1">
<span class="bubble"> <span class="bubble">
好景不长v计划表v看见啊办法深V合计阿萨办法v啊好几把送达方v科技啊表达法v 请输入或选择最近一年半内日期;每次查询时间段不超过3个月;日期标准输入格式20180101,表示2018年1月1日
</span> </span>
</span> </span>
</div> </div>
...@@ -375,7 +391,12 @@ ...@@ -375,7 +391,12 @@
<label><input type="radio" name="reverse" checked> 显示</label> <label><input type="radio" name="reverse" checked> 显示</label>
<label><input type="radio" name="reverse"> 不显示</label> <label><input type="radio" name="reverse"> 不显示</label>
</div> </div>
<span class="form-question question2"></span> <span class="form-question question2">
<span class="bubble">
冲正交易流水查询仅供查询使用,不提供电子回执
</span>
</span>
</div> </div>
<div class="form-row mt-20"> <div class="form-row mt-20">
...@@ -409,7 +430,12 @@ ...@@ -409,7 +430,12 @@
<input type="text" class="form-input small" name="detailFrom"> <input type="text" class="form-input small" name="detailFrom">
<span class="px-2">-</span> <span class="px-2">-</span>
<input type="text" class="form-input small" name="detailTo"> <input type="text" class="form-input small" name="detailTo">
<span class="form-question"></span> <span class="form-question">
<span class="bubble">
仅输入起始编号时,查询该编号(含)之后的明细;仅输入截止编号时,查询该编号(含)之前的明细;起止编号同时输入时,截止编号需大于或等于起始编号。
</span>
</span>
</div> </div>
<div style="height: 90px;"></div> <div style="height: 90px;"></div>
...@@ -438,26 +464,67 @@ ...@@ -438,26 +464,67 @@
</html> </html>
<script> <script>
/**
* 解析 YYYYMMDD 格式为 Date 对象
* @param {string} dateStr - 8位数字字符串,如 20250101
* @returns {Date}
*/
function parseYYYYMMDD(dateStr) {
const year = parseInt(dateStr.substring(0, 4));
const month = parseInt(dateStr.substring(4, 6)) - 1;
const day = parseInt(dateStr.substring(6, 8));
return new Date(year, month, day);
}
/**
* 检测 YYYYMMDD 格式的日期间隔是否大于365天
* @param {string} startStr - 开始日期,如 20250101
* @param {string} endStr - 结束日期,如 20260101
* @returns {boolean}
*/
function isOver365DaysYYYYMMDD(startStr, endStr) {
const start = parseYYYYMMDD(startStr);
const end = parseYYYYMMDD(endStr);
const diffDays = (end - start) / (1000 * 60 * 60 * 24);
return Math.abs(diffDays) > 365;
}
let startDate = '';
let endDate = '';
const handleBlur = (dateId) => {
console.log('handleBlur', startDate, endDate, isOver365DaysYYYYMMDD(startDate, endDate));
if (startDate && endDate && isOver365DaysYYYYMMDD(startDate, endDate)) {
alert('起止日期间隔不能超过365天');
if (dateId === 'dateFrom') {
startDate = '';
document.getElementById(dateId).value = '未选择';
} else {
endDate = '';
document.getElementById(dateId).value = '未选择';
}
const checkedQuickDate = document.querySelector('input[name="quickDate"]:checked');
checkedQuickDate && (checkedQuickDate.checked = false);
}
};
const pickerStart = new CalendarPlugin({ const pickerStart = new CalendarPlugin({
trigger: '#sDate', trigger: '#sDate',
onSelect: function (date) { onSelect: function (date) {
const result = date.replaceAll('-', ''); startDate = date.replaceAll('-', '');
document.getElementById('dateFrom').value = result || '未选择'; handleBlur('dateFrom');
} }
}); });
const pickerEnd = new CalendarPlugin({ const pickerEnd = new CalendarPlugin({
trigger: '#eDate', trigger: '#eDate',
onSelect: function (date) { onSelect: function (date) {
const result = date.replaceAll('-', ''); endDate = date.replaceAll('-', '');
document.getElementById('dateTo').value = result || '未选择'; handleBlur('dateTo');
} }
}); });
// 快速日期 // 快速日期
document.querySelectorAll('input[name=quickDate]').forEach(el => { document.querySelectorAll('input[name=quickDate]').forEach(el => {
el.addEventListener('change', () => { el.addEventListener('change', () => {
...@@ -470,15 +537,15 @@ ...@@ -470,15 +537,15 @@
const formattedDate = `${year}-${month}-${day}`; const formattedDate = `${year}-${month}-${day}`;
const today = new Date(formattedDate); const today = new Date(formattedDate);
const from = new Date(today.getTime() - days * 86400000); const from = new Date(today.getTime() - days * 86400000);
const fmt = d => d.getFullYear() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0'); const fmt = d => d.getFullYear() + String(d.getMonth() + 1).padStart(2, '0') + String(d.getDate()).padStart(2, '0');
document.getElementById('dateFrom').value = fmt(from); startDate = fmt(from);
document.getElementById('dateTo').value = fmt(today); endDate = fmt(today);
document.getElementById('dateFrom').value = startDate;
document.getElementById('dateTo').value = endDate;
}); });
}); });
const form = document.getElementById('myForm'); const form = document.getElementById('myForm');
function onAccount(number) { function onAccount(number) {
...@@ -488,9 +555,9 @@ ...@@ -488,9 +555,9 @@
// 方式1:使用 FormData 自动搜集 // 方式1:使用 FormData 自动搜集
const formData = new FormData(form); const formData = new FormData(form);
localStorage.setItem('accountQueryParams', JSON.stringify(Object.fromEntries(formData.entries())));
setTimeout(() => { setTimeout(() => {
window.location.href = './account_list.html' window.localStorage.setItem('dateFrom', startDate);
window.location.href = './account_list.html';
}, 500); }, 500);
}); });
} }
......
...@@ -160,14 +160,13 @@ ...@@ -160,14 +160,13 @@
.detail-table th { .detail-table th {
background-color: #c8e0f5; background-color: #c8e0f5;
padding: 0px 5px; padding: 0px 5px;
text-align: left;
font-weight: normal; font-weight: normal;
} }
.detail-table tr:nth-child(2n+1) { .detail-table tr:nth-child(2n+1) {
background-color: #ebf4fb; background-color: #f7fafd;
} }
.detail-table tr:nth-child(2n) { .detail-table tr:nth-child(2n) {
...@@ -175,7 +174,7 @@ ...@@ -175,7 +174,7 @@
} }
.detail-table tr:hover { .detail-table tr:hover {
background-color: #ebf4fb; background-color: #f7fafd;
} }
.detail-table td { .detail-table td {
...@@ -301,6 +300,8 @@ ...@@ -301,6 +300,8 @@
/* 箭头 */ /* 箭头 */
.detail-table .xflex { .detail-table .xflex {
width: fit-content;
margin: 0 auto;
display: flex; display: flex;
text-align: center; text-align: center;
align-items: flex-end; align-items: flex-end;
...@@ -337,7 +338,7 @@ ...@@ -337,7 +338,7 @@
font-size: 14px; font-size: 14px;
} }
table.grid td.center { table.grid td {
text-align: center; text-align: center;
} }
...@@ -348,6 +349,7 @@ ...@@ -348,6 +349,7 @@
th { th {
height: 70px; height: 70px;
cursor: pointer;
} }
/* table.grid td.col-select, /* table.grid td.col-select,
...@@ -509,6 +511,10 @@ ...@@ -509,6 +511,10 @@
background: url(https://xc.b2bstatic3.ccb.com/V6/STY6/CN/images7/tablePrint.png) no-repeat left center; background: url(https://xc.b2bstatic3.ccb.com/V6/STY6/CN/images7/tablePrint.png) no-repeat left center;
} }
.more {
background: url(https://xc.b2bstatic3.ccb.com/V6/STY6/CN/images7/yun_genDuo.png) no-repeat left center;
}
.moreUl li { .moreUl li {
width: 150px !important; width: 150px !important;
display: flex; display: flex;
...@@ -644,7 +650,7 @@ ...@@ -644,7 +650,7 @@
</div> </div>
<div class="tableLBtn_box"> <div class="tableLBtn_box">
<div class="tableLBtn_active"> <div class="tableLBtn_active">
<a href="#" class="tableRBtn download">更多功能</a> <a href="#" class="tableRBtn more">更多功能</a>
</div> </div>
<div class="downloadBox downloadBox2" style="left: 0;"> <div class="downloadBox downloadBox2" style="left: 0;">
<ul class="moreUl"> <ul class="moreUl">
...@@ -665,8 +671,8 @@ ...@@ -665,8 +671,8 @@
<table class="detail-table grid" id="resultTable"> <table class="detail-table grid" id="resultTable">
<thead> <thead>
<tr> <tr>
<th class="col-select" style="width: 42px;">选择</th> <th class="col-select" style="width: 44px;">选择</th>
<th style="width: 77px;"> <th style="width: 88px;">
<div class="xflex"> <div class="xflex">
<span>交易时间 </span> <span>交易时间 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -675,7 +681,7 @@ ...@@ -675,7 +681,7 @@
</div> </div>
</div> </div>
</th> </th>
<th colspan="2" style="width: 220px;"> <th colspan="2" style="width: 211px;">
<div class="thbox"> <div class="thbox">
<div>发生额/元</div> <div>发生额/元</div>
<div class="flex"> <div class="flex">
...@@ -696,7 +702,7 @@ ...@@ -696,7 +702,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 110px;"> <th style="width: 88px;">
<div class="xflex"> <div class="xflex">
<span>余额 </span> <span>余额 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -705,7 +711,7 @@ ...@@ -705,7 +711,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 151px;"> <th style="width: 166px;">
<div class="xflex"> <div class="xflex">
<span>对方户名 </span> <span>对方户名 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -714,7 +720,7 @@ ...@@ -714,7 +720,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 150px;"> <th style="width: 143px;">
<div class="xflex"> <div class="xflex">
<span>对方账号 </span> <span>对方账号 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -723,7 +729,7 @@ ...@@ -723,7 +729,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 101px;"> <th style="width: 104px;">
<div class="xflex"> <div class="xflex">
<span>对方<br>开户机构 </span> <span>对方<br>开户机构 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -732,7 +738,7 @@ ...@@ -732,7 +738,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 86px;"> <th style="width: 88px;">
<div class="xflex"> <div class="xflex">
<span>记账日期 </span> <span>记账日期 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -741,7 +747,7 @@ ...@@ -741,7 +747,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 76px;"> <th style="width: 85px;">
<div class="xflex"> <div class="xflex">
<span>摘要 </span> <span>摘要 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -750,7 +756,7 @@ ...@@ -750,7 +756,7 @@
</div> </div>
</div> </div>
</th> </th>
<th style="width: 101px;"> <th style="width: 95px;">
<div class="xflex"> <div class="xflex">
<span>备注 </span> <span>备注 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -768,7 +774,7 @@ ...@@ -768,7 +774,7 @@
</div> </div>
</div> </div>
</th> </th>
<th class="thmore" style="width: 70px;display: none;"> <th class="thmore" style="width: 64px;display: none;">
<div class="xflex"> <div class="xflex">
<span>企业-<br>流水号 </span> <span>企业-<br>流水号 </span>
<div class="jiantou"> <div class="jiantou">
...@@ -795,7 +801,7 @@ ...@@ -795,7 +801,7 @@
</div> </div>
</div> </div>
</th> </th>
<th class="thmore" style="width: 108px;display: none;"> <th class="thmore" style="width: 109px;display: none;">
<div class="xflex"> <div class="xflex">
<span>交易介质编号</span> <span>交易介质编号</span>
<div class="jiantou"> <div class="jiantou">
...@@ -807,7 +813,7 @@ ...@@ -807,7 +813,7 @@
<th style="width: 40px;"> <th style="width: 40px;">
<div id="moreright" class="lashen" onclick="lookmore()">>></div> <div id="moreright" class="lashen" onclick="lookmore()">>></div>
<div id="moreleft" style="display: none;" class="lashen" onclick="noLookmore()"> <div id="moreleft" style="display: none;" class="lashen" onclick="noLookmore()">
<<< /div> << </div>
</th> </th>
</tr> </tr>
</thead> </thead>
...@@ -817,7 +823,7 @@ ...@@ -817,7 +823,7 @@
<div class="empty" id="emptyMsg" style="display:none;">未查询到符合条件的记录</div> <div class="empty" id="emptyMsg" style="display:none;">未查询到符合条件的记录</div>
<!-- 工具栏/统计栏 --> <!-- 工具栏/统计栏 -->
<div class="toolbar-box"> <div class="toolbar-box">
<div class="toolbar"> <!-- <div class="toolbar">
<div class="toolbar-left"> <div class="toolbar-left">
<label><input type="checkbox"> 全选</label> <label><input type="checkbox"> 全选</label>
<span>本次查询时间:20250901-20260428</span> <span>本次查询时间:20250901-20260428</span>
...@@ -826,13 +832,13 @@ ...@@ -826,13 +832,13 @@
<a href="#"><img src="/images/tableDownload.png" alt=""> 下载当前页</a> <a href="#"><img src="/images/tableDownload.png" alt=""> 下载当前页</a>
<a href="#"><img src="/images/tableDownload.png" alt=""> 下载全部</a> <a href="#"><img src="/images/tableDownload.png" alt=""> 下载全部</a>
</div> </div>
</div> </div> -->
<!-- 工具栏 --> <!-- 工具栏 -->
<!-- <div class="toolbar"> <div class="toolbar2">
<div class="left"> <div class="toolbar-left">
<input type="checkbox" id="filterCheckbox" onclick="openFilter(this)"> <label><input type="checkbox"> 全选</label>
<span>筛选</span> <span>本次查询时间:20250901-20260428</span>
</div> </div>
<div id="rightType"> <div id="rightType">
<div class="right"> <div class="right">
...@@ -845,7 +851,7 @@ ...@@ -845,7 +851,7 @@
<li onclick="submitSelect('0','1')" class="firstLi">Txt下载</li> <li onclick="submitSelect('0','1')" class="firstLi">Txt下载</li>
<li onclick="submitSelect('0','2')">Excel下载</li> <li onclick="submitSelect('0','2')">Excel下载</li>
<li onclick="submitSelect('0','3')">Csv下载</li> <li onclick="submitSelect('0','3')">Csv下载</li>
<li onclick="submitSelect('0','4')">PDF下载</li> <li onclick="downloadPDF2()">PDF下载</li>
</ul> </ul>
</div> </div>
</div> </div>
...@@ -858,7 +864,21 @@ ...@@ -858,7 +864,21 @@
<li onclick="submitSelect('0','1')" class="firstLi">Txt下载</li> <li onclick="submitSelect('0','1')" class="firstLi">Txt下载</li>
<li onclick="submitSelect('0','2')">Excel下载</li> <li onclick="submitSelect('0','2')">Excel下载</li>
<li onclick="submitSelect('0','3')">Csv下载</li> <li onclick="submitSelect('0','3')">Csv下载</li>
<li onclick="submitSelect('0','4')">PDF下载</li> <li onclick="downloadPDF2()">PDF下载</li>
</ul>
</div>
</div>
<div class="tableLBtn_box">
<div class="tableLBtn_active">
<a href="#" class="tableRBtn download">下载其他格式</a>
</div>
<div class="downloadBox" style="left: 0;">
<ul>
<li class="firstLi">网银Txt</li>
<li>网银Excel</li>
<li>网银Csv</li>
<li>现金Txt</li>
<li>现金Excel</li>
</ul> </ul>
</div> </div>
</div> </div>
...@@ -875,7 +895,7 @@ ...@@ -875,7 +895,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> --> </div>
<!-- 交易统计+分页栏 --> <!-- 交易统计+分页栏 -->
<div class="toolbar" style="border-top:none;"> <div class="toolbar" style="border-top:none;">
...@@ -1127,81 +1147,119 @@ ...@@ -1127,81 +1147,119 @@
return bytes; return bytes;
} }
// 数字字形 CID 表 (从 PDF 字体反推) function selectPdfByYear(year) {
const DIGIT_CID = { if (year === '2024') return PDF_2024_B64;
'0': '0013', '1': '0014', '2': '0015', '3': '0016', '4': '0017', if (year === '2025') return PDF_2025_B64;
'5': '0018', '6': '0019', '7': '001A', '8': '001B', '9': '001C' if (year === '2026') return PDF_2026_B64;
}; return PDF_2026_B64;
const TE = new TextEncoder(); }
const TD = new TextDecoder('latin1'); const TD = new TextDecoder('latin1');
// 数字 0-9 对应的 CID (来自 PDF 字体)
const DIGIT_CIDS = ['0013', '0014', '0015', '0016', '0017', '0018', '0019', '001a', '001b', '001c'];
const DIGIT_SET = new Set(DIGIT_CIDS);
const CID_YEAR = '14a4'; // 年
const CID_MONTH = '1d38'; // 月
const CID_DAY = '1c15'; // 日
const CID_COLON = '001d'; // :
// 扫描 inflated 流, 把所有 <...> 中的 4 字符 hex CID 抽出, 记录每个 CID 的字节偏移
function extractCids(inflated) {
const text = TD.decode(inflated);
const cids = [];
const re = /<([0-9a-fA-F]+)>/g;
let m;
while ((m = re.exec(text)) !== null) {
const hex = m[1];
if (hex.length % 4 !== 0) continue;
const groupStart = m.index + 1;
for (let k = 0; k < hex.length; k += 4) {
const orig = hex.substr(k, 4);
cids.push({ cid: orig.toLowerCase(), offset: groupStart + k, origCase: orig });
}
}
return cids;
}
// "2026年02月28日" 的字节模式 — 11 个 CID + kerning 组成的固定 ASCII 串 // 在 inflated 中找日期序列(4 数字+年+2 数字+月+2 数字+日), 替换成新日期;
// <2><0><2><6><年><0><2><月><2><8><日> // 紧随其后 5 个 CID 内若出现(2 数字+冒号+2 数字), 替换成新时间
function buildDateBytes(year, month, day) { // 返回 {bytes, dates, times} 或 null
const Y = String(year).padStart(4, '0').split(''); function patchInflated(inflated, year, month, day, hour, minute) {
const M = String(month).padStart(2, '0').split(''); const cids = extractCids(inflated);
const D = String(day).padStart(2, '0').split(''); const Y = String(year).padStart(4, '0');
return TE.encode( const M = String(month).padStart(2, '0');
`<${DIGIT_CID[Y[0]]}>-10.000000<${DIGIT_CID[Y[1]]}>` + const D = String(day).padStart(2, '0');
`<${DIGIT_CID[Y[2]]}>-10.000000<${DIGIT_CID[Y[3]]}>` + const H = String(hour).padStart(2, '0');
`-10.000000<14A4>` + const Mn = String(minute).padStart(2, '0');
`<${DIGIT_CID[M[0]]}>-10.000000<${DIGIT_CID[M[1]]}>` + const newDate = (Y + M + D).split('');
`<1D38>` + const newTime = (H + Mn).split('');
`<${DIGIT_CID[D[0]]}>-10.000000<${DIGIT_CID[D[1]]}>` +
`-10.000000<1C15>` const repls = [];
); let nDates = 0, nTimes = 0;
} const pushRepl = (cid, digit) => {
// "09:42" 时间的字节模式 — 5 个 CID let nc = DIGIT_CIDS[parseInt(digit, 10)];
function buildTimeBytes(hour, minute) { if (/[A-F]/.test(cid.origCase)) nc = nc.toUpperCase();
const H = String(hour).padStart(2, '0').split(''); repls.push({ offset: cid.offset, newCid: nc });
const Mn = String(minute).padStart(2, '0').split(''); };
return TE.encode(
`<${DIGIT_CID[H[0]]}><${DIGIT_CID[H[1]]}>` +
`-10.000000<001D>-10.000000` +
`<${DIGIT_CID[Mn[0]]}>-10.000000<${DIGIT_CID[Mn[1]]}>`
);
}
function indexOfBytes(haystack, needle, fromIdx = 0) { let i = 0;
outer: for (let i = fromIdx; i <= haystack.length - needle.length; i++) { while (i <= cids.length - 11) {
for (let j = 0; j < needle.length; j++) { const c = cids;
if (haystack[i + j] !== needle[j]) continue outer; const ok =
DIGIT_SET.has(c[i].cid) && DIGIT_SET.has(c[i + 1].cid) &&
DIGIT_SET.has(c[i + 2].cid) && DIGIT_SET.has(c[i + 3].cid) &&
c[i + 4].cid === CID_YEAR &&
DIGIT_SET.has(c[i + 5].cid) && DIGIT_SET.has(c[i + 6].cid) &&
c[i + 7].cid === CID_MONTH &&
DIGIT_SET.has(c[i + 8].cid) && DIGIT_SET.has(c[i + 9].cid) &&
c[i + 10].cid === CID_DAY;
if (!ok) { i++; continue; }
[0, 1, 2, 3, 5, 6, 8, 9].forEach((p, idx) => pushRepl(c[i + p], newDate[idx]));
nDates++;
// 仅在日期附近找时间(避开数据表里的交易时间), 上限往后 5 个 CID
const lookEnd = Math.min(cids.length - 5, i + 16);
for (let j = i + 11; j <= lookEnd; j++) {
if (
DIGIT_SET.has(c[j].cid) && DIGIT_SET.has(c[j + 1].cid) &&
c[j + 2].cid === CID_COLON &&
DIGIT_SET.has(c[j + 3].cid) && DIGIT_SET.has(c[j + 4].cid)
) {
[0, 1, 3, 4].forEach((p, idx) => pushRepl(c[j + p], newTime[idx]));
nTimes++;
break;
}
} }
return i; i += 11;
} }
return -1;
}
function replaceAllBytes(buf, oldBytes, newBytes) { if (!repls.length) return null;
if (oldBytes.length !== newBytes.length) throw new Error('length mismatch'); const out = new Uint8Array(inflated);
let n = 0, idx = 0; for (const r of repls) {
while ((idx = indexOfBytes(buf, oldBytes, idx)) >= 0) { for (let k = 0; k < 4; k++) out[r.offset + k] = r.newCid.charCodeAt(k);
buf.set(newBytes, idx);
idx += newBytes.length;
n++;
} }
return n; return { bytes: out, dates: nDates, times: nTimes };
} }
// 解析 PDF 顶层结构,遍历所有 FlateDecode 流,inflate→替换→deflate+pad→写回
function patchPdf(pdfBytes, oldDate, newDate, oldTime, newTime) { // 解析 PDF 顶层结构, 遍历所有 FlateDecode 流, inflate→替换→deflate+pad→写回
// 用 latin1 视角扫描 stream/endstream 标记 (字节 1:1) function patchPdf(pdfBytes, year, month, day, hour, minute) {
const text = TD.decode(pdfBytes); const text = TD.decode(pdfBytes);
const re = /(\d+)\s+(\d+)\s+obj\s*([\s\S]*?)\bstream\r?\n([\s\S]*?)\r?\nendstream/g; const re = /(\d+)\s+(\d+)\s+obj\s*([\s\S]*?)\bstream\r?\n([\s\S]*?)\r?\nendstream/g;
let totalReplaced = 0; let totalReplaced = 0;
let m;
// 收集所有命中的修改 (offset, oldLen, newBytes)
const patches = []; const patches = [];
let m;
while ((m = re.exec(text)) !== null) { while ((m = re.exec(text)) !== null) {
const dictText = m[3]; const dictText = m[3];
if (!/\/Filter\s*(\[\s*)?\/FlateDecode/.test(dictText)) continue;
const streamStart = m.index + m[0].indexOf('stream') + 'stream'.length; const streamStart = m.index + m[0].indexOf('stream') + 'stream'.length;
const nl = pdfBytes[streamStart] === 0x0D ? 2 : 1; // \r\n or \n const nl = pdfBytes[streamStart] === 0x0D ? 2 : 1;
const payloadStart = streamStart + nl; const payloadStart = streamStart + nl;
// 找 endstream 的开始
const endIdx = m.index + m[0].lastIndexOf('endstream'); const endIdx = m.index + m[0].lastIndexOf('endstream');
let payloadEnd = endIdx; let payloadEnd = endIdx;
// PDF 规范: endstream 前最多有一个 EOL 标志(\r\n / \n / \r). 只剥一个, 否则可能误删数据字节 // endstream 前最多有一个 EOL, 只剥一个
if (payloadEnd - payloadStart >= 2 && pdfBytes[payloadEnd - 2] === 0x0D && pdfBytes[payloadEnd - 1] === 0x0A) { if (payloadEnd - payloadStart >= 2 && pdfBytes[payloadEnd - 2] === 0x0D && pdfBytes[payloadEnd - 1] === 0x0A) {
payloadEnd -= 2; payloadEnd -= 2;
} else if (payloadEnd - payloadStart >= 1 && (pdfBytes[payloadEnd - 1] === 0x0A || pdfBytes[payloadEnd - 1] === 0x0D)) { } else if (payloadEnd - payloadStart >= 1 && (pdfBytes[payloadEnd - 1] === 0x0A || pdfBytes[payloadEnd - 1] === 0x0D)) {
...@@ -1209,39 +1267,27 @@ ...@@ -1209,39 +1267,27 @@
} }
const payload = pdfBytes.subarray(payloadStart, payloadEnd); const payload = pdfBytes.subarray(payloadStart, payloadEnd);
// 仅处理 /Filter /FlateDecode 流
if (!/\/Filter\s*(\[\s*)?\/FlateDecode/.test(dictText)) continue;
let inflated; let inflated;
try { inflated = pako.inflate(payload); } catch (e) { continue; } try { inflated = pako.inflate(payload); } catch (e) { continue; }
let mutated = new Uint8Array(inflated); // copy const r = patchInflated(inflated, year, month, day, hour, minute);
let n1 = replaceAllBytes(mutated, oldDate, newDate); if (!r) continue;
let n2 = replaceAllBytes(mutated, oldTime, newTime);
if (n1 + n2 === 0) continue;
// 重新压缩并 pad 到原长度 let deflated = pako.deflate(r.bytes, { level: 9 });
let deflated = pako.deflate(mutated, { level: 9 });
if (deflated.length > payload.length) { if (deflated.length > payload.length) {
// 试更高压缩仍超 — 用 level 9 已是上限, 改成移除 filter 不现实 deflated = pako.deflate(r.bytes, { level: 0 });
// 退化: 用 store mode
deflated = pako.deflate(mutated, { level: 0 });
} }
if (deflated.length > payload.length) { if (deflated.length > payload.length) {
throw new Error(`deflate length ${deflated.length} > original ${payload.length}, stream ${m[1]}`); throw new Error(`deflate length ${deflated.length} > original ${payload.length}, stream ${m[1]}`);
} }
// pad: 直接补 0 字节会破坏 zlib 校验, 改用追加 sync flush 块到长度
const out = new Uint8Array(payload.length); const out = new Uint8Array(payload.length);
out.set(deflated); out.set(deflated);
// 多余空间填 0; 由于 zlib 在解码 deflated 完成后就结束读取, 后续字节是 PDF 外的"垃圾" totalReplaced += r.dates + r.times;
// 测试:大多 PDF reader 容忍 stream 末尾有冗余
totalReplaced += n1 + n2;
patches.push({ start: payloadStart, end: payloadEnd, replacement: out }); patches.push({ start: payloadStart, end: payloadEnd, replacement: out });
} }
if (!patches.length) return null; if (!patches.length) return null;
// 应用 patch
const result = new Uint8Array(pdfBytes); const result = new Uint8Array(pdfBytes);
for (const p of patches) { for (const p of patches) {
result.set(p.replacement, p.start); result.set(p.replacement, p.start);
// 末尾余量填 0x20 (空格), 不影响 PDF 文法且解码器会忽略
for (let i = p.start + p.replacement.length; i < p.end; i++) { for (let i = p.start + p.replacement.length; i < p.end; i++) {
result[i] = 0x20; result[i] = 0x20;
} }
...@@ -1251,18 +1297,16 @@ ...@@ -1251,18 +1297,16 @@
function downloadPDF2() { function downloadPDF2() {
if (typeof pako === 'undefined') { alert('pako 未加载'); return; } if (typeof pako === 'undefined') { alert('pako 未加载'); return; }
// const linkEl = document.querySelector('.result-bar .actions a:last-child'); const dateFrom=window.localStorage.getItem('dateFrom'); // 先清空缓存, 避免调试时旧数据干扰
// const orig = linkEl.textContent; console.log('dateFrom', dateFrom)
// linkEl.textContent = '生成中...';
setTimeout(() => { setTimeout(() => {
try { try {
const pdf = base64ToBytes(PDF_B64); // const dateFrom = document.getElementById('dateFrom').value.trim();
const year = dateFrom.slice(0, 4);
const pdfB64 = selectPdfByYear(year);
const pdf = base64ToBytes(pdfB64);
const now = new Date(); const now = new Date();
const oldDate = buildDateBytes(2026, 2, 28); const r = patchPdf(pdf, now.getFullYear(), now.getMonth() + 1, now.getDate(), now.getHours(), now.getMinutes());
const newDate = buildDateBytes(now.getFullYear(), now.getMonth() + 1, now.getDate());
const oldTime = buildTimeBytes(9, 42);
const newTime = buildTimeBytes(now.getHours(), now.getMinutes());
const r = patchPdf(pdf, oldDate, newDate, oldTime, newTime);
const finalBytes = r ? r.bytes : pdf; const finalBytes = r ? r.bytes : pdf;
const blob = new Blob([finalBytes], { type: 'application/pdf' }); const blob = new Blob([finalBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
...@@ -1271,7 +1315,7 @@ ...@@ -1271,7 +1315,7 @@
a.download = '活期账户明细下载全部.pdf'; a.download = '活期账户明细下载全部.pdf';
document.body.appendChild(a); a.click(); a.remove(); document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000); setTimeout(() => URL.revokeObjectURL(url), 1000);
console.log('日期替换次数:', r ? r.count : 0); console.log('替换次数:', r ? r.count : 0);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert('PDF 处理失败: ' + e.message); alert('PDF 处理失败: ' + e.message);
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
margin: 10px 0; margin: 10px 0;
} }
/* 筛选与工具栏 */ /* 筛选与工具栏 */
.filter { .filter {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
padding-left: 10px; padding-left: 10px;
padding-right: 11px; padding-right: 11px;
} }
.filter .left { .filter .left {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -364,7 +365,8 @@ ...@@ -364,7 +365,8 @@
table.grid th.col-select { table.grid th.col-select {
display: none; display: none;
} */ } */
.form-question {
.form-question {
cursor: pointer; cursor: pointer;
margin-left: 8px; margin-left: 8px;
display: inline-block; display: inline-block;
...@@ -377,6 +379,53 @@ ...@@ -377,6 +379,53 @@
color: #fff; color: #fff;
padding-left: 4px; padding-left: 4px;
} }
/* 气泡框 */
.bubble {
position: absolute;
bottom: calc(100% + 14px);
left: -25px;
width: 150px;
padding: 10px 5px;
color: #000000;
border-radius: 4px;
border: 1px solid #62A7E3;
background-color: #EAF5FE;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease, visibility 0.2s ease;
z-index: 100;
pointer-events: none;
}
/* 显示气泡 */
.form-question:hover .bubble {
visibility: visible;
opacity: 1;
}
.bubble::before {
content: '';
position: absolute;
bottom: -12px;
left: 23px;
border-width: 12px 4px 0 10px;
border-style: solid;
border-color: #62A7E3 transparent transparent transparent;
z-index: 1;
}
/* 背景三角(内层,盖住部分边框) */
.bubble::after {
content: '';
position: absolute;
bottom: -9px;
left: 24px;
border-width: 9px 4px 0 8px;
border-style: solid;
border-color: #EAF5FE transparent transparent transparent;
z-index: 2;
}
</style> </style>
</head> </head>
...@@ -402,7 +451,11 @@ ...@@ -402,7 +451,11 @@
<div class="left"> <div class="left">
<input type="checkbox" id="filterCheckbox" onclick="openFilter(this)"> <input type="checkbox" id="filterCheckbox" onclick="openFilter(this)">
<span>筛选</span> <span>筛选</span>
<span class="form-question"></span> <span class="form-question">
<span class="bubble">
支持通过任务设定日期等要素筛选异步查询任务,设定日期仅支持按单日筛选
</span>
</span>
</div> </div>
</div> </div>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment