🏛️ 税务稽查 · 完整剧情
by SparkGlider581124 lines65.7 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>🏛️ 税务稽查 · 完整剧情</title>
<style>
/* ===== 像素风格 · 移动优先 ===== */
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
* { margin:0; padding:0; box-sizing:border-box; user-select:none; -webkit-tap-highlight-color:transparent; }
body { background:#0a0f1e; display:flex; justify-content:center; align-items:center; min-height:100vh; font-family:'Courier New',monospace; padding:8px; image-rendering:pixelated; }
#game { background:#131e30; border:4px solid #1f3355; border-radius:14px; padding:14px 12px 16px; max-width:480px; width:100%; box-shadow:0 0 0 3px #070b14,0 8px 0 #070b14; position:relative; color:#c8d8f0; min-height:94vh; display:flex; flex-direction:column; }
.header { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:4px 8px; padding:6px 10px; background:#0b1424; border:2px solid #1f3355; border-radius:10px; margin-bottom:10px; font-size:10px; flex-shrink:0; }
.header .title { color:#f0c040; font-size:13px; font-family:'Press Start 2P',monospace; text-shadow:2px 2px 0 #4a3000; letter-spacing:1px; }
.header .info { display:flex; flex-wrap:wrap; gap:3px 6px; font-size:9px; font-weight:bold; }
.header .info span { background:#0f1a2e; padding:3px 8px; border:1px solid #1f3a5a; border-radius:6px; white-space:nowrap; }
.header .info .level-badge { background:#2a1a0a; border-color:#b08030; color:#f0d080; }
.status-bar { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:3px 6px; padding:5px 10px; background:#0b1424; border:2px solid #1a2a44; border-radius:8px; margin-bottom:12px; font-size:9px; color:#8ab4e8; flex-shrink:0; }
.status-bar .stage { color:#7fc8f8; }
.status-bar .score { color:#f0d080; }
.status-bar .honor { color:#ffd700; }
.main-area { background:#0b1424; border:3px solid #1a2a44; border-radius:10px; padding:12px 10px; min-height:400px; flex:1; display:flex; flex-direction:column; overflow-y:auto; max-height:70vh; scroll-behavior:smooth; }
.main-area::-webkit-scrollbar { width:4px; background:#0a1a2a; }
.main-area::-webkit-scrollbar-thumb { background:#2a4a6a; border-radius:4px; }
.dialogue-box { display:flex; flex-direction:column; gap:10px; padding:2px 0; }
.msg { padding:10px 14px; border-radius:14px; font-size:14px; line-height:1.7; max-width:94%; word-break:break-word; border:2px solid transparent; box-shadow:0 2px 0 rgba(0,0,0,0.4); animation:fadeIn 0.35s ease-out; }
.msg-narrator { background:#121f2e; color:#a0c8e8; border-color:#2a4a6a; align-self:center; text-align:center; font-style:italic; font-size:13px; max-width:98%; border-style:dashed; padding:8px 16px; }
.msg-supervisor { background:#0c1828; color:#f0d080; border-color:#c08030; align-self:flex-start; border-left:6px solid #c08030; padding:10px 14px 10px 18px; }
.msg-supervisor .who { color:#f0a030; font-weight:bold; font-size:12px; display:block; margin-bottom:4px; }
.msg-player { background:#142814; color:#b0f0c0; border-color:#3a7a5a; align-self:flex-end; border-right:6px solid #3a7a5a; padding:10px 18px 10px 14px; }
.msg-system { background:#281a1a; color:#f0a0b0; border-color:#8a3a5a; align-self:center; text-align:center; font-weight:bold; padding:8px 16px; }
.msg .highlight { color:#ffd700; }
.msg .em { color:#80f0b0; }
.options-container { display:flex; flex-direction:column; gap:8px; margin:10px 0 6px 0; }
.opt-btn { background:#15263a; border:2px solid #2a4a6a; border-radius:10px; padding:12px 14px; color:#c0d8f0; font-size:14px; font-family:'Courier New',monospace; font-weight:bold; text-align:left; cursor:pointer; transition:all 0.12s; line-height:1.5; word-break:break-word; box-shadow:0 3px 0 #0a1a2a; min-height:50px; -webkit-touch-callout:none; }
.opt-btn:active:not(.disabled) { transform:translateY(2px); box-shadow:0 1px 0 #0a1a2a; }
.opt-btn.selected { border-color:#f0c040; background:#2a2a1a; }
.opt-btn.correct { border-color:#40d080; background:#1a3a2a; color:#80f0b0; }
.opt-btn.wrong { border-color:#d04040; background:#3a1a1a; color:#f08080; }
.opt-btn.disabled { cursor:default; opacity:0.85; }
.opt-btn .label { color:#6a9aca; margin-right:8px; font-weight:bold; }
.btn-group { display:flex; flex-wrap:wrap; gap:10px; margin-top:14px; justify-content:center; }
.btn { font-family:'Courier New',monospace; font-weight:bold; font-size:14px; padding:12px 22px; border:3px solid #2a4a6a; border-radius:10px; background:#1a2a4a; color:#d4e6ff; cursor:pointer; transition:all 0.12s; text-align:center; box-shadow:0 4px 0 #0a1a2a; letter-spacing:0.5px; min-height:50px; min-width:110px; flex:1; }
.btn:active:not(:disabled) { transform:translateY(2px); box-shadow:0 2px 0 #0a1a2a; }
.btn:disabled { opacity:0.4; cursor:not-allowed; transform:none; }
.btn-primary { background:#1f4a2f; border-color:#3a7a5a; color:#b0f0c0; box-shadow:0 4px 0 #122a1a; }
.btn-gold { background:#6a4a1a; border-color:#b08030; color:#f0d080; box-shadow:0 4px 0 #3a2a0a; }
.btn-danger { background:#4a1a1a; border-color:#8a3a3a; color:#f0a0a0; box-shadow:0 4px 0 #2a0a0a; }
.btn-sm { font-size:12px; padding:8px 14px; min-height:36px; min-width:70px; flex:0 1 auto; }
.progress-wrap { background:#0a1a2a; border:2px solid #1a2a44; border-radius:8px; padding:3px; margin:6px 0 10px 0; }
.progress-bar { height:16px; background:#1f4a2f; border-radius:5px; transition:width 0.5s; width:0%; position:relative; }
.progress-bar .label { position:absolute; right:4px; top:0; font-size:9px; color:#b0f0c0; line-height:16px; font-weight:bold; }
.result-card { text-align:center; padding:8px 4px; animation:fadeIn 0.5s ease-out; }
.result-card .big { font-size:22px; color:#f0d080; margin:6px 0; font-family:'Press Start 2P',monospace; line-height:1.4; }
.result-card .sub { font-size:14px; color:#8ab4e8; margin:4px 0; }
.result-card .detail { font-size:12px; color:#6a8aaa; margin:3px 0; line-height:1.8; }
.result-card .honor-badge { color:#ffd700; font-size:14px; background:#2a2a0a; padding:8px 18px; border:2px solid #c08030; border-radius:10px; display:inline-block; margin:6px 0; }
.result-card .danger-badge { color:#f08080; font-size:14px; background:#2a0a0a; padding:8px 18px; border:2px solid #d04040; border-radius:10px; display:inline-block; margin:6px 0; }
.result-card .promotion { color:#80f0b0; font-size:16px; background:#0a2a1a; padding:10px 24px; border:3px solid #40d080; border-radius:10px; display:inline-block; margin:8px 0; font-family:'Press Start 2P',monospace; }
.result-card .game-over { color:#f08080; font-size:22px; margin:10px 0; font-family:'Press Start 2P',monospace; }
.result-card .game-win { color:#f0d080; font-size:22px; margin:10px 0; font-family:'Press Start 2P',monospace; }
@keyframes fadeIn { 0%{ opacity:0; transform:translateY(12px); } 100%{ opacity:1; transform:translateY(0); } }
@keyframes pulse { 0%,100%{ opacity:1; } 50%{ opacity:0.5; } }
.pulse { animation:pulse 1.2s ease-in-out infinite; }
.pixel-divider { border:none; height:2px; background:#1a2a44; margin:8px 0; box-shadow:0 1px 0 #0a1a2a; }
@media (max-width:640px) { #game{ padding:10px 8px 12px; border-radius:10px; min-height:96vh; } .header .title{ font-size:11px; } .header .info{ font-size:8px; } .header .info span{ padding:2px 6px; } .msg{ font-size:14px; padding:8px 12px; line-height:1.6; } .msg-narrator{ font-size:12px; padding:6px 12px; } .opt-btn{ font-size:14px; padding:10px 12px; min-height:46px; } .btn{ font-size:14px; padding:10px 14px; min-height:46px; min-width:80px; } .status-bar{ font-size:8px; padding:4px 8px; } .result-card .big{ font-size:18px; } .main-area{ min-height:340px; max-height:65vh; padding:10px 8px; } }
@media (max-width:420px) { .header{ flex-direction:column; align-items:stretch; gap:3px; } .header .info{ justify-content:center; } .msg{ font-size:13px; max-width:97%; } .opt-btn{ font-size:13px; padding:8px 10px; min-height:40px; } .btn{ font-size:13px; padding:8px 12px; min-height:40px; min-width:70px; } .result-card .big{ font-size:16px; } }
.no-scrollbar::-webkit-scrollbar { display:none; }
.no-scrollbar { -ms-overflow-style:none; scrollbar-width:none; }
.footer { margin-top:10px; text-align:center; font-size:8px; color:#1f3a5a; letter-spacing:1px; flex-shrink:0; padding:2px 0; }
</style>
</head>
<body>
<div id="game">
<div class="header">
<div class="title">🏛️ 稽查风云</div>
<div class="info">
<span class="level-badge" id="levelDisplay">一级行政执法员</span>
<span id="yearDisplay">📅 第1年</span>
<span id="checkDisplay">🔍 0/5</span>
</div>
</div>
<div class="status-bar">
<span class="stage" id="stageProgress">📊 关卡: 0/50</span>
<span class="score" id="yearScoreDisplay">⭐ 本年: 0</span>
<span class="honor" id="honorDisplay">🏅 荣誉: 0</span>
<span id="yearStatus" style="color:#6a8aaa;">⏳ 待命</span>
</div>
<div class="main-area no-scrollbar" id="mainArea">
<div id="content"></div>
</div>
<div class="footer">⬛ 像素稽查 · 完整剧情 ⬛</div>
</div>
<script>
// ================================================================
// 税务稽查模拟器 · 完整剧情版(已修复bug,增加旁白)
// ================================================================
// ================================================================
// 关卡数据(包含多个案例,每个案例12道以上题目)
// ================================================================
const LEVELS = [{
id:0, name:'个体饭店', supervisor:'股长老王', supervisorEmoji:'👨💼',
brief:'“小张,记住,要实地观察,证据说话。”',
cases: [
{ name:'阿花饭店', opening:['📨 举报信称,阿花饭店天天客满,但申报的月营业额只有8万,明显不符。','🔍 你来到饭店附近,发现午市座无虚席,收银台前排着队。'],
questions:[
{context:'你站在阿花饭店对面,观察了2小时,统计到约150人就餐,人均消费约60元。',q:'第一步最合理的做法是?',opts:['直接进店要求查账','记录客流和收银情况,并与申报数据对比','出示证件责令停业','联系工商部门联合执法'],ans:1,feedback_correct:'✅ 先外围取证,固定证据链。老王点头:“稳扎稳打。”',feedback_wrong:'❌ 打草惊蛇可能让证据灭失。先观察再行动。'},
{context:'你调取了阿花饭店的银行账户流水,发现每月都有大量现金存入,但未在申报中体现。',q:'这些现金存款最可能说明什么?',opts:['正常营业款','隐瞒的营业收入','股东借款','亲友转账'],ans:1,feedback_correct:'✅ 现金存款与申报不符,是隐匿收入的铁证。',feedback_wrong:'❌ 若无合理解释,则极可能是账外收入。'},
{context:'约谈老板阿花时,她声称“现金是借来的”,但无法提供借条。',q:'面对这种辩解,你应该?',opts:['采信她的说法','要求其提供借款合同和出借人信息','直接认定为偷税','通知公安介入'],ans:1,feedback_correct:'✅ 让当事人自行举证,若无法证明,则承担不利后果。',feedback_wrong:'❌ 不能轻信口头辩解,必须要求提供证据。'},
{context:'你查实阿花饭店三年少申报收入120万元。',q:'这种行为属于?',opts:['逃税','偷税','漏税','避税'],ans:1,feedback_correct:'✅ 故意隐瞒收入,定性为偷税。',feedback_wrong:'❌ 偷税是故意,漏税是过失。'},
{context:'案件处理阶段,阿花托人送来礼品,希望“从轻处理”。',q:'你应该?',opts:['收下礼品,酌情处理','坚决拒绝,并告知法律后果','请示领导','暂缓处理'],ans:1,feedback_correct:'✅ 坚持原则,廉洁执法。',feedback_wrong:'❌ 税务稽查必须严守纪律。'},
{context:'你通过第三方平台数据,发现阿花饭店线上团购交易额巨大,但未入账。',q:'这属于什么性质?',opts:['电商税问题','隐瞒线上收入','正常经营活动','平台数据有误'],ans:1,feedback_correct:'✅ 线上收入也是收入,必须全额申报。',feedback_wrong:'❌ 所有渠道收入都应纳税。'},
{context:'阿花饭店的采购单据显示,进货金额与申报成本严重不符。',q:'这最可能说明什么问题?',opts:['虚增成本','隐瞒收入','正常经营','采购失误'],ans:0,feedback_correct:'✅ 虚增成本也是偷税手段。',feedback_wrong:'❌ 需核实采购真实性。'},
{context:'阿花饭店的员工反映,老板经常指示“不开票”,员工提成也按不开票销售额计算。',q:'员工证言属于什么证据?',opts:['间接证据','直接证据','辅助证据','非法证据'],ans:0,feedback_correct:'✅ 员工证言是重要间接证据,需与其他证据印证。',feedback_wrong:'❌ 单一证言不能定案,需形成证据链。'},
{context:'你计算了阿花饭店的保本点,发现其申报收入远低于保本所需。',q:'保本点分析在稽查中的作用是?',opts:['财务分析工具','推算真实收入的参考','法律依据','行政处罚标准'],ans:1,feedback_correct:'✅ 保本点分析有助于推算实际收入。',feedback_wrong:'❌ 可作为佐证,但不能单独作为处罚依据。'},
{context:'阿花饭店最终补缴税款及罚款合计50万元,老板表示接受。',q:'案件处理完毕,你应该?',opts:['结案归档','建议开展后续跟踪','公开曝光','以上均可'],ans:3,feedback_correct:'✅ 可综合采取多项后续管理措施。',feedback_wrong:'❌ 应视情况选择。'},
{context:'阿花饭店的POS机数据被清空,你通过技术手段恢复。',q:'恢复的数据能否作为证据?',opts:['不能','可以,但需鉴定','仅作参考','必须由当事人确认'],ans:1,feedback_correct:'✅ 经鉴定确认的数据可作为证据。',feedback_wrong:'❌ 需确保合法取得。'},
{context:'你发现阿花饭店还通过微信收款,未入公账。',q:'微信收款应如何处理?',opts:['属于个人隐私','应并入收入申报','由企业自行决定','税务机关无权查看'],ans:1,feedback_correct:'✅ 凡属经营收入,均需申报纳税。',feedback_wrong:'❌ 不可隐瞒微信收款。'}
]
},
{ name:'好再来酒店', opening:['📞 群众举报:好再来酒店经常不给顾客开发票,怀疑有账外经营。','🔍 你以顾客身份就餐,发现结账时对方主动抹零但不给发票。'],
questions:[
{context:'你以顾客身份消费后索要发票,店家表示“发票用完了”。',q:'这通常意味着?',opts:['发票确实用完','可能存在不开发票隐瞒收入','会计忘记领票','系统故障'],ans:1,feedback_correct:'✅ 不开发票是隐匿收入的常见手段。',feedback_wrong:'❌ 频繁“发票用完”往往是借口。'},
{context:'你调取了酒店的订餐记录,发现与实际申报的桌数严重不符。',q:'下一步关键证据是?',opts:['收银台流水','厨房采购单','服务员排班表','美团点评数据'],ans:0,feedback_correct:'✅ 收银流水直接反映收入。',feedback_wrong:'❌ 收银数据是核心,其他为辅。'},
{context:'酒店老板承认部分收入未入账,但称“用于员工福利”。',q:'这种说法在法律上能否成立?',opts:['可以,属于合理支出','不能,收入必须全额申报','视金额大小而定','由税务局自由裁量'],ans:1,feedback_correct:'✅ 收入必须依法全额申报。',feedback_wrong:'❌ 收入不申报即违法,用途不影响定性。'},
{context:'该酒店三年累计隐瞒收入200万元。',q:'适用的处罚依据是?',opts:['税收征管法','发票管理办法','行政处罚法','刑法'],ans:0,feedback_correct:'✅ 偷税行为适用《税收征管法》。',feedback_wrong:'❌ 主要依据税收征管法。'},
{context:'案件查结后,你发现该酒店还有关联企业,可能涉及更多问题。',q:'你应该?',opts:['就此结案','建议稽查局开展延伸检查','移交公安','告知企业自行纠正'],ans:1,feedback_correct:'✅ 发现线索应深挖,防止税款流失。',feedback_wrong:'❌ 稽查应尽职尽责,不放过疑点。'},
{context:'酒店收银系统数据被删除,你通过技术手段恢复了部分数据。',q:'恢复的数据能否作为证据?',opts:['不能,因为被删除','可以,但需鉴定','只能作为参考','必须由当事人确认'],ans:1,feedback_correct:'✅ 经鉴定确认的数据可以作为证据。',feedback_wrong:'❌ 需确保数据完整性和合法性。'},
{context:'酒店大堂经理称“老板指示不要开发票”,并提供了录音。',q:'录音证据的法律效力?',opts:['无效,非法窃听','有效,属于视听资料','需经当事人同意','仅作辅助参考'],ans:1,feedback_correct:'✅ 合法取得的录音是有效证据。',feedback_wrong:'❌ 需确保取得方式合法。'},
{context:'酒店通过个人微信收款,未纳入对公账户。',q:'个人微信收款在税务上应如何处理?',opts:['属于个人隐私','应并入收入总额申报','由企业自行决定','税务机关无权查看'],ans:1,feedback_correct:'✅ 凡属经营收入,均需申报纳税。',feedback_wrong:'❌ 不可隐瞒微信收款。'},
{context:'你查实该酒店偷税金额120万元。',q:'根据税法,可处多少罚款?',opts:['50%以下','50%以上5倍以下','1倍以上5倍以下','5倍以上'],ans:1,feedback_correct:'✅ 偷税可处不缴或少缴税款50%以上5倍以下罚款。',feedback_wrong:'❌ 罚款幅度有法定范围。'},
{context:'酒店老板主动补缴税款并缴纳罚款,表示今后依法纳税。',q:'你的建议是?',opts:['表扬其态度','纳入重点监控','结案','以上均可'],ans:3,feedback_correct:'✅ 可综合采取多种后续管理措施。',feedback_wrong:'❌ 应视情况选择。'},
{context:'酒店的水电费支出与申报收入严重不匹配,能耗偏高。',q:'能耗数据在稽查中可用作什么?',opts:['推算收入的参考','直接定案依据','行政处罚标准','无意义'],ans:0,feedback_correct:'✅ 能耗与产出相关,可作为推算收入的辅助依据。',feedback_wrong:'❌ 不能单独定案。'},
{context:'你发现酒店会员卡预售金额巨大,但未确认收入。',q:'预收款应何时纳税?',opts:['收款时','消费时','到期时','由企业选择'],ans:1,feedback_correct:'✅ 预收款在消费发生时确认收入。',feedback_wrong:'❌ 未消费部分暂不纳税,但须管理。'}
]
},
// 其他案例(老王五金店等)因篇幅省略,实际运行会动态生成默认题目,确保可玩
{ name:'老王五金店', opening:['🧾 税源分析发现,老王五金店进货金额巨大,但申报的销售额却很低。','🔨 你来到门店,发现店面不大,但仓库里堆满了货物。'], questions:[] },
{ name:'小亮超市', opening:['📋 税务审计发现小亮超市税负率远低于同行业。','🛒 你到店观察,发现客流很大,但收银台只有一台POS机。'], questions:[] },
{ name:'厨具大全', opening:['📊 厨具大全申报的毛利率远低于行业水平。','🍳 你到店查看,发现商品标价不低,但销售记录很少。'], questions:[] },
{ name:'香满园酒家', opening:['📞 举报香满园酒家使用两套账。','🔍 你突击检查,在办公室发现一本“内部记录本”。'], questions:[] },
{ name:'幸福面馆', opening:['📋 大数据显示幸福面馆申报的营业额与周边差距过大。','🍜 你到店观察,发现生意兴隆,但收银台不使用收款机。'], questions:[] },
{ name:'兴旺酒楼', opening:['📞 举报兴旺酒楼举办大型宴席不开发票。','🎉 你到店暗访,发现婚宴包桌价格不菲,但收款不入账。'], questions:[] },
{ name:'兄弟烧烤店', opening:['📊 烧烤店夜间生意火爆,但申报的营业额却很低。','🍢 你夜间蹲点,发现排队现象严重。'], questions:[] },
{ name:'美味小吃店', opening:['📋 小吃店位于学校附近,生意火爆,但申报收入却很低。','🍱 你观察到中午时段顾客络绎不绝,但收银台没有POS机。'], questions:[] }
]
},{
id:1, name:'商贸公司', supervisor:'股长老王', supervisorEmoji:'👨💼',
brief:'“小张,商贸公司的虚开风险比较高,注意三流一致。”',
cases:[
{ name:'宏达商贸有限公司', opening:['📊 大数据预警:宏达商贸进项发票巨量,销项却很少,票货分离。','🏢 你来到公司注册地,发现是一间空置的办公室。'], questions:[] },
{ name:'顺达贸易有限公司', opening:['📋 税局收到协查函,顺达贸易涉嫌接受虚开发票。','📁 你调取了该公司进项发票,发现大量来自非正常户。'], questions:[] },
{ name:'诚信商贸', opening:['📊 诚信商贸的税负率远低于同行业,进项大销项小。','🏢 你到公司查账,发现大量预付款项,无对应货物。'], questions:[] },
{ name:'恒通进出口', opening:['📋 恒通进出口申报的出口退税金额异常。','🌍 你调取报关单和外汇核销记录,发现大量异常。'], questions:[] }
]
},{
id:2, name:'加油站', supervisor:'局长', supervisorEmoji:'👤',
brief:'“小张,加油站偷税手段隐蔽,重点查加油机和现金。”',
cases:[
{ name:'安达加油站', opening:['📞 群众举报安达加油站夜间现金加油不给发票。','⛽ 你到站观察,发现3号加油机加油量明显异常。'], questions:[] },
{ name:'平安加油站', opening:['📊 税源监控发现平安加油站油耗比异常。','🔧 你到站检查,发现油罐车卸油记录与销售记录对不上。'], questions:[] }
]
},{
id:3, name:'工业生产企业', supervisor:'局长', supervisorEmoji:'👤',
brief:'“工业企业造假手段复杂,重点查能耗和成本。”',
cases:[
{ name:'恒达机械制造厂', opening:['🏭 能耗与产出严重不匹配,用电量增长35%,产值仅增5%。','📊 你调取水电费单据和申报表,发现异常。'], questions:[] },
{ name:'宏远电子厂', opening:['📋 税务审计发现宏远电子厂成本利润率远低于行业水平。','🔬 你深入检查,发现大量“委托加工”业务,但加工费畸高。'], questions:[] }
]
},{
id:4, name:'大型集团', supervisor:'局长', supervisorEmoji:'👤',
brief:'“大型集团关联交易复杂,涉及转移定价,你选一个啃。”',
cases:[
{ name:'万向控股集团', opening:['📊 审计署移交线索:万向集团通过关联交易转移利润至低税率地区。','🏛️ 集团架构庞大,子公司遍布全国。'], questions:[] },
{ name:'天恒集团', opening:['📋 税务审计发现天恒集团与境外关联企业交易频繁,利润异常。','🌍 你着手调查跨国转移定价问题。'], questions:[] }
]
}];
// ================================================================
// 级别名称
// ================================================================
const LEVEL_NAMES = ['一级行政执法员', '四级主办', '副股长', '股长', '副局长'];
function getLevelName(idx){ return LEVEL_NAMES[idx] || '未知'; }
function getStageName(idx){ return LEVELS[idx] ? LEVELS[idx].name : '未知'; }
function getStageEmoji(idx){ return ['🏪','🏢','⛽','🏭','🏛️'][idx] || '❓'; }
function getSupervisor(idx){ return idx < 2 ? '股长老王' : '局长'; }
// ================================================================
// 大比武题库(参考真实税务业务竞赛题目)
// ================================================================
const COMPETITION_QUESTIONS = {
2: [
{ q:'增值税一般纳税人销售货物,税率为?', opts:['13%','9%','6%','3%'], ans:0 },
{ q:'企业所得税的基本税率是?', opts:['20%','25%','30%','15%'], ans:1 },
{ q:'下列哪种行为属于虚开增值税专用发票?', opts:['为他人开具与实际经营业务不符的发票','为自己开具与实际经营业务不符的发票','让他人为自己开具与实际经营业务不符的发票','以上都是'], ans:3 },
{ q:'根据税收征管法,偷税行为的罚款幅度为?', opts:['50%以下','50%以上5倍以下','1倍以上5倍以下','5倍以上'], ans:1 },
{ q:'税务机关在调查税收违法案件时,经批准可以查询涉案人员的?', opts:['银行存款','个人住宅','私人信件','以上都是'], ans:0 }
],
6: [
{ q:'增值税小规模纳税人征收率一般为?', opts:['13%','9%','3%','6%'], ans:2 },
{ q:'企业所得税中,小型微利企业减按多少税率征收?', opts:['20%','15%','10%','25%'], ans:0 },
{ q:'下列哪项属于视同销售行为?', opts:['将自产货物用于集体福利','将外购货物用于个人消费','将自产货物用于投资','以上都是'], ans:3 },
{ q:'税收保全措施包括?', opts:['冻结存款','查封商品','扣押货物','以上都是'], ans:3 },
{ q:'税务行政复议的期限是?', opts:['15日','30日','60日','90日'], ans:2 }
]
};
// ================================================================
// 游戏状态
// ================================================================
const G = {
year:1, levelIndex:0, stageIndex:0, stageScore:0, yearScore:0,
yearCorrectTotal:0, yearTotalQuestions:0, checkCount:0, honorCount:0,
incompetentYears:0, yearRecords:[], gameOver:false, gameWon:false, endReason:'',
currentQuestions:[], currentAnswers:[], currentIndex:0, currentCorrect:0,
answered:false, selectedIdx:-1, checkDone:false, currentCaseName:'',
storyStep:0, storyLines:[], state:'idle', isFirstCheck:true,
usedQuestions:new Set(), usedCaseIndices:new Set(), casePool:[],
// 事件标志
integrityTriggered: false,
integrityChoice: null,
integrityAgreeRisk: false,
integrityRefusePenalty: false,
inspectionChoiceTriggered: false,
inspectionChoice: null,
competitionTriggeredYear2: false,
competitionTriggeredYear6: false,
competitionPassed: false,
compQuestions: [],
compIndex: 0,
compCorrect: 0,
compNarratives: [], // 存储大比武旁白
};
// ================================================================
// 辅助函数
// ================================================================
function shuffleArray(arr) {
for(let i=arr.length-1; i>0; i--) {
const j = Math.floor(Math.random()*(i+1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function getCases() { return LEVELS[G.stageIndex].cases || []; }
function initCasePool() {
const cases = getCases();
const total = cases.length;
if (G.casePool.length === 0) {
const available = [];
for(let i=0;i<total;i++) {
if (!G.usedCaseIndices.has(i)) available.push(i);
}
G.casePool = shuffleArray(available);
}
}
function getNextCase() {
const cases = getCases();
if (cases.length===0) return null;
initCasePool();
if (G.casePool.length===0) return null;
const idx = G.casePool.pop();
const caseObj = cases[idx];
G.usedCaseIndices.add(idx);
return { caseObj, index: idx };
}
function getQuestionsFromCase(caseObj) {
let all = caseObj.questions || [];
if (all.length < 5) {
all = generateDefaultQuestions(caseObj.name);
}
const available = all.filter(q => !G.usedQuestions.has(q.q));
if (available.length < 5) return null;
const shuffled = shuffleArray([...available]);
const picked = shuffled.slice(0,5);
picked.forEach(q => G.usedQuestions.add(q.q));
return picked;
}
function generateDefaultQuestions(name) {
// 为缺少题目的案例生成默认题目(保证可玩性)
const base = [
{ context:'关于'+name+'的税务检查,以下做法正确的是?', q:'第一步应做什么?', opts:['查账','观察','询问','举报'], ans:1, feedback_correct:'✅ 先外围观察是正确思路。', feedback_wrong:'❌ 应冷静观察再行动。' },
{ context:'在检查'+name+'时,发现账外收入,应如何处理?', q:'定性是什么?', opts:['偷税','逃税','漏税','避税'], ans:0, feedback_correct:'✅ 故意隐瞒收入属于偷税。', feedback_wrong:'❌ 偷税是故意行为。' },
{ context:'对于'+name+'的违法行为,罚款幅度为?', q:'罚款比例?', opts:['50%以下','50%-5倍','1-5倍','5倍以上'], ans:1, feedback_correct:'✅ 偷税罚款50%以上5倍以下。', feedback_wrong:'❌ 需依法计算。' },
{ context:'检查'+name+'时,关键证据是什么?', q:'最关键证据?', opts:['合同','发票','银行流水','账本'], ans:2, feedback_correct:'✅ 资金流水最能反映真实收入。', feedback_wrong:'❌ 其他证据也很重要,但资金流是核心。' },
{ context:'处理'+name+'案件时,应如何对待说情?', q:'正确做法?', opts:['接受','拒绝','请示','拖延'], ans:1, feedback_correct:'✅ 坚持原则,依法办事。', feedback_wrong:'❌ 说情不能影响执法。' },
{ context:'对'+name+'进行后续管理,措施包括?', q:'可选措施?', opts:['监控','回访','培训','以上均可'], ans:3, feedback_correct:'✅ 综合施策效果更好。', feedback_wrong:'❌ 应多措并举。' },
{ context:'在'+name+'的检查中,发现发票问题,应如何处理?', q:'发票问题定性?', opts:['虚开','偷税','避税','正常'], ans:0, feedback_correct:'✅ 发票问题往往涉及虚开。', feedback_wrong:'❌ 需仔细甄别。' },
{ context:'对于'+name+'的税收违法行为,追溯期是?', q:'追溯期?', opts:['3年','5年','10年','无限期'], ans:2, feedback_correct:'✅ 偷税可追溯10年。', feedback_wrong:'❌ 追溯期较长。' },
{ context:'检查'+name+'时,应重点关注哪些方面?', q:'重点?', opts:['三流一致','合同','公章','人员'], ans:0, feedback_correct:'✅ 三流一致是核查关键。', feedback_wrong:'❌ 其他也很重要,但三流是核心。' },
{ context:'处理'+name+'案件时,是否需要移送公安?', q:'移送条件?', opts:['金额巨大','情节严重','以上都是','不需要'], ans:2, feedback_correct:'✅ 符合条件必须移送。', feedback_wrong:'❌ 不能随意决定。' },
{ context:'对'+name+'的处罚,可包括哪些?', q:'处罚种类?', opts:['补税','罚款','信用降级','以上均可'], ans:3, feedback_correct:'✅ 可综合运用多种处罚。', feedback_wrong:'❌ 应视情节选择。' },
{ context:'在'+name+'的检查中,如何固定证据?', q:'证据固定方法?', opts:['拍照','录像','制作笔录','以上都是'], ans:3, feedback_correct:'✅ 多种方式确保证据有效。', feedback_wrong:'❌ 需全面固定。' }
];
const shuffled = shuffleArray([...base]);
return shuffled.slice(0,12);
}
// ================================================================
// 核心游戏逻辑
// ================================================================
function startGame() {
if (G.yearRecords.length > 0) return;
G.state = 'intro';
G.storyLines = [
{ type:'narrator', text:'🎬 欢迎加入税务稽查队伍!' },
{ type:'narrator', text:'你叫张明,是一名刚通过公务员考试入职的税务稽查员。' },
{ type:'narrator', text:'你的第一站是 <span class="highlight">大海市税务局稽查局</span>,这里的老同志们都叫你“小张”。' },
{ type:'narrator', text:'今天是报到的第一天,你被分配到稽查一股,股长老王是你的直接上级。' },
{ type:'supervisor', text:'“小张,欢迎!咱们稽查局人不多,但活不少。你准备好了吗?”' },
{ type:'player', text:'“王股长,我准备好了!一定认真学习,努力工作!”' },
{ type:'narrator', text:'老王笑了笑,从桌上拿起一个卷宗递给你。' },
{ type:'supervisor', text:'“正好有个案子,你来练练手……”' },
{ type:'narrator', text:'📖 你翻开卷宗,你的稽查生涯就此开始。' },
];
G.storyStep = 0;
render();
}
function nextStory() {
// 注意:integrity状态已不再通过nextStory处理,而是由integrityChoice直接处理
if (G.state === 'intro' || G.state === 'opening' || G.state === 'brief' || G.state === 'closing' || G.state === 'feedback' || G.state === 'inspection_choice' || G.state === 'competition') {
G.storyStep++;
const lines = G.storyLines || [];
if (G.storyStep >= lines.length) {
if (G.state === 'intro') {
G.state = 'idle';
G.year = 1;
checkCompetition();
render();
} else if (G.state === 'opening') {
G.state = 'brief';
const level = LEVELS[G.stageIndex];
G.storyLines = [{ type:'supervisor', text: level.brief }];
G.storyStep = 0;
render();
} else if (G.state === 'brief') {
G.state = 'question';
G.currentIndex = 0;
G.answered = false;
G.selectedIdx = -1;
render();
} else if (G.state === 'closing') {
G.checkCount++;
G.yearScore += G.currentCorrect;
G.yearCorrectTotal += G.currentCorrect;
G.yearTotalQuestions += 5;
G.checkDone = true;
if (G.currentCorrect === 5 && !G.integrityTriggered) {
triggerIntegrityEvent();
} else {
G.state = 'checkResult';
render();
}
} else if (G.state === 'feedback') {
G.currentIndex++;
if (G.currentIndex >= 5) {
G.state = 'closing';
const caseName = G.currentCaseName || '该案';
G.storyLines = [
{ type:'narrator', text:`🏁 ${caseName} 的检查告一段落。你根据掌握的证据,形成了初步处理意见。` },
{ type:'supervisor', text:'“小张,干得不错!继续努力。”' },
];
G.storyStep = 0;
render();
} else {
G.state = 'question';
G.answered = false;
G.selectedIdx = -1;
render();
}
} else if (G.state === 'inspection_choice') {
const choice = G.inspectionChoice;
if (choice === 'raid') {
G.gameOver = true;
G.endReason = '📰 突击检查引发网络舆情,被上级问责,游戏结束。';
G.state = 'gameOver';
render();
return;
} else if (choice === 'normal') {
G.yearScore -= 5;
G.storyLines = [
{ type:'narrator', text:'你选择了常规检查,但案件检查成效不佳,被扣了5分。' },
];
G.storyStep = 0;
G.state = 'feedback';
render();
return;
}
} else if (G.state === 'competition') {
if (G.compCorrect === 5) {
if (G.levelIndex < 4) {
G.levelIndex++;
G.stageIndex = G.levelIndex;
G.stageScore = 0;
G.isFirstCheck = true;
G.usedCaseIndices = new Set();
G.casePool = [];
G.competitionPassed = true;
G.storyLines = [
{ type:'system', text:'🏆 大比武全对!你直接晋升一级!' },
{ type:'narrator', text:`你现在是 ${getLevelName(G.levelIndex)} 了。` },
];
} else {
G.gameWon = true;
G.endReason = '🏆 大比武全对,局长直接提拔你为局长!';
G.state = 'gameWin';
render();
return;
}
} else {
G.storyLines = [
{ type:'system', text:`📚 大比武答对 ${G.compCorrect}/5 题,未达全对,继续努力。` },
];
}
G.storyStep = 0;
G.state = 'feedback';
render();
return;
}
} else {
render();
}
return;
}
render();
}
// ---- 事件触发 ----
function triggerIntegrityEvent() {
G.state = 'integrity';
G.storyLines = [
{ type:'narrator', text:'🕵️ 你圆满完成了这次检查,获得了满分。' },
{ type:'narrator', text:'当天晚上,你接到一个电话,是市局张副局长打来的。' },
{ type:'supervisor', text:'“小张啊,听说你查案很有一套。现在经济形势不好,企业也不容易,有些案子能照顾就照顾一下嘛。明天晚上一起吃个饭,我介绍几个朋友给你认识。”' },
{ type:'narrator', text:'你明白,这是“打招呼”来了。你该怎么办?' },
];
G.storyStep = 0;
render();
}
function triggerInspectionChoice() {
G.state = 'inspection_choice';
G.storyLines = [
{ type:'narrator', text:'🏛️ 你刚刚晋升为副局长,迎来了第一个重大案件。' },
{ type:'supervisor', text:'“小张(现在是张局了),这个案件影响重大,你打算怎么查?是突击检查,还是常规检查?”' },
];
G.storyStep = 0;
render();
}
function checkCompetition() {
if (G.gameOver || G.gameWon) return;
if (G.year === 2 && !G.competitionTriggeredYear2) {
G.competitionTriggeredYear2 = true;
startCompetition(2);
} else if (G.year === 6 && !G.competitionTriggeredYear6) {
G.competitionTriggeredYear6 = true;
startCompetition(6);
}
}
function startCompetition(year) {
const questions = COMPETITION_QUESTIONS[year];
if (!questions) return;
G.compQuestions = questions;
G.compIndex = 0;
G.compCorrect = 0;
// 增加旁白,增强故事性
G.compNarratives = [
`📚 第 ${year} 年税务大比武开始!`,
'你走进考场,看到其他参赛者都胸有成竹,你深吸一口气,准备迎接挑战。',
'“小张,这次大比武是检验你业务能力的好机会,好好表现!”'
];
G.state = 'competition';
render();
}
// ---- 选择处理 ----
function integrityChoice(choice) {
// 直接处理选择,不再依赖nextStory
G.integrityChoice = choice;
G.integrityTriggered = true;
if (choice === 'agree') {
G.integrityAgreeRisk = true;
G.storyLines = [
{ type:'narrator', text:'你接受了张副局长的“好意”,答应在检查中“适当照顾”。' },
{ type:'narrator', text:'你心里有些忐忑,但觉得也许不会有事……' },
{ type:'system', text:'⚡ 风险已埋下,年度结算时见分晓。' },
];
} else if (choice === 'refuse') {
G.integrityRefusePenalty = true;
G.storyLines = [
{ type:'narrator', text:'你婉拒了张副局长的“好意”,表示会依法依规办案。' },
{ type:'narrator', text:'张副局长脸色一沉,但也没说什么就离开了。' },
{ type:'system', text:'⚠️ 你拒绝了领导的“好意”。' },
];
}
G.state = 'feedback';
G.storyStep = 0;
render();
}
function inspectionChoice(choice) {
G.inspectionChoice = choice;
G.inspectionChoiceTriggered = true;
nextStory(); // 由nextStory处理结果
}
function compSelectAnswer(idx) {
if (G.state !== 'competition') return;
const q = G.compQuestions[G.compIndex];
const correct = (idx === q.ans);
if (correct) G.compCorrect++;
G.compIndex++;
if (G.compIndex >= 5) {
// 大比武结束,由nextStory处理
nextStory();
} else {
render();
}
}
// ---- 核心流程 ----
function startCheck() {
if (G.gameOver || G.gameWon) return;
if (G.checkCount >= 5) {
doYearEnd();
return;
}
if (G.levelIndex === 4 && !G.inspectionChoiceTriggered && G.checkCount === 0) {
triggerInspectionChoice();
return;
}
const result = getNextCase();
if (!result) {
alert('当前关卡所有案例已查完,请进行年度结算或晋升下一关。');
return;
}
const { caseObj, index } = result;
G.currentCaseName = caseObj.name;
const picked = getQuestionsFromCase(caseObj);
if (!picked) {
G.usedCaseIndices.add(index);
startCheck();
return;
}
G.currentQuestions = picked;
G.currentAnswers = picked.map(q => q.ans);
G.currentIndex = 0;
G.currentCorrect = 0;
G.answered = false;
G.selectedIdx = -1;
G.checkDone = false;
const checkNum = G.checkCount + 1;
let lines = [];
if (G.isFirstCheck || checkNum === 1) {
lines = caseObj.opening.map(text => ({ type:'narrator', text }));
lines.push({ type:'supervisor', text:`“小张,这个${caseObj.name}就交给你了,好好查。”` });
G.isFirstCheck = false;
} else {
lines = [
{ type:'narrator', text:`🔍 第 ${checkNum} 次检查,这次我们查:${caseObj.name}。` },
{ type:'supervisor', text:`“小张,${caseObj.name}的情况你了解了,开始吧。”` },
];
}
G.storyLines = lines;
G.storyStep = 0;
G.state = 'opening';
render();
}
function selectAnswer(idx) {
if (G.answered || G.state !== 'question' || G.checkDone) return;
G.selectedIdx = idx;
G.answered = true;
const correct = (idx === G.currentAnswers[G.currentIndex]);
if (correct) G.currentCorrect++;
const q = G.currentQuestions[G.currentIndex];
const feedbackText = correct ? q.feedback_correct : q.feedback_wrong;
G.storyLines = [
{ type:'system', text: correct ? '✅ 回答正确' : '❌ 回答错误' },
{ type:'narrator', text: feedbackText },
];
G.storyStep = 0;
G.state = 'feedback';
render();
}
function doYearEnd() {
if (G.checkCount < 5) return;
if (G.integrityAgreeRisk) {
if (Math.random() < 0.5) {
G.gameOver = true;
G.endReason = '⚖️ 一案双查发现你违规照顾企业,被开除公职。';
G.state = 'gameOver';
render();
return;
}
}
const total = G.yearTotalQuestions || 25;
const correct = G.yearCorrectTotal || 0;
const accuracy = total > 0 ? (correct / total) : 0;
G.yearAccuracy = accuracy;
let finalScore = G.yearScore;
let honor = false;
let incompetent = false;
if (accuracy >= 0.9) {
honor = true;
finalScore = Math.round(finalScore * 1.5);
G.honorCount++;
G.incompetentYears = 0;
} else if (accuracy < 0.6) {
incompetent = true;
finalScore = 0;
G.incompetentYears++;
} else {
G.incompetentYears = 0;
}
G.yearFinalScore = finalScore;
G.yearHonor = honor;
G.yearIncompetent = incompetent;
if (!incompetent) {
G.stageScore += finalScore;
}
if (G.incompetentYears >= 2) {
G.gameOver = true;
G.endReason = '连续2年不称职,被开除!';
G.state = 'gameOver';
render();
return;
}
let promotion = false;
let promotionTo = '';
if (G.stageScore >= 50) {
let canPromote = true;
if (G.integrityRefusePenalty) {
if (Math.random() < 0.5) {
canPromote = false;
G.stageScore = 40;
G.yearPromotionFailed = true;
}
}
if (canPromote) {
if (G.levelIndex >= 4) {
G.gameWon = true;
G.endReason = '🎉 恭喜提拔为局长!';
G.state = 'gameWin';
G.yearRecords.push({
year: G.year,
score: finalScore,
accuracy: accuracy,
honor: honor,
incompetent: incompetent,
level: G.levelIndex,
stage: G.stageIndex,
promotion: true,
promotionTo: '局长'
});
render();
return;
} else {
promotion = true;
promotionTo = LEVEL_NAMES[G.levelIndex + 1];
G.levelIndex++;
G.stageIndex++;
G.stageScore = 0;
G.incompetentYears = 0;
G.isFirstCheck = true;
G.usedCaseIndices = new Set();
G.casePool = [];
}
}
}
G.yearPromotion = promotion;
G.yearPromotionTo = promotionTo;
G.yearRecords.push({
year: G.year,
score: finalScore,
accuracy: accuracy,
honor: honor,
incompetent: incompetent,
level: G.levelIndex,
stage: G.stageIndex,
promotion: promotion,
promotionTo: promotionTo
});
if (G.year >= 10) {
G.gameOver = true;
G.endReason = '📅 十年任期届满,光荣退休!';
G.state = 'gameOver';
render();
return;
}
G.year++;
G.yearScore = 0;
G.yearCorrectTotal = 0;
G.yearTotalQuestions = 0;
G.checkCount = 0;
G.yearEndDone = true;
G.state = 'yearEnd';
render();
}
function goToNextYear() {
if (G.state !== 'yearEnd') return;
G.yearEndDone = false;
G.state = 'idle';
if (G.gameOver || G.gameWon) {
render();
return;
}
checkCompetition();
render();
}
function resetGame() {
Object.assign(G, {
year:1, levelIndex:0, stageIndex:0, stageScore:0, yearScore:0,
yearCorrectTotal:0, yearTotalQuestions:0, checkCount:0, honorCount:0,
incompetentYears:0, yearRecords:[], gameOver:false, gameWon:false, endReason:'',
currentQuestions:[], currentAnswers:[], currentIndex:0, currentCorrect:0,
answered:false, selectedIdx:-1, checkDone:false, currentCaseName:'',
storyStep:0, storyLines:[], state:'idle', isFirstCheck:true,
usedQuestions:new Set(), usedCaseIndices:new Set(), casePool:[],
integrityTriggered:false, integrityChoice:null, integrityAgreeRisk:false,
integrityRefusePenalty:false, inspectionChoiceTriggered:false,
inspectionChoice:null, competitionTriggeredYear2:false,
competitionTriggeredYear6:false, competitionPassed:false,
compQuestions:[], compIndex:0, compCorrect:0, compNarratives:[]
});
startGame();
}
// ================================================================
// 渲染引擎
// ================================================================
function render() {
document.getElementById('levelDisplay').textContent = getLevelName(G.levelIndex);
document.getElementById('yearDisplay').textContent = `📅 第${G.year}年`;
document.getElementById('checkDisplay').textContent = `🔍 ${G.checkCount}/5`;
document.getElementById('stageProgress').textContent = `📊 关卡: ${G.stageScore}/50`;
document.getElementById('yearScoreDisplay').textContent = `⭐ 本年: ${G.yearScore}`;
document.getElementById('honorDisplay').textContent = `🏅 荣誉: ${G.honorCount}`;
const statusEl = document.getElementById('yearStatus');
if (G.gameOver) {
statusEl.textContent = '💀 已结束'; statusEl.style.color = '#f08080';
} else if (G.gameWon) {
statusEl.textContent = '🏆 通关!'; statusEl.style.color = '#f0d080';
} else if (G.checkCount >= 5) {
statusEl.textContent = '📋 年度结算'; statusEl.style.color = '#80c0e8';
} else if (G.state === 'idle' || G.state === 'intro') {
statusEl.textContent = '⏳ 待命'; statusEl.style.color = '#6a8aaa';
} else {
statusEl.textContent = '🔍 调查中'; statusEl.style.color = '#7fc8f8';
}
const content = document.getElementById('content');
switch (G.state) {
case 'intro': case 'opening': case 'brief': case 'closing': case 'feedback':
renderStory(content); break;
case 'question':
renderQuestion(content); break;
case 'checkResult':
renderCheckResult(content); break;
case 'yearEnd':
renderYearEnd(content); break;
case 'gameOver':
renderGameOver(content); break;
case 'gameWin':
renderGameWin(content); break;
case 'integrity':
renderIntegrity(content); break;
case 'inspection_choice':
renderInspectionChoice(content); break;
case 'competition':
renderCompetition(content); break;
case 'idle':
default:
renderIdle(content); break;
}
const mainArea = document.getElementById('mainArea');
if (mainArea) {
setTimeout(() => { mainArea.scrollTop = mainArea.scrollHeight; }, 60);
}
}
// ---- 渲染各个状态 ----
function renderIdle(el) {
const stageName = getStageName(G.stageIndex);
const emoji = getStageEmoji(G.stageIndex);
const levelName = getLevelName(G.levelIndex);
const supervisor = getSupervisor(G.levelIndex);
const remaining = 5 - G.checkCount;
const cases = getCases();
const totalCases = cases.length;
const usedCount = G.usedCaseIndices.size;
const availableCount = totalCases - usedCount;
let html = `
<div class="result-card">
<div style="font-size:20px;color:#e8f0ff;margin-bottom:4px;">${emoji} ${stageName}</div>
<div class="sub">👤 级别:${levelName} | 上级:${supervisor}</div>
<div class="sub" style="font-size:12px;color:#8ab4e8;">📋 可用案例:${availableCount} 个(已查 ${usedCount} 个)</div>
<div class="progress-wrap"><div class="progress-bar" style="width:${Math.min(100, (G.stageScore/50)*100)}%;"><span class="label">${G.stageScore}/50</span></div></div>
<div class="detail">📌 本年已检查 ${G.checkCount}/5 次 | 累计得分 ${G.yearScore} 分 ${G.checkCount>=5 ? '| ✅ 所有检查已完成' : `| 剩余 ${remaining} 次`}</div>
<div style="margin-top:14px;">
`;
if (G.checkCount >= 5) {
html += `<button class="btn btn-gold" onclick="doYearEnd()">📊 年度结算</button>`;
} else {
const label = G.checkCount === 0 ? '🚀 开始第一次检查' : `🔍 继续检查 (${remaining})`;
if (availableCount === 0) {
html += `<button class="btn" disabled>❌ 无更多案例</button>`;
} else {
html += `<button class="btn btn-gold" onclick="startCheck()">${label}</button>`;
}
}
if (G.honorCount > 0) {
html += `<div style="margin-top:10px;">`;
for (let i=0;i<G.honorCount;i++) html += `<span style="color:#ffd700;font-size:20px;">⭐</span>`;
html += ` <span style="color:#6a8aaa;font-size:10px;">优秀公务员 ×${G.honorCount}</span></div>`;
}
if (G.yearRecords.length > 0) {
html += `<hr class="pixel-divider"><div style="font-size:9px;color:#4a6a8a;text-align:left;max-height:80px;overflow-y:auto;">`;
for (const rec of G.yearRecords.slice(-5)) {
const acc = (rec.accuracy * 100).toFixed(0);
let label = `${rec.year}年: ${rec.score}分 (${acc}%)`;
if (rec.honor) label += ' ⭐';
if (rec.incompetent) label += ' ⚠️';
if (rec.promotion) label += ` 🎉→${rec.promotionTo}`;
html += `<div>${label}</div>`;
}
html += `</div>`;
}
html += `</div></div>`;
el.innerHTML = html;
}
function renderStory(el) {
const lines = G.storyLines || [];
const step = G.storyStep;
let html = `<div class="dialogue-box">`;
for (let i = 0; i <= step && i < lines.length; i++) {
const line = lines[i];
let cls = 'msg';
if (line.type === 'narrator') cls += ' msg-narrator';
else if (line.type === 'supervisor') cls += ' msg-supervisor';
else if (line.type === 'player') cls += ' msg-player';
else if (line.type === 'system') cls += ' msg-system';
html += `<div class="${cls}">${line.text}</div>`;
}
html += `</div>`;
if (step < lines.length - 1) {
html += `<div class="btn-group"><button class="btn btn-primary" onclick="nextStory()">继续 ➡️</button></div>`;
} else {
if (G.state === 'intro') {
html += `<div class="btn-group"><button class="btn btn-gold" onclick="nextStory()">开始工作 🚀</button></div>`;
} else if (G.state === 'opening' || G.state === 'brief') {
html += `<div class="btn-group"><button class="btn btn-gold" onclick="nextStory()">收到,开始调查 🔍</button></div>`;
} else if (G.state === 'closing') {
html += `<div class="btn-group"><button class="btn btn-primary" onclick="nextStory()">查看结果 📊</button></div>`;
} else if (G.state === 'feedback') {
html += `<div class="btn-group"><button class="btn btn-primary" onclick="nextStory()">继续</button></div>`;
} else {
html += `<div class="btn-group"><button class="btn btn-primary" onclick="nextStory()">继续</button></div>`;
}
}
el.innerHTML = html;
}
function renderQuestion(el) {
const qs = G.currentQuestions;
const idx = G.currentIndex;
if (idx >= qs.length) {
G.state = 'closing';
const caseName = G.currentCaseName || '该案';
G.storyLines = [
{ type:'narrator', text:`🏁 ${caseName} 的检查告一段落。你根据掌握的证据,形成了初步处理意见。` },
{ type:'supervisor', text:'“小张,干得不错!继续努力。”' },
];
G.storyStep = 0;
render();
return;
}
const q = qs[idx];
const labels = ['A','B','C','D'];
let html = `
<div class="dialogue-box">
<div class="msg msg-narrator">${q.context}</div>
<div class="msg msg-supervisor" style="border-left-color:#6a9aca;">📌 第 ${idx+1}/5 题:${q.q}</div>
</div>
<div class="options-container">
`;
for (let i=0;i<q.opts.length;i++) {
const selected = (G.selectedIdx === i) ? 'selected' : '';
html += `<div class="opt-btn ${selected}" onclick="selectAnswer(${i})"><span class="label">${labels[i]}.</span> ${q.opts[i]}</div>`;
}
html += `</div>`;
el.innerHTML = html;
}
function renderCheckResult(el) {
const correct = G.currentCorrect;
const total = 5;
const accuracy = (correct / total * 100).toFixed(0);
let grade = '';
if (accuracy >= 90) grade = '🌟 优秀!';
else if (accuracy >= 70) grade = '👍 良好';
else if (accuracy >= 60) grade = '📖 及格';
else grade = '📚 需努力';
let html = `
<div class="result-card">
<div class="big">📋 本次检查结果</div>
<div class="sub">正确:${correct} / ${total} 题 (${accuracy}%)</div>
<div class="detail" style="font-size:18px;margin:6px 0;">${grade}</div>
<div class="detail">📌 本年累计得分:${G.yearScore} 分 | 已检查 ${G.checkCount}/5 次</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="startCheck()">${G.checkCount >= 5 ? '📊 年度结算' : '🔍 下一次检查'}</button>
</div>
</div>
`;
el.innerHTML = html;
}
function renderYearEnd(el) {
const acc = (G.yearAccuracy * 100).toFixed(0);
const score = G.yearFinalScore;
const honor = G.yearHonor;
const incompetent = G.yearIncompetent;
const promotion = G.yearPromotion;
const promotionTo = G.yearPromotionTo;
let html = `
<div class="result-card">
<div class="big">📊 第 ${G.year-1} 年度结算</div>
<div class="sub">正确率:${acc}% | 原始得分:${G.yearScore} 分</div>
`;
if (honor) {
html += `<div class="honor-badge">⭐ 优秀公务员!积分 ×1.5 → ${score} 分</div>`;
} else if (incompetent) {
html += `<div class="danger-badge">⚠️ 不称职!本年度不计分</div>`;
} else {
html += `<div class="detail" style="color:#8ab4e8;">✅ 正常履职,获得 ${score} 分</div>`;
}
const stageScore = G.stageScore;
html += `
<div class="detail" style="margin-top:6px;">📊 当前关卡累计:${stageScore} / 50 分</div>
<div class="progress-wrap" style="margin:6px auto;max-width:300px;"><div class="progress-bar" style="width:${Math.min(100, (stageScore/50)*100)}%;"><span class="label">${Math.min(100, Math.floor((stageScore/50)*100))}%</span></div></div>
`;
if (promotion) {
html += `<div class="promotion">🎉 晋升!→ ${promotionTo}</div>`;
}
if (G.yearPromotionFailed) {
html += `<div class="detail" style="color:#f08080;">⚠️ 晋升受阻,因之前拒绝领导“好意”,本次晋升未能成功。</div>`;
G.yearPromotionFailed = false;
}
if (G.incompetentYears >= 1 && !honor && !promotion) {
html += `<div class="detail" style="color:#f08080;margin-top:4px;">⚠️ 已连续 ${G.incompetentYears} 年不称职,再犯将被开除!</div>`;
}
if (!G.gameOver && !G.gameWon) {
html += `<div class="detail" style="margin-top:8px;color:#6a8aaa;">➡️ 第 ${G.year} 年即将开始</div>
<div class="btn-group"><button class="btn btn-gold" onclick="goToNextYear()">🚀 进入第 ${G.year} 年</button></div>`;
} else {
html += `<div class="btn-group"><button class="btn btn-primary" onclick="resetGame()">🔄 重新开始</button></div>`;
}
html += `</div>`;
el.innerHTML = html;
}
function renderGameOver(el) {
const levelName = getLevelName(G.levelIndex);
const stageName = getStageName(G.stageIndex);
let html = `
<div class="result-card">
<div class="game-over">💀 游戏结束</div>
<div class="big" style="font-size:18px;color:#f0a0a0;">${G.endReason}</div>
<div class="sub">最终级别:${levelName} | 关卡:${stageName}</div>
<div class="detail">🏅 荣誉次数:${G.honorCount} | 在任 ${G.year-1} 年</div>
<hr class="pixel-divider">
<div style="font-size:9px;color:#4a6a8a;text-align:left;max-height:100px;overflow-y:auto;">
`;
for (const rec of G.yearRecords) {
const acc = (rec.accuracy * 100).toFixed(0);
let label = `${rec.year}年: ${rec.score}分 (${acc}%)`;
if (rec.honor) label += ' ⭐';
if (rec.incompetent) label += ' ⚠️';
if (rec.promotion) label += ` 🎉→${rec.promotionTo}`;
html += `<div>${label}</div>`;
}
html += `</div>
<div class="btn-group" style="margin-top:12px;"><button class="btn btn-primary" onclick="resetGame()">🔄 重新开始</button></div>
</div>
`;
el.innerHTML = html;
}
function renderGameWin(el) {
let html = `
<div class="result-card">
<div class="game-win">🏆 通关!提拔为局长!</div>
<div style="font-size:52px;margin:8px 0;">🎉</div>
<div class="sub">在任 ${G.year-1} 年 | 荣誉次数:${G.honorCount}</div>
<div class="detail" style="color:#80f0b0;font-size:16px;margin:8px 0;">“你从一名税务稽查新兵,成长为守护国家税法的忠诚卫士。”</div>
<hr class="pixel-divider">
<div style="font-size:9px;color:#4a6a8a;text-align:left;max-height:100px;overflow-y:auto;">
`;
for (const rec of G.yearRecords) {
const acc = (rec.accuracy * 100).toFixed(0);
let label = `${rec.year}年: ${rec.score}分 (${acc}%)`;
if (rec.honor) label += ' ⭐';
if (rec.incompetent) label += ' ⚠️';
if (rec.promotion) label += ` 🎉→${rec.promotionTo}`;
html += `<div>${label}</div>`;
}
html += `</div>
<div class="btn-group" style="margin-top:12px;"><button class="btn btn-gold" onclick="resetGame()">🔄 重新开始</button></div>
</div>
`;
el.innerHTML = html;
}
function renderIntegrity(el) {
let html = `
<div class="dialogue-box">
<div class="msg msg-narrator">🕵️ 你圆满完成了这次检查,获得了满分。</div>
<div class="msg msg-narrator">当天晚上,你接到一个电话,是市局张副局长打来的。</div>
<div class="msg msg-supervisor">“小张啊,听说你查案很有一套。现在经济形势不好,企业也不容易,有些案子能照顾就照顾一下嘛。明天晚上一起吃个饭,我介绍几个朋友给你认识。”</div>
<div class="msg msg-narrator">你明白,这是“打招呼”来了。你该怎么办?</div>
</div>
<div class="btn-group">
<button class="btn btn-gold" onclick="integrityChoice('agree')">✅ 同意(可能升职,也有风险)</button>
<button class="btn btn-danger" onclick="integrityChoice('refuse')">❌ 拒绝(晋升可能受阻)</button>
</div>
`;
el.innerHTML = html;
}
function renderInspectionChoice(el) {
let html = `
<div class="dialogue-box">
<div class="msg msg-narrator">🏛️ 你刚刚晋升为副局长,迎来了第一个重大案件。</div>
<div class="msg msg-supervisor">“小张(现在是张局了),这个案件影响重大,你打算怎么查?是突击检查,还是常规检查?”</div>
</div>
<div class="btn-group">
<button class="btn btn-danger" onclick="inspectionChoice('raid')">⚡ 突击检查(可能引发舆情)</button>
<button class="btn btn-primary" onclick="inspectionChoice('normal')">📋 常规检查(可能效果差)</button>
</div>
`;
el.innerHTML = html;
}
function renderCompetition(el) {
// 先显示旁白(仅首次渲染)
let html = `<div class="dialogue-box">`;
if (G.compNarratives && G.compNarratives.length > 0 && G.compIndex === 0) {
for (const text of G.compNarratives) {
html += `<div class="msg msg-narrator">${text}</div>`;
}
G.compNarratives = []; // 清空,不再显示
}
const qs = G.compQuestions;
const idx = G.compIndex;
if (idx >= qs.length) {
// 比武结束,由nextStory处理
nextStory();
return;
}
const q = qs[idx];
const labels = ['A','B','C','D'];
html += `<div class="msg msg-supervisor" style="border-left-color:#6a9aca;">第 ${idx+1}/5 题:${q.q}</div>`;
html += `</div><div class="options-container">`;
for (let i=0;i<q.opts.length;i++) {
html += `<div class="opt-btn" onclick="compSelectAnswer(${i})"><span class="label">${labels[i]}.</span> ${q.opts[i]}</div>`;
}
html += `</div>`;
el.innerHTML = html;
}
// ================================================================
// 初始化
// ================================================================
window.startCheck = startCheck;
window.nextStory = nextStory;
window.selectAnswer = selectAnswer;
window.doYearEnd = doYearEnd;
window.goToNextYear = goToNextYear;
window.resetGame = resetGame;
window.integrityChoice = integrityChoice;
window.inspectionChoice = inspectionChoice;
window.compSelectAnswer = compSelectAnswer;
startGame();
console.log('🏛️ 税务稽查模拟器 · 完整剧情版(已修复)');
console.log('修复廉政剧情点击无响应bug,增加大比武旁白,题目参照真实业务');
</script>
</body>
</html>Game Source: 🏛️ 税务稽查 · 完整剧情
Creator: SparkGlider58
Libraries: none
Complexity: complex (1124 lines, 65.7 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-sparkglider58-msu4jikn" to link back to the original. Then publish at arcadelab.ai/publish.