diff --git a/apps/thu-info-app/src/assets/translations/en.ts b/apps/thu-info-app/src/assets/translations/en.ts index 69e4966d7..8853144cd 100644 --- a/apps/thu-info-app/src/assets/translations/en.ts +++ b/apps/thu-info-app/src/assets/translations/en.ts @@ -609,4 +609,5 @@ export default { "Service provider has stopped maintaining status for new washers, so any feedback about missing washing machines will not be accepted. Data in this page comes solely from \"华清捷利\" mini program, thus any feedback about extending the page's functionality will not be accepted.", noMoreData: "No more data", hideWeekend: "Hide Weekend", + loadAllData: "Load all data", }; diff --git a/apps/thu-info-app/src/assets/translations/zh.ts b/apps/thu-info-app/src/assets/translations/zh.ts index d07644e11..6fe0ab2e8 100644 --- a/apps/thu-info-app/src/assets/translations/zh.ts +++ b/apps/thu-info-app/src/assets/translations/zh.ts @@ -575,4 +575,5 @@ export default { "厂家已停止对新洗衣机的维护,因此我们不接受对缺少洗衣机的反馈。此外,本系统数据完全来源于“华清捷利”小程序,因此我们不接受有关扩展功能的反馈。", noMoreData: "没有更多数据了~", hideWeekend: "隐藏周末", + loadAllData: "加载全部数据", }; diff --git a/apps/thu-info-app/src/ui/home/bankPayment.tsx b/apps/thu-info-app/src/ui/home/bankPayment.tsx index c7c2a6d2e..1d0a02e9f 100644 --- a/apps/thu-info-app/src/ui/home/bankPayment.tsx +++ b/apps/thu-info-app/src/ui/home/bankPayment.tsx @@ -26,6 +26,8 @@ export const BankPaymentScreen = () => { const [foundation, setFoundation] = useState(false); const [modalOpen, setModalOpen] = useState(false); + const [loadPartial, setLoadPartial] = useState(true); + const headerHeight = useHeaderHeight(); const themeName = useColorScheme(); @@ -43,7 +45,7 @@ export const BankPaymentScreen = () => { let cancelled = false; helper - .getBankPayment(foundation) + .getBankPayment(foundation, loadPartial) .then((r) => { if (cancelled) { return; @@ -65,7 +67,7 @@ export const BankPaymentScreen = () => { }; }; - useEffect(fetchData, [foundation]); + useEffect(fetchData, [foundation, loadPartial]); return ( @@ -268,12 +270,14 @@ export const BankPaymentScreen = () => { - {refreshing ? getStr("loading") : getStr("noMoreData")} + }} + onPress={() => loadPartial && setLoadPartial(false)} + > + {refreshing ? getStr("loading") : loadPartial ? getStr("loadAllData") : getStr("noMoreData")} diff --git a/packages/thu-info-lib/src/index.ts b/packages/thu-info-lib/src/index.ts index 089421bcb..61eba1f38 100644 --- a/packages/thu-info-lib/src/index.ts +++ b/packages/thu-info-lib/src/index.ts @@ -8,7 +8,7 @@ import { getPhysicalExamResult, getReport, postAssessmentForm, - getBankPayment, + // getBankPaymentParellize, getCalendar, getInvoiceList, getInvoicePDF, @@ -16,6 +16,7 @@ import { naiveSendMail, getCalendarImageUrl, getSchoolCalendarYear, getGraduateIncome, + getBankPaymentParellize, } from "./lib/basics"; import {forgetDevice, login, logout} from "./lib/core"; import {getDormScore, getElePayRecord, getEleRechargePayCode, getEleRemainder, resetDormPassword} from "./lib/dorm"; @@ -407,7 +408,7 @@ export class InfoHelper { * Get the bank payment records of the user. * @param foundation whether to get bank payment result by 基金会 or not */ - public getBankPayment = async (foundation = false): Promise => getBankPayment(this, foundation); + public getBankPayment = async (foundation = false, loadPartial = false): Promise => getBankPaymentParellize(this, foundation, loadPartial); /** * Get the graduate income records of the user according to the date range diff --git a/packages/thu-info-lib/src/lib/basics.ts b/packages/thu-info-lib/src/lib/basics.ts index dc62484a2..a30342e88 100644 --- a/packages/thu-info-lib/src/lib/basics.ts +++ b/packages/thu-info-lib/src/lib/basics.ts @@ -500,76 +500,77 @@ export const getInvoicePDF = (helper: InfoHelper, busNumber: string): Promise => -// roamingWrapperWithMocks( -// helper, -// "default", -// foundation ? "C1ADD6B60D050B64E0C7B8F195CE89EC" : "2A5182CB3F36E80395FC2091001BDEA6", -// async (s) => { -// if (s === undefined) { -// throw new LibError(); -// } -// const options = cheerio.load(s)("option").map((_, e) => (e as TagElement).attribs.value).get(); -// if (options.length === 0) { -// return []; -// } -// const form = options.map((o) => `year=${encodeURIComponent(o)}`).join("&"); -// const result = await uFetch(foundation ? FOUNDATION_BANK_PAYMENT_SEARCH_URL : BANK_PAYMENT_SEARCH_URL, form as never as object, 60000, "UTF-8", true); -// const $ = cheerio.load(result); -// const titles = $("div strong") -// .map((_, e) => { -// const titleElement = e as TagElement; -// const text = (titleElement.children[0] as DataNode).data?.trim(); -// if (text === undefined) { -// return undefined; -// } -// const res = /(\d+年\d+月)银行代发结果/g.exec(text); -// if (res === null || res[1] === undefined) { -// return undefined; -// } -// if (((titleElement.parentNode?.next?.next as TagElement)?.firstChild as TagElement)?.name !== "table") { -// return undefined; -// } -// return res[1]; -// }) -// .get() -// .filter((text) => text !== undefined) as string[]; -// return $("div table tbody") -// .filter(index => index < titles.length) -// .map((index, e) => { -// const rows = cheerio.load(e)("tr"); -// const data = rows.slice(1, rows.length - 1); -// return { -// month: titles[index], -// payment: data.map((_, row) => { -// const columns = cheerio.load(row)("td"); -// return { -// department: getCheerioText(columns[1], 0), -// project: getCheerioText(columns[2], 0), -// usage: getCheerioText(columns[3], 0), -// description: getCheerioText(columns[4], 0), -// bank: getCheerioText(columns[5], 0), -// time: getCheerioText(columns[6], 0), -// total: getCheerioText((columns[7] as TagElement).children[0], 0), -// deduction: getCheerioText((columns[8] as TagElement).children[0], 0), -// actual: getCheerioText((columns[9] as TagElement).children[0], 0), -// deposit: getCheerioText((columns[10] as TagElement).children[0], 0), -// cash: getCheerioText((columns[11] as TagElement).children[0], 0), -// } as BankPayment; -// }).get().reverse(), -// }; -// }) -// .get() as BankPaymentByMonth[]; -// }, -// MOCK_BANK_PAYMENT, -// ); - export const getBankPayment = async ( helper: InfoHelper, foundation: boolean, +): Promise => + roamingWrapperWithMocks( + helper, + "default", + foundation ? "C1ADD6B60D050B64E0C7B8F195CE89EC" : "2A5182CB3F36E80395FC2091001BDEA6", + async (s) => { + if (s === undefined) { + throw new LibError(); + } + const options = cheerio.load(s)("option").map((_, e) => (e as TagElement).attribs.value).get(); + if (options.length === 0) { + return []; + } + const form = options.map((o) => `year=${encodeURIComponent(o)}`).join("&"); + const result = await uFetch(foundation ? FOUNDATION_BANK_PAYMENT_SEARCH_URL : BANK_PAYMENT_SEARCH_URL, form as never as object, 60000, "UTF-8", true); + const $ = cheerio.load(result); + const titles = $("div strong") + .map((_, e) => { + const titleElement = e as TagElement; + const text = (titleElement.children[0] as DataNode).data?.trim(); + if (text === undefined) { + return undefined; + } + const res = /(\d+年\d+月)银行代发结果/g.exec(text); + if (res === null || res[1] === undefined) { + return undefined; + } + if (((titleElement.parentNode?.next?.next as TagElement)?.firstChild as TagElement)?.name !== "table") { + return undefined; + } + return res[1]; + }) + .get() + .filter((text) => text !== undefined) as string[]; + return $("div table tbody") + .filter(index => index < titles.length) + .map((index, e) => { + const rows = cheerio.load(e)("tr"); + const data = rows.slice(1, rows.length - 1); + return { + month: titles[index], + payment: data.map((_, row) => { + const columns = cheerio.load(row)("td"); + return { + department: getCheerioText(columns[1], 0), + project: getCheerioText(columns[2], 0), + usage: getCheerioText(columns[3], 0), + description: getCheerioText(columns[4], 0), + bank: getCheerioText(columns[5], 0), + time: getCheerioText(columns[6], 0), + total: getCheerioText((columns[7] as TagElement).children[0], 0), + deduction: getCheerioText((columns[8] as TagElement).children[0], 0), + actual: getCheerioText((columns[9] as TagElement).children[0], 0), + deposit: getCheerioText((columns[10] as TagElement).children[0], 0), + cash: getCheerioText((columns[11] as TagElement).children[0], 0), + } as BankPayment; + }).get().reverse(), + }; + }) + .get() as BankPaymentByMonth[]; + }, + MOCK_BANK_PAYMENT, + ); + +export const getBankPaymentParellize = async ( + helper: InfoHelper, + foundation: boolean, + loadPartial: boolean = false, ): Promise => { return roamingWrapperWithMocks( helper, @@ -583,52 +584,73 @@ export const getBankPayment = async ( if (options.length === 0) { return []; } - const requests = options.map((o) => { - const form = `year=${encodeURIComponent(o)}`; + const loadOptions = (loadPartial ? options.slice(0, 3) : options.map(o => `year=${encodeURIComponent(o)}`)); + const jointOptions = [ + loadOptions.slice(0, Math.ceil(options.length / 3)).join("&"), + loadOptions + .slice( + Math.floor(options.length / 3), + Math.ceil((2 * options.length) / 3) + ) + .join("&"), + loadOptions + .slice(Math.floor((2 * options.length) / 3)) + .join("&"), + ]; + const requests = jointOptions.map((o) => { + const form = `${encodeURIComponent(o)}`; return uFetch(foundation ? FOUNDATION_BANK_PAYMENT_SEARCH_URL : BANK_PAYMENT_SEARCH_URL, form as never as object, 60000, "UTF-8", true); }); const results = await Promise.all(requests); - const parsedResults = results.flatMap((result, index) => { - const $ = cheerio.load(result); - const titleElement = $("div strong"); - const text = (titleElement[0].children[0] as DataNode).data?.trim(); - if (text === undefined) { - return []; - } - const res = /(\d+年\d+月)银行代发结果/g.exec(text); - if (res === null || res[1] === undefined) { - return []; - } - if (((titleElement[0].parentNode?.next?.next as TagElement)?.firstChild as TagElement)?.name !== "table") { - return []; - } - - const title = options[index]; - const rows = $("div table tbody tr").slice(1, -1); - const payments = rows.map((_, row) => { - const columns = cheerio.load(row)("td"); - return { - department: getCheerioText(columns[1], 0), - project: getCheerioText(columns[2], 0), - usage: getCheerioText(columns[3], 0), - description: getCheerioText(columns[4], 0), - bank: getCheerioText(columns[5], 0), - time: getCheerioText(columns[6], 0), - total: getCheerioText((columns[7] as TagElement).children[0], 0), - deduction: getCheerioText((columns[8] as TagElement).children[0], 0), - actual: getCheerioText((columns[9] as TagElement).children[0], 0), - deposit: getCheerioText((columns[10] as TagElement).children[0], 0), - cash: getCheerioText((columns[11] as TagElement).children[0], 0), - } as BankPayment; - }).get().reverse(); - return { month: title, payment: payments }; - }); + const parsedResults = results.map((result) => { + return parseAndFiltBankPayment(result); + }).flatMap((it) => it); return parsedResults; }, MOCK_BANK_PAYMENT, ); }; +const parseAndFiltBankPayment = (html: string) => { + const $ = cheerio.load(html); + const titleElements = $("div strong"); + return titleElements.map((_, e) => { + const titleElement = e as TagElement; + const text = (titleElement.children[0] as DataNode).data?.trim(); + if (text === undefined) { + return undefined; + } + const res = /(\d+年\d+月)银行代发结果/g.exec(text); + if (res === null || res[1] === undefined) { + return undefined; + } + if (((titleElement.parentNode?.next?.next as TagElement)?.firstChild as TagElement)?.name !== "table") { + return undefined; + } + const data = cheerio.load((titleElement.parent?.next?.next as TagElement)?.firstChild as cheerio.AnyNode)("tr").slice(1, -1); + const payment = data.map((_, row) => { + const columns = cheerio.load(row)("td"); + return { + department: getCheerioText(columns[1], 0), + project: getCheerioText(columns[2], 0), + usage: getCheerioText(columns[3], 0), + description: getCheerioText(columns[4], 0), + bank: getCheerioText(columns[5], 0), + time: getCheerioText(columns[6], 0), + total: getCheerioText((columns[7] as TagElement).children[0], 0), + deduction: getCheerioText((columns[8] as TagElement).children[0], 0), + actual: getCheerioText((columns[9] as TagElement).children[0], 0), + deposit: getCheerioText((columns[10] as TagElement).children[0], 0), + cash: getCheerioText((columns[11] as TagElement).children[0], 0), + } as BankPayment; + }).get().reverse(); + return { + month: res[1], + payment, + }; + }).get().filter((it) => it !== undefined) as BankPaymentByMonth[]; +}; + export const getGraduateIncome = async ( helper: InfoHelper, begin: string, // YYYYMMDD