香港公司秘書/公司治理專業資格考試 CGQP|《風險管理》模擬題庫

每位學員用自己的帳號登入,錯題本與練習記錄會分開存放,不會互相混淆。 帳號資料储存在您目前使用的這台裝置瀏覽器內。
📖 考點速覽|12 章重點蒸餾(開考前 5 分鐘過一遍)(點擊展開)
第1章 · 風險治理框架
  • 董事會對風險管理承擔最終責任;須設風險治理架構
  • 三道防線模型:業務管理/風險合規職能/內部審計
  • 風險文化與風險胃口(risk appetite)聲明
第2章 · 風險識別與評估
  • 風險登記冊:識別—評估—應對—監測的循環
  • 評估維度:可能性 × 影響;固有風險 vs 剩餘風險
  • 風險應對四策略:規避、降低、轉移、接受
第3章 · 內部控制與審計
  • COSO 內控框架五要素:控制環境、風險評估、控制活動、信息溝通、監測
  • 控制類型:預防性/偵測性、自動化/人工
  • 控制缺陷的匯報與補救
第4章 · 合規與信譽風險
  • 合規風險來源:法規變化、跨境經營、員工行為
  • 信譽風險的傳導:事件→輿論→客戶流失→財務損失
  • 舉報(whistleblowing)機制與保密保護
第5章 · 危機管理與業務連續性
  • 業務連續性管理(BCM):BIA 分析、RTO/RPO 設定
  • 危機管理:預案、危機指揮、內外溝通
  • 演練與事後復盤(post-mortem)
第6章 · 網絡與新興風險
  • 網絡風險:勒索軟件、釣魚、第三方系統滲透
  • 數據洩露響應:通報、取證、修復
  • 新興風險:氣候、AI、地緣政治
第7章 · 財務風險
  • 市場風險:利率、匯率、商品價格波動
  • 流動性風險:資金流動性 vs 市場流動性
  • 對沖工具:遠期、期貨、掉期、期權的基本用途
第8章 · 保險與風險融資
  • 風險融資:保險、自保公司(captive)、風險保留
  • 保險原則:最高誠信、可保利益、彌償、代位
  • 免賠額與承保範圍的取捨
第9章 · 保證與匯報
  • 保證的分類:直接保證、建設性保證、顧問式保證
  • 獨立鑒證準則:ISAE 3000、AA1000 系列
  • 保證匯報:合理保證 vs 有限保證
第10章 · 科技與數據風險
  • 《個人資料(私隱)條例》六項保障資料原則
  • 數據治理:分級、留存、跨境傳輸
  • 科技風險的董事會監督與問責
第11章 · 第三方與外包
  • 外包與第三方風險:盡職調查、合同 SLA、退出安排
  • 監管要求(如 HKMA 外包指引):須保留最終責任
  • 供應商集中度與應急替代
第12章 · 情景應用
  • 綜合情景:危機決策、風險升級路徑、保證安排的推演

※ 以上為按課程大綱整理的自編備考重點,並非官方考試內容;系統學習請參閱 HKCGI 官方課程資料。

120題庫總題數
50%合格線
75%優秀線
第 1 / 100 題 模擬練習
0%
合格

📊 各章節表現

❌ 錯題回顧(已加入錯題庫)

📕 我的錯題本(0 題)

每次答錯的題目會自動記錄到這裡;在「錯題重做」中答對後會自動移出。

⚠️ 我的異議清單(0 條)

做題時點「⚠️ 質疑此題」即可記錄。確認後請一次過傳送給我們跟進核實。

🗂 我的練習記錄

👤 我的帳號

🔑 修改密碼

