diff --git a/doc.md b/doc.md index ca46096..9566c3c 100644 --- a/doc.md +++ b/doc.md @@ -113,4 +113,134 @@ git push origin test 2. **每次合并前先拉取最新代码**,避免覆盖他人改动 3. **体验版发布前确认代码已提交**,避免遗漏 4. **开发分支命名建议**:`feature/姓名-功能名`,如 `feature/zhangsan-login` -5. **删除已合并的开发分支**:`git branch -d feature/your-name-work` \ No newline at end of file +5. **删除已合并的开发分支**:`git branch -d feature/your-name-work` + +--- + +## 六、比赛服 Proto 更新流程 + +### 1. 新开发人员快速接手 + +首次拉取项目后,先安装依赖并确认当前协议与运行时解码器一致: + +```bash +npm install +npm run proto:check +npm run build +``` + +如果 `proto:check` 提示协议不同步,先执行 `npm run proto:generate`,不要直接修改生成区块。 + +### 2. 相关文件职责 + +| 文件 | 职责 | 修改规则 | +|------|------|----------| +| `src/utils/match.min.js` | 后端比赛服协议描述,是生成字段表的输入文件 | 后端协议更新时整体替换;禁止在小程序运行时代码中导入 | +| `scripts/generate-match-schema.mjs` | 解析协议、校验字段并生成运行时字段表 | 只有新增通用协议能力时才修改 | +| `src/utils/matchProtocol.js` | 小程序实际使用的 protobuf 编解码器 | `` 标记区间内禁止手动修改 | +| `src/matchWebsocket.js` | 服务端消息路由、ACK 和业务事件分发 | 新增消息业务行为时人工接入 | +| `src/utils/matchAdapter.js` | 将解码结果从 snake_case 统一转换为 camelCase | 页面继续使用适配后的字段名 | + +`match.min.js` 不是小程序运行时解码器。运行时仍使用 `protobufjs/minimal.js` 的 `Reader/Writer`,`int64` 字段统一保留为字符串,避免大整数精度丢失。 + +### 3. 日常协议更新步骤 + +```text +替换 src/utils/match.min.js + ↓ +执行 npm run proto:update + ↓ +检查生成差异和业务接入范围 + ↓ +执行 git diff --check + ↓ +微信开发者工具检查包体并上传 +``` + +具体操作: + +1. 从后端获取最新的 `match.min.js`,整体覆盖 `src/utils/match.min.js`。 +2. 执行推荐命令: + +```bash +npm run proto:update +``` + +3. 该命令会依次完成: + - 解析 `match.min.js` + - 更新 `matchProtocol.js` 内的生成区块 + - 校验生成区块与协议源文件一致 + - 执行微信小程序正式构建 +4. 检查 Git 差异。普通字段更新通常只应涉及: + - `src/utils/match.min.js` + - `src/utils/matchProtocol.js` 的生成区块 +5. 如果新增了消息业务行为,再单独检查 `src/matchWebsocket.js`、页面或组件的接入改动。 +6. 执行空白和换行检查: + +```bash +git diff --check +``` + +7. 打开微信开发者工具,导入 `dist/build/mp-weixin`,检查代码依赖分析和主包体积后再上传。 + +### 4. Proto 命令说明 + +| 命令 | 用途 | 是否修改文件 | +|------|------|--------------| +| `npm run proto:generate` | 根据 `match.min.js` 更新生成区块 | 是 | +| `npm run proto:check` | 检查生成区块是否与协议同步 | 否 | +| `npm run proto:update` | 执行生成,然后运行正式构建 | 是,日常更新推荐使用 | +| `npm run dev` | 启动开发构建,启动前自动执行 `proto:check` | 协议不同步时直接中止 | +| `npm run build` | 执行正式构建,构建前自动执行 `proto:check` | 协议不同步时直接中止 | + +`npm run dev` 和 `npm run build` 只负责检查,不会自动改写协议;发现不同步时应执行 `npm run proto:generate` 或 `npm run proto:update`。 + +### 5. 自动生成与人工接入边界 + +以下内容由生成器自动处理: + +- 服务端和客户端消息枚举 +- 普通 scalar 字段 +- 嵌套 message +- repeated message +- 当前解码器支持的 map +- snake_case 字段名和 oneof 兼容信息 + +以下情况仍需人工处理: + +- **新增服务端消息类型**:枚举会自动生成,但 `matchWebsocket.js` 的路由、ACK、音频或页面事件仍需接入。 +- **新增客户端指令**:枚举会自动生成,但发送函数、字段编码和业务调用入口仍需接入。 +- **修改既有字段编号或类型**:属于协议兼容性变更,必须先与后端确认,不能只看构建是否通过。 +- **新增解码器不支持的类型**:生成器会直接报错,需要同时扩展生成器和 `matchProtocol.js` 的通用解码能力。 + +当前生成器会拒绝不支持的 scalar、map key、字段规则以及 packed scalar repeated,避免继续构建后静默丢字段。 + +### 6. 常见报错处理 + +| 报错或现象 | 原因 | 处理方式 | +|------------|------|----------| +| 生成区块与 `match.min.js` 不同步 | 替换协议后没有重新生成 | 执行 `npm run proto:update` | +| 找不到 `Root.create(...)` | 后端提供的文件格式发生变化或文件不完整 | 停止更新,确认协议文件来源和生成格式 | +| 使用了不支持的类型、规则或 map key | 新协议超出当前通用解码器能力 | 不要手补生成区块,先扩展生成器和解码器并补充验证 | +| packed scalar repeated 不支持 | protobuf 默认可能使用 packed 编码,当前通用解码器未覆盖 | 增加 packed 解码能力后再重新生成 | +| `matchProtocol.js` 缺少生成区块标记 | 标记被误删或生成区块被手改 | 恢复 `` 标记,重新执行生成 | +| 协议生成成功但构建失败 | 问题位于项目构建或业务代码,不是字段表同步 | 保留生成结果,按构建错误继续定位 | + +### 7. 禁止事项与验收清单 + +禁止: + +- 禁止在运行时代码中导入 `match.min.js`。 +- 禁止手动修改 `` 与 `` 之间的内容。 +- 禁止绕过 `proto:check` 后直接上传。 +- 禁止把 `int64` 字段直接转换为普通 `Number`。 +- 禁止把“枚举已经生成”等同于“业务消息已经完成接入”。 + +每次协议更新至少确认: + +1. `npm run proto:check` 通过。 +2. `npm run build` 通过,或直接确认 `npm run proto:update` 已完整通过。 +3. `git diff --check` 通过。 +4. 生成区块之外没有意外改动。 +5. 新消息已完成必要的 WebSocket 路由和页面验证。 +6. 微信开发者工具中的主包、分包体积符合上传限制。 diff --git a/package.json b/package.json index 4fe0d15..b4d948c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { + "proto:generate": "node scripts/generate-match-schema.mjs", + "proto:check": "node scripts/generate-match-schema.mjs --check", + "proto:update": "npm run proto:generate && npm run build", + "predev": "npm run proto:check", "dev": "uni -p mp-weixin", + "prebuild": "npm run proto:check", "build": "uni build -p mp-weixin" }, "dependencies": { diff --git a/project.config.json b/project.config.json index 0ffafb0..b9375da 100644 --- a/project.config.json +++ b/project.config.json @@ -235,6 +235,14 @@ { "type": "file", "value": "static/tab-mall.png" + }, + { + "type": "folder", + "value": "static/training-home" + }, + { + "type": "folder", + "value": "static/training-difficulty-design" } ], "include": [] diff --git a/scripts/generate-match-schema.mjs b/scripts/generate-match-schema.mjs new file mode 100644 index 0000000..5684419 --- /dev/null +++ b/scripts/generate-match-schema.mjs @@ -0,0 +1,440 @@ +import { createHash } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import vm from "node:vm"; + +const scriptDirectory = dirname(fileURLToPath(import.meta.url)); +const projectRoot = resolve(scriptDirectory, ".."); +const sourcePath = resolve(projectRoot, "src/utils/match.min.js"); +const runtimePath = resolve(projectRoot, "src/utils/matchProtocol.js"); +const generatedStartMarker = "// "; +const generatedEndMarker = "// "; + +const supportedScalarKinds = new Set([ + "int32", + "int64", + "float", + "double", + "bool", + "string", + "bytes", +]); +const supportedMapKeyKinds = new Set(["int32", "int64", "bool", "string"]); +// 新版描述文件未携带旧协议的 oneof 元数据,保留现有解码结果中的 payload 标识。 +const compatibilityOneofs = { + ServerMessage: { + match_info: "payload", + shoot_data: "payload", + practice_info: "payload", + }, +}; + +function isRecord(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function extractRootCreateArgument(source) { + const match = /(?:\$protobuf\.)?Root\.create\s*\(/.exec(source); + if (!match) { + throw new Error("match.min.js 中未找到 Root.create(...) 协议描述"); + } + + const openIndex = source.indexOf("(", match.index); + let depth = 0; + let quote = ""; + let escaped = false; + let lineComment = false; + let blockComment = false; + + for (let index = openIndex; index < source.length; index += 1) { + const char = source[index]; + const next = source[index + 1]; + + if (lineComment) { + if (char === "\n") lineComment = false; + continue; + } + if (blockComment) { + if (char === "*" && next === "/") { + blockComment = false; + index += 1; + } + continue; + } + if (quote) { + if (escaped) { + escaped = false; + } else if (char === "\\") { + escaped = true; + } else if (char === quote) { + quote = ""; + } + continue; + } + if (char === '"' || char === "'" || char === "`") { + quote = char; + continue; + } + if (char === "/" && next === "/") { + lineComment = true; + index += 1; + continue; + } + if (char === "/" && next === "*") { + blockComment = true; + index += 1; + continue; + } + if (char === "(") { + depth += 1; + continue; + } + if (char === ")") { + depth -= 1; + if (depth === 0) { + return source.slice(openIndex + 1, index).trim(); + } + } + } + + throw new Error("match.min.js 中的 Root.create(...) 括号不完整"); +} + +function stripQuotedText(source) { + let output = ""; + let quote = ""; + let escaped = false; + + for (const char of source) { + if (quote) { + output += " "; + if (escaped) { + escaped = false; + } else if (char === "\\") { + escaped = true; + } else if (char === quote) { + quote = ""; + } + continue; + } + if (char === '"' || char === "'") { + quote = char; + output += " "; + } else { + output += char; + } + } + + return output; +} + +function parseDescriptor(argumentSource) { + if (!argumentSource.startsWith("{") || !argumentSource.endsWith("}")) { + throw new Error("Root.create(...) 参数不是静态对象字面量"); + } + + try { + return JSON.parse(argumentSource); + } catch { + // 兼容旧版 pbjs 生成的未加引号对象键;只允许静态对象语法。 + const syntaxOnly = stripQuotedText(argumentSource); + if (/[();`=]/.test(syntaxOnly)) { + throw new Error("旧版协议描述包含非静态表达式,已拒绝执行"); + } + const descriptor = vm.runInNewContext( + `(${argumentSource})`, + Object.create(null), + { timeout: 1000 } + ); + return JSON.parse(JSON.stringify(descriptor)); + } +} + +function findProtocolNamespace(node, path = []) { + if (!isRecord(node)) return null; + const entries = isRecord(node.nested) ? node.nested : node; + + if (isRecord(entries.ServerMessage?.fields) && isRecord(entries.ClientMessage?.fields)) { + return { entries, path }; + } + + for (const [name, child] of Object.entries(entries)) { + if (!isRecord(child) || isRecord(child.fields)) continue; + const found = findProtocolNamespace(child, [...path, name]); + if (found) return found; + } + + return null; +} + +function getEnumValues(definition) { + if (!isRecord(definition)) return null; + const candidate = isRecord(definition.values) ? definition.values : definition; + const entries = Object.entries(candidate); + if (entries.length === 0 || entries.some(([, value]) => !Number.isInteger(value))) { + return null; + } + return Object.fromEntries(entries.sort((left, right) => left[1] - right[1])); +} + +function normalizeTypeName(type) { + return String(type || "") + .replace(/^\./, "") + .split(".") + .pop(); +} + +function getOneofByField(definition) { + const result = new Map(); + if (!isRecord(definition.oneofs)) return result; + + for (const [groupName, groupDefinition] of Object.entries(definition.oneofs)) { + const fieldNames = Array.isArray(groupDefinition) + ? groupDefinition + : groupDefinition?.oneof; + if (!Array.isArray(fieldNames)) continue; + for (const fieldName of fieldNames) result.set(fieldName, groupName); + } + + return result; +} + +function buildSchema({ messageName, definition, messages, enumNames }) { + const schema = {}; + const usedIds = new Set(); + const oneofByField = getOneofByField(definition); + + for (const [fieldKey, fieldDefinition] of Object.entries(definition.fields)) { + const id = Number(fieldDefinition.id); + if (!Number.isInteger(id) || id <= 0) { + throw new Error(`${messageName}.${fieldKey} 的字段编号无效`); + } + if (usedIds.has(id)) { + throw new Error(`${messageName} 存在重复字段编号 ${id}`); + } + usedIds.add(id); + + const rule = fieldDefinition.rule; + if (rule && !["optional", "required", "repeated", "map"].includes(rule)) { + throw new Error(`${messageName}.${fieldKey} 使用了不支持的规则 ${rule}`); + } + + const fieldName = fieldDefinition.protoName || fieldKey; + const typeName = normalizeTypeName(fieldDefinition.type); + const keyTypeName = normalizeTypeName( + fieldDefinition.keyType ?? fieldDefinition.keytype + ); + const isMap = rule === "map" || Boolean(keyTypeName); + + if (isMap) { + if (!supportedMapKeyKinds.has(keyTypeName)) { + throw new Error( + `${messageName}.${fieldKey} 使用了不支持的 map key 类型 ${keyTypeName}` + ); + } + const valueIsMessage = messages.has(typeName); + const valueKind = enumNames.has(typeName) ? "int32" : typeName; + if (!valueIsMessage && !supportedScalarKinds.has(valueKind)) { + throw new Error( + `${messageName}.${fieldKey} 使用了不支持的 map value 类型 ${typeName}` + ); + } + schema[id] = valueIsMessage + ? { + name: fieldName, + kind: "map", + keyKind: keyTypeName, + valueKind: "message", + valueType: typeName, + } + : { + name: fieldName, + kind: "map", + keyKind: keyTypeName, + valueKind, + }; + continue; + } + + const isMessage = messages.has(typeName); + const kind = enumNames.has(typeName) ? "int32" : typeName; + if (!isMessage && !supportedScalarKinds.has(kind)) { + throw new Error(`${messageName}.${fieldKey} 使用了不支持的类型 ${typeName}`); + } + + const repeated = rule === "repeated"; + if (repeated && !isMessage && !["string", "bytes"].includes(kind)) { + throw new Error( + `${messageName}.${fieldKey} 是 packed scalar repeated,当前通用解码器尚不支持` + ); + } + + const field = isMessage + ? { name: fieldName, kind: "message", type: typeName } + : { name: fieldName, kind }; + if (repeated) field.repeated = true; + + const oneof = + fieldDefinition.oneof || + oneofByField.get(fieldKey) || + compatibilityOneofs[messageName]?.[fieldName]; + if (oneof) field.oneof = oneof; + schema[id] = field; + } + + return schema; +} + +function formatPropertyKey(key) { + return /^(?:[A-Za-z_$][\w$]*|\d+)$/.test(key) ? key : JSON.stringify(key); +} + +function formatJsValue(value, depth = 0) { + if (!isRecord(value)) return JSON.stringify(value); + + const entries = Object.entries(value); + if (entries.length === 0) return "{}"; + + const indent = " ".repeat(depth); + const primitiveEntries = entries.every(([, child]) => !isRecord(child)); + if (primitiveEntries && value.kind !== "map") { + const singleLine = `{ ${entries + .map(([key, child]) => `${formatPropertyKey(key)}: ${JSON.stringify(child)}`) + .join(", ")} }`; + if (indent.length + singleLine.length <= 100) return singleLine; + } + + const childIndent = " ".repeat(depth + 1); + const lines = entries.map( + ([key, child]) => + `${childIndent}${formatPropertyKey(key)}: ${formatJsValue(child, depth + 1)},` + ); + return `{\n${lines.join("\n")}\n${indent}}`; +} + +function createGeneratedSource(source) { + const descriptor = parseDescriptor(extractRootCreateArgument(source)); + const namespace = findProtocolNamespace(descriptor); + if (!namespace) { + throw new Error("协议描述中未找到 ServerMessage 和 ClientMessage"); + } + + const messages = new Map(); + const enums = new Map(); + for (const [name, definition] of Object.entries(namespace.entries)) { + if (isRecord(definition?.fields)) { + messages.set(name, definition); + continue; + } + const values = getEnumValues(definition); + if (values) enums.set(name, values); + } + + const serverMessageType = enums.get("ServerMessageType"); + const clientMessageType = enums.get("ClientMessageType"); + if (!serverMessageType || !clientMessageType) { + throw new Error("协议描述缺少 ServerMessageType 或 ClientMessageType"); + } + + const enumNames = new Set(enums.keys()); + const schemaByName = new Map(); + for (const [messageName, definition] of messages) { + schemaByName.set( + messageName, + buildSchema({ messageName, definition, messages, enumNames }) + ); + } + + const reachable = new Set(); + function collectReachable(messageName) { + if (reachable.has(messageName)) return; + const schema = schemaByName.get(messageName); + if (!schema) throw new Error(`找不到消息定义 ${messageName}`); + reachable.add(messageName); + for (const field of Object.values(schema)) { + if (field.kind === "message") collectReachable(field.type); + if (field.kind === "map" && field.valueKind === "message") { + collectReachable(field.valueType); + } + } + } + collectReachable("ServerMessage"); + + const schemas = {}; + for (const messageName of messages.keys()) { + if (reachable.has(messageName)) schemas[messageName] = schemaByName.get(messageName); + } + + const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16); + const namespaceName = namespace.path.join(".") || "root"; + const fieldCount = Object.values(schemas).reduce( + (total, schema) => total + Object.keys(schema).length, + 0 + ); + const generatedBlock = [ + generatedStartMarker, + "// 此区块由 scripts/generate-match-schema.mjs 自动生成,请勿手动修改。", + `// 来源:src/utils/match.min.js(sha256: ${sourceHash})`, + `// 协议命名空间:${namespaceName};消息数:${Object.keys(schemas).length};字段数:${fieldCount}`, + "", + `export const ServerMessageType = ${formatJsValue(serverMessageType)};`, + "", + `export const ClientMessageType = ${formatJsValue(clientMessageType)};`, + "", + `const SCHEMAS = ${formatJsValue(schemas)};`, + generatedEndMarker, + ].join("\n"); + + return { + generatedBlock, + messageCount: Object.keys(schemas).length, + fieldCount, + }; +} + +function main() { + const args = process.argv.slice(2); + const unknownArgs = args.filter((arg) => arg !== "--check"); + if (unknownArgs.length > 0) { + throw new Error(`未知参数:${unknownArgs.join(", ")}`); + } + + const source = readFileSync(sourcePath, "utf8"); + const { generatedBlock, messageCount, fieldCount } = createGeneratedSource(source); + const runtimeSource = readFileSync(runtimePath, "utf8"); + const startIndex = runtimeSource.indexOf(generatedStartMarker); + const endMarkerIndex = runtimeSource.indexOf(generatedEndMarker); + if (startIndex < 0 || endMarkerIndex < startIndex) { + throw new Error("matchProtocol.js 缺少协议生成区块标记"); + } + const endIndex = endMarkerIndex + generatedEndMarker.length; + const currentBlock = runtimeSource.slice(startIndex, endIndex); + + if (args.includes("--check")) { + if (currentBlock !== generatedBlock) { + throw new Error( + "matchProtocol.js 的生成区块与 match.min.js 不同步,请运行 npm run proto:generate" + ); + } + console.log(`[match-schema] 同步校验通过:${messageCount} 个消息,${fieldCount} 个字段`); + return; + } + + if (currentBlock === generatedBlock) { + console.log(`[match-schema] 无需更新:${messageCount} 个消息,${fieldCount} 个字段`); + return; + } + + const nextRuntimeSource = + runtimeSource.slice(0, startIndex) + generatedBlock + runtimeSource.slice(endIndex); + writeFileSync(runtimePath, nextRuntimeSource, "utf8"); + console.log(`[match-schema] 已生成:${messageCount} 个消息,${fieldCount} 个字段`); +} + +try { + main(); +} catch (error) { + console.error(`[match-schema] ${error.message}`); + process.exitCode = 1; +} diff --git a/src/manifest.json b/src/manifest.json index ff67d53..16a0bbe 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -286,6 +286,14 @@ { "type" : "file", "value" : "static/tab-mall.png" + }, + { + "type" : "folder", + "value" : "static/training-home" + }, + { + "type" : "folder", + "value" : "static/training-difficulty-design" } ] }, diff --git a/src/mock/index.js b/src/mock/index.js index 78da2c2..c716202 100644 --- a/src/mock/index.js +++ b/src/mock/index.js @@ -4,43 +4,43 @@ export const trainingHomeWeekSchedule = [ key: "mon", label: "周一", status: "done", - icon: "/pages/training/static/training-home/done.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/done.png", }, { key: "tue", label: "周二", status: "done", - icon: "/pages/training/static/training-home/done.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/done.png", }, { key: "wed", label: "周三", status: "missed", - icon: "/pages/training/static/training-home/missed.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/missed.png", }, { key: "thu", label: "周四", status: "missed", - icon: "/pages/training/static/training-home/missed.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/missed.png", }, { key: "fri", label: "周五", status: "done", - icon: "/pages/training/static/training-home/done.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/done.png", }, { key: "sat", label: "周六", status: "done", - icon: "/pages/training/static/training-home/done.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/done.png", }, { key: "sun", label: "周日", status: "missed", - icon: "/pages/training/static/training-home/missed.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/missed.png", }, ]; @@ -73,7 +73,7 @@ export const trainingHomeModes = [ key: "endurance", title: "耐力训练", progressText: "当前进度 LV5 >", - icon: "/pages/training/static/training-home/img_3.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_3.png", recommended: true, disabled: false, }, @@ -81,7 +81,7 @@ export const trainingHomeModes = [ key: "precision", title: "精准训练", progressText: "当前进度 LV3 >", - icon: "/pages/training/static/training-home/img_4.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_4.png", recommended: false, disabled: false, }, @@ -89,7 +89,7 @@ export const trainingHomeModes = [ key: "rhythm", title: "节奏训练", progressText: "当前进度 LV6 >", - icon: "/pages/training/static/training-home/img_5.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_5.png", recommended: false, disabled: false, }, @@ -97,7 +97,7 @@ export const trainingHomeModes = [ key: "power", title: "力量训练", progressText: "Coming! LV10", - icon: "/pages/training/static/training-home/img_6.png", + icon: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_6.png", recommended: false, disabled: true, }, diff --git a/src/pages/training/components/BowData.vue b/src/pages/training/components/BowData.vue index b3f3d21..b6108ff 100644 --- a/src/pages/training/components/BowData.vue +++ b/src/pages/training/components/BowData.vue @@ -36,7 +36,7 @@ const props = defineProps({ {{ user.nickName }} - {{ user.lvlName }} + diff --git a/src/pages/training/components/ScorePanel.vue b/src/pages/training/components/ScorePanel.vue index 04a3884..d03814e 100644 --- a/src/pages/training/components/ScorePanel.vue +++ b/src/pages/training/components/ScorePanel.vue @@ -85,8 +85,8 @@ onBeforeUnmount(() => { class="score-item-bg" :src=" isLowScore(arrows[index]) - ? '../static/training-difficulty-design/block-gray.png' - : '../static/training-difficulty-design/block-gold.png' + ? 'https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/block-gray.png' + : 'https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/block-gold.png' " /> { :key="index" class="score-card" > - + - + - + Lv{{ resultDifficultyLevel }} @@ -330,7 +330,7 @@ const calories = computed( - + {{ row.label }} @@ -345,13 +345,13 @@ const calories = computed( {{ row.delta > 0 ? "+" : "-" }}{{ row.deltaText }} {{ row.deltaUnit }} + src="https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-up.png" mode="widthFix" /> -- - + 消耗卡路里 @@ -363,7 +363,7 @@ const calories = computed( + src="https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/result-rice.png" mode="widthFix" /> @@ -371,15 +371,15 @@ const calories = computed( - + 查看靶纸 - + 教练点评 - + 分享成绩 diff --git a/src/pages/training/components/ShootProgress.vue b/src/pages/training/components/ShootProgress.vue index f53ed2b..7f91b6d 100644 --- a/src/pages/training/components/ShootProgress.vue +++ b/src/pages/training/components/ShootProgress.vue @@ -54,10 +54,14 @@ const props = defineProps({ }); const trainingTitleIconMap = Object.freeze({ - base: "../static/training-difficulty-design/text-icon-jcxl.png", - precision: "../static/training-difficulty-design/text-icon-jingzxl.png", - rhythm: "../static/training-difficulty-design/text-icon-jzxl.png", - endurance: "../static/training-difficulty-design/text-icon-nlxl.png", + base: + "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/text-icon-jcxl.png", + precision: + "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/text-icon-jingzxl.png", + rhythm: + "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/text-icon-jzxl.png", + endurance: + "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/text-icon-nlxl.png", }); const trainingTitleIcon = computed( () => diff --git a/src/pages/training/components/TestDistance.vue b/src/pages/training/components/TestDistance.vue index 79d114e..45374a8 100644 --- a/src/pages/training/components/TestDistance.vue +++ b/src/pages/training/components/TestDistance.vue @@ -82,7 +82,7 @@ onBeforeUnmount(() => { diff --git a/src/pages/training/difficulty.vue b/src/pages/training/difficulty.vue index 62f5988..2529501 100644 --- a/src/pages/training/difficulty.vue +++ b/src/pages/training/difficulty.vue @@ -784,7 +784,7 @@ onUnload(() => { v-for="connector in difficultyConnectors" :key="connector.id" class="difficulty-page__connector" - src="./static/training-difficulty-design/jiantou.png" + src="https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/jiantou.png" mode="aspectFit" :style="connector" /> diff --git a/src/pages/training/index.vue b/src/pages/training/index.vue index dfcd8b0..218cb4a 100644 --- a/src/pages/training/index.vue +++ b/src/pages/training/index.vue @@ -5,8 +5,10 @@ import Container from "@/components/Container.vue"; import TargetPicker from "@/components/TargetPicker.vue"; import { getPersonalTrainingAPI } from "@/apis"; -const checkedIcon = "./static/training-home/done.png"; -const missedIcon = "./static/training-home/missed.png"; +const checkedIcon = + "https://static.shelingxingqiu.com/shootmini/static/training-home/done.png"; +const missedIcon = + "https://static.shelingxingqiu.com/shootmini/static/training-home/missed.png"; // 后端训练项目 id 与难度页 mode 参数的映射关系。 const trainingModeRouteMap = { base: "basic", @@ -17,18 +19,25 @@ const trainingModeRouteMap = { }; // 训练项目卡片右侧主图标。 const trainingModeIconMap = { - base_bow: "./static/training-home/img_22.png", - bow: "./static/training-home/img_3.png", - target: "./static/training-home/img_4.png", - wave: "./static/training-home/img_5.png", - muscle: "./static/training-home/img_6.png", + base_bow: + "https://static.shelingxingqiu.com/shootmini/static/training-home/img_22.png", + bow: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_3.png", + target: + "https://static.shelingxingqiu.com/shootmini/static/training-home/img_4.png", + wave: "https://static.shelingxingqiu.com/shootmini/static/training-home/img_5.png", + muscle: + "https://static.shelingxingqiu.com/shootmini/static/training-home/img_6.png", }; -// 训练项目卡片标题图,按接口 id 映射本地资源。 +// 训练项目卡片标题图,按接口 id 映射 CDN 资源。 const trainingModeTitleImageMap = { - endurance: "./static/training-home/nailixunlian.png", - precision: "./static/training-home/jingzhunxunlian.png", - rhythm: "./static/training-home/jiezouxunlian.png", - strength: "./static/training-home/liliangxulian.png", + endurance: + "https://static.shelingxingqiu.com/shootmini/static/training-home/nailixunlian.png", + precision: + "https://static.shelingxingqiu.com/shootmini/static/training-home/jingzhunxunlian.png", + rhythm: + "https://static.shelingxingqiu.com/shootmini/static/training-home/jiezouxunlian.png", + strength: + "https://static.shelingxingqiu.com/shootmini/static/training-home/liliangxulian.png", }; const defaultWeekDays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; const defaultRadarDimensions = [ @@ -360,12 +369,12 @@ onShow(async () => { @@ -440,7 +449,7 @@ onShow(async () => { @@ -451,7 +460,7 @@ onShow(async () => { 我的训练记录 @@ -479,7 +488,7 @@ onShow(async () => { { /> @@ -501,7 +510,7 @@ onShow(async () => { diff --git a/src/pages/training/practise-one.vue b/src/pages/training/practise-one.vue index cf7f3f1..71e2b25 100644 --- a/src/pages/training/practise-one.vue +++ b/src/pages/training/practise-one.vue @@ -953,7 +953,7 @@ onBeforeUnmount(() => { @@ -998,7 +998,7 @@ onBeforeUnmount(() => { diff --git a/src/pages/training/static/training-difficulty-design/block-gold.png b/src/pages/training/static/training-difficulty-design/block-gold.png deleted file mode 100644 index 9d70a59..0000000 Binary files a/src/pages/training/static/training-difficulty-design/block-gold.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/block-gray.png b/src/pages/training/static/training-difficulty-design/block-gray.png deleted file mode 100644 index e397dd2..0000000 Binary files a/src/pages/training/static/training-difficulty-design/block-gray.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/btn.png b/src/pages/training/static/training-difficulty-design/btn.png deleted file mode 100644 index 8b7c04a..0000000 Binary files a/src/pages/training/static/training-difficulty-design/btn.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/cha.png b/src/pages/training/static/training-difficulty-design/cha.png deleted file mode 100644 index 31f1cfc..0000000 Binary files a/src/pages/training/static/training-difficulty-design/cha.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/dao-icon.png b/src/pages/training/static/training-difficulty-design/dao-icon.png deleted file mode 100644 index 428e8fc..0000000 Binary files a/src/pages/training/static/training-difficulty-design/dao-icon.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/gou.png b/src/pages/training/static/training-difficulty-design/gou.png deleted file mode 100644 index e108513..0000000 Binary files a/src/pages/training/static/training-difficulty-design/gou.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/jiantou.png b/src/pages/training/static/training-difficulty-design/jiantou.png deleted file mode 100644 index a232722..0000000 Binary files a/src/pages/training/static/training-difficulty-design/jiantou.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/lock.svg b/src/pages/training/static/training-difficulty-design/lock.svg deleted file mode 100644 index 7d77123..0000000 --- a/src/pages/training/static/training-difficulty-design/lock.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - 编组 11 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/pages/training/static/training-difficulty-design/par-bg.png b/src/pages/training/static/training-difficulty-design/par-bg.png deleted file mode 100644 index ff05d28..0000000 Binary files a/src/pages/training/static/training-difficulty-design/par-bg.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/par-star.png b/src/pages/training/static/training-difficulty-design/par-star.png deleted file mode 100644 index 9d1b80f..0000000 Binary files a/src/pages/training/static/training-difficulty-design/par-star.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-bg.png b/src/pages/training/static/training-difficulty-design/result-bg.png deleted file mode 100644 index d998253..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-bg.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-c-bg.png b/src/pages/training/static/training-difficulty-design/result-c-bg.png deleted file mode 100644 index 8fce567..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-c-bg.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-icon-1.png b/src/pages/training/static/training-difficulty-design/result-icon-1.png deleted file mode 100644 index 3d61cfa..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-icon-1.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-icon-2.png b/src/pages/training/static/training-difficulty-design/result-icon-2.png deleted file mode 100644 index 9bd76b5..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-icon-2.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-icon-3.png b/src/pages/training/static/training-difficulty-design/result-icon-3.png deleted file mode 100644 index 5f9dd86..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-icon-3.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-rice.png b/src/pages/training/static/training-difficulty-design/result-rice.png deleted file mode 100644 index c77f776..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-rice.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-t-bg.png b/src/pages/training/static/training-difficulty-design/result-t-bg.png deleted file mode 100644 index 00616ef..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-t-bg.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/result-up.png b/src/pages/training/static/training-difficulty-design/result-up.png deleted file mode 100644 index 66cd43f..0000000 Binary files a/src/pages/training/static/training-difficulty-design/result-up.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/text-icon-jcxl.png b/src/pages/training/static/training-difficulty-design/text-icon-jcxl.png deleted file mode 100644 index d84fa47..0000000 Binary files a/src/pages/training/static/training-difficulty-design/text-icon-jcxl.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/text-icon-jingzxl.png b/src/pages/training/static/training-difficulty-design/text-icon-jingzxl.png deleted file mode 100644 index aae2f6f..0000000 Binary files a/src/pages/training/static/training-difficulty-design/text-icon-jingzxl.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/text-icon-jzxl.png b/src/pages/training/static/training-difficulty-design/text-icon-jzxl.png deleted file mode 100644 index 8e9315a..0000000 Binary files a/src/pages/training/static/training-difficulty-design/text-icon-jzxl.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/text-icon-nlxl.png b/src/pages/training/static/training-difficulty-design/text-icon-nlxl.png deleted file mode 100644 index c916589..0000000 Binary files a/src/pages/training/static/training-difficulty-design/text-icon-nlxl.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/text.png b/src/pages/training/static/training-difficulty-design/text.png deleted file mode 100644 index e71442c..0000000 Binary files a/src/pages/training/static/training-difficulty-design/text.png and /dev/null differ diff --git a/src/pages/training/static/training-difficulty-design/unlock.svg b/src/pages/training/static/training-difficulty-design/unlock.svg deleted file mode 100644 index 348bea1..0000000 --- a/src/pages/training/static/training-difficulty-design/unlock.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - 编组 9 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/pages/training/static/training-home/done.png b/src/pages/training/static/training-home/done.png deleted file mode 100644 index 118e769..0000000 Binary files a/src/pages/training/static/training-home/done.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_16.png b/src/pages/training/static/training-home/img_16.png deleted file mode 100644 index ac2142d..0000000 Binary files a/src/pages/training/static/training-home/img_16.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_17.png b/src/pages/training/static/training-home/img_17.png deleted file mode 100644 index 691dda7..0000000 Binary files a/src/pages/training/static/training-home/img_17.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_19.png b/src/pages/training/static/training-home/img_19.png deleted file mode 100644 index dd55d9b..0000000 Binary files a/src/pages/training/static/training-home/img_19.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_20.png b/src/pages/training/static/training-home/img_20.png deleted file mode 100644 index c4aff5a..0000000 Binary files a/src/pages/training/static/training-home/img_20.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_21.png b/src/pages/training/static/training-home/img_21.png deleted file mode 100644 index 2a91b81..0000000 Binary files a/src/pages/training/static/training-home/img_21.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_22.png b/src/pages/training/static/training-home/img_22.png deleted file mode 100644 index eb4d989..0000000 Binary files a/src/pages/training/static/training-home/img_22.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_28.png b/src/pages/training/static/training-home/img_28.png deleted file mode 100644 index 3c1fc81..0000000 Binary files a/src/pages/training/static/training-home/img_28.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_3.png b/src/pages/training/static/training-home/img_3.png deleted file mode 100644 index 1a7833b..0000000 Binary files a/src/pages/training/static/training-home/img_3.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_4.png b/src/pages/training/static/training-home/img_4.png deleted file mode 100644 index a9f5fa6..0000000 Binary files a/src/pages/training/static/training-home/img_4.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_5.png b/src/pages/training/static/training-home/img_5.png deleted file mode 100644 index 3c9a87d..0000000 Binary files a/src/pages/training/static/training-home/img_5.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_6.png b/src/pages/training/static/training-home/img_6.png deleted file mode 100644 index 59bee8b..0000000 Binary files a/src/pages/training/static/training-home/img_6.png and /dev/null differ diff --git a/src/pages/training/static/training-home/img_7.png b/src/pages/training/static/training-home/img_7.png deleted file mode 100644 index c281676..0000000 Binary files a/src/pages/training/static/training-home/img_7.png and /dev/null differ diff --git a/src/pages/training/static/training-home/jiezouxunlian.png b/src/pages/training/static/training-home/jiezouxunlian.png deleted file mode 100644 index 45ff66e..0000000 Binary files a/src/pages/training/static/training-home/jiezouxunlian.png and /dev/null differ diff --git a/src/pages/training/static/training-home/jingzhunxunlian.png b/src/pages/training/static/training-home/jingzhunxunlian.png deleted file mode 100644 index 40b7e69..0000000 Binary files a/src/pages/training/static/training-home/jingzhunxunlian.png and /dev/null differ diff --git a/src/pages/training/static/training-home/liliangxulian.png b/src/pages/training/static/training-home/liliangxulian.png deleted file mode 100644 index 61af0d4..0000000 Binary files a/src/pages/training/static/training-home/liliangxulian.png and /dev/null differ diff --git a/src/pages/training/static/training-home/missed.png b/src/pages/training/static/training-home/missed.png deleted file mode 100644 index a0e965c..0000000 Binary files a/src/pages/training/static/training-home/missed.png and /dev/null differ diff --git a/src/pages/training/static/training-home/nailixunlian.png b/src/pages/training/static/training-home/nailixunlian.png deleted file mode 100644 index a0b580c..0000000 Binary files a/src/pages/training/static/training-home/nailixunlian.png and /dev/null differ diff --git a/src/utils/matchProtocol.js b/src/utils/matchProtocol.js index 707864c..c6134a4 100644 --- a/src/utils/matchProtocol.js +++ b/src/utils/matchProtocol.js @@ -5,6 +5,11 @@ const { Reader, Writer } = protobuf; // 比赛服 protobuf 协议适配层: // 小程序环境不支持 protobufjs 反射模式里的动态 Function codegen, // 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。 +// +// 此区块由 scripts/generate-match-schema.mjs 自动生成,请勿手动修改。 +// 来源:src/utils/match.min.js(sha256: b4f272aad40fb951) +// 协议命名空间:rpc;消息数:12;字段数:137 + export const ServerMessageType = { SERVER_MSG_UNKNOWN: 0, SERVER_MSG_MATCH_READY: 1, @@ -21,6 +26,7 @@ export const ServerMessageType = { SERVER_MSG_HEARTBEAT: 12, SERVER_MSG_PRACTICE_END: 13, SERVER_MSG_SYNC_PRACTICE_INFO: 14, + SERVER_MSG_SYNC_MATCH_INFO: 15, }; export const ClientMessageType = { @@ -30,18 +36,9 @@ export const ClientMessageType = { CLIENT_MSG_ACK: 3, CLIENT_MSG_LEAVE: 4, CLIENT_MSG_SYNC_PRACTICE_INFO: 5, + CLIENT_MSG_SYNC_MATCH_INFO: 6, }; -// protobufjs 的 enum 默认是 name -> value,这里反转成 value -> name 用于日志打印。 -const ServerMessageTypeNameByValue = Object.keys(ServerMessageType).reduce( - (result, name) => { - result[ServerMessageType[name]] = name; - return result; - }, - {} -); - -// 当前只需要解码比赛服下发的字段并打印,字段表按后端 proto 定义维护。 const SCHEMAS = { MatchShoot: { 1: { name: "player_id", kind: "int64" }, @@ -210,12 +207,7 @@ const SCHEMAS = { 17: { name: "win_team", kind: "int32" }, 18: { name: "mvp", kind: "message", type: "PlayerFull" }, 19: { name: "room_id", kind: "string" }, - 20: { - name: "result_list", - kind: "message", - type: "PlayerMatchResult", - repeated: true, - }, + 20: { name: "result_list", kind: "message", type: "PlayerMatchResult", repeated: true }, 21: { name: "timeout_time", kind: "int64" }, 22: { name: "target_type", kind: "int32" }, 23: { name: "event_type", kind: "int32" }, @@ -233,6 +225,16 @@ const SCHEMAS = { 7: { name: "sequence", kind: "int64" }, }, }; +// + +// protobufjs 的 enum 默认是 name -> value,这里反转成 value -> name 用于日志打印。 +const ServerMessageTypeNameByValue = Object.keys(ServerMessageType).reduce( + (result, name) => { + result[ServerMessageType[name]] = name; + return result; + }, + {} +); // 小程序 websocket 收到的 data 可能是 ArrayBuffer、TypedArray 或字符串,这里统一成 Uint8Array。 function toUint8Array(data) {