From eef46f6ec0f59e10782eef761dcd6b11e40bb880 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 4 Oct 2026 22:10:03 +0800 Subject: [PATCH] fix: verify cloud image flow and clarify save feedback --- README.md | 10 ++++++++++ VERSION | 2 +- geography.py | 2 +- test_geography.py | 11 +++++++++++ test_households_ui.js | 43 +++++++++++++++++++++++++++++++++++++++++++ web/app.js | 2 +- web/households.js | 3 ++- web/index.html | 6 +++--- 8 files changed, 72 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 08881c2..f6258eb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ # 赵府智家 · 视觉系统 +## v0.1.32 · 真实网盘验收与保存反馈 + +- 修复家庭档案已保存、后续列表刷新失败时提示不可见的问题;立即提示保存结果,刷新失败单独提示,无需重复提交。 +- 网盘设置中展示封面、平台外观、个人记事、数据库备份四类存储说明;修正“全部本地存储”的旧文案。 +- 异常国家编码类型统一返回字段校验错误,不再触发服务器异常,失败保留原档案。 +- 真实百度网盘验收:在应用内“验收资料”目录上传 100867 字节合成图片,清空进程缓存后读回校验一致;重复上传返回相同文件 ID。正式家庭的已上传封面也通过独立回读及浏览器刷新、重新打开验证。验收不修改正式家庭档案。 +- 手机宽度检查覆盖已上传封面与编辑对话框。自动文件选择器仍受 Edge 扩展文件 URL 权限限制,不将接口上传或用户自行上传等同为自动选文件验收。 + +验证:150 项 Python、120 项 JavaScript 回归通过。真实网盘和正式封面回读校验通过;360px 网盘说明、390px 封面及对话框无横向溢出。上线前备份数据库,发布源码后部署。 + ## v0.1.31 · 地域分级选择与网盘图片 - 家庭/场所、空间和家谱籍贯/出生地共用地域控件:中国省市区联动、世界国家/地区选择、自定义历史地名或其他天体。更换上级清空下级;服务端以编码校验父子关系。旧地名无法匹配时原样保留为自定义,权限仍按家庭/场所划分。 diff --git a/VERSION b/VERSION index db7a480..28d0075 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.31 +0.1.32 diff --git a/geography.py b/geography.py index f6ee4ad..b4b3f13 100644 --- a/geography.py +++ b/geography.py @@ -34,7 +34,7 @@ def normalize(value, clean): children = item.get('children', []) elif mode == 'world': code = value.get('countryCode', '') - if code not in DATA['countries']: raise ValueError('请选择国家或地区') + if not isinstance(code, str) or code not in DATA['countries']: raise ValueError('请选择国家或地区') if code == 'CN': raise ValueError('中国地区请使用中国分级选择') result.update(countryCode=code, country=DATA['countries'][code]) for field in ('province','city','district'): diff --git a/test_geography.py b/test_geography.py index 18834e8..e7761d4 100644 --- a/test_geography.py +++ b/test_geography.py @@ -25,6 +25,11 @@ class GeographyUnitTests(unittest.TestCase): result=self.norm(dict(mode='custom',body='火星',path='基地 / 一区 / 一栋')) self.assertEqual(result['body'],'火星');self.assertEqual(result['countryCode'],'') + def test_invalid_country_types_have_a_validation_error(self): + for code in ([], {}, None, 42, True): + with self.subTest(code=code),self.assertRaises(ValueError): + self.norm(dict(mode='world',countryCode=code)) + def test_legacy_edits_drop_stale_codes_and_other_updates_preserve(self): old=dict(geography=self.norm(dict(mode='china',provinceCode='37',cityCode='3706')),province='山东省',city='烟台市') item={};geography.apply(item,{},old,app.clean_text,app.Problem) @@ -39,6 +44,12 @@ class GeographyIntegrationTests(unittest.TestCase): stop_server=appearance_fixture.AppearanceTests.stop_server request=appearance_fixture.AppearanceTests.request + def test_bad_country_input_returns_400_and_preserves_profile(self): + old=app.get_object('households',self.aid) + status,result=self.request('/api/households',self.root,dict(id=self.aid,name='不应写入',geography=dict(mode='world',countryCode=[]))) + self.assertEqual(status,400);self.assertIn('国家',result['error']) + self.assertEqual(app.get_object('households',self.aid),old) + def test_household_cover_and_path_roundtrip(self): v=dict(mode='china',provinceCode='37',cityCode='3706',districtCode='370682') status,saved=self.request('/api/households',self.root,dict(id=self.aid,name='封面与地域',geography=v,coverData=appearance_fixture.image_data())) diff --git a/test_households_ui.js b/test_households_ui.js index 97cf6ea..80d87b7 100644 --- a/test_households_ui.js +++ b/test_households_ui.js @@ -30,3 +30,46 @@ test('owner is visibly distinct from family and super administrators',()=>{ assert.equal(c.label({id:'backup',role:'family_admin'}),'家庭管理员'); assert.equal(c.label({id:'root',role:'admin'}),'超级管理员'); }); + +function saveHarness({saveError,refreshError}={}){ + const events=[],error={textContent:''},elements=[{disabled:false},{disabled:true}]; + elements.id={value:'home'};elements.profileRevision={value:0}; + const form={elements,querySelector:()=>error,closest:()=>({close:()=>events.push('closed')})}; + let submit; + const state={households:[{id:'home',name:'原档案'}]}; + const c=vm.createContext({coverSaving:false,coverDraft:'data:image/png;base64,test',sessionGeneration:1, + $:()=>({addEventListener:(_type,fn)=>{submit=fn;}}),imageJobPending:()=>false,imageJobs:new Map(), + formValues:()=>({id:'home',name:'已保存'}),state, + api:async()=>{if(saveError)throw saveError;return {id:'home',name:'已保存',profileRevision:1,coverRevision:'new',coverRef:{fsId:'1'}};}, + renderHouseholds:()=>events.push('rendered'),toast:t=>events.push(t), + refresh:async()=>{events.push('refresh');if(refreshError)throw refreshError;}}); + vm.runInContext(part(homes,"$('#household-form').addEventListener('submit'",'document.addEventListener'),c); + return {run:()=>submit({preventDefault(){},currentTarget:form}),events,error,elements,state,c}; +} + +test('saved cover stays committed and success is visible before a failed refresh',async()=>{ + const h=saveHarness({refreshError:Error('network')});await h.run(); + assert.equal(h.state.households[0].name,'已保存');assert.equal(h.elements.profileRevision.value,1); + assert.match(h.state.households[0].coverUrl,/v=new/); + assert.ok(h.events.indexOf('封面已存百度网盘,归属档案已保存')e.disabled),[false,true]);assert.equal(h.c.coverSaving,false); +}); + +test('failed cover save keeps the draft open and does not claim success',async()=>{ + const h=saveHarness({saveError:Error('网盘暂时不可用')});await h.run(); + assert.equal(h.state.households[0].name,'原档案');assert.equal(h.error.textContent,'网盘暂时不可用'); + assert.deepEqual(h.events,[]);assert.equal(h.c.coverDraft,'data:image/png;base64,test'); +}); + +test('a stale post-save refresh does not show a new-session warning',async()=>{ + const h=saveHarness({refreshError:Object.assign(Error('stale'),{stale:true})});await h.run(); + assert.equal(h.events.at(-1),'refresh');assert.equal(h.state.households[0].name,'已保存'); +}); + +test('storage guide names cloud categories and avoids old all-local claims',()=>{ + const html=fs.readFileSync('web/index.html','utf8'); + assert.ok(html.includes('id="baidu-storage-guide"')); + for(const label of ['家庭与场所:','平台外观:','个人记事:','数据库备份:'])assert.ok(html.includes(label)); + assert.ok(!html.includes('自有部署 · 本地存储'));assert.ok(!app.includes('数据存于本机')); +}); diff --git a/web/app.js b/web/app.js index 7c2259e..d399bb4 100644 --- a/web/app.js +++ b/web/app.js @@ -94,7 +94,7 @@ function render() { $('#metric-used').textContent = state.storage.usedGB == null ? '—' : state.storage.usedGB + ' GB'; $('#service-status').textContent = state.mediaOnline ? '视频服务已连接' : '视频服务未连接'; $('#service-status').classList.toggle('off', !state.mediaOnline); - $('#version').textContent = state.version ? 'v' + state.version + ' / 数据存于本机' : '独立部署 / 数据自有'; + $('#version').textContent = state.version ? 'v' + state.version + ' / 独立部署 · 数据自有' : '独立部署 / 数据自有'; const warning = state.storage.paused ? state.storage.reason + ',已暂停录像。' : ''; $('#banner').textContent = warning; $('#banner').hidden = !warning; setOptions($('#site-filter'), '' + state.sites.map(s=>``).join('')); diff --git a/web/households.js b/web/households.js index 7bcc2ff..bb0a25d 100644 --- a/web/households.js +++ b/web/households.js @@ -64,7 +64,8 @@ $('#household-form').addEventListener('submit',async e=>{ const view={...saved,coverUrl:saved.coverData||saved.coverRef?'/api/households/cover?id='+encodeURIComponent(saved.id)+'&v='+saved.coverRevision:''}; delete view.coverData;delete view.coverRef;const previous=(state.households||[]).find(x=>x.id===saved.id); if(previous)Object.assign(previous,view);else (state.households||=[]).push(view); - renderHouseholds();f.closest('dialog').close();await refresh();toast(payload.coverData?'封面已存百度网盘,归属档案已保存':payload.coverData===''?'归属档案已保存,已使用默认封面':'归属档案已保存'); + renderHouseholds();f.closest('dialog').close();toast(payload.coverData?'封面已存百度网盘,归属档案已保存':payload.coverData===''?'归属档案已保存,已使用默认封面':'归属档案已保存'); + try{await refresh();}catch(error){if(!error.stale&&generation===sessionGeneration)toast('档案已保存,列表刷新暂未完成,请稍后刷新页面。无需重复保存。');} }catch(error){if(!error.stale&&generation===sessionGeneration)f.querySelector('.form-error').textContent=error.message;} finally{coverSaving=false;controls.forEach((x,i)=>x.disabled=disabled[i]);} }); diff --git a/web/index.html b/web/index.html index 01c0efe..d2cdfb8 100644 --- a/web/index.html +++ b/web/index.html @@ -26,7 +26,7 @@ -
自有部署 · 本地存储

按空间组织画面
让每一段记录有处可寻

+
独立部署 · 数据自有

按空间组织画面
让每一段记录有处可寻

@@ -38,14 +38,14 @@