const BANK = [[1,1,"1","COSO《內部控制——整合框架》的五項組成部分是:","控制環境、風險評估、控制活動、資訊與溝通、監控活動","計劃、執行、檢查、行動、報告","識別、分析、評價、處理、監察","戰略、市場、財務、運營、人員","A","COSO 內控框架五要素:Control Environment, Risk Assessment, Control Activities, Information & Communication, Monitoring Activities。"],[2,1,"1","ISO 31000 對「風險」的定義是:","不確定性對目標的影響","損失的金額","員工的錯誤率","保險單的條款","B","ISO 31000 將風險定義為「不確定性對目標的影響」(effect of uncertainty on objectives),涵蓋正負兩面。"],[3,1,"1","三道防線(Three Lines of Defence)模型中,第二道防線指:","風險管理與合規等職能部門","前台業務部門","內部審計","外部核數師","B","第一道是業務營運的風險擁有者;第二道是風險管理/合規職能;第三道是內部審計的獨立保證。"],[4,1,"1","企業風險管理(ERM)與傳統「孤島式」風險管理的區別在於:","ERM 將風險統籌納入戰略與整體治理,跨部門整合","ERM 只關注財務風險","ERM 無須董事會參與","ERM 只使用保險工具","A","ERM 是全公司層面的風險識別、評估與管理整合,連接戰略、治理與績效管理,由董事會監督。"],[5,1,"1","風險偏好(risk appetite)的最佳定義是:","董事會接受並願意承擔的風險總量與類型","員工對風險的恐懼程度","監管允許的上限","核數師的意見","A","風險偏好由董事會設定,指引管理層在追求目標時可接受的風險水準,並落實為風險限額(risk limits)。"],[6,1,"1","風險文化(risk culture)的建立主要依靠:","治理層示範、培訓、激勵及問責機制的一致性","只發放風險手冊","只靠處罰","僅在年會提及","A","風險文化須由高層以身作則,配合績效激勵與問責制度,讓風險意識滲透日常決策。"],[7,1,"1","《企業管治守則》對風險管理與內部監控的要求是:","董事會須負責評估及決定發行人在達成目標過程中承受的風險性質與程度,並確保系統及程序妥為執行","只須購買保險","由核數師全權負責","無相關要求","B","守則D.2規定董事會對風險管理及內部監控系統負最終責任,並須每年檢討其有效性。"],[8,1,"1","董事會在風險管理中的職責包括:","設定風險偏好、批准風險管理框架並監督其執行有效性","親自執行所有風險控制","免除管理層風險責任","只關注歷史業績","A","董事會設定方向與監督;管理層負責日常識別、評估及管理風險,兩者分工明確。"],[9,1,"1","風險管理委員會與審計委員會在風險治理上的協調,最佳實踐是:","明確劃分職權範圍、共享資訊、定期聯席溝通,避免監察缺口","完全獨立互不往來","合併為一個委員會","風險事宜全部交由審計委員會","A","兩委會職能須互補並協調,尤其在財匯風險、內控與企業風險的交疊處,避免重疊或遺漏。"],[10,1,"1","COSO ERM 框架(2017)強調的核心變化是:","將風險管理與戰略設定及績效管理整合","只關注財務報告風險","簡化為三個步驟","取消治理內容","A","COSO ERM 2017 版以「戰略與績效整合」為核心,把風險管理嵌入戰略制定、業務執行與績效評估。"],[11,2,"2","固有風險(inherent risk)與剩餘風險(residual risk)的關係是:","剩餘風險=固有風險經控制措施後的剩餘部分","兩者相同","剩餘風險永遠大於固有風險","固有風險不存在","A","固有風險是在未考慮控制前的風險;經控制緩解後剩餘的即為剩餘風險,是風險決策的基礎。"],[12,2,"2","風險登記冊(risk register)的核心作用是:","統一記錄風險描述、負責人、評級、緩解措施及狀態,供董事會監督","作為宣傳材料","取代財務報表","供客戶查閱","A","風險登記冊是 ERM 的基本工具,使風險責任與緩解進度可追蹤、可問責。"],[13,2,"2","風險評估的常見方法是:","定性(風險矩陣)與定量(機率×影響、情景分析、VaR)相結合","只憑直覺","只看歷史虧損","由外部人士決定","A","評估方法依風險性質選擇,定性矩陣配合定量模型(情景分析、敏感度、VaR)更全面。"],[14,2,"2","「尾部風險」(tail risk)是指:","分佈極端情況下發生的低機率高影響風險","日常營運錯誤","員工請假","匯率正常波動","A","尾部風險指分佈極端的低機率、高衝擊事件(如金融危機、疫情),須以情景分析及壓力測試應對。"],[15,2,"2","壓力測試(stress testing)的主要目的是:","評估極端情景下公司的承受能力與資本充足性","增加報表篇幅","滿足客戶好奇","計算員工績效","A","壓力測試透過模擬嚴重不利情景(市場崩盤、流動性枯竭),檢視公司生存能力與緩衝。"],[16,2,"2","「新興風險」(emerging risk)的特點是:","資料不足、演變快、影響難量化,如人工智能、氣候轉型、地緣政治","早已成熟的已知風險","員工流失","辦公用品短缺","A","新興風險特徵是不確定性高且缺乏歷史數據,須以前瞻性監察及情景思維管理。"],[17,2,"2","關鍵風險指標(KRI)的用途是:","提供風險狀況變化的預警訊號,觸發行動閾值","衡量員工滿意度","計算股息","評估稅務負擔","A","KRI 設定閾值,當指標越限時觸發升級與緩解行動,是風險監察的前瞻工具。"],[18,2,"2","風險互依性(risk interdependencies)的管理重點是:","識別風險之間的連鎖效應(如供應鏈中斷引發信譽及財務風險)","將所有風險獨立處理","忽略次要風險","只管理單一最大風險","A","現代風險多互為因果,須以網絡思維識別連鎖傳導,避免單一風險評估遺漏系統性影響。"],[19,2,"2","供應鏈風險的常見緩解措施不包括:","建立供應商多元化及替代方案","庫存與合同彈性安排","持續監察供應商財務健康","完全依賴單一供應商並削減監察","D","單一來源依賴是供應鏈風險的典型脆弱點;多元化、彈性庫存與持續監察是標準緩解。"],[20,2,"2","情景分析(scenario analysis)在董事會層面的價值是:","協助戰略決策考慮多種未來可能性,檢視戰略韌性","預測精確利潤數字","取代審計","增加會議次數","A","情景分析讓董事會在重大決策時考慮不同情境下的影響,增強戰略與風險承受力的匹配。"],[21,3,"3","職責分離(segregation of duties)的核心目的是:","避免同一人控制交易全流程而產生舞弊或錯誤","增加人手","方便輪休","提升工作效率","A","授權、執行、記錄、保管職能分離是防範舞弊與錯誤的基本內控原則。"],[22,3,"3","授權矩陣(authorization matrix)的作用是:","界定不同層級的審批權限與限額,防止越權","增加文書工作","作為薪酬依據","供客戶查閱","A","清晰的審批權限與限額是內控的基石,配合系統設定可自動執行。"],[23,3,"3","內部審計的獨立性保障主要透過:","職能上向審計委員會報告,避免受管理層干預","隸屬財務部","由行政總裁考核其薪酬","只做外部溝通","A","內審的組織匯報與職能匯報分離,職能上向審計委員會負責,確保審計發現不受管理層壓制。"],[24,3,"3","內部審計與外部審計的主要區別是:","內審關注整體風險與內控有效性(面向管理層/董事會),外審對財務報表發表獨立意見(面向公眾)","內審範圍更窄","兩者完全相同","外審向內審匯報","A","內審是治理內部保證職能,範圍涵蓋營運、合規與風險;外審為法定財務報表審計,向股東負責。"],[25,3,"3","控制缺陷(control deficiency)的評估應考慮:","缺陷的性質、發生可能性及潛在影響,並分類為重大缺陷或重要弱點","只看金額","一律立即辭退相關人員","無須評估","A","缺陷須按可能性與影響評級,重大缺陷(material weakness)須向審計委員會及必要時向市場披露。"],[26,3,"3","補救計劃(remediation plan)的關鍵要素是:","明確負責人、完成期限及跟進驗證機制","口頭承諾","無限期拖延","轉交外部處理即可","A","缺陷整改須有責任人、時限及複核驗證,並向審計委員會匯報進度。"],[27,3,"3","《企業管治守則》要求董事會每年檢討風險管理及內部監控系統的有效性,並:","在企業管治報告中披露檢討過程及董事會的意見","無須披露","只作口頭確認","由核數師代替確認","B","守則D.2.2要求董事會每年檢討有效性,企業管治報告須披露相關過程及董事會的確認。"],[28,3,"3","舞弊風險三角(fraud triangle)的三要素是:","壓力、機會、合理化","動機、能力、運氣","金錢、時間、人力","動機、監督、懲罰","A","Cressey 舞弊三角:壓力(財務壓力)、機會(控制薄弱)、合理化(自我辯解),內控重點是消除「機會」。"],[29,3,"3","電子支付及銀行授權的內控要點是:","多重授權(dual authorisation)、限額控制及定期對賬","單一人員全權處理","免除對賬","密碼共享","A","資金控制要求多重授權、限額、白名單及定期對賬,防範內部及外部詐騙。"],[30,3,"3","內部控制「成本效益」原則是指:","控制的設計須與風險重要性相稱,避免對低風險事項施加過度控制","一切以省錢為準","控制越嚴越好","毋須考慮風險","A","內控講求與風險相稱(proportionality),過度控制會削弱效率並增加成本。"],[31,4,"4","合規風險(compliance risk)的定義是:","因違反法律、規例、守則或內部政策而遭受法律制裁、監管處罰或損失的風險","市場價格波動","客戶流失","系統故障","A","合規風險源於違反外部法規或內部規定,後果包括罰款、停牌、訴訟及信譽損害。"],[32,4,"4","信譽風險(reputational risk)的特點是:","與其他風險高度關聯,且一旦爆發傳播迅速、恢復期長","完全獨立於其他風險","只影響短期業績","可以用保險完全覆蓋","A","信譽風險往往是其他風險(舞弊、安全事故、合規違規)的連鎖結果,且極難量化,須以危機管理及真誠溝通應對。"],[33,4,"4","反洗錢(AML)治理的三道核心措施是:","客戶盡職調查(KYC)、交易監察及可疑交易報告(STR)","只查身份證","現金交易無須記錄","自行判斷即可","A","AML 制度以 KYC/CDD、持續監察及向可疑交易報告主任/財富情報科(JFIU)報告為支柱。"],[34,4,"4","數據隱私方面,香港《個人資料(私隱)條例》的六項保障資料原則不包括:","個人資料可用於任何用途而無須同意","收集目的及方式須合法直接","資料須準確且保留時間不超過必要","保障資料安全","A","六項原則涵蓋收集、準確性、保留、使用(須符合目的並取得同意)、保安及透明度(開放查閱)。"],[35,4,"4","制裁合規(sanctions compliance)的基本要求是:","篩查交易對手及相關方是否列入制裁名單,並及時更新名單","無須篩查","只在年審時檢查","由客戶自行聲明","A","制裁名單(如 OFAC、聯合國)須定期更新並對客戶/交易進行篩查,違反制裁可致嚴重刑事與信譽後果。"],[36,4,"4","利益衝突管理的制度基礎是:","登記冊、申報機制、迴避安排及定期聲明","只靠個人自覺","不設任何機制","僅在訴訟時處理","A","系統性利益衝突管理包括登記、申報、獨立審查/迴避及年度聲明,配合違規問責。"],[37,4,"4","監管處罰後的「補救計劃」通常包括:","根源分析、控制修復、問責處理及向監管匯報進度","只繳罰款","無須跟進","轉移資產","A","監管機構期望的補救不止於罰款:須查明根源、修復控制、問責及持續匯報。"],[38,4,"4","第三方風險(供應商/代理商)管理的核心程序是:","盡職調查、合同風險條款、持續監察及退出安排","一次審批終身有效","只看報價","無須合同","A","第三方帶來的合規與信譽風險須透過入門盡調、合同管控、持續監察及退出機制管理。"],[39,4,"4","舉報線索的處理流程應包括:","保密登記、獨立調查、調查結果匯報及跟進整改","交由被舉報人調查","立即公開身份","直接解僱舉報人","A","舉報須循保密、獨立調查、匯報(審計委員會)與整改閉環處理,並保障舉報人。"],[40,4,"4","「合規文化」的衡量指標可以是:","培訓覆蓋率、違規事件趨勢、舉報數量及調查時效","只看罰款金額","無法衡量","員工人數","A","合規文化可透過培訓滲透率、違規/舉報數據趨勢及處理時效等指標間接衡量。"],[41,5,"5","業務連續性計劃(BCP)的核心目標是:","確保關鍵業務在重大中斷後於可接受時間內恢復","削減成本","增加收入","取代保險","A","BCP 聚焦關鍵流程的恢復時間目標(RTO)與恢復點目標(RPO),保障營運連續。"],[42,5,"5","業務影響分析(BIA)的用途是:","識別關鍵業務流程、其中斷影響及最大可容忍中斷時間(MTPD)","評估市場份額","計算稅款","招聘規劃","A","BIA 是 BCP 的基礎:確定哪些流程關鍵、可容忍多長中斷,從而制定恢復策略與資源配置。"],[43,5,"5","危機溝通的基本原則是:","快速、真誠、一致,指定唯一發言渠道","隱瞞事實","推卸責任","分頭發布不同說法","A","危機溝通遲疑或失真會放大信譽損害;須設發言人、統一訊息並及時向持份者披露。"],[44,5,"5","災難恢復(DR)與 BCP 的關係是:","DR 是 BCP 中專注於 IT 系統與數據恢復的部分","完全相同","毫無關聯","DR 取代 BCP","A","BCP 涵蓋人員、場所、流程與系統;DR 專注 IT/數據的技術恢復,是 BCP 的組成部分。"],[45,5,"5","BCP 測試(演習)的合理頻率是:","定期(如每年)進行桌面演習及關鍵系統實測","只在設立時做一次","從不測試","每十年一次","A","未經測試的計劃形同虛設;每年桌面演習、關鍵場景實測及更新維持有效性。"],[46,5,"5","關鍵人員風險(key person risk)的緩解措施包括:","繼任規劃、知識文件化及交叉培訓","僅購買保險","無須處理","依靠單一員工","A","關鍵人員離任/意外須以繼任計劃、知識管理與崗位備援減低衝擊。"],[47,5,"5","網絡安全事件的「第一時間響應」原則是:","隔離受影響系統、保留證據、通知事故響應團隊並評估披露義務","立即刪除全部數據","向公眾隱瞞","先重啟伺服器再說","A","事故響應須止損(隔離)、取證、啟動團隊,並評估對客戶及監管的通知/披露義務。"],[48,5,"5","勒索軟件(ransomware)防禦的基本措施是:","定期離線備份、系統修補、員工意識培訓及網絡分段","只靠殺毒軟件","允許全員管理員權限","關閉所有防火牆","A","離線備份是恢復關鍵;配合補丁管理、分段與員工釣魚意識形成縱深防禦。"],[49,5,"5","保險在風險管理框架中的定位是:","風險轉移工具之一,用於緩解特定財務風險,不能取代控制措施","唯一風險管理手段","與風險管理無關","適用於所有風險","A","保險是風險轉移(financing)工具,適用於可保風險;營運與信譽風險仍須靠控制與應變管理。"],[50,5,"5","企業韌性(organisational resilience)的內涵是:","預判、吸收、適應及從衝擊中恢復並成長的綜合能力","只在危機時反應","削減成本","停止創新","A","企業韌性整合風險管理、BCP、危機管理與敏捷文化,強調持續適應而非單點恢復。"],[51,6,"6","網絡風險治理的董事會職責包括:","確保網絡安全策略、資源及事故響應框架到位,並定期聽取匯報","交由 IT 部門全權處理後不再過問","視為純技術問題","無須任何關注","A","網絡風險屬企業風險而非純技術問題,董事會須設定方向、批准策略並監督執行(如 HKMA/證監會的網絡守則)。"],[52,6,"6","《上市規則》要求上市發行人就網絡事故的披露義務是:","在合理切實可行範圍內盡快公布重大網絡事故及其影響","無須披露","一年後披露","只告知大股東","A","重大網絡事故可能構成須予披露事項/內幕消息,發行人須及時評估並披露。"],[53,6,"6","人工智能治理的關鍵風險包括:","演算法偏見、數據私隱、可解釋性及責任歸屬","只影響網速","無任何風險","只涉及成本","A","AI 風險涵蓋公平性(偏見)、私隱、透明度/可解釋性及問責,須建立治理框架與人工監督。"],[54,6,"6","氣候風險分為:","實體風險(physical)及轉型風險(transition)","內部及外部風險","短期及長期風險","財務及稅務風險","A","氣候風險二分:實體風險(極端天氣對資產營運的影響)及轉型風險(政策、技術、市場轉變帶來的影響)。"],[55,6,"6","地緣政治風險對企業的影響主要體現在:","供應鏈重組、制裁暴露、市場准入變化及匯率波動","只影響旅遊業","與企業無關","只影響政府","A","地緣政治透過貿易政策、制裁、供應鏈及資本市場傳導,須納入企業風險地圖。"],[56,6,"6","第三方數據共享的私隱風險管控措施是:","數據處理協議(DPA)、最小化共享及定期安全評估"," freely 共享","口頭協議即可","無須評估","A","與第三方共享個人資料須簽訂處理協議、遵循最小必要原則並定期審計其保安措施。"],[57,6,"6","風險匯報給董事會的最佳實踐是:","聚焦重大風險變動、剩餘風險與偏好的差距及緩解進度,簡明且具前瞻性","列出所有風險不分主次","只報喜不報憂","使用大量技術術語","A","董事會風險匯報須讓非專業董事理解重大變化、風險偏好偏離及行動計劃,避免資訊過載。"],[58,6,"6","風險管理成熟度評估(maturity assessment)的用途是:","對照業界框架評估現有風險管理能力水準,規劃改進路線","代替審計意見","計算員工工資","無實際作用","A","成熟度模型(如分五級)協助董事會了解風險管理現狀與差距,作為資源投入與改進的依據。"],[59,6,"6","「僅遵合規」(box-ticking)文化的主要弊端是:","形式上完成要求但未能真正識別與管理實質風險","合規成本太高","過於透明","促進風險意識","A","機械式合規忽略實質判斷,令治理流於形式;真正的風險管理須結合專業判斷與持續質疑。"],[60,6,"6","董事會在治理新興風險(如 AI、氣候)時,最合適的做法是:","建立識別與監察機制,納入風險登記冊並定期於董事會討論","永遠不予理會","只看短期業績","完全外包給顧問後不管","A","新興風險須主動納入治理視野:識別、評估、監察並定期向董事會匯報,確保戰略韌性。"],[121,7,"7","流動性風險的定義是:","無法以合理成本取得資金應付到期義務的風險","利潤下降","股價波動","員工流失","A","流動性風險分資金流動性(償付能力)與市場流動性(變現能力)。"],[122,7,"7","現金流預測(cash flow forecasting)的管理作用是:","提前識別資金缺口與盈餘,安排融資或配置","預測利潤","編製報表","滿足審計","A","13周滾動現金流預測是流動性管理的基礎工具。"],[123,7,"7","信用風險的核心控制是:","客戶信用評估、限額設定與應收賬款監察","全部預付","不賒銷","無控制","A","信控政策(評級、限額、催收)平衡銷售增長與壞賬風險。"],[124,7,"7","利率風險的對沖工具是:","利率掉期(IRS)、遠期利率協議(FRA)","無對沖","增加存款","減少負債即可","A","利率衍生品把浮動利率負債轉為固定或相反,管理利息成本波動。"],[125,7,"7","匯率風險的類型包括:","交易、折算與經濟(營運)風險","只有一種","只有會計風險","無風險","A","三類匯率風險影響現金流、報表與長期競爭力,管理策略各異。"],[126,7,"7","「利息保障倍數下降」的預警意義是:","盈利對利息的覆蓋減弱,償債壓力上升","利潤上升","現金充足","無意義","A","倍數趨近1時財務風險急升,是信貸與流動性警號。"],[127,7,"7","「集中度風險」在資金管理的表現是:","存款或交易對手過度集中於單一銀行","分散存款","多元融資","無風險","A","交易對手集中度須設限額並分散存放。"],[128,7,"7","「資本結構優化」的考量是:","平衡債務稅盾、財務風險與股權稀釋成本","負債越多越好","股權越多越好","固定不變","A","目標資本結構因行業現金流穩定性而異,是董事會財務決策核心。"],[129,7,"7","「或有負債」的風險特徵是:","是否發生不確定(如訴訟、擔保),但一旦觸發影響重大","確定發生","無影響","只入報表","A","或有負債須評估可能性並按準則披露或計提。"],[130,7,"7","「資金池」(cash pooling)的風險點是:","集團內資金互抵可能引發關聯交易與破產撤銷風險","無風險","只省費用","監管禁止","A","資金池安排須關注法律與稅務影響,尤其集團成員清盤時。"],[131,8,"8","「可保風險」的特徵是:","風險單位獨立、可評估頻率與損失、非投機性","任何風險","投機風險","戰略風險","A","保險適用於純粹風險(只有損失可能),投機與戰略風險須其他手段。"],[132,8,"8","「免賠額」(deductible)的設計目的是:","由被保險人承擔小額損失,降低保費與道德風險","增加理賠","無目的","法定要求","A","免賠額讓雙方分擔小額損失,激勵風險控制。"],[133,8,"8","「投保不足」(underinsurance)的後果是:","按比例賠償條款使賠償低於實際損失","全額賠償","無影響","保險作廢","A","保額低於實際價值時,賠償按比例縮減,須定期重估。"],[134,8,"8","「營業中斷保險」賠償的基礎是:","中斷期間的毛利損失與持續開支,而非資產損失本身","設備價值","收入全額","股息","A","BI 保險補償利潤損失,須準確估計毛利與恢復期。"],[135,8,"8","「網絡保險」通常覆蓋:","數據洩露響應、業務中斷、勒索及第三方責任(視保單)","只覆蓋硬件","無覆蓋","股價損失","A","網絡保險範圍差異大,須仔細審閱除外條款。"],[136,8,"8","「自保公司」(captive insurer)的功能是:","集團內設立保險公司自留風險,改善成本與風險數據","外部保險","無作用","違法","A","大型集團以自保方式內部融資風險並累積承保數據。"],[137,8,"8","「保單免責」審閱的重點是:","除外責任、通知義務、索賠時限與擔保條款","只看保費","只看品牌","無須審閱","A","免責與條件決定實際保障範圍,是投保決策核心。"],[138,8,"8","「風險自留與轉移的平衡」取決於:","公司承受能力、風險頻率與嚴重度、保險成本","一律轉移","一律自留","隨機","A","高頻低損宜自留+控制,低頻高損宜轉移。"],[139,8,"8","「集團保險計劃」統籌的好處是:","規模議價、統一保障標準與集中風險視野","增加費用","無好處","分散管理","A","集中管理保險組合提升效率與透明度。"],[140,8,"8","「索賠管理」的關鍵是:","及時通知、完整證據與持續溝通","拖延通知","隱瞞事實","口頭報告","A","理賠質素影響賠償結果與續保條件。"],[141,9,"9","「第二道防線」的主要保證局限是:","其評估由管理層設計,獨立性不及第三道防線","無局限","最有保證","替代審計","A","三道防線的保證等級遞增,內審提供最獨立保證。"],[142,9,"9","「聯合保證」(combined assurance)的目標是:","協調內外保證提供者,避免重複與缺口","增加報告","取消審計","無目標","A","南非 King Code 提出的概念,已成為金融業最佳實踐。"],[143,9,"9","「審計委員會對保證的審視」應包括:","各保證來源的覆蓋、質量與協調機制","只看內審報告","只看外審意見","無須審視","A","委員會須統攬保證全景,判斷是否足以支撐治理決策。"],[144,9,"9","「風險匯報頻率」的合理設計是:","重大風險即時上報,整體風險概覽按季度向董事會匯報","每年一次","只報喜訊","無須匯報","A","分層匯報確保及時性與董事會議程可行性。"],[145,9,"9","「KRI 超限」的標準處理是:","觸發升級程序,由責任人提出緩解並向委員會匯報","忽略","口頭記錄","無程序","A","KRI 的價值在於閾值觸發的行動機制。"],[146,9,"9","「壓力測試結果」的治理用途是:","評估資本與流動性緩衝是否充足,指導風險偏好調整","裝飾報告","無用途","只在監管要求時","A","結果應轉化為緩衝、限額與應變計劃的調整。"],[147,9,"9","「風險文化指標」的示例是:","員工風險培訓覆蓋、事件主動申報率","股價","員工人數","辦公面積","A","文化指標反映風險行為的實際狀態。"],[148,9,"9","「保證報告的獨立性」判斷標準是:","報告者是否獨立於被評估活動的控制與執行","報告長度","報告格式","顏色","A","獨立性決定保證等級,內審直屬審計委員會是保障。"],[149,9,"9","「外部審計範圍與內審」的協調方式是:","共享風險資訊與發現,避免重複工作(在獨立性允許下)","完全獨立無溝通","內審向外審匯報","無協調","A","協調提升整體保證效率,但內審不得喪失獨立性。"],[150,9,"9","「風險年報」應呈現的核心內容是:","風險偏好執行情況、重大風險變化與管理行動","純理論","無數據","只有目錄","A","年度風險報告讓董事會與市場評估風險管理的成熟度。"],[151,10,"10","「網絡分段」(network segmentation)的作用是:","限制入侵横向移動,縮小事故影響範圍","提高網速","無作用","增加成本","A","分段是縱深防禦的核心,限制勒索軟件擴散。"],[152,10,"10","「零信任架構」(zero trust)的原則是:","永不默認信任,每次存取均須驗證","內網即安全","免認證","無此概念","A","零信任以身份與設備驗證取代邊界信任模型。"],[153,10,"10","「數據分級」的目的在於:","按敏感度設定存取、加密與保留策略","增加流程","無目的","美觀","A","分級使保護措施與風險相稱,避免一刀切。"],[154,10,"10","「第三方軟件供應鏈攻擊」的防禦要點是:","供應商安全評估、更新管理與最小權限","信任大廠即可","無防禦","禁用軟件","A","供應鏈攻擊(如 SolarWinds)顯示第三方管理是網安關鍵。"],[155,10,"10","「數據備份」的 3-2-1 原則是:","三份副本、兩種媒介、一份異地(或離線)","一份即可","只雲端","無原則","A","異地/離線副本是勒索防禦的最後防線。"],[156,10,"10","「事故響應計劃」的核心角色包括:","IT 取證、法律/合規、公關及管理層決策者","只 IT 參與","外包即可","無需角色","A","跨職能響應確保技術止損與披露義務兼顧。"],[157,10,"10","「AI 模型風險」的治理要點是:","模型驗證、偏見測試、人類監督與可解釋性","無須治理","直接部署","只在出事後處理","A","模型風險管理(MRM)借鑒金融業實踐,要求全生命周期治理。"],[158,10,"10","「數據保留政策」的合規基準是:","按法定與業務需要設定保留期,到期安全銷毀","永久保留","立即刪除","無政策","A","過度保留增加洩露與違規風險,不足則違法。"],[159,10,"10","「雲服務」的風險管控要點是:","數據所在地、訪問控制、退出策略與供應商審計","選大廠即可","無風險","禁用雲","A","雲責任共擔模型下,客戶仍須管理配置與存取。"],[160,10,"10","「員工釣魚演練」的目的是:","測試並提升員工識別社交工程的能力","懲罰員工","無目的","代替培訓","A","演練+再培訓是安全意識建設的閉環。"],[161,11,"11","外包的治理原則是:","可以外包工作,不能外包責任","外包即免責","禁止外包","由供應商決定","A","監管(HKMA/SFC)明確董事會對外包活動的最終責任。"],[162,11,"11","「關鍵外包」的額外要求是:","更深入的盡調、合同審計權、退出計劃與監管知會(如需要)","無要求","口頭協議","一次審批","A","關鍵營運外包須全程治理介入並備退出安排。"],[163,11,"11","「供應商績效評審」的頻率應基於:","風險與關鍵程度分級設定(關鍵供應商更頻密)","全部一年一次","無評審","由供應商定","A","分級管理使資源與風險相稱。"],[164,11,"11","「退出條款」應包含:","數據交還/銷毀、過渡協助與知識轉移","無條款","口頭約定","自動續約","A","退出安排是外包治理的底線,須在合同中預設。"],[165,11,"11","「集中度」在第三方風險的表現是:","多個服務依賴同一雲/同一供應商","多元供應","分散地區","無風險","A","看似分散的服務可能共享底層設施,須穿透識別。"],[166,11,"11","「代理行為」對公司的法律責任影響是:","供應商受權行事可令公司對外承擔責任","無影響","由供應商承擔","自動免責","A","公司對代理人在授權範圍內的行為負責,須嚴格限定授權。"],[167,11,"11","「外包審計權」條款的必要性是:","確保公司或其指定方可查核供應商控制","無必要","由供應商拒絕","口頭承諾","A","無審計權則無法驗證外包控制,構成保證缺口。"],[168,11,"11","「數據處理協議」的核心內容是:","處理目的、安全措施、次處理者與違約通知","只列價格","無協議","口頭同意","A","DPA 是數據合規(GDPR/私隱條例)的基本工具。"],[169,11,"11","「供應商倒閉」的應變準備是:","備選供應商、關鍵知識內化與過渡預案","無準備","依賴合同","祈禱","A","關鍵供應商失效須有可執行的替代路徑。"],[170,11,"11","「外包中的監管通報」義務取決於:","活動是否關鍵營運及監管規則要求","一律通報","永不通報","供應商決定","A","金融機構關鍵外包須按規則知會監管機構。"],[171,12,"12","供應商遭勒索攻擊致服務中斷,第一步是:","啟動業務連續性安排與事故指揮,評估影響與披露義務","立即起訴","隱瞞","等待恢復","A","先止損與評估,再考慮法律與披露。"],[172,12,"12","發現內部人員異常大量下載客戶數據,應:","即時凍結帳戶、保留證據並啟動調查與私隱評估","口頭警告","忽略","公開處理","A","數據外洩事故須按調查與通知義務程序處理。"],[173,12,"12","風險委員會發現某 KRI 連續三月超限,應:","要求責任部門說明根因與緩解計劃,必要時升級董事會","視為正常","刪除指標","等年報再說","A","持續超限顯示控制失效,須升級處理。"],[174,12,"12","新產品上市前的風險把關是:","新產品風險評估(NPRA)涵蓋合規、營運與信譽","直接上市","事後補評","由銷售決定","A","NPRA 將風險前置,是穩健創新的標準程序。"],[175,12,"12","管理層認為某重大風險「可接受」但缺乏數據,董事會應:","要求提供評估依據,獨立質詢或尋求外部意見","照單全收","立即否決","無須理會","A","重大風險判斷須有證據基礎,董事會質詢是治理職能。"],[176,12,"12","併購盡調發現標的公司有未披露訴訟,應:","重新評估交易條款與風險定價,調整陳述保證","照原計劃","立即退出","事後處理","A","重大或有負債影響估值與交易結構。"],[177,12,"12","疫情式突發事件下最優先的風險決策是:","保障員工安全與關鍵營運連續性","削減成本","擴大投資","停止溝通","A","人員安全與營運韌性是危機響應的第一優先。"],[178,12,"12","風險匯報與業務匯報衝突(樂觀 vs 謹慎),董事會應:","要求兩方對齊數據與假設,識別差異根源","各信一半","只信樂觀","只信謹慎","A","假設差異是關鍵,董事會須整合出一致的決策基礎。"],[179,12,"12","審計發現管理層繞過內控審批,應:","作為重大缺陷上報審計委員會並啟動問責","內部消化","忽略","只警告當事人","A","繞過控制是重大內控失效,須正式治理處理。"],[180,12,"12","建立「風險問責」文化的關鍵機制是:","清晰的風險所有權、考核掛鈎與行為示範","罰款為主","無機制","靠自覺","A","責任明確+激勵一致+高層示範構成風險文化的三支柱。"]]; const IDS = BANK.map(q => q[0]); const QMAP = {}; BANK.forEach(q => { QMAP[q[0]] = q; }); const PASS = 50, EXC = 75; let quiz = [], idx = 0, score = 0, answers = [], mode = 'normal'; let ME = null, quizStart = 0; /* ==================== 本地账号系统 ==================== */ const PAPER = 'cgqp_rm'; const K_ACCOUNTS = 'sunai_accounts_cgqp_rm'; const K_SESSION = 'sunai_session_cgqp_rm'; function wrongKey(){ return 'sunai_wrong_cgqp_rm::' + (ME ? ME.uid : 'guest'); } function recKey(){ return 'sunai_records_cgqp_rm::' + (ME ? ME.uid : 'guest'); } function lsGet(k, d){ try { const v = localStorage.getItem(k); return v == null ? d : JSON.parse(v); } catch(e){ return d; } } function lsSet(k, v){ try { localStorage.setItem(k, JSON.stringify(v)); } catch(e){} } function val(id){ const el = document.getElementById(id); return el ? el.value : ''; } function esc(s){ const d = document.createElement('div'); d.textContent = s == null ? '' : s; return d.innerHTML; } /* SHA-256:在 file:// 打开时 crypto.subtle 不可用,用它做兜底 */ function sha256hex(msg){ function rotr(x, n){ return (x >>> n) | (x << (32 - n)); } const K = [ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 ]; let H = [0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19]; const bytes = []; const u = unescape(encodeURIComponent(msg)); for (let i = 0; i < u.length; i++) bytes.push(u.charCodeAt(i)); const l = bytes.length; bytes.push(0x80); while (bytes.length % 64 !== 56) bytes.push(0); const bitLen = l * 8; bytes.push(0, 0, 0, 0, (bitLen >>> 24) & 255, (bitLen >>> 16) & 255, (bitLen >>> 8) & 255, bitLen & 255); const w = new Array(64); for (let i = 0; i < bytes.length; i += 64){ for (let j = 0; j < 16; j++){ w[j] = (bytes[i+j*4] << 24) | (bytes[i+j*4+1] << 16) | (bytes[i+j*4+2] << 8) | bytes[i+j*4+3]; } for (let j = 16; j < 64; j++){ const s0 = rotr(w[j-15],7) ^ rotr(w[j-15],18) ^ (w[j-15] >>> 3); const s1 = rotr(w[j-2],17) ^ rotr(w[j-2],19) ^ (w[j-2] >>> 10); w[j] = (w[j-16] + s0 + w[j-7] + s1) | 0; } let a=H[0],b=H[1],c=H[2],d=H[3],e=H[4],f=H[5],g=H[6],h=H[7]; for (let j = 0; j < 64; j++){ const S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25); const ch = (e & f) ^ (~e & g); const t1 = (h + S1 + ch + K[j] + w[j]) | 0; const S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22); const maj = (a & b) ^ (a & c) ^ (b & c); const t2 = (S0 + maj) | 0; h=g; g=f; f=e; e=(d+t1)|0; d=c; c=b; b=a; a=(t1+t2)|0; } H = [(H[0]+a)|0,(H[1]+b)|0,(H[2]+c)|0,(H[3]+d)|0,(H[4]+e)|0,(H[5]+f)|0,(H[6]+g)|0,(H[7]+h)|0]; } let out = ''; for (let i = 0; i < 8; i++) out += ('00000000' + (H[i] >>> 0).toString(16)).slice(-8); return out; } function bytesToHex(b){ let s = ''; for (let i = 0; i < b.length; i++) s += ('0' + b[i].toString(16)).slice(-2); return s; } function hexToBytes(h){ const o = new Uint8Array(h.length / 2); for (let i = 0; i < o.length; i++) o[i] = parseInt(h.substr(i*2, 2), 16); return o; } function randSalt(){ const b = new Uint8Array(16); if (window.crypto && crypto.getRandomValues) crypto.getRandomValues(b); else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256); return bytesToHex(b); } /* 有 WebCrypto 时用 PBKDF2(10万次),否则用迭代 SHA-256 */ async function makeHash(pwd, salt, algo){ if (algo !== 'sha256x3000' && window.crypto && crypto.subtle && window.TextEncoder){ try { const enc = new TextEncoder(); const key = await crypto.subtle.importKey('raw', enc.encode(pwd), 'PBKDF2', false, ['deriveBits']); const bits = await crypto.subtle.deriveBits( { name: 'PBKDF2', salt: hexToBytes(salt), iterations: 100000, hash: 'SHA-256' }, key, 256); return { algo: 'pbkdf2', hash: bytesToHex(new Uint8Array(bits)) }; } catch(e) { /* 落到下面 */ } } let h = salt + '|' + pwd; for (let i = 0; i < 3000; i++) h = sha256hex(h); return { algo: 'sha256x3000', hash: h }; } function getAccounts(){ return lsGet(K_ACCOUNTS, []); } function saveAccounts(a){ lsSet(K_ACCOUNTS, a); } function showMsg(id, text, kind){ const el = document.getElementById(id); if (!el) return; el.className = 'auth-msg show ' + (kind || ''); el.textContent = text; } function hideMsg(id){ const el = document.getElementById(id); if (el) el.className = 'auth-msg'; } function authMsg(text, kind){ const onReg = document.getElementById('rg-box').style.display !== 'none'; showMsg(onReg ? 'rg-msg' : 'lg-msg', text, kind); } async function doRegister(){ const login = val('rg-login').trim(); const p1 = val('rg-pwd'), p2 = val('rg-pwd2'); if (!login) return authMsg('請填寫登入帳號', 'bad'); if (login.length < 3) return authMsg('帳號至少 3 個字元', 'bad'); if (!p1) return authMsg('請設定密碼', 'bad'); if (p1.length < 4) return authMsg('密碼至少 4 位', 'bad'); if (p1 !== p2) return authMsg('兩次輸入的密碼不一致', 'bad'); const accs = getAccounts(); if (accs.some(a => a.login.toLowerCase() === login.toLowerCase())) return authMsg('此帳號已被使用,請換一個或直接登入', 'bad'); const salt = randSalt(); const h = await makeHash(p1, salt); const acc = { uid: 'u_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7), login: login, salt: salt, algo: h.algo, hash: h.hash, name: login, cls: '', createdAt: Date.now() }; accs.push(acc); saveAccounts(accs); loginAs(acc); } async function doLogin(){ const login = val('lg-login').trim(); const pwd = val('lg-pwd'); if (!login || !pwd) return showMsg('lg-msg', '請輸入帳號與密碼', 'bad'); const acc = getAccounts().find(a => a.login.toLowerCase() === login.toLowerCase()); if (!acc) return showMsg('lg-msg', '帳號或密碼不正確', 'bad'); const h = await makeHash(pwd, acc.salt, acc.algo); if (h.hash !== acc.hash) return showMsg('lg-msg', '帳號或密碼不正確', 'bad'); loginAs(acc); } function loginAs(acc){ ME = acc; lsSet(K_SESSION, acc.uid); // 首次登入时,继承旧版(未分账号)的错题本 const legacy = lsGet('sunai_wrong_cgqp_rm', []); if (legacy && legacy.length){ const mine = loadWrong(); if (!mine.length) saveWrong(legacy.slice()); } hideMsg('lg-msg'); hideMsg('rg-msg'); document.getElementById('lg-pwd').value = ''; document.getElementById('rg-pwd').value = ''; document.getElementById('rg-pwd2').value = ''; renderUserBar(); goHome(); } function doLogout(){ if (!confirm('確定登出?登出後可用其他帳號登入。')) return; ME = null; try { localStorage.removeItem(K_SESSION); } catch(e){} renderUserBar(); document.getElementById('lg-login').value = ''; document.getElementById('lg-pwd').value = ''; show('sc-auth'); } function renderUserBar(){ const bar = document.getElementById('userbar'); if (!bar) return; if (ME){ bar.style.display = 'flex'; document.getElementById('ub-name').textContent = ME.name || ME.login; document.getElementById('ub-cls').textContent = ME.cls ? '(' + ME.cls + ')' : ''; } else { bar.style.display = 'none'; } } function updateMe(){ if (!ME) return; document.getElementById('me-name').value = ME.name || ''; document.getElementById('me-class').value = ME.cls || ''; document.getElementById('me-login').value = ME.login || ''; hideMsg('me-msg'); hideMsg('pw-msg'); document.getElementById('pw-old').value = ''; document.getElementById('pw-new').value = ''; } function saveProfile(){ const accs = getAccounts(); const acc = accs.find(a => a.uid === ME.uid); if (!acc) return showMsg('me-msg', '找不到帳號,請重新登入', 'bad'); acc.name = val('me-name').trim() || acc.login; acc.cls = val('me-class').trim(); saveAccounts(accs); ME = acc; renderUserBar(); showMsg('me-msg', '已儲存', 'good'); } async function changePwd(){ const o = val('pw-old'), n = val('pw-new'); if (!o || !n) return showMsg('pw-msg', '請填寫目前密碼與新密碼', 'bad'); if (n.length < 4) return showMsg('pw-msg', '新密碼至少 4 位', 'bad'); const accs = getAccounts(); const acc = accs.find(a => a.uid === ME.uid); if (!acc) return showMsg('pw-msg', '找不到帳號,請重新登入', 'bad'); const ho = await makeHash(o, acc.salt, acc.algo); if (ho.hash !== acc.hash) return showMsg('pw-msg', '目前密碼不正確', 'bad'); const salt = randSalt(); const hn = await makeHash(n, salt); acc.salt = salt; acc.algo = hn.algo; acc.hash = hn.hash; saveAccounts(accs); ME = acc; document.getElementById('pw-old').value = ''; document.getElementById('pw-new').value = ''; showMsg('pw-msg', '密碼已修改', 'good'); } /* ==================== 错题本 / 记录(按账号) ==================== */ function loadWrong(){ return lsGet(wrongKey(), []); } function wcKey(){ return 'sunai_wrongcnt_' + PAPER + '::' + (ME ? ME.uid : 'guest'); } function loadWC(){ return lsGet(wcKey(), {}); } function saveWC(c){ lsSet(wcKey(), c); } function bumpWrong(id){ const c = loadWC(); c[id] = (c[id] || 0) + 1; saveWC(c); } function clearWrongCnt(id){ const c = loadWC(); if (c[id]) { delete c[id]; saveWC(c); } } function loadFreq(min){ const c = loadWC(); return Object.keys(c) .filter(id => QMAP[id] && c[id] >= min) .sort((a, b) => c[b] - c[a]).map(id => +id); } function saveWrong(w){ lsSet(wrongKey(), w); } function loadRecords(){ return lsGet(recKey(), []); } function addRecord(rec){ const r = loadRecords(); r.unshift(rec); if (r.length > 300) r.length = 300; lsSet(recKey(), r); } /* ==================== 異議回報 ==================== */ function rpKey(){ return 'sunai_reports_' + PAPER + '::' + (ME ? ME.uid : 'guest'); } function loadReports(){ return lsGet(rpKey(), []); } function toggleReport(){ const box = document.getElementById('rp-box'); const show = box.style.display === 'none'; box.style.display = show ? 'block' : 'none'; document.getElementById('rp-msg').style.display = 'none'; document.getElementById('rp-text').value = ''; } function submitReport(){ const q = quiz[idx]; const reason = document.getElementById('rp-text').value.trim(); if (!reason) { alert('請先填寫你的疑問。'); return; } const a = answers[answers.length - 1]; const r = loadReports(); r.push({ paper: PAPER, qid: q.id, ch: q.ch, ref: q.ref, q: q.q.slice(0, 120), myAns: a ? a.pick : '-', correctAns: q.ans, reason: reason.slice(0, 500), at: Date.now() }); lsSet(rpKey(), r); document.getElementById('rp-msg').style.display = 'block'; document.getElementById('rp-msg').textContent = '✅ 已記錄你的回報,感謝!你可以在「異議清單」中一次過傳送給我們。'; setTimeout(()=>{ document.getElementById('rp-box').style.display = 'none'; }, 1800); } function reportTextAll(){ const r = loadReports(); if (!r.length) return ''; return '【IIQE 題目異議回報 - ' + PAPER + '】\n' + r.map((x, i) => (i + 1) + '. 題號#' + x.qid + '(第' + x.ch + '章 ' + (x.ref || '') + ')\n' + '題目:' + x.q + '…\n' + '我的答案:' + x.myAns + '|現有答案:' + x.correctAns + '\n' + '疑問:' + x.reason ).join('\n\n'); } function copyReports(){ const txt = reportTextAll(); if (!txt) { alert('目前沒有異議回報。'); return; } (navigator.clipboard ? navigator.clipboard.writeText(txt) : Promise.reject()) .then(()=>alert('已複製,可貼上給我們。')) .catch(()=>{ const ta = document.createElement('textarea'); ta.value = txt; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); alert('已複製,可貼上給我們。'); }); } function waReports(){ const txt = reportTextAll(); if (!txt) { alert('目前沒有異議回報。'); return; } window.open('https://wa.me/85268751658?text=' + encodeURIComponent(txt), '_blank'); } function openReports(){ const r = loadReports(); const box = document.getElementById('rp-list'); document.getElementById('rp-n').textContent = r.length; document.getElementById('rp-copy').disabled = r.length === 0; document.getElementById('rp-wa').disabled = r.length === 0; box.innerHTML = r.length ? r.slice().reverse().map(x => { const d = new Date(x.at); const pad = n => (n < 10 ? '0' + n : '' + n); return '
' + '
題號#' + x.qid + '(第' + x.ch + '章 ' + (x.ref || '') + ')
' + '
' + esc(x.q) + '…
' + '
我的答案 ' + x.myAns + '|現有答案 ' + x.correctAns + '
' + '
💬 ' + esc(x.reason) + '
' + '
' + d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) + '
' + '
'; }).join('') : '
目前沒有異議回報。
'; show('sc-reports'); } function clearWrongBank(){ if (!confirm('確定清空錯題本?此動作無法復原。')) return; saveWrong([]); openWrong(); } function openWrong(){ const w = loadWrong(); document.getElementById('wb-n').textContent = w.length; document.getElementById('wb-redo').disabled = w.length === 0; const fq = loadFreq(2); const wf = document.getElementById('wb-freq'); if (wf) wf.disabled = fq.length === 0; const box = document.getElementById('wb-list'); if (!w.length){ box.innerHTML = '
目前沒有錯題,繼續保持!
'; } else { box.innerHTML = w.map(id => { const q = QMAP[id]; if (!q) return ''; const ai = ['A','B','C','D'].indexOf(q[8]); const at = ai >= 0 ? q[4 + ai] : ''; const wc = loadWC()[id] || 1; const hot = wc >= 2 ? '❌ 錯 ' + wc + ' 次 ' : ''; return '
' + '
' + hot + esc(q[3].slice(0, 90)) + (q[3].length > 90 ? '…' : '') + '
' + '
正確答案:' + q[8] + '. ' + esc(at) + '
' + (q[9] ? '
💡 ' + esc(q[9]) + '
' : '') + '
參考章節 ' + esc(q[2] || '—') + '
' + '
'; }).join(''); } show('sc-wrong'); } function openRecords(){ const r = loadRecords(); const box = document.getElementById('rc-list'); if (!r.length){ box.innerHTML = '
還沒有練習記錄,先做一份模擬練習吧!
'; } else { box.innerHTML = r.map(x => { const d = new Date(x.at); const pad = n => (n < 10 ? '0' + n : '' + n); const when = d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); const tone = x.pct >= EXC ? 'var(--ok)' : (x.pct >= PASS ? 'var(--navy2)' : 'var(--bad)'); return '
' + '
' + x.mode + '' + '' + x.pct + '% ' + x.grade + '
' + '
答對 ' + x.correct + ' / ' + x.total + ' 題 · ' + when + '
' + '
'; }).join(''); } show('sc-records'); } function openMe(){ updateMe(); show('sc-me'); } /* ==================== 首页 / 抽题 / 答题 ==================== */ function refreshHome(){ document.getElementById('st-bank').textContent = BANK.length; const w = loadWrong(); document.getElementById('wrong-n').textContent = w.length; document.getElementById('btn-wrong').disabled = w.length === 0; const fn = loadFreq(2); const fe = document.getElementById('freq-n'); if (fe) fe.textContent = fn.length; const bf = document.getElementById('btn-freq'); if (bf) bf.disabled = fn.length === 0; const rn = loadReports().length; const rh = document.getElementById('rp-home-n'); if (rh) rh.textContent = rn; renderUserBar(); } let COUNT = 100; document.querySelectorAll('#seg-count button').forEach(b=>{ b.onclick = ()=>{ document.querySelectorAll('#seg-count button').forEach(x=>x.classList.remove('on')); b.classList.add('on'); COUNT = b.dataset.n === 'all' ? BANK.length : +b.dataset.n; }; }); (function(){ const ab = document.querySelector('#seg-count button[data-n="all"]'); if(ab) ab.textContent = '全部題庫 ' + BANK.length + ' 題'; })(); document.querySelectorAll('#seg-auth button').forEach(b=>{ b.onclick = ()=>{ document.querySelectorAll('#seg-auth button').forEach(x=>x.classList.remove('on')); b.classList.add('on'); const isLogin = b.dataset.tab === 'login'; document.getElementById('lg-box').style.display = isLogin ? 'block' : 'none'; document.getElementById('rg-box').style.display = isLogin ? 'none' : 'block'; hideMsg('lg-msg'); hideMsg('rg-msg'); }; }); document.addEventListener('keydown', function(e){ if (e.key !== 'Enter') return; if (document.getElementById('sc-auth').classList.contains('on')){ if (document.getElementById('rg-box').style.display === 'none') doLogin(); else doRegister(); } }); function show(id){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-'+id.replace('sc-','')).classList.add('on'); window.scrollTo(0,0); } function goHome(){ refreshHome(); document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-home').classList.add('on'); window.scrollTo(0,0); } function pickQuestions(){ const w = loadWrong(); if(mode === 'freq'){ const set = new Set(loadFreq(2)); return BANK.filter(q=>set.has(q[0])).sort((a,b)=>{ const c = loadWC(); return (c[b.id]||0)-(c[a.id]||0); }).map(q=>{ const letters = ['A','B','C','D']; const opts = [q[4],q[5],q[6],q[7]]; const ansIdx = letters.indexOf(q[8]); const idxs = [0,1,2,3].sort(()=>Math.random()-.5); const newOpts = idxs.map(j=>opts[j]); const newAns = letters[idxs.indexOf(ansIdx)]; return {id:q[0], ch:q[1], ref:q[2], q:q[3], opts:newOpts, ans:newAns, expl:q[9]}; }); } if(mode === 'wrong'){ const set = new Set(w); return BANK.filter(q=>set.has(q[0])).sort(()=>Math.random()-.5).map(q=>{ const letters = ['A','B','C','D']; const opts = [q[4],q[5],q[6],q[7]]; const ansIdx = letters.indexOf(q[8]); const idxs = [0,1,2,3].sort(()=>Math.random()-.5); const newOpts = idxs.map(j=>opts[j]); const newAns = letters[idxs.indexOf(ansIdx)]; return {id:q[0], ch:q[1], ref:q[2], q:q[3], opts:newOpts, ans:newAns, expl:q[9]}; }); } const byCh = {}; BANK.forEach(q=>{ (byCh[q[1]] = byCh[q[1]]||[]).push(q); }); Object.values(byCh).forEach(a=>a.sort(()=>Math.random()-.5)); const total = BANK.length; const picks = []; const counts = {}; let assigned = 0; for(const ch in byCh){ counts[ch] = Math.floor(byCh[ch].length/total*COUNT); assigned += counts[ch]; } let rem = COUNT - assigned; const order = Object.keys(byCh).sort((a,b)=>byCh[b].length-byCh[a].length); let i = 0; while(rem > 0){ counts[order[i%order.length]]++; rem--; i++; } for(const ch in counts){ counts[ch] = Math.min(counts[ch], byCh[ch].length); picks.push(...byCh[ch].slice(0, counts[ch])); } if(picks.length < COUNT){ const used = new Set(picks.map(q=>q[0])); for(const q of BANK){ if(picks.length>=COUNT) break; if(!used.has(q[0])) picks.push(q); } } return picks.sort(()=>Math.random()-.5).map(q=>{ const letters = ['A','B','C','D']; const opts = [q[4],q[5],q[6],q[7]]; const ansIdx = letters.indexOf(q[8]); const idxs = [0,1,2,3].sort(()=>Math.random()-.5); const newOpts = idxs.map(j=>opts[j]); const newAns = letters[idxs.indexOf(ansIdx)]; return {id:q[0], ch:q[1], ref:q[2], q:q[3], opts:newOpts, ans:newAns, expl:q[9]}; }); } function startQuiz(isWrong, isFreq){ if(!ME){ show('sc-auth'); return; } mode = isWrong ? 'wrong' : (isFreq ? 'freq' : 'normal'); quiz = pickQuestions(); if(!quiz.length){ alert(mode==='wrong' ? '錯題庫是空的,先去練習吧!' : (mode==='freq' ? '暫時沒有常錯題(答錯 2 次以上才會收錄)。' : '沒有可用題目')); return; } idx = 0; score = 0; answers = []; quizStart = Date.now(); document.getElementById('q-mode').textContent = mode==='wrong' ? '錯題重做' : (mode==='freq' ? '🔥 常錯題重做' : '模擬練習'); document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-quiz').classList.add('on'); window.scrollTo(0,0); renderQ(); } let curLocked = false; function renderQ(){ const q = quiz[idx]; curLocked = false; document.getElementById('q-idx').textContent = '第 ' + (idx+1) + ' / ' + quiz.length + ' 題'; document.getElementById('pbar').style.width = (idx/quiz.length*100)+'%'; document.getElementById('q-text').textContent = q.q; const box = document.getElementById('q-opts'); box.innerHTML = ''; ['A','B','C','D'].forEach(L=>{ const b = document.createElement('button'); b.className = 'opt'; b.innerHTML = '' + L + '.' + esc(q.opts[['A','B','C','D'].indexOf(L)]) + ''; b.onclick = ()=>answer(L, b); box.appendChild(b); }); const ex = document.getElementById('q-expl'); ex.className = 'expl'; ex.innerHTML = ''; document.getElementById('btn-next').disabled = true; document.getElementById('btn-next').textContent = (idx === quiz.length-1) ? '查看成績 🏁' : '下一題 →'; } function answer(L, btn){ if(curLocked) return; curLocked = true; const q = quiz[idx]; const correct = (L === q.ans); const btns = document.querySelectorAll('#q-opts .opt'); btns.forEach(b=>{ b.disabled = true; }); const ansIdx = ['A','B','C','D'].indexOf(q.ans); btns[ansIdx].classList.add('correct'); if(!correct){ btn.classList.add('wrong'); } btns.forEach(b=>{ if(b!==btns[ansIdx] && b!==btn) b.classList.add('dim'); }); if(correct){ score++; clearWrongCnt(q.id); } else { const w = loadWrong(); if(!w.includes(q.id)){ w.push(q.id); saveWrong(w); } bumpWrong(q.id); } answers.push({q, pick:L, correct}); const ex = document.getElementById('q-expl'); ex.className = 'expl show ' + (correct ? 'good' : 'bad'); const expl = q.expl && q.expl.length > 3 ? q.expl : '建議重溫本章節課程內容,加深理解。'; ex.innerHTML = (correct ? '\u2705 答對了!' : '\u274C 答錯了。正確答案:' + q.ans + '
') + '
💡 ' + esc(expl) + '
' + '參考章節 ' + esc(q.ref||'—') + ''; document.getElementById('btn-next').disabled = false; } function nextQ(){ if(idx < quiz.length-1){ idx++; renderQ(); } else showResult(); } function quitQuiz(){ if(confirm('確定離開本次練習?作答紀錄將不會保存。')) goHome(); } function showResult(){ const pct = Math.round(score/quiz.length*100); document.getElementById('score-num').textContent = pct; const ring = document.getElementById('ring'); ring.style.background = 'conic-gradient(' + (pct>=PASS?'var(--navy2)':'var(--bad)') + ' ' + (pct*3.6) + 'deg, #ece7db 0deg)'; const g = document.getElementById('grade-badge'); if(pct >= EXC){ g.className='grade exc'; g.textContent='🏆 優秀'; } else if(pct >= PASS){ g.className='grade pass'; g.textContent='\u2705 合格'; } else { g.className='grade fail'; g.textContent='\u274C 不合格'; } document.getElementById('score-detail').textContent = '答對 ' + score + ' / ' + quiz.length + ' 題(合格線 ' + PASS + '%,優秀線 ' + EXC + '%)'; const byCh = {}; answers.forEach(a=>{ const ch = a.q.ch; byCh[ch] = byCh[ch]||[0,0]; byCh[ch][1]++; if(a.correct) byCh[ch][0]++; }); document.getElementById('ch-stats').innerHTML = Object.keys(byCh).sort((a,b)=>a-b).map(ch=>{ const c = byCh[ch][0], t = byCh[ch][1]; return '
第 ' + ch + ' 章:答對 ' + c + ' / ' + t + ' 題(' + Math.round(c/t*100) + '%)
'; }).join(''); const wrongs = answers.filter(a=>!a.correct); document.getElementById('wrong-review').innerHTML = wrongs.length ? wrongs.map(a=> '
答錯' + esc(a.q.q.slice(0,60)) + '…
' + '正確答案:' + a.q.ans + '. ' + esc(a.q.opts[['A','B','C','D'].indexOf(a.q.ans)].slice(0,50)) + '
' ).join('') : '
🎉 全部答對,沒有錯題!
'; if(mode === 'wrong'){ let w = loadWrong(); const corrected = answers.filter(a=>a.correct).map(a=>a.q.id); w = w.filter(id=>!corrected.includes(id)); saveWrong(w); } if(ME){ const gradeTxt = pct >= EXC ? '優秀' : (pct >= PASS ? '合格' : '不合格'); addRecord({ at: Date.now(), mode: mode === 'wrong' ? '錯題重做' : ('模擬練習 ' + quiz.length + ' 題'), total: quiz.length, correct: score, pct: pct, grade: gradeTxt, sec: Math.round((Date.now() - quizStart)/1000) }); } document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-result').classList.add('on'); window.scrollTo(0,0); } /* ==================== 启动 ==================== */ (function init(){ const sid = lsGet(K_SESSION, null); if (sid){ const acc = getAccounts().find(a => a.uid === sid); if (acc) ME = acc; } renderUserBar(); if (ME){ document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-home').classList.add('on'); refreshHome(); } else { document.querySelectorAll('.screen').forEach(s=>s.classList.remove('on')); document.getElementById('sc-auth').classList.add('on'); } })(); refreshHome();