
简介这是一套开源的修仙题材放置类网页文字游戏源码面向前端开发者与Web全栈学习者提供可直接运行的PCH5双端自适应实践项目解决修仙类轻量级互动游戏快速搭建与跨设备适配的学习需求。资源共57个文件含29个JavaScript逻辑模块如combat.js、cultivation.js、exploration.js、12个Vue组件如Dungeon.vue、Cultivation.vue、GM.vue、3个HTML入口页及配套配置文件vite.config.js、postcss.config.cjs、package.json整体包体仅511KB结构清晰、模块解耦便于理解游戏状态管理、异步事件驱动与UI响应式设计。已有377人学习下载源码完整包含玩家成长系统、炼丹/探索/副本/成就等核心玩法模块附带暗黑模式切换与本地数据库stores/db.js实现同时提供LICENSE、README.md及免责声明等规范文档适合用于前端工程化实践、小游戏开发入门与修仙题材交互逻辑研究。1. 这不是“仙侠皮肤包”而是一套可运行、可调试、可二次开发的 Vue 修仙游戏骨架你打开浏览器输入本地地址看到一个带暗黑模式开关、能点击炼丹、打坐、闯秘境、刷装备的修仙界面——它不是静态页面不是 Demo 演示而是真实跑在 NginxPHP7.2 环境下的完整 Vue 应用。App.vue是入口stores/player.js存着你的灵根、境界、灵石数combat.js里写着「金丹期对战筑基后期胜率 68.3%」的硬逻辑dungeon.js用纯 JS 实现了随机生成三层秘境BOSS机制。它不依赖任何后端 API除 PHP 提供的极简 session 支持所有状态持久化靠db.js的 IndexedDB localStorage 双写策略H5 自适应不是靠媒体查询凑数而是components/LogPanel.vue内置响应式断点监听 vite.config.js中明确配置了build.target: es2018兼容 Android 8/iOS 12。适合两类人想快速验证修仙类玩法原型的策划或独立开发者以及需要在 Vue3 工程中嵌入轻量文字 RPG 模块的前端工程师——它不教你怎么写元婴渡劫动画但告诉你「境界突破」该在哪改状态、「丹药合成」该在哪触发事件链、「探索失败」时 log 如何结构化输出。2. Vue3 Vite 构建体系解析从 vite.config.js 到组件加载链路2.1 构建配置核心参数与 H5 兼容性设计源码使用 Vite 作为构建工具关键配置集中在vite.config.js。该文件并非默认模板而是针对修仙游戏场景做了三处实质性调整// vite.config.js 片段 export default defineConfig({ base: ./, // 强制相对路径避免部署到子目录如 /docs时资源 404 build: { target: es2018, // 明确指定语法目标确保 async/await、Promise.finally 在旧版 WebView 正常运行 rollupOptions: { output: { manualChunks: { // 将高频交互模块拆包combat、cultivation、alchemy 各自独立 chunk combat: [src/utils/combat.js], cultivation: [src/stores/cultivation.js], alchemy: [src/utils/herbs.js, src/utils/pills.js] } } } }, server: { host: 0.0.0.0, // 开发时允许局域网设备访问方便真机调试 H5 port: 3000, proxy: { /api: { // 仅保留此代理占位实际游戏逻辑无后端调用PHP 仅用于 session 初始化 target: http://localhost, changeOrigin: true } } } })提示base: ./是部署到/docs目录的强制要求。若忽略此配置index.html中script typemodule src/assets/index.abc123.js会请求根路径导致 404。这是 PC/H5 自适应的第一道门槛——路径不是适配屏幕而是适配部署位置。postcss.config.cjs中启用autoprefixer并配置browserslist为 1%, last 2 versions, not dead确保.css文件中flex、grid属性自动补全-webkit-前缀。实测在微信内置浏览器X5 内核、QQ 浏览器、华为浏览器中Inventory.vue的格子布局display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr))均能正确渲染。2.2 组件通信与状态管理Pinia 替代 Vuex 的轻量化实践项目未使用 Vuex而是基于stores/目录实现 Pinia 风格的状态管理。player.js是核心 store其设计体现修仙游戏特有状态耦合性// stores/player.js import { defineStore } from pinia import { ref, computed } from vue export const usePlayerStore defineStore(player, () { const data ref({ name: 李逍遥, level: 1, exp: 0, maxExp: 100, realm: 凡人, // 凡人 | 炼气 | 筑基 | 金丹 | 元婴 ... cultivationSpeed: 1.0, // 影响打坐收益 inventory: [], // 装备/丹药/灵草数组 stats: { // 动态计算属性非原始数据 attack: 10, defense: 5, speed: 8 } }) // 修为突破逻辑封装为 action const breakthrough () { if (data.value.exp data.value.maxExp) { data.value.level data.value.exp 0 data.value.maxExp Math.floor(data.value.maxExp * 1.5) // 根据境界更新 realm 字段 const realms [凡人, 炼气, 筑基, 金丹, 元婴, 化神, 渡劫] data.value.realm realms[Math.min(data.value.level - 1, realms.length - 1)] // 触发全局事件通知 UI 更新 window.dispatchEvent(new CustomEvent(realm-upgraded, { detail: data.value.realm })) } } // 计算属性实时战斗力 基础攻击 × (1 装备加成) × (1 境界系数) const power computed(() { const equipBonus data.value.inventory .filter(i i.type weapon) .reduce((sum, item) sum (item.attack || 0), 0) const realmCoeff { 凡人: 1, 炼气: 1.2, 筑基: 1.5, 金丹: 2.0 }[data.value.realm] || 1 return Math.round(data.value.stats.attack * (1 equipBonus / 100) * realmCoeff) }) return { data, breakthrough, power } })注意power计算中未使用watch监听inventory变化而是依赖computed的响应式追踪。这避免了在equipment.js中修改装备时重复触发计算符合修仙游戏「状态变更频次低、计算复杂度高」的特点。若将inventory改为ref([])而非嵌套在data中会导致computed失效——这是新手易踩的坑。router/index.js使用 Vue Router 4路由守卫中注入了beforeEach检查玩家是否初始化// router/index.js router.beforeEach((to, from, next) { const player usePlayerStore() if (!player.data.name to.name ! Home) { next({ name: Home }) // 未创建角色强制跳首页 } else { next() } })views/Home.vue中调用player.breakthrough()时会同步触发realm-upgraded事件LogPanel.vue通过window.addEventListener(realm-upgraded, ...)捕获并追加日志形成跨组件通信闭环。3. 游戏核心系统实现从打坐 Cultivation 到秘境 Dungeon 的 JS 逻辑拆解3.1 打坐修炼系统时间驱动 状态机控制views/Cultivation.vue是玩家提升境界的主界面其逻辑由src/utils/cultivation.js驱动。该模块采用「时间片轮询 状态机」设计而非 setInterval 粗暴轮询// utils/cultivation.js let cultivationState { isRunning: false, startTime: 0, duration: 0, // 当前已打坐秒数 intervalId: null } export const startCultivation (durationSeconds) { if (cultivationState.isRunning) return cultivationState.isRunning true cultivationState.startTime Date.now() cultivationState.duration 0 cultivationState.intervalId setInterval(() { const elapsed Math.floor((Date.now() - cultivationState.startTime) / 1000) cultivationState.duration Math.min(elapsed, durationSeconds) // 每 10 秒触发一次经验增长模拟灵气吸收 if (cultivationState.duration % 10 0 cultivationState.duration 0) { const player usePlayerStore() player.data.exp Math.floor(player.data.level * 5 * player.data.cultivationSpeed) // 达到阈值立即突破不等定时器结束 if (player.data.exp player.data.maxExp) { player.breakthrough() } } }, 1000) } export const stopCultivation () { if (cultivationState.intervalId) { clearInterval(cultivationState.intervalId) cultivationState.intervalId null } cultivationState.isRunning false }逻辑说明startCultivation(300)启动 5 分钟打坐durationSeconds为总时长。setInterval每秒检查elapsed但只在elapsed % 10 0时增加经验——这模拟了「灵气需积累到临界点才转化为修为」的设定。player.breakthrough()可在任意时刻被调用无需等待durationSeconds结束符合修仙「顿悟即突破」的叙事逻辑。Cultivation.vue中的暂停按钮绑定stopCultivation()恢复按钮重新调用startCultivation()并传入剩余时间300 - cultivationState.duration。progress标签的value绑定cultivationState.durationmax绑定durationSeconds实现可视化进度条。3.2 秘境探索系统伪随机 dungeon 生成与事件驱动战斗views/Dungeon.vue加载src/utils/dungeon.js该模块不依赖外部地图数据完全在内存中生成三层秘境// utils/dungeon.js export const generateDungeon (level) { const floors [] for (let floor 1; floor 3; floor) { const roomCount 3 Math.floor(Math.random() * 3) // 每层 3~5 个房间 const rooms [] for (let i 0; i roomCount; i) { const roomType Math.random() 0.7 ? boss : (Math.random() 0.4 ? enemy : treasure) const difficulty Math.max(1, level floor - 1) // 难度随层数递增 rooms.push({ id: floor${floor}-room${i}, type: roomType, difficulty, rewards: roomType treasure ? generateTreasure(difficulty) : [], enemies: roomType enemy || roomType boss ? generateEnemies(roomType, difficulty) : [] }) } floors.push({ floor, rooms }) } return { floors, bossDefeated: false } } const generateEnemies (type, difficulty) { const baseStats { hp: 50, attack: 10, exp: 20 } const multiplier type boss ? 3 : 1.5 return [{ name: type boss ? 赤炎魔君 : 山魈, hp: Math.floor(baseStats.hp * difficulty * multiplier), attack: Math.floor(baseStats.attack * difficulty * multiplier), exp: Math.floor(baseStats.exp * difficulty * multiplier) }] }Dungeon.vue中点击「进入秘境」触发generateDungeon(player.level)生成结果存入dungeonStore。玩家点击某个房间时调用src/utils/combat.js的startCombat()// utils/combat.js export const startCombat (enemy) { const player usePlayerStore() let playerHp player.data.stats.hp || 100 let enemyHp enemy.hp const log [] const round () { // 玩家先攻伤害 攻击力 × (1 ± 0.2 闪避/暴击) const playerDamage Math.max(1, Math.floor(player.power * (0.8 Math.random() * 0.4))) enemyHp - playerDamage log.push(你对${enemy.name}造成${playerDamage}点伤害) // 敌人反击若存活 if (enemyHp 0) { const enemyDamage Math.max(1, Math.floor(enemy.attack * (0.7 Math.random() * 0.3))) playerHp - enemyDamage log.push(${enemy.name}对你造成${enemyDamage}点伤害) } } // 模拟回合制每 800ms 执行一回合 const interval setInterval(() { round() if (playerHp 0 || enemyHp 0) { clearInterval(interval) if (playerHp 0) { log.push(你战败了) player.data.exp Math.max(0, player.data.exp - 10) // 战败损失经验 } else { log.push(击败${enemy.name}获得${enemy.exp}点经验) player.data.exp enemy.exp // 检查是否突破 if (player.data.exp player.data.maxExp) player.breakthrough() } // 将 log 推入全局日志 store const logStore useLogStore() logStore.add(log.join(\n)) } }, 800) return { log, playerHp, enemyHp } }参数说明startCombat()返回{ log, playerHp, enemyHp }供 UI 实时渲染。player.power是computed属性已包含装备与境界加成enemy.attack来自generateEnemies()的动态计算。800ms回合间隔是平衡节奏与性能的关键值——过短如 200ms导致手机端卡顿过长如 1500ms降低战斗沉浸感。4. 暗黑模式与多端适配CSS 变量 媒体查询的落地细节4.1 暗黑模式切换CSS 自定义属性与 localStorage 持久化暗黑模式并非简单切换 class而是通过 CSS 自定义属性CSS Custom Properties实现主题色动态注入。src/assets/styles/dark-mode.css定义了两套变量/* dark-mode.css */ :root { --bg-primary: #ffffff; --bg-secondary: #f8f9fa; --text-primary: #212529; --text-secondary: #6c757d; --accent: #007bff; } [data-themedark] { --bg-primary: #1a1a1a; --bg-secondary: #2d2d2d; --text-primary: #e9ecef; --text-secondary: #adb5bd; --accent: #0d6efd; }App.vue的mounted钩子中读取 localStorage 并应用script setup import { onMounted } from vue onMounted(() { const savedTheme localStorage.getItem(theme) || light document.documentElement.setAttribute(data-theme, savedTheme) // 监听系统偏好但以用户选择为准 window.matchMedia((prefers-color-scheme: dark)).addEventListener(change, e { if (savedTheme auto) { document.documentElement.setAttribute(data-theme, e.matches ? dark : light) } }) }) /scriptcomponents/Settings.vue中的开关组件绑定localStoragetemplate label classswitch input typecheckbox changetoggleTheme :checkedisDarkMode / span classslider/span /label /template script setup import { ref, onMounted } from vue const isDarkMode ref(false) onMounted(() { const theme localStorage.getItem(theme) isDarkMode.value theme dark || (theme auto window.matchMedia((prefers-color-scheme: dark)).matches) }) const toggleTheme (e) { const newTheme e.target.checked ? dark : light localStorage.setItem(theme, newTheme) document.documentElement.setAttribute(data-theme, newTheme) } /script注意document.documentElement.setAttribute(data-theme, ...)是触发 CSS 变量重计算的唯一可靠方式。仅修改class无法让:root下的--bg-primary生效必须作用于根元素。4.2 H5 自适应关键断点与触摸优化src/assets/styles/base.css中定义了四档断点覆盖主流设备断点名称宽度范围适用设备关键样式mobilemax-width: 480pxiPhone SE、老款安卓小屏font-size: 14px;padding: 8pxtablet481px - 768pxiPad mini、中屏安卓grid-template-columns: repeat(3, 1fr);button padding: 12pxdesktop769px - 1200px笔记本、大屏手机横屏sidebar width: 240px;main max-width: 800pxlargemin-width: 1201px2K 显示器font-size: 16px;container max-width: 1200pxLogPanel.vue中的滚动区域针对触摸设备优化template div classlog-container touchmovehandleTouchMove pre classlog-content{{ logText }}/pre /div /template script setup const handleTouchMove (e) { // 阻止 iOS Safari 默认滚动行为避免误触 if (e.target.classList.contains(log-container)) { e.preventDefault() } } /script style scoped .log-container { overflow-y: auto; -webkit-overflow-scrolling: touch; /* 启用 iOS 惯性滚动 */ height: 200px; } /style-webkit-overflow-scrolling: touch是 H5 滚动流畅性的关键。实测在 iOS 15 上LogPanel日志滚动帧率稳定在 60fps若移除此属性滚动会卡顿且无回弹效果。5. 部署与调试技巧Nginx 配置、PHP Session 初始化及常见报错定位5.1 Nginx 配置要点与 PHP7.2 兼容性处理源码要求网站运行目录为/docs对应 Nginx 的root配置必须精确匹配server { listen 80; server_name localhost; root /var/www/my-xiutu/docs; # 必须指向 docs 目录非项目根目录 index index.html; location / { try_files $uri $uri/ /index.html; # 支持 Vue Router history 模式 } # PHP 处理仅用于 session 初始化 location ~ \.php$ { fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 静态资源缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } }关键点root必须设为/docs的绝对路径否则index.html中的script src/assets/index.js会 404。try_files规则确保 Vue Router 的/dungeon等路径能 fallback 到index.html避免刷新 404。PHP7.2 仅用于src/main.php源码中未直接提供但README.txt提示需此文件初始化 session。标准实现如下?php // docs/main.php session_start(); if (!isset($_SESSION[player_id])) { $_SESSION[player_id] uniqid(player_, true); // 初始化基础玩家数据可选 $_SESSION[player_data] [ name 无名修士, level 1, exp 0, realm 凡人 ]; } echo json_encode([status ok]); ?src/main.js中在createApp(App).mount(#app)前发起一次fetch(/main.php)确保 session 已建立。若跳过此步db.js中的 IndexedDB 初始化可能因跨域限制失败。5.2 常见报错与定位方法报错现象可能原因定位命令/步骤页面空白控制台报Failed to load resource: net::ERR_ABORTEDvite.config.js中base未设为./或 Nginxroot路径错误curl http://localhost/assets/index.js检查返回状态查看浏览器 Network 标签页中.js请求的 Request URL 是否为/docs/assets/...打坐无经验增长player.exp不变cultivation.js中setInterval未启动或player.breakthrough()被异常捕获在startCultivation()内console.log(cultivation started)在breakthrough()函数首行加console.trace()H5 端点击无响应PC 端正常未启用-webkit-overflow-scrolling: touch或touchmove事件阻止不当Chrome DevTools → Sensors → Emulate touch → 检查log-container元素是否有overflow-y: auto且-webkit-overflow-scrolling: touch生效暗黑模式切换后颜色不变>{ scripts: { dev: vite, // 本地开发自动开启 http://localhost:3000 build: vite build, // 构建到 docs 目录 preview: vite preview --host 0.0.0.0 --port 5000, // 预览构建产物支持局域网访问 lint: eslint --ext .js,.vue src/, // 检查 JS/Vue 代码规范 format: prettier --write \src/**/*.{js,vue}\ // 代码格式化 } }执行npm run build后dist/目录内容需完整复制到 Nginx 的/docs目录下注意不是dist目录本身。npm run preview可在无 Nginx 环境下验证构建产物是否可运行——这是上线前必做的交叉验证步骤。提示vite preview默认不支持 PHP因此main.php调用会失败但不影响前端逻辑测试。若需验证 PHP 集成必须部署到真实 NginxPHP 环境。docs/assets/下的spirit-cjQ8fchE.js等哈希命名文件是 Vite 构建时自动生成的代码分割产物。其文件名中的哈希值如cjQ8fchE由文件内容决定确保浏览器缓存更新——修改spirit.js后新构建的文件名必然变化旧缓存自动失效。本文还有配套的精品资源点击获取