初始提交:浏览器首页 MyHomePage 全栈项目
# 项目概述 个人浏览器首页导航应用,支持书签分类管理、搜索引擎快捷搜索、 必应每日壁纸轮播、前后端分离部署,适配 1Panel 服务器(Docker 模式)。 # 技术栈 - 前端:Vue 3 + TypeScript + Vite + Pinia + Capacitor(Android 打包) - 后端:.NET 8 + SqlSugar(多数据库) + SQLite/MySQL + Swashbuckle - 部署:1Panel 应用商店自定义应用(Docker Compose 模式) # 项目结构 - backend/ .NET 8 API 后端(8 个 Controller + 15 个 Service) - frontend/ Vue 3 前端(19 个组件 + 9 个 API 模块 + 5 个 Store) - docker/ Docker 部署文件(后端镜像 + Nginx 反代) - docs/ 部署手册(1Panel 实战版) - scripts/ E2E 测试脚本 # 已实现功能 - 书签管理:增删改查 + 树形分类 + 拖拽排序 + 主色自适应 - 搜索引擎:8 个内置引擎 + 自定义引擎 + favicon 自动抓取 - 必应壁纸:每日轮播 + 多分辨率自动选择 + 1.6MP 质量优先 - 全局设置:主题/行为/数据/工具 4 分类 + 跨设备同步 - 文件上传:图标/书签/通用(容器持久化 + 跨域 URL 拼接) - 同步:基于变更日志的设备间数据同步 - 跨域部署:前后端分离 + runtime config.json 无需重新编译 # 进度记录 - 已完成 P0~P52 共 53 个开发节点(详细见 说明文档.md) - 当前版本:v1.0 部署就绪 # 部署文档 - README.md:项目说明 + 快速开始 - 说明文档.md:完整开发进度(中文) - docs/DEPLOY.md:1Panel 部署手册(Docker 模式)
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
$BASE = "http://127.0.0.1:5080"
|
||||
$pass = 0; $fail = 0
|
||||
|
||||
function Jget([string]$Url) {
|
||||
$r = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 5
|
||||
return ($r.Content | ConvertFrom-Json).data
|
||||
}
|
||||
function Jpost([string]$Url, [string]$Body) {
|
||||
$r = Invoke-WebRequest -Uri $Url -Method POST -ContentType "application/json" -Body $Body -UseBasicParsing -TimeoutSec 5
|
||||
return ($r.Content | ConvertFrom-Json).data
|
||||
}
|
||||
function Jput([string]$Url, [string]$Body) {
|
||||
$r = Invoke-WebRequest -Uri $Url -Method PUT -ContentType "application/json" -Body $Body -UseBasicParsing -TimeoutSec 5
|
||||
return ($r.Content | ConvertFrom-Json).data
|
||||
}
|
||||
function Jdel([string]$Url) {
|
||||
Invoke-WebRequest -Uri $Url -Method DELETE -UseBasicParsing -TimeoutSec 5 | Out-Null
|
||||
}
|
||||
|
||||
function Step([string]$Name, [scriptblock]$A) {
|
||||
try {
|
||||
$r = & $A
|
||||
Write-Host " PASS $Name" -ForegroundColor Green
|
||||
$script:pass++
|
||||
return $r
|
||||
} catch {
|
||||
Write-Host " FAIL $Name :: $($_.Exception.Message)" -ForegroundColor Red
|
||||
$script:fail++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "===== E2E 完整链路 =====" -ForegroundColor Cyan
|
||||
|
||||
# 1) Root
|
||||
$root = Step "Create root category" { Jpost "$BASE/api/categories" '{"parentId":0,"name":"e2e-root","icon":"star","sort":99}' }
|
||||
if ($root) { Write-Host " id=$($root.id) parentId=$($root.parentId)" -ForegroundColor DarkGray }
|
||||
|
||||
# 2) Child
|
||||
$child = Step "Create child category" { Jpost "$BASE/api/categories" ('{"parentId":' + $root.id + ',"name":"e2e-child","icon":"link","sort":1}') }
|
||||
if ($child) { Write-Host " id=$($child.id) parentId=$($child.parentId)" -ForegroundColor DarkGray }
|
||||
|
||||
# 3) Update child
|
||||
$upd = Step "Update child category" { Jput "$BASE/api/categories/$($child.id)" ('{"parentId":' + $root.id + ',"name":"e2e-child-renamed","icon":"link","sort":2}') }
|
||||
if ($upd) { Write-Host " name=$($upd.name) sort=$($upd.sort)" -ForegroundColor DarkGray }
|
||||
|
||||
# 4) Create bookmark
|
||||
$bm = Step "Create bookmark" { Jpost "$BASE/api/bookmarks" ('{"categoryId":' + $child.id + ',"title":"e2e-bookmark","url":"https://e2e.com","description":"e2e","icon":"link","iconType":"lucide","iconUrl":null,"sort":0}') }
|
||||
if ($bm) { Write-Host " id=$($bm.id) categoryId=$($bm.categoryId)" -ForegroundColor DarkGray }
|
||||
|
||||
# 5) Update bookmark
|
||||
$bmUpd = Step "Update bookmark" { Jput "$BASE/api/bookmarks/$($bm.id)" ('{"categoryId":' + $child.id + ',"title":"e2e-renamed","url":"https://e2e.com","description":"new","icon":"link","iconType":"lucide","iconUrl":null,"sort":1}') }
|
||||
if ($bmUpd) { Write-Host " title=$($bmUpd.title) sort=$($bmUpd.sort)" -ForegroundColor DarkGray }
|
||||
|
||||
# 6) Settings
|
||||
$set = Step "Update settings" { Jput "$BASE/api/settings" '{"themeMode":"light","accentColor":"#00b894","backgroundImage":"wp5","backgroundType":"preset"}' }
|
||||
if ($set) { Write-Host " theme=$($set.themeMode) accent=$($set.accentColor) bg=$($set.backgroundImage)" -ForegroundColor DarkGray }
|
||||
|
||||
# 7) Upload PNG (multipart)
|
||||
$png = [System.IO.Path]::GetTempFileName() + ".png"
|
||||
$bytes = [byte[]](0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x08,0x06,0x00,0x00,0x00,0x1F,0x15,0xC4,0x89,0x00,0x00,0x00,0x0A,0x49,0x44,0x41,0x54,0x78,0x9C,0x63,0x00,0x01,0x00,0x00,0x05,0x00,0x01,0x0D,0x0A,0x2D,0xB4,0x00,0x00,0x00,0x00,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82)
|
||||
[System.IO.File]::WriteAllBytes($png, $bytes)
|
||||
$up = Step "Upload PNG (multipart)" {
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
$uri = "http://127.0.0.1:5080/api/upload"
|
||||
$form = [System.Net.Http.MultipartFormDataContent]::new()
|
||||
$fs = [System.IO.File]::OpenRead($png)
|
||||
$content = [System.Net.Http.StreamContent]::new($fs)
|
||||
$content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("image/png")
|
||||
$form.Add($content, "file", [System.IO.Path]::GetFileName($png))
|
||||
$client = [System.Net.Http.HttpClient]::new()
|
||||
try {
|
||||
$r = $client.PostAsync($uri, $form).GetAwaiter().GetResult()
|
||||
if ($r.IsSuccessStatusCode) {
|
||||
$body = $r.Content.ReadAsStringAsync().GetAwaiter().GetResult() | ConvertFrom-Json
|
||||
return $body.data
|
||||
} else { throw "status=" + $r.StatusCode }
|
||||
} finally {
|
||||
$fs.Close()
|
||||
$form.Dispose()
|
||||
$client.Dispose()
|
||||
}
|
||||
}
|
||||
if ($up) { Write-Host " url=$($up.url)" -ForegroundColor DarkGray }
|
||||
Remove-Item $png -ErrorAction SilentlyContinue
|
||||
|
||||
# 8) Default engine
|
||||
$eng = Step "Set default search engine" { Jput "$BASE/api/search-engines/3/default" "" }
|
||||
if ($eng) { Write-Host " default=$($eng.name) isDefault=$($eng.isDefault)" -ForegroundColor DarkGray }
|
||||
|
||||
# 9) Sync
|
||||
$sync = Step "Sync changes" { Jget "$BASE/api/sync/changes?since=2026-07-04T00:00:00Z" }
|
||||
if ($sync) { Write-Host " changes=$($sync.changes.Count) snapshot(cats=$($sync.snapshot.categories.Count), bookmarks=$($sync.snapshot.bookmarks.Count))" -ForegroundColor DarkGray }
|
||||
|
||||
# 10) Tree
|
||||
$tree = Step "Get category tree" { Jget "$BASE/api/categories" }
|
||||
if ($tree) { Write-Host " top-level=$($tree.Count)" -ForegroundColor DarkGray }
|
||||
|
||||
# 11) Delete root with children should be 400
|
||||
$deny = Step "Delete root with children (expect 400)" {
|
||||
$got = "unknown"
|
||||
try {
|
||||
Invoke-WebRequest -Uri "$BASE/api/categories/$($root.id)" -Method DELETE -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop | Out-Null
|
||||
$got = "unexpected-200"
|
||||
} catch {
|
||||
$resp = $_.Exception.Response
|
||||
$code = 0
|
||||
if ($resp) { $code = [int]$resp.StatusCode }
|
||||
$got = "rejected-" + $code
|
||||
}
|
||||
if ($got -eq "rejected-400") { return $got } else { throw $got }
|
||||
}
|
||||
|
||||
# 12) Cleanup
|
||||
Step "Cleanup: delete bookmark" { Jdel "$BASE/api/bookmarks/$($bm.id)" }
|
||||
Step "Cleanup: delete child" { Jdel "$BASE/api/categories/$($child.id)" }
|
||||
Step "Cleanup: delete root" { Jdel "$BASE/api/categories/$($root.id)" }
|
||||
Step "Reset: default engine" { Jput "$BASE/api/search-engines/1/default" "" | Out-Null }
|
||||
Step "Reset: settings" { Jput "$BASE/api/settings" '{"themeMode":"dark","accentColor":"#6c5ce7","backgroundImage":"wp1","backgroundType":"preset"}' | Out-Null }
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== TOTAL: pass=$pass fail=$fail =====" -ForegroundColor $(if ($fail -eq 0) { "Green" } else { "Red" })
|
||||
Reference in New Issue
Block a user