用户希望将基于 UniApp/Vue.js 的现有逻辑(主要涉及 pages/tpl/style3/index.vue, pages/tpl/style3/signup.vue, pages/tpl/style3/rankList.vue, pages/tpl/style3/rankOverview.vue)迁移到一套新的、纯 HTML/JS/CSS 的 UI 设计中。新的 UI 文件位于 pages/tpl/style3/new/ 目录下,包括 index.html (入口页), signup.html (报名页), ranklist.html (排行榜/总览页)。
核心挑战:
common/api.js, common/tools.js, common/cardfunc.js) 大量使用了 uni.request、uni.showToast 等 UniApp 特有的 API,这些在标准浏览器环境中不可用。v-if, v-for, v-model)实现动态渲染和交互。纯 HTML/JS 环境需要手动操作 DOM。export, import) 语法。在纯 HTML 环境中直接使用需要 <script type="module"> 并且要解决模块路径和依赖顺序问题。结论:迁移完全可行,但需要构建一个“UniApp 兼容层”来适配旧有逻辑,并采用原生 JavaScript 来实现数据与 UI 的绑定及交互。
为了更好地管理代码,建议在 pages/tpl/style3/new/ 下创建 js/ 目录来存放所有 JavaScript 文件。
pages/tpl/style3/new/
├── index.html (新UI入口页)
├── signup.html (新UI报名页)
├── ranklist.html (新UI排行榜/总览页)
├── bd.png (新UI图片资源)
├── gd.png (新UI图片资源)
├── css/ (可选:如果需要将 Tailwind/自定义样式提取到单独文件)
└── js/
├── uni-compat.js (新建:核心兼容层,模拟 uni.xyz API)
├── define.js (移植:常量定义,如 defaultPopUpDataList)
├── api.js (移植:API 接口地址定义)
├── tools.js (移植:工具函数)
├── cardfunc.js (移植:配置加载与处理逻辑)
├── logic-index.js (新建:针对 index.html 的业务逻辑)
├── logic-signup.js (新建:针对 signup.html 的业务逻辑)
└── logic-ranklist.js (新建:针对 ranklist.html 的业务逻辑)
js/uni-compat.js)这是一个关键的适配器,用于在标准浏览器环境中模拟 UniApp 的部分 API。
草稿示例 (js/uni-compat.js):
// pages/tpl/style3/new/js/uni-compat.js
(function() {
window.uni = window.uni || {}; // 确保全局uni对象存在
// 1. 模拟网络请求 (核心)
uni.request = function(options) {
let headers = options.header || {};
if (options.method === 'POST' && !headers['Content-Type']) {
headers['Content-Type'] = 'application/json'; // 默认JSON
// 如果是 x-www-form-urlencoded,需要转换data
if (options.header && options.header['Content-Type'] === 'application/x-www-form-urlencoded') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
options.body = Object.keys(options.data).map(key => `${key}=${encodeURIComponent(options.data[key])}`).join('&');
} else if (options.data && typeof options.data === 'object') {
options.body = JSON.stringify(options.data);
}
} else if (options.method === 'GET' && options.data) {
const queryString = Object.keys(options.data).map(key => `${key}=${encodeURIComponent(options.data[key])}`).join('&');
options.url = `${options.url}?${queryString}`;
}
fetch(options.url, {
method: options.method || 'GET',
headers: headers,
body: options.body || undefined
})
.then(response => response.json())
.then(data => {
if (options.success) {
options.success({ statusCode: 200, data: data }); // 适配 uni.request 的回调格式
}
})
.catch(err => {
console.error('uni.request error:', err);
if (options.fail) options.fail(err);
});
};
// 2. 模拟本地存储
uni.setStorageSync = function(key, data) {
localStorage.setItem(key, JSON.stringify(data));
};
uni.getStorageSync = function(key) {
const val = localStorage.getItem(key);
try { return JSON.parse(val); } catch(e) { return val; }
};
uni.getStorage = function(obj) { // 模拟异步版本
try {
const data = uni.getStorageSync(obj.key);
if(obj.success) obj.success({ data: data });
} catch (e) {
if(obj.fail) obj.fail(e);
}
};
uni.setStorage = function(obj) { // 模拟异步版本
try {
uni.setStorageSync(obj.key, obj.data);
if(obj.success) obj.success();
} catch (e) {
if(obj.fail) obj.fail(e);
}
};
// 3. 模拟交互反馈
uni.showToast = function(options) {
// 可替换为更美观的自定义 Toast 实现
alert(`Toast: ${options.title}`); // 简单实现
// console.log('Toast:', options.title);
};
// 4. 模拟系统信息
uni.getSystemInfoSync = function() {
return { appVersion: '1.0.0' }; // 默认值
};
// 5. 模拟导航
uni.navigateTo = function(options) {
window.location.href = options.url;
};
// 6. 模拟 getApp() 全局对象 (用于 cardfunc.js)
window.getApp = function() {
return {
globalData: { defaultMatchLogo: '' }, // 需根据实际情况填充
$cardconfigType: 'remote' // 'remote' 或 'local',根据需要调整
};
};
// 7. 模拟原生AppAction (由 tools.js 调用)
window.appAction = function(url, actType = "") {
if (url.startsWith('action://')) {
console.log(`Simulating native app action: ${url}`);
// 在纯HTML中,action://通常无法直接处理。
// 可以在这里做一些兼容性处理,比如弹出提示或者跳转到特定H5页面。
// 例如,如果 action://to_login/,可以跳到H5登录页
if (url.includes('to_login/')) {
// window.location.href = '/login.html'; // 假设有H5登录页
alert('请登录');
} else if (url.includes('to_home/')) {
window.location.href = 'index.html'; // 假设首页
} else if (url.includes('to_detail/')) {
// window.location.href = '/game_detail.html?id=' + url.split('id=')[1].split('&')[0];
alert(`进入比赛详情: ${url}`);
}
} else if (url.startsWith('http') || url.startsWith('/')) {
window.location.href = url;
} else {
console.warn(`Unknown app action URL: ${url}`);
}
};
})();
js/)将原 common/ 目录下的 JS 文件内容复制到 pages/tpl/style3/new/js/ 相应文件,并进行改造以适应浏览器环境。
js/define.js:
export const。window.define = {...} 挂载。// pages/tpl/style3/new/js/define.js
window.tplStyleList = [];
tplStyleList[0] = 'blue';
// ... 其他 tplStyleList 的定义
window.teamName = [];
teamName[0] = [];
// ... 其他 teamName 的定义
window.defaultPopUpDataList = [...];
window.defaultPopUpDataList2 = [...];
window.defaultPopUpDataList3 = [...];
js/api.js:export const。process.env.OSS_URL 和 process.env.API_BASE_URL 需硬编码。token 变量可以设为初始空字符串,或从 URL 参数获取。checkResCode, checkToken 函数可以直接定义在全局。// pages/tpl/style3/new/js/api.js
const API_BASE_URL = 'YOUR_ACTUAL_API_SERVER_URL/'; // !!! 替换为实际的 API 地址
const OSS_URL = 'YOUR_ACTUAL_OSS_URL/'; // !!! 替换为实际的 OSS 地址
window.ossUrl = OSS_URL;
window.apiServer = API_BASE_URL;
window.token = ''; // 初始为空,会从 URL 参数中获取或动态设置
// 所有 API 接口路径
window.apiCardBaseQuery = API_BASE_URL + 'CardBaseQuery';
// ... 其他 apiXXXQuery 定义
// 辅助函数直接定义
window.checkResCode = function(res, failLabel='') {
// ... 原 common/api.js 中的 checkResCode 逻辑,将 uni.showToast 替换为 window.uni.showToast
if (res.data.code == 0) {
return true;
} else if (res.statusCode == 401) {
window.uni.showToast({ title: `您尚未登录`, icon: 'none' });
window.appAction(`action://to_login/`);
return false;
} else {
window.uni.showToast({ title: `${failLabel}${res.data.message}`, icon: 'none' });
return false;
}
};
window.checkToken = function(token) {
// ... 原 common/api.js 中的 checkToken 逻辑
const regex = /^[0-9A-Za-z]{32}$/;
if (regex.test(token)) {
return true;
} else {
window.uni.showToast({ title: `您尚未登录`, icon: 'none' });
window.appAction(`action://to_login/`);
return false;
}
};
js/tools.js:
import tools from '/common/tools'; 和 export default tools;。tools 对象直接定义在 window 上。uni 的调用会通过 uni-compat.js 提供的 window.uni 对象。appAction 的调用需要改为 window.appAction (或者确保 appAction 在全局)。// pages/tpl/style3/new/js/tools.js
// 假设 window.uni 和 window.appAction 已经存在 (由 uni-compat.js 提供)
window.tools = {
// ... 原 common/tools.js 中的所有方法
// 注意:内部对 uni.xyz 的调用会自动映射到 window.uni.xyz
// appAction 方法会调用到 window.appAction (由 uni-compat.js 提供模拟)
};
js/cardfunc.js:import ... 语句。cardfunc 对象直接定义在 window 上。window.tools, window.uni, window.checkResCode, window.apiXXX 等变量在加载 cardfunc.js 之前已定义。// pages/tpl/style3/new/js/cardfunc.js
// 假设 window.tools, window.uni, window.apiXXX, window.checkResCode, window.defaultPopUpDataList 等都已定义
window.cardfunc = {
caller: null, // 在纯HTML中,caller的概念可能不再适用,或者需要重新定义
token: "",
ecId: 0,
isNewUser: false,
cardConfigData: { /* ... */ },
userConfigData: { /* ... */ },
init(token, ecId) { // 调整 init 参数,去除 caller
this.token = token;
this.ecId = ecId;
// this.removeCss(); // 如果纯HTML没有uni.css,则可能不需要
},
// ... 其他 cardfunc 中的所有方法
// 内部对 uni.request 会使用 window.uni.request
// 内部对 tools.xxx 会使用 window.tools.xxx
// 内部对 apiXXX 会使用 window.apiXXX
// 内部对 checkResCode 会使用 window.checkResCode
// 内部对 getApp().xxx 会使用 window.getApp().xxx
};
js/logic-*.js)每个 HTML 页面将有一个专属的 logic-*.js 文件来处理其特定的业务逻辑。
pages/tpl/style3/new/js/logic-index.js (对应 index.html)逻辑来源: pages/tpl/style3/index.vue
token, ecId, beginSecond, endSecond, mcState, isJoin 等)。initPage() 函数: 页面加载时执行。
window.location.search 获取 token, id (即 ecId)。cardfunc: window.cardfunc.init(state.token, state.ecId);window.cardfunc.getCardConfig(loadConfigCallback);apiCardBaseQuery 获取赛事基本信息(beginSecond, endSecond)。apiUserJoinCardQuery 获取用户报名状态 (isJoin)。beginSecond, endSecond, isJoin 更新 action-btn 的文本、样式,以及倒计时显示。action-btn 绑定点击事件。updateUI() 函数: 根据 mcState 和 isJoin 动态修改 action-btn 和 timer-container 的内容和样式。btnClick() 事件处理:
mcState 和 isJoin 决定跳转到 signup.html 或 ranklist.html。window.uni.navigateTo({ url: '...' }); 或 window.appAction('...');。pages/tpl/style3/new/js/logic-signup.js (对应 signup.html)逻辑来源: pages/tpl/style3/signup.vue
token, ecId, mcName, beginSecond, endSecond, coiRs (组织列表), nickName, coiId 等。initPage() 函数:
token, id, from。cardfunc: window.cardfunc.init(state.token, state.ecId);window.cardfunc.getCardConfig(loadConfigCallback);apiCardDetailQuery 获取赛事详情和用户已填信息。apiOnlineMcSignUpDetail 获取组织列表 (coiRs),填充到“选择战队”的下拉菜单。btnSignup() 函数: 验证输入,弹出确认框,然后调用 apiOnlineMcSignUp。selectOption() 函数: 更新下拉菜单选中项的显示和隐藏 coiId 值。ranklist.html。pages/tpl/style3/new/js/logic-ranklist.js (对应 ranklist.html)逻辑来源: pages/tpl/style3/rankList.vue 和 pages/tpl/style3/rankOverview.vue
token, ecId, mcId, mcName, currentTab, currentMetric, rankList (所有榜单数据), userInfo (个人信息), mapList (地图列表), ocaId (当前地图ID) 等。initPage() 函数:
token, id。cardfunc: window.cardfunc.init(state.token, state.ecId);window.cardfunc.getCardConfig(loadConfigCallback);apiMatchRsDetailQuery 获取赛事和个人概览数据。apiCompStatisticQuery 获取全局统计数据(用于跑马灯)。apiMapListQuery 获取地图列表。apiCardRankDetailQuery 获取榜单数据。renderLeaderboard() 函数: 负责根据 currentTab, currentMetric 和 rankList 数据渲染排行榜列表。switchMainTab(), switchMetric() 函数: 切换 Tab 时更新 state.currentTab/state.currentMetric,然后调用 renderLeaderboard()。openDrawer(), closeDrawer() 函数: 控制底部抽屉的显示与隐藏。editProfile(), saveProfile() 函数: 弹出/关闭编辑资料模态框,保存时调用 apiOnlineMcSignUp 更新。action://to_detail 或其他 H5 页面。返回按钮跳转到 index.html。每个 HTML 文件的底部需要引入其所需的 JS 文件,注意顺序:
<!-- 在所有 HTML 文件中 (例如 index.html, signup.html, ranklist.html) -->
<!-- 1. Tailwind CSS 和 FontAwesome (已存在) -->
<!-- 2. 兼容层 -->
<script src="js/uni-compat.js"></script>
<!-- 3. 基础定义 -->
<script src="js/define.js"></script>
<script src="js/api.js"></script>
<script src="js/tools.js"></script>
<!-- 4. 业务模块 -->
<script src="js/cardfunc.js"></script>
<!-- 5. 页面特定逻辑 (每个页面引入自己的) -->
<!-- index.html --> <script src="js/logic-index.js"></script>
<!-- signup.html --> <script src="js/logic-signup.js"></script>
<!-- ranklist.html --> <script src="js/logic-ranklist.js"></script>
<script>
// 在每个页面的 script 标签底部,调用各自的初始化函数
// 例如对于 index.html:
document.addEventListener('DOMContentLoaded', function() {
initIndexPage(); // logic-index.js 中定义的入口函数
});
</script>
@apply 规则来集成。api.js 中的 API_BASE_URL 和 OSS_URL 必须替换为真实的服务器地址。src="https://orienteering.beswell.com/card/nanning/...", ./gd.png, ./bd.png) 在部署时需要确保路径正确。action://): uni-compat.js 中的 window.appAction 只是一个模拟。在实际的混合应用 (Hybrid App) 中,需要由原生 App 拦截并处理 action:// 协议的 URL。cardfunc.init 的 caller 参数: 在 Vue 组件中 this 就是 caller。在纯 HTML 中,caller 的概念不再直接适用。cardfunc.init(caller, token, ecId) 可以简化为 cardfunc.init(token, ecId),或者 caller 可以是当前页面的一个全局对象或空对象。这个方案提供了将现有 UniApp/Vue 逻辑迁移到纯 HTML/JS 环境的详细路线图,并考虑了 UniApp 特有 API 的兼容性问题。