From 49467c1e3da48571a8f82758a326c63fdda48abf Mon Sep 17 00:00:00 2001 From: R524809 Date: Mon, 26 Oct 2026 18:29:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E7=BA=B8=20footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- miniprogram/config/api.ts | 9 +-- .../paperSheet/draw/paperDrawService.ts | 6 +- .../papersPages/paperSheet/paperSheet.ts | 19 ++++-- tests/auth-api.test.mjs | 2 + tests/environment-switch.test.mjs | 36 ++++++++++- tests/paper-sheet.test.mjs | 61 +++++++++++++++++++ 7 files changed, 124 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 525ec32..05cfc93 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ await apiRequest('/me'); - `localBaseUrl`:本地开发接口。 - `stagingBaseUrl`:未来测试接口。 -- `productionBaseUrl`:未来正式接口。 +- `productionBaseUrl`:线上接口 `https://doodle-api.joeyone.cn/api/v1`。 -目前按要求,三个地址都暂填 `http://127.0.0.1:8800/api/v1`,客户端缓存和会话按环境隔离,但实际请求仍访问同一个本地后端和数据库。真机的 `127.0.0.1` 指向手机;调试时 local 需改为电脑局域网地址。部署时把 staging/production 占位地址改为对应 HTTPS 地址,不会因地址缺失自动回退 local。 +开发版默认选择 `production`,体验版和正式版始终连接线上 `https://doodle-api.joeyone.cn/api/v1`。本次启用 v2 环境选择键,旧版保存的 local 选择不会覆盖线上默认值;之后在 Debug 中手动选择的环境仍会持久保存。`local / staging` 暂保留 `http://127.0.0.1:8800/api/v1`,staging 尚非独立测试服务;真机调试 local 时需改为电脑局域网地址。客户端缓存和会话按环境与接口地址隔离,不会因线上连接失败自动回退 local。 切换会清空共享用户和内容状态,读取目标环境凭证,并强制从目标接口加载首页;原环境的缓存与会话保留。所有 API 响应按环境版本检查,即使快速 A→B→A,旧请求也不能覆盖当前数据。写请求(含登录等待及上传重试)未完成时禁止切换。目标接口不可用时提示连接失败并保留所选环境,不展示上一环境的内容。打印偏好继续共用。 -本轮仅调整开发版数据来源;体验/正式包原预拉取模式、地址及全部旧云端服务保持原样。 +本轮调整 HTTP 接口默认环境和线上地址;体验/正式包原预拉取模式及全部旧云端服务保持原样。 环境切换回归:`node --import tsx --test tests/environment-switch.test.mjs`。 diff --git a/miniprogram/config/api.ts b/miniprogram/config/api.ts index 409a6de..bdc560b 100644 --- a/miniprogram/config/api.ts +++ b/miniprogram/config/api.ts @@ -2,16 +2,17 @@ export type ApiEnvironment = 'local' | 'staging' | 'production'; type MiniProgramVersion = 'develop' | 'trial' | 'release'; const LOCAL_BASE_URL = 'http://127.0.0.1:8800/api/v1'; -const STORAGE_KEY = 'doodle_develop_api_environment_v1'; +// 默认来源改为线上时启用新选择键,旧版保存的 local 不覆盖本次默认值。 +const STORAGE_KEY = 'doodle_develop_api_environment_v2'; let selectedEnvironment: ApiEnvironment | undefined; let revision = 0; -/** 暂时共用本地地址;部署后分别替换 staging/production,真机 local 使用电脑局域网 IP。 */ +/** 默认访问线上;local 保留调试,staging 暂沿用本机占位地址。 */ export const apiConfig = { - developmentEnvironment: 'local' as ApiEnvironment, + developmentEnvironment: 'production' as ApiEnvironment, localBaseUrl: LOCAL_BASE_URL, stagingBaseUrl: LOCAL_BASE_URL, - productionBaseUrl: LOCAL_BASE_URL, + productionBaseUrl: 'https://doodle-api.joeyone.cn/api/v1', timeout: 10000, }; diff --git a/miniprogram/papersPages/paperSheet/draw/paperDrawService.ts b/miniprogram/papersPages/paperSheet/draw/paperDrawService.ts index 5a46862..3f9ad2c 100644 --- a/miniprogram/papersPages/paperSheet/draw/paperDrawService.ts +++ b/miniprogram/papersPages/paperSheet/draw/paperDrawService.ts @@ -174,7 +174,11 @@ export default class PaperDrawService extends BaseDrawService { ctx.globalAlpha = 0.35; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; - ctx.fillText('涂鸦丫小程序', canvasWidth / 2, canvasHeight - 16); + ctx.fillText( + '© 涂鸦丫小程序 | 原创出品', + canvasWidth / 2, + canvasHeight - 16, + ); ctx.restore(); } diff --git a/miniprogram/papersPages/paperSheet/paperSheet.ts b/miniprogram/papersPages/paperSheet/paperSheet.ts index 63f5de8..20172cc 100644 --- a/miniprogram/papersPages/paperSheet/paperSheet.ts +++ b/miniprogram/papersPages/paperSheet/paperSheet.ts @@ -106,12 +106,19 @@ createPage( // 草稿在公开详情中固定为 404;开发版使用本地注册配置,不通过失败请求判断预览权限。 if (!this.data.isDevEnv) { const remote = await getWorksheet(entry.id); - if ( - entry.id !== PAPER_SHEET_WORKSHEET_ID && - (remote.rendering?.rendererKey !== entry.rendererKey || - remote.rendering?.paperType !== paperType) - ) { - throw new Error('作业纸配置暂不可用,请更新小程序'); + if (entry.id !== PAPER_SHEET_WORKSHEET_ID) { + // 旧服务端可能未下发 rendering。仅对本地已注册 ID、且公开路由一致的条目使用固定配置。 + // 显式下发的配置仍须匹配;404、网络异常及未知条目不进入此兼容分支。 + const matches = + remote.rendering === undefined + ? remote.path === + getPublishMetaByMode(entry.id)?.path + : remote.rendering?.rendererKey === + entry.rendererKey && + remote.rendering?.paperType === paperType; + if (!matches) { + throw new Error('作业纸配置异常,请稍后重试'); + } } } if (request !== this._entryRequest) return; diff --git a/tests/auth-api.test.mjs b/tests/auth-api.test.mjs index 69e9310..b41c889 100644 --- a/tests/auth-api.test.mjs +++ b/tests/auth-api.test.mjs @@ -117,6 +117,8 @@ function fixture(initialStorage = new Map()) { run((name) => load(resolve(dirname(file), name)), exports); return exports; } + // 认证回归显式使用本机配置,避免依赖产品默认环境。 + load('config/api').apiConfig.developmentEnvironment = 'local'; return { auth: load('utils/auth'), session: load('services/auth-session'), diff --git a/tests/environment-switch.test.mjs b/tests/environment-switch.test.mjs index af2a743..e19b2d4 100644 --- a/tests/environment-switch.test.mjs +++ b/tests/environment-switch.test.mjs @@ -15,7 +15,7 @@ const deferred = () => { const tick = () => new Promise(setImmediate); /** 加载真实配置、请求、认证、App 与切换流程,仅模拟微信运行时和服务端响应。 */ -function fixture(storage = new Map()) { +function fixture(storage = new Map(), useDefaultEnvironment = false) { let version = 'develop'; let app; let pages = []; @@ -131,6 +131,12 @@ function fixture(storage = new Map()) { return exports; } load('app'); + const config = load('config/api'); + if (!useDefaultEnvironment) { + // 既有切换场景从 local 开始,并显式模拟三个环境同址。 + config.apiConfig.developmentEnvironment = 'local'; + config.apiConfig.productionBaseUrl = config.apiConfig.localBaseUrl; + } return { config: load('config/api'), auth: load('utils/auth'), @@ -423,3 +429,31 @@ test('Debug 页面调用统一切换入口,失败时隐藏 loading 后显示 }); assert.equal(toasts.length, 0); }); + +test('所有包默认请求线上;忽略旧 local 选择,新手动选择仍可持久保存', async () => { + const online = 'https://doodle-api.joeyone.cn/api/v1'; + const f = fixture( + new Map([['doodle_develop_api_environment_v1', 'local']]), + true, + ); + for (const version of ['develop', 'trial', 'release', 'unknown']) { + f.version(version); + assert.equal(f.config.getApiConfig().environment, 'production'); + assert.equal(f.config.getApiConfig().baseUrl, online); + } + f.version('develop'); + await f.api.apiRequest('/categories', { auth: 'none' }); + assert.equal(f.requests.at(-1).url, `${online}/categories`); + f.config.selectApiEnvironment('local'); + assert.equal( + f.config.getApiConfig().baseUrl, + 'http://127.0.0.1:8800/api/v1', + ); + assert.equal( + fixture(f.storage, true).config.getApiConfig().environment, + 'local', + ); + f.version('trial'); + assert.equal(f.config.getApiConfig().baseUrl, online); + assert.equal(f.config.getApiConfig().environment, 'production'); +}); diff --git a/tests/paper-sheet.test.mjs b/tests/paper-sheet.test.mjs index 93f4f30..788f3a9 100644 --- a/tests/paper-sheet.test.mjs +++ b/tests/paper-sheet.test.mjs @@ -306,3 +306,64 @@ test('体验版与正式版均校验公开详情,404 和网络故障不能降 } } }); + +test('体验/正式版兼容旧接口缺少 rendering,十种纸张保持独立分享、收藏及下载归属', async () => { + for (const version of ['trial', 'release']) { + const requested = []; + const { page, wx, favorites, downloads, toasts } = createPage({ + getWorksheet: async (id) => { + requested.push(id); + return { id, path: config.getPublishMetaByMode(id).path }; + }, + }); + wx.getAccountInfoSync = () => ({ + miniProgram: { envVersion: version }, + }); + for (const paper of config.PAPER_TYPE_OPTIONS) { + const id = config.getWorksheetIdForPaperType(paper.id); + await page.openEntry({ id }); + assert.equal(page.data.hasContent, true); + assert.equal(page.data.selectedPaperType, paper.id); + assert.equal(page.data.functionId, id); + assert.equal(page.onShareAppMessage().path, `/${route}?id=${id}`); + await page.onPreviewFavorite(); + assert.deepEqual(favorites.at(-1), ['add', id]); + await page.exportToPrint(); + assert.equal(downloads.at(-1).worksheetId, id); + } + assert.equal(requested.length, 10); + assert.equal( + toasts.filter((title) => title.includes('配置')).length, + 0, + ); + } +}); + +test('兼容只允许缺省配置,错误路由、空/残缺/冲突配置仍阻止生成', async () => { + const path = config.getPublishMetaByMode('paper-mi').path; + for (const version of ['trial', 'release']) { + for (const remote of [ + { path: `/${route}?id=paper-square` }, + { path: '/mathPages/mathDraw/mathDraw?id=paper-mi' }, + {}, + { path, rendering: null }, + { path, rendering: {} }, + { path, rendering: { rendererKey: 'unknown', paperType: 'mi' } }, + { + path, + rendering: { rendererKey: 'paper-sheet', paperType: 'square' }, + }, + ]) { + const { page, wx, toasts } = createPage({ + getWorksheet: async () => remote, + }); + wx.getAccountInfoSync = () => ({ + miniProgram: { envVersion: version }, + }); + await page.onLoad({ id: 'paper-mi' }); + assert.equal(page.data.hasContent, false); + assert.equal(page.data.isWorksheetReady, false); + assert.ok(toasts.includes('作业纸配置异常,请稍后重试')); + } + } +});