Compare commits

..
4 Commits
Author SHA1 Message Date
lxy 5342c5e489 清理: 忽略dist构建产物目录 2026-10-03 10:19:34 +08:00
lxy efe54d6a41 新增: 天气城市扩展至全国地级市附生成脚本
- 城市列表由18个常用扩至386个地级市(和风LocationList地市级)
- 附 gen-cities.ps1 从源数据一键再生成, 支持双源下载
- 前端城市选择器由后端 citiesByProv 驱动, 无需改动
2026-10-03 10:19:33 +08:00
lxy 9b29dff067 修复: Bing壁纸浏览方向改为旧到新并保最新2000张
- 历史排序改为日期升序, 左键更旧右键更新, 位置显示 idx+1/total
- 修复排序翻转后裁剪逻辑误删最新图的问题, 改为淘汰最旧
- dislike跳过与定时切换同步改为向更新方向推进
2026-10-03 10:19:26 +08:00
lxy 2036c9c89a 新增: Bing 壁纸多市场获取/2000张历史/第三方图源/dislike/重下/方向修复 2026-10-01 01:02:41 +08:00
12 changed files with 4501 additions and 83 deletions
+1
View File
@@ -25,6 +25,7 @@ vendor/
# Build # Build
u-desktop u-desktop
dist/
# Web UI # Web UI
web-ui/node_modules/ web-ui/node_modules/
+347 -55
View File
@@ -13,15 +13,26 @@ import (
"time" "time"
) )
const bingAPIBase = "https://www.bing.com/HPImageArchive.aspx?format=js&n=8&mkt=zh-CN" const bingAPIPrefix = "https://www.bing.com/HPImageArchive.aspx?format=js&n=8"
type bingImage struct {
StartDate string `json:"startdate"`
URL string `json:"url"`
URLBase string `json:"urlbase"`
Copyright string `json:"copyright"`
}
type bingResponse struct { type bingResponse struct {
Images []struct { Images []bingImage `json:"images"`
StartDate string `json:"startdate"` }
URL string `json:"url"`
URLBase string `json:"urlbase"` type biturlResponse struct {
Copyright string `json:"copyright"` StartDate string `json:"start_date"`
} `json:"images"` EndDate string `json:"end_date"`
URL string `json:"url"`
Copyright string `json:"copyright"`
CopyrightLink string `json:"copyright_link"`
Resolution string `json:"resolution"`
} }
type BingRecord struct { type BingRecord struct {
@@ -30,8 +41,33 @@ type BingRecord struct {
Copyright string `json:"copyright"` Copyright string `json:"copyright"`
Filename string `json:"filename"` Filename string `json:"filename"`
Favorited bool `json:"favorited"` Favorited bool `json:"favorited"`
Hidden bool `json:"hidden"`
Market string `json:"market"`
Source string `json:"source"`
} }
const maxBingRecords = 2000
var bingMarkets = []string{
"zh-CN", "en-US", "ja-JP", "de-DE", "fr-FR",
"en-GB", "ko-KR", "ru-RU", "pt-BR", "es-ES",
}
var bingMarketLabels = map[string]string{
"zh-CN": "中国",
"en-US": "美国",
"ja-JP": "日本",
"de-DE": "德国",
"fr-FR": "法国",
"en-GB": "英国",
"ko-KR": "韩国",
"ru-RU": "俄罗斯",
"pt-BR": "巴西",
"es-ES": "西班牙",
}
const bingBiturlAPI = "https://bing.biturl.top/?format=json"
type BingHistory struct { type BingHistory struct {
Records []BingRecord `json:"records"` Records []BingRecord `json:"records"`
CurrentIdx int `json:"currentIdx"` CurrentIdx int `json:"currentIdx"`
@@ -60,91 +96,250 @@ func loadBingHistory() *BingHistory {
} }
func saveBingHistory(h *BingHistory) error { func saveBingHistory(h *BingHistory) error {
data, _ := json.MarshalIndent(h, "", " ") data, err := json.MarshalIndent(h, "", " ")
if err != nil {
log.Printf("Bing 历史序列化失败: %v", err)
return err
}
return os.WriteFile(bingHistoryPath(), data, 0644) return os.WriteFile(bingHistoryPath(), data, 0644)
} }
// bingKey builds a composite dedup key: market + "_" + date
func bingKey(market, date string) string {
if market == "" {
return "_" + date
}
return market + "_" + date
}
// downloadBingImage downloads a single Bing image from the record's URLBase
func downloadBingImage(r *BingRecord) error {
imgURL := r.URLBase
if imgURL == "" {
return fmt.Errorf("empty URLBase for %s", r.Filename)
}
if !strings.HasPrefix(imgURL, "http") {
imgURL = "https://www.bing.com" + imgURL
}
// For official Bing source, ensure resolution suffix
if r.Source == "bing_official" || r.Source == "" {
if !strings.Contains(imgURL, "_1920x1080") && !strings.HasSuffix(imgURL, ".jpg") {
imgURL += "_1920x1080.jpg"
}
}
imgResp, err := httpClient.Get(imgURL)
if err != nil {
return fmt.Errorf("http get failed: %w", err)
}
defer imgResp.Body.Close()
imgData, err := io.ReadAll(imgResp.Body)
if err != nil {
return fmt.Errorf("read body failed: %w", err)
}
if len(imgData) == 0 {
return fmt.Errorf("empty response body")
}
localPath := filepath.Join(bingDir(), r.Filename)
return os.WriteFile(localPath, imgData, 0644)
}
// fetchBingHistory fetches Bing wallpapers from multiple markets and sources.
// Phase 1: Official Bing API per market (idx=0,8,...192)
// Phase 2: biturl.top supplement per market (5 attempts each)
// Phase 3: Enforce maxBingRecords cap
func fetchBingHistory() { func fetchBingHistory() {
bingMu.Lock() bingMu.Lock()
defer bingMu.Unlock() defer bingMu.Unlock()
os.MkdirAll(bingDir(), 0755) os.MkdirAll(bingDir(), 0755)
existing := loadBingHistory() existing := loadBingHistory()
// Build composite-key map from existing records
existingMap := make(map[string]BingRecord) existingMap := make(map[string]BingRecord)
for _, r := range existing.Records { for _, r := range existing.Records {
existingMap[r.Date] = r existingMap[bingKey(r.Market, r.Date)] = r
// Migration: old records with Market="" were from zh-CN single market
if r.Market == "" {
existingMap[bingKey("zh-CN", r.Date)] = r
}
} }
// 分页下载: idx=0,8,16... 直到命中已有记录或无新图片 cfg := loadConfig()
for idx := 0; idx < 80; idx += 8 { markets := cfg.BingMarkets
url := fmt.Sprintf("%s&idx=%d", bingAPIBase, idx) if len(markets) == 0 {
resp, err := httpClient.Get(url) markets = bingMarkets
if err != nil { }
log.Printf("Bing API 请求失败 (idx=%d): %v", idx, err)
break
}
data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
var br bingResponse
if json.Unmarshal(data, &br) != nil || len(br.Images) == 0 {
break
}
newCount := 0 // track new records per key for dedup across phases
for _, img := range br.Images { newRecords := make(map[string]BingRecord)
date := img.StartDate
if date == "" { // ---- Phase 1: Official Bing API per market ----
for _, market := range markets {
for idx := 0; idx < 192; idx += 8 {
url := fmt.Sprintf("%s&mkt=%s&idx=%d", bingAPIPrefix, market, idx)
resp, err := httpClient.Get(url)
if err != nil {
log.Printf("Bing API 请求失败 (market=%s, idx=%d): %v", market, idx, err)
break
}
data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
var br bingResponse
if json.Unmarshal(data, &br) != nil || len(br.Images) == 0 {
break
}
pageNew := 0
for _, img := range br.Images {
date := img.StartDate
if date == "" {
continue
}
key := bingKey(market, date)
if _, exists := existingMap[key]; exists {
continue
}
if _, exists := newRecords[key]; exists {
continue
}
// Download the image
imgURL := img.URL
if !strings.HasPrefix(imgURL, "http") {
imgURL = "https://www.bing.com" + imgURL
}
imgResp, err := httpClient.Get(imgURL)
if err != nil {
continue
}
imgData, _ := io.ReadAll(imgResp.Body)
imgResp.Body.Close()
if len(imgData) == 0 {
continue
}
filename := fmt.Sprintf("%s_%s.jpg", market, date)
localPath := filepath.Join(bingDir(), filename)
os.WriteFile(localPath, imgData, 0644)
log.Printf("Bing 壁纸已下载: %s (%d bytes)", filename, len(imgData))
newRecords[key] = BingRecord{
Date: date,
URLBase: img.URLBase,
Copyright: img.Copyright,
Filename: filename,
Market: market,
Source: "bing_official",
}
pageNew++
}
if pageNew == 0 && idx > 0 {
break
}
time.Sleep(500 * time.Millisecond)
}
}
// ---- Phase 2: biturl.top supplement ----
for _, market := range markets {
for attempt := 0; attempt < 5; attempt++ {
url := fmt.Sprintf("%s&mkt=%s", bingBiturlAPI, market)
resp, err := httpClient.Get(url)
if err != nil {
log.Printf("Bing biturl 请求失败 (market=%s): %v", market, err)
time.Sleep(300 * time.Millisecond)
continue continue
} }
if _, exists := existingMap[date]; exists { data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
var bit biturlResponse
if json.Unmarshal(data, &bit) != nil || bit.StartDate == "" {
time.Sleep(300 * time.Millisecond)
continue continue
} }
imgURL := img.URL date := bit.StartDate
if !strings.HasPrefix(imgURL, "http") { key := bingKey(market, date)
imgURL = "https://www.bing.com" + imgURL if _, exists := existingMap[key]; exists {
time.Sleep(300 * time.Millisecond)
continue
}
if _, exists := newRecords[key]; exists {
time.Sleep(300 * time.Millisecond)
continue
} }
// Download from biturl URL
imgURL := bit.URL
imgResp, err := httpClient.Get(imgURL) imgResp, err := httpClient.Get(imgURL)
if err != nil { if err != nil {
time.Sleep(300 * time.Millisecond)
continue continue
} }
imgData, _ := io.ReadAll(imgResp.Body) imgData, _ := io.ReadAll(imgResp.Body)
imgResp.Body.Close() imgResp.Body.Close()
if len(imgData) == 0 { if len(imgData) == 0 {
time.Sleep(300 * time.Millisecond)
continue continue
} }
filename := date + ".jpg" filename := fmt.Sprintf("%s_%s.jpg", market, date)
localPath := filepath.Join(bingDir(), filename) localPath := filepath.Join(bingDir(), filename)
os.WriteFile(localPath, imgData, 0644) os.WriteFile(localPath, imgData, 0644)
log.Printf("Bing 壁纸已下载: %s (%d bytes)", filename, len(imgData)) log.Printf("Bing biturl 壁纸已下载: %s (%d bytes)", filename, len(imgData))
existingMap[date] = BingRecord{ newRecords[key] = BingRecord{
Date: date, Date: date,
URLBase: img.URLBase, URLBase: bit.URL,
Copyright: img.Copyright, Copyright: bit.Copyright,
Filename: filename, Filename: filename,
Market: market,
Source: "bing_biturl",
} }
newCount++ time.Sleep(300 * time.Millisecond)
} }
if newCount == 0 && idx > 0 {
break
}
time.Sleep(500 * time.Millisecond)
} }
// 按 API 返回顺序重建 records (newest first) // Merge existing + new records
// 用 existingMap 里所有记录按 date 降序排列 allMap := make(map[string]BingRecord)
for k, r := range existingMap {
allMap[k] = r
}
for k, r := range newRecords {
allMap[k] = r
}
// Sort by date ascending (oldest first), so index 0 = oldest, index N = newest
// Left-to-right navigation: left = older (smaller idx), right = newer (larger idx)
var records []BingRecord var records []BingRecord
for _, r := range existingMap { for _, r := range allMap {
records = append(records, r) records = append(records, r)
} }
sort.Slice(records, func(i, j int) bool { sort.Slice(records, func(i, j int) bool {
return records[i].Date > records[j].Date if records[i].Date != records[j].Date {
return records[i].Date < records[j].Date
}
return records[i].Market < records[j].Market
}) })
// ---- Phase 3: Enforce 2000 cap ----
if len(records) > maxBingRecords {
// Records sorted oldest-first, so oldest are at the head
toRemove := records[:len(records)-maxBingRecords]
records = records[len(records)-maxBingRecords:]
for _, r := range toRemove {
p := filepath.Join(bingDir(), r.Filename)
os.Remove(p)
}
log.Printf("Bing 壁纸: 裁剪到 %d 张, 删除 %d 张旧图", maxBingRecords, len(toRemove))
}
history := &BingHistory{ history := &BingHistory{
Records: records, Records: records,
CurrentIdx: 0, CurrentIdx: 0,
@@ -154,7 +349,7 @@ func fetchBingHistory() {
} }
saveBingHistory(history) saveBingHistory(history)
log.Printf("Bing 壁纸: 共 %d 张", len(records)) log.Printf("Bing 壁纸: 共 %d 张 (新增 %d 张)", len(records), len(newRecords))
reloadWallpaper() reloadWallpaper()
} }
@@ -163,7 +358,18 @@ func getCurrentBingPath() string {
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
return "" return ""
} }
return filepath.Join(bingDir(), h.Records[h.CurrentIdx].Filename) r := &h.Records[h.CurrentIdx]
p := filepath.Join(bingDir(), r.Filename)
if _, err := os.Stat(p); err == nil {
return p
}
// File missing, try re-download
log.Printf("Bing 壁纸文件缺失, 尝试重新下载: %s", r.Filename)
if err := downloadBingImage(r); err != nil {
log.Printf("Bing 重新下载失败: %s, %v", r.Filename, err)
return ""
}
return p
} }
func getCurrentBingRecord() *BingRecord { func getCurrentBingRecord() *BingRecord {
@@ -174,6 +380,8 @@ func getCurrentBingRecord() *BingRecord {
return &h.Records[h.CurrentIdx] return &h.Records[h.CurrentIdx]
} }
// bingPrev moves to the previous (older) image, skipping hidden ones.
// Records sorted oldest-first, so prev = decrement index.
func bingPrev() { func bingPrev() {
bingMu.Lock() bingMu.Lock()
defer bingMu.Unlock() defer bingMu.Unlock()
@@ -182,12 +390,24 @@ func bingPrev() {
if len(h.Records) == 0 { if len(h.Records) == 0 {
return return
} }
h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records) attempts := 0
for {
h.CurrentIdx = (h.CurrentIdx - 1 + len(h.Records)) % len(h.Records)
if !h.Records[h.CurrentIdx].Hidden {
break
}
attempts++
if attempts >= len(h.Records) {
return
}
}
saveBingHistory(h) saveBingHistory(h)
log.Printf("Bing 壁纸: 上一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date) log.Printf("Bing 壁纸: 上一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
bingReloadImage() bingReloadImage()
} }
// bingNext moves to the next (newer) image, skipping hidden ones.
// Records sorted oldest-first, so next = increment index.
func bingNext() { func bingNext() {
bingMu.Lock() bingMu.Lock()
defer bingMu.Unlock() defer bingMu.Unlock()
@@ -196,7 +416,17 @@ func bingNext() {
if len(h.Records) == 0 { if len(h.Records) == 0 {
return return
} }
h.CurrentIdx = (h.CurrentIdx - 1 + len(h.Records)) % len(h.Records) attempts := 0
for {
h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records)
if !h.Records[h.CurrentIdx].Hidden {
break
}
attempts++
if attempts >= len(h.Records) {
return
}
}
saveBingHistory(h) saveBingHistory(h)
log.Printf("Bing 壁纸: 下一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date) log.Printf("Bing 壁纸: 下一个 (idx=%d, date=%s)", h.CurrentIdx, h.Records[h.CurrentIdx].Date)
bingReloadImage() bingReloadImage()
@@ -221,10 +451,46 @@ func bingToggleFavorite() string {
return "★ 收藏当前壁纸" return "★ 收藏当前壁纸"
} }
// bingToggleDislike toggles the hidden flag on the current record.
// When hidden, auto-advances to the next visible image.
func bingToggleDislike() string {
bingMu.Lock()
defer bingMu.Unlock()
h := loadBingHistory()
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
return bingCurrentState()
}
r := &h.Records[h.CurrentIdx]
r.Hidden = !r.Hidden
saveBingHistory(h)
if r.Hidden {
log.Printf("Bing 壁纸: 已隐藏 (date=%s)", r.Date)
// Advance to next visible (newer = +1)
attempts := 0
for {
h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records)
if !h.Records[h.CurrentIdx].Hidden {
break
}
attempts++
if attempts >= len(h.Records) {
return bingCurrentState()
}
}
saveBingHistory(h)
bingReloadImage()
return bingCurrentState()
}
log.Printf("Bing 壁纸: 已取消隐藏 (date=%s)", r.Date)
return bingCurrentState()
}
func bingCurrentState() string { func bingCurrentState() string {
h := loadBingHistory() h := loadBingHistory()
if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) { if h.CurrentIdx < 0 || h.CurrentIdx >= len(h.Records) {
return `{"fav":false,"label":"☆","copyright":"","idx":0,"total":0}` return `{"fav":false,"label":"☆","copyright":"","idx":0,"total":0,"hidden":false}`
} }
r := h.Records[h.CurrentIdx] r := h.Records[h.CurrentIdx]
label := "☆" label := "☆"
@@ -239,6 +505,8 @@ func bingCurrentState() string {
"filename": r.Filename, "filename": r.Filename,
"idx": h.CurrentIdx, "idx": h.CurrentIdx,
"total": len(h.Records), "total": len(h.Records),
"hidden": r.Hidden,
"market": r.Market,
}) })
return string(data) return string(data)
} }
@@ -253,7 +521,7 @@ func bingFavoritesJSON() string {
} }
var favs []favItem var favs []favItem
for i, r := range h.Records { for i, r := range h.Records {
if r.Favorited { if r.Favorited && !r.Hidden {
favs = append(favs, favItem{r.Date, r.Copyright, r.Filename, i}) favs = append(favs, favItem{r.Date, r.Copyright, r.Filename, i})
} }
} }
@@ -279,6 +547,18 @@ func bingSetByIdx(idx int) string {
func bingThumbDataURI(filename string) string { func bingThumbDataURI(filename string) string {
p := filepath.Join(bingDir(), filename) p := filepath.Join(bingDir(), filename)
// Auto redownload if missing
if _, err := os.Stat(p); err != nil {
// Try to find the record and re-download
h := loadBingHistory()
for _, r := range h.Records {
if r.Filename == filename {
log.Printf("Bing 缩略图文件缺失, 尝试重新下载: %s", filename)
downloadBingImage(&r)
break
}
}
}
return imageToDataURI(p) return imageToDataURI(p)
} }
@@ -293,7 +573,7 @@ func bingWallpaperLoop() {
} }
} }
// 定时切换壁纸 (1 小时间隔) // Auto-refresh: cycle through images (newest direction)
go func() { go func() {
ticker := time.NewTicker(1 * time.Hour) ticker := time.NewTicker(1 * time.Hour)
for range ticker.C { for range ticker.C {
@@ -307,7 +587,18 @@ func bingWallpaperLoop() {
bingMu.Unlock() bingMu.Unlock()
continue continue
} }
h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records) // Move to next (newer) image, skip hidden
attempts := 0
for {
h.CurrentIdx = (h.CurrentIdx + 1) % len(h.Records)
if !h.Records[h.CurrentIdx].Hidden {
break
}
attempts++
if attempts >= len(h.Records) {
break
}
}
saveBingHistory(h) saveBingHistory(h)
bingMu.Unlock() bingMu.Unlock()
bingReloadImage() bingReloadImage()
@@ -315,6 +606,7 @@ func bingWallpaperLoop() {
} }
}() }()
// Periodic fetch
go func() { go func() {
fetchTicker := time.NewTicker(4 * time.Hour) fetchTicker := time.NewTicker(4 * time.Hour)
for range fetchTicker.C { for range fetchTicker.C {
@@ -324,4 +616,4 @@ func bingWallpaperLoop() {
} }
} }
}() }()
} }
+4
View File
@@ -68,6 +68,7 @@ type Config struct {
HideKnowledge bool `json:"hideKnowledge"` HideKnowledge bool `json:"hideKnowledge"`
SavedColors []SavedColor `json:"savedColors"` SavedColors []SavedColor `json:"savedColors"`
BingAutoRefresh bool `json:"bingAutoRefresh"` BingAutoRefresh bool `json:"bingAutoRefresh"`
BingMarkets []string `json:"bingMarkets,omitempty"`
AutoStart bool `json:"autoStart"` AutoStart bool `json:"autoStart"`
QWeatherKey string `json:"qweatherKey,omitempty"` QWeatherKey string `json:"qweatherKey,omitempty"`
TianapiKey string `json:"tianapiKey,omitempty"` TianapiKey string `json:"tianapiKey,omitempty"`
@@ -116,6 +117,9 @@ func loadConfig() *Config {
if cfg.Layout == "" { if cfg.Layout == "" {
cfg.Layout = LayoutSingle cfg.Layout = LayoutSingle
} }
if len(cfg.BingMarkets) == 0 {
cfg.BingMarkets = []string{"zh-CN", "en-US", "ja-JP", "de-DE", "fr-FR", "en-GB", "ko-KR", "ru-RU", "pt-BR", "es-ES"}
}
return &cfg return &cfg
} }
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
$ErrorActionPreference = "Stop"
$csvPath = Join-Path $PSScriptRoot "china-cities.csv"
if (-not (Test-Path $csvPath)) {
Write-Host "下载城市数据 CSV..."
$urls = @(
"https://raw.githubusercontent.com/qwd/LocationList/master/China-City-List-latest.csv",
"https://cdn.jsdelivr.net/gh/qwd/LocationList@master/China-City-List-latest.csv"
)
$ok = $false
foreach ($u in $urls) {
try { Invoke-WebRequest -Uri $u -OutFile $csvPath -UseBasicParsing; Write-Host "OK: $u"; $ok = $true; break }
catch { Write-Host "失败: $($_.Exception.Message)" }
}
if (-not $ok) { Write-Host "CSV 下载失败"; exit 1 }
}
$provMap = @{
"北京市"="北京"; "天津市"="天津"; "上海市"="上海"; "重庆市"="重庆";
"河北省"="河北"; "山西省"="山西"; "辽宁省"="辽宁"; "吉林省"="吉林"; "黑龙江省"="黑龙江";
"江苏省"="江苏"; "浙江省"="浙江"; "安徽省"="安徽"; "福建省"="福建"; "江西省"="江西";
"山东省"="山东"; "河南省"="河南"; "湖北省"="湖北"; "湖南省"="湖南"; "广东省"="广东";
"海南省"="海南"; "四川省"="四川"; "贵州省"="贵州"; "云南省"="云南"; "陕西省"="陕西";
"甘肃省"="甘肃"; "青海省"="青海";
"内蒙古自治区"="内蒙古"; "广西壮族自治区"="广西"; "西藏自治区"="西藏"; "宁夏回族自治区"="宁夏"; "新疆维吾尔自治区"="新疆";
"香港特别行政区"="香港"; "澳门特别行政区"="澳门"; "台湾省"="台湾";
}
$fullMap = @{}
foreach ($k in $provMap.Keys) { $fullMap[$provMap[$k]] = $k }
$provOrder = @(
"北京","天津","上海","重庆",
"河北","山西","辽宁","吉林","黑龙江",
"江苏","浙江","安徽","福建","江西","山东",
"河南","湖北","湖南","广东","海南",
"四川","贵州","云南","陕西","甘肃","青海",
"内蒙古","广西","西藏","宁夏","新疆",
"香港","澳门","台湾"
)
# 解析 CSV(跳过第1行版本号)
$lines = Get-Content $csvPath -Encoding UTF8
$tempCsv = Join-Path $PSScriptRoot "_cities_temp.csv"
$lines[1..($lines.Length-1)] | Set-Content $tempCsv -Encoding UTF8
$csv = Import-Csv $tempCsv -Encoding UTF8
Remove-Item $tempCsv -Force
Write-Host "CSV 记录: $($csv.Count)"
# 按 Adm1 一次性分桶
$byAdm1 = @{}
foreach ($r in $csv) {
$a = $r.'Adm1_Name_ZH'
if (-not $byAdm1.ContainsKey($a)) { $byAdm1[$a] = New-Object System.Collections.ArrayList }
[void]$byAdm1[$a].Add($r)
}
$result = New-Object System.Collections.ArrayList
$weird = New-Object System.Collections.ArrayList
foreach ($provShort in $provOrder) {
$provFull = $fullMap[$provShort]
$rows = @()
if ($byAdm1.ContainsKey($provFull)) { $rows = @($byAdm1[$provFull]) }
if ($rows.Count -eq 0) {
foreach ($k in $byAdm1.Keys) { if ($k -like "*$provShort*") { $rows += @($byAdm1[$k]) } }
}
if ($rows.Count -eq 0) { Write-Host "省份未找到: $provShort"; continue }
# 按 Adm2 分组,每 Adm2 取 Location_ID 最小(地级市中心)
$adm2Groups = $rows | Group-Object 'Adm2_Name_ZH'
$cnt = 0
foreach ($g in $adm2Groups) {
if ($g.Name -match "省直辖|直辖县|县级行政区") {
[void]$weird.Add("$provShort/$($g.Name)"); continue
}
$center = $g.Group | Sort-Object Location_ID | Select-Object -First 1
# 过滤非数字 ID(台湾乡镇异常十六进制 ID,非和风标准 9 位)
if ($center.Location_ID -notmatch '^\d+$') {
[void]$weird.Add("$provShort/$($g.Name)/$($center.Location_ID)"); continue
}
# name 用 Adm2 名去末尾"市"/"特别行政区"(避免区名如"双桥",简化港澳)
$name = $g.Name -replace '市$','' -replace '特别行政区$',''
[void]$result.Add([pscustomobject]@{ id=$center.Location_ID; name=$name; adm1=$provShort })
$cnt++
}
Write-Host (" {0,-8} : {1} 城" -f $provShort, $cnt)
}
Write-Host ""
Write-Host "总计: $($result.Count) 城"
if ($weird.Count -gt 0) { Write-Host "已过滤 ($($weird.Count)): $($weird -join '; ')" }
# 输出
$utf8 = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText((Join-Path $PSScriptRoot "cities.json"), ($result | ConvertTo-Json -Depth 2), $utf8)
$goLines = $result | ForEach-Object { ' {"' + $_.id + '", "' + $_.name + '", "' + $_.name + '", "' + $_.adm1 + '"},' }
$goBlock = "var cities = []City{`n" + ($goLines -join "`n") + "`n}"
[System.IO.File]::WriteAllText((Join-Path $PSScriptRoot "cities.go.txt"), $goBlock, $utf8)
$vueLines = $result | ForEach-Object { " { id: '" + $_.id + "', name: '" + $_.name + "', province: '" + $_.adm1 + "' }," }
$vueBlock = "const allCities = [`n" + ($vueLines -join "`n") + "`n].map((c, i) => ({ ...c, _idx: i }))"
[System.IO.File]::WriteAllText((Join-Path $PSScriptRoot "cities.vue.txt"), $vueBlock, $utf8)
Write-Host "已生成: cities.json / cities.go.txt / cities.vue.txt"
+17
View File
@@ -390,6 +390,23 @@ func openSettingsWindow() {
w.Bind("bingThumbDataURI", func(filename string) string { w.Bind("bingThumbDataURI", func(filename string) string {
return bingThumbDataURI(filename) return bingThumbDataURI(filename)
}) })
w.Bind("bingToggleDislike", func() string {
return bingToggleDislike()
})
w.Bind("getBingMarkets", func() string {
cfg := loadConfig()
data, _ := json.Marshal(cfg.BingMarkets)
return string(data)
})
w.Bind("saveBingMarkets", func(marketsJSON string) string {
var markets []string
if json.Unmarshal([]byte(marketsJSON), &markets) == nil {
cfg := loadConfig()
cfg.BingMarkets = markets
saveConfig(cfg)
}
return ""
})
w.Bind("pickSolidColor", func() string { w.Bind("pickSolidColor", func() string {
hwnd := uintptr(w.Window()) hwnd := uintptr(w.Window())
+382 -14
View File
@@ -23,23 +23,391 @@ type City struct {
var cities = []City{ var cities = []City{
{"101010100", "北京", "北京", "北京"}, {"101010100", "北京", "北京", "北京"},
{"101030100", "天津", "天津", "天津"},
{"101020100", "上海", "上海", "上海"}, {"101020100", "上海", "上海", "上海"},
{"101280101", "广州", "广州", "广东"}, {"101040100", "重庆", "重庆", "重庆"},
{"101280601", "深圳", "深圳", "广东"},
{"101200101", "武汉", "武汉", "湖北"},
{"101110101", "西安", "西安", "陕西"},
{"101210101", "杭州", "杭州", "浙江"},
{"101190101", "南京", "南京", "江苏"},
{"101070101", "沈阳", "沈阳", "辽宁"},
{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
{"101250101", "长沙", "长沙", "湖南"},
{"101270101", "成都", "成都", "四川"},
{"101090101", "石家庄", "石家庄", "河北"}, {"101090101", "石家庄", "石家庄", "河北"},
{"101090206", "任丘", "任丘", "河北"}, {"101090201", "保定", "保定", "河北"},
{"101090301", "邯郸", "邯郸", "河北"}, {"101090301", "张家口", "张家口", "河北"},
{"101290106", "宣威", "宣威", "云南"}, {"101090401", "承德", "承德", "河北"},
{"101290101", "昆明", "昆明", "云南"}, {"101090501", "唐山", "唐山", "河北"},
{"101090601", "廊坊", "廊坊", "河北"},
{"101090701", "沧州", "沧州", "河北"},
{"101090801", "衡水", "衡水", "河北"},
{"101090901", "邢台", "邢台", "河北"},
{"101091001", "邯郸", "邯郸", "河北"},
{"101091101", "秦皇岛", "秦皇岛", "河北"},
{"101091201", "雄安新区", "雄安新区", "河北"},
{"101100101", "太原", "太原", "山西"},
{"101100201", "大同", "大同", "山西"},
{"101100301", "阳泉", "阳泉", "山西"},
{"101100401", "晋中", "晋中", "山西"},
{"101100501", "长治", "长治", "山西"},
{"101100601", "晋城", "晋城", "山西"},
{"101100701", "临汾", "临汾", "山西"},
{"101100801", "运城", "运城", "山西"},
{"101100901", "朔州", "朔州", "山西"},
{"101101001", "忻州", "忻州", "山西"},
{"101101100", "吕梁", "吕梁", "山西"},
{"101070101", "沈阳", "沈阳", "辽宁"},
{"101070201", "大连", "大连", "辽宁"},
{"101070301", "鞍山", "鞍山", "辽宁"},
{"101070401", "抚顺", "抚顺", "辽宁"},
{"101070501", "本溪", "本溪", "辽宁"},
{"101070601", "丹东", "丹东", "辽宁"},
{"101070701", "锦州", "锦州", "辽宁"},
{"101070801", "营口", "营口", "辽宁"},
{"101070901", "阜新", "阜新", "辽宁"},
{"101071001", "辽阳", "辽阳", "辽宁"},
{"101071101", "铁岭", "铁岭", "辽宁"},
{"101071201", "朝阳", "朝阳", "辽宁"},
{"101071301", "盘锦", "盘锦", "辽宁"},
{"101071401", "葫芦岛", "葫芦岛", "辽宁"},
{"101060101", "长春", "长春", "吉林"},
{"101060201", "吉林", "吉林", "吉林"},
{"101060301", "延边朝鲜族自治州", "延边朝鲜族自治州", "吉林"},
{"101060401", "四平", "四平", "吉林"},
{"101060501", "通化", "通化", "吉林"},
{"101060601", "白城", "白城", "吉林"},
{"101060701", "辽源", "辽源", "吉林"},
{"101060801", "松原", "松原", "吉林"},
{"101060901", "白山", "白山", "吉林"},
{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
{"101050201", "齐齐哈尔", "齐齐哈尔", "黑龙江"},
{"101050301", "牡丹江", "牡丹江", "黑龙江"},
{"101050401", "佳木斯", "佳木斯", "黑龙江"},
{"101050501", "绥化", "绥化", "黑龙江"},
{"101050601", "黑河", "黑河", "黑龙江"},
{"101050701", "大兴安岭地区", "大兴安岭地区", "黑龙江"},
{"101050801", "伊春", "伊春", "黑龙江"},
{"101050901", "大庆", "大庆", "黑龙江"},
{"101051001", "七台河", "七台河", "黑龙江"},
{"101051101", "鸡西", "鸡西", "黑龙江"},
{"101051201", "鹤岗", "鹤岗", "黑龙江"},
{"101051301", "双鸭山", "双鸭山", "黑龙江"},
{"101190101", "南京", "南京", "江苏"},
{"101190201", "无锡", "无锡", "江苏"},
{"101190301", "镇江", "镇江", "江苏"},
{"101190401", "苏州", "苏州", "江苏"},
{"101190501", "南通", "南通", "江苏"},
{"101190601", "扬州", "扬州", "江苏"},
{"101190701", "盐城", "盐城", "江苏"},
{"101190801", "徐州", "徐州", "江苏"},
{"101190901", "淮安", "淮安", "江苏"},
{"101191001", "连云港", "连云港", "江苏"},
{"101191101", "常州", "常州", "江苏"},
{"101191201", "泰州", "泰州", "江苏"},
{"101191301", "宿迁", "宿迁", "江苏"},
{"101210101", "杭州", "杭州", "浙江"},
{"101210201", "湖州", "湖州", "浙江"},
{"101210301", "嘉兴", "嘉兴", "浙江"},
{"101210401", "宁波", "宁波", "浙江"},
{"101210501", "绍兴", "绍兴", "浙江"},
{"101210601", "台州", "台州", "浙江"},
{"101210701", "温州", "温州", "浙江"},
{"101210801", "丽水", "丽水", "浙江"},
{"101210901", "金华", "金华", "浙江"},
{"101211001", "衢州", "衢州", "浙江"},
{"101211101", "舟山", "舟山", "浙江"},
{"101220101", "合肥", "合肥", "安徽"},
{"101220201", "蚌埠", "蚌埠", "安徽"},
{"101220301", "芜湖", "芜湖", "安徽"},
{"101220401", "淮南", "淮南", "安徽"},
{"101220501", "马鞍山", "马鞍山", "安徽"},
{"101220601", "安庆", "安庆", "安徽"},
{"101220701", "宿州", "宿州", "安徽"},
{"101220801", "阜阳", "阜阳", "安徽"},
{"101220901", "亳州", "亳州", "安徽"},
{"101221001", "黄山", "黄山", "安徽"},
{"101221101", "滁州", "滁州", "安徽"},
{"101221201", "淮北", "淮北", "安徽"},
{"101221301", "铜陵", "铜陵", "安徽"},
{"101221401", "宣城", "宣城", "安徽"},
{"101221501", "六安", "六安", "安徽"},
{"101221701", "池州", "池州", "安徽"},
{"101230101", "福州", "福州", "福建"},
{"101230201", "厦门", "厦门", "福建"},
{"101230301", "宁德", "宁德", "福建"},
{"101230401", "莆田", "莆田", "福建"},
{"101230501", "泉州", "泉州", "福建"},
{"101230601", "漳州", "漳州", "福建"},
{"101230701", "龙岩", "龙岩", "福建"},
{"101230801", "三明", "三明", "福建"},
{"101230901", "南平", "南平", "福建"},
{"101240101", "南昌", "南昌", "江西"},
{"101240201", "九江", "九江", "江西"},
{"101240301", "上饶", "上饶", "江西"},
{"101240401", "抚州", "抚州", "江西"},
{"101240501", "宜春", "宜春", "江西"},
{"101240601", "吉安", "吉安", "江西"},
{"101240701", "赣州", "赣州", "江西"},
{"101240801", "景德镇", "景德镇", "江西"},
{"101240901", "萍乡", "萍乡", "江西"},
{"101241001", "新余", "新余", "江西"},
{"101241101", "鹰潭", "鹰潭", "江西"},
{"101120101", "济南", "济南", "山东"},
{"101120201", "青岛", "青岛", "山东"},
{"101120301", "淄博", "淄博", "山东"},
{"101120401", "德州", "德州", "山东"},
{"101120501", "烟台", "烟台", "山东"},
{"101120601", "潍坊", "潍坊", "山东"},
{"101120701", "济宁", "济宁", "山东"},
{"101120801", "泰安", "泰安", "山东"},
{"101120901", "临沂", "临沂", "山东"},
{"101121001", "菏泽", "菏泽", "山东"},
{"101121101", "滨州", "滨州", "山东"},
{"101121201", "东营", "东营", "山东"},
{"101121301", "威海", "威海", "山东"},
{"101121401", "枣庄", "枣庄", "山东"},
{"101121501", "日照", "日照", "山东"},
{"101121701", "聊城", "聊城", "山东"},
{"101180101", "郑州", "郑州", "河南"},
{"101180201", "安阳", "安阳", "河南"},
{"101180301", "新乡", "新乡", "河南"},
{"101180401", "许昌", "许昌", "河南"},
{"101180501", "平顶山", "平顶山", "河南"},
{"101180601", "信阳", "信阳", "河南"},
{"101180701", "南阳", "南阳", "河南"},
{"101180801", "开封", "开封", "河南"},
{"101180901", "洛阳", "洛阳", "河南"},
{"101181001", "商丘", "商丘", "河南"},
{"101181101", "焦作", "焦作", "河南"},
{"101181201", "鹤壁", "鹤壁", "河南"},
{"101181301", "濮阳", "濮阳", "河南"},
{"101181401", "周口", "周口", "河南"},
{"101181501", "漯河", "漯河", "河南"},
{"101181601", "驻马店", "驻马店", "河南"},
{"101181701", "三门峡", "三门峡", "河南"},
{"101181801", "济源", "济源", "河南"},
{"101200101", "武汉", "武汉", "湖北"},
{"101200201", "襄阳", "襄阳", "湖北"},
{"101200301", "鄂州", "鄂州", "湖北"},
{"101200401", "孝感", "孝感", "湖北"},
{"101200501", "黄冈", "黄冈", "湖北"},
{"101200601", "黄石", "黄石", "湖北"},
{"101200701", "咸宁", "咸宁", "湖北"},
{"101200801", "荆州", "荆州", "湖北"},
{"101200901", "宜昌", "宜昌", "湖北"},
{"101201001", "恩施土家族苗族自治州", "恩施土家族苗族自治州", "湖北"},
{"101201101", "十堰", "十堰", "湖北"},
{"101201201", "神农架林区", "神农架林区", "湖北"},
{"101201301", "随州", "随州", "湖北"},
{"101201401", "荆门", "荆门", "湖北"},
{"101201501", "天门", "天门", "湖北"},
{"101201601", "仙桃", "仙桃", "湖北"},
{"101201701", "潜江", "潜江", "湖北"},
{"101250101", "长沙", "长沙", "湖南"},
{"101250201", "湘潭", "湘潭", "湖南"},
{"101250301", "株洲", "株洲", "湖南"},
{"101250401", "衡阳", "衡阳", "湖南"},
{"101250501", "郴州", "郴州", "湖南"},
{"101250601", "常德", "常德", "湖南"},
{"101250700", "益阳", "益阳", "湖南"},
{"101250801", "娄底", "娄底", "湖南"},
{"101250901", "邵阳", "邵阳", "湖南"},
{"101251001", "岳阳", "岳阳", "湖南"},
{"101251101", "张家界", "张家界", "湖南"},
{"101251201", "怀化", "怀化", "湖南"},
{"101251401", "永州", "永州", "湖南"},
{"101251501", "湘西土家族苗族自治州", "湘西土家族苗族自治州", "湖南"},
{"101280101", "广州", "广州", "广东"},
{"101280201", "韶关", "韶关", "广东"},
{"101280301", "惠州", "惠州", "广东"},
{"101280401", "梅州", "梅州", "广东"},
{"101280501", "汕头", "汕头", "广东"},
{"101280601", "深圳", "深圳", "广东"},
{"101280701", "珠海", "珠海", "广东"},
{"101280800", "佛山", "佛山", "广东"},
{"101280901", "肇庆", "肇庆", "广东"},
{"101281001", "湛江", "湛江", "广东"},
{"101281101", "江门", "江门", "广东"},
{"101281201", "河源", "河源", "广东"},
{"101281301", "清远", "清远", "广东"},
{"101281401", "云浮", "云浮", "广东"},
{"101281501", "潮州", "潮州", "广东"},
{"101281601", "东莞", "东莞", "广东"},
{"101281701", "中山", "中山", "广东"},
{"101281801", "阳江", "阳江", "广东"},
{"101281901", "揭阳", "揭阳", "广东"},
{"101282001", "茂名", "茂名", "广东"},
{"101282101", "汕尾", "汕尾", "广东"},
{"101310101", "海口", "海口", "海南"},
{"101310201", "三亚", "三亚", "海南"},
{"101310202", "东方", "东方", "海南"},
{"101310203", "临高县", "临高县", "海南"},
{"101310204", "澄迈县", "澄迈县", "海南"},
{"101310205", "儋州", "儋州", "海南"},
{"101310206", "昌江黎族自治县", "昌江黎族自治县", "海南"},
{"101310207", "白沙黎族自治县", "白沙黎族自治县", "海南"},
{"101310208", "琼中黎族苗族自治县", "琼中黎族苗族自治县", "海南"},
{"101310209", "定安县", "定安县", "海南"},
{"101310210", "屯昌县", "屯昌县", "海南"},
{"101310211", "琼海", "琼海", "海南"},
{"101310212", "文昌", "文昌", "海南"},
{"101310214", "保亭黎族苗族自治县", "保亭黎族苗族自治县", "海南"},
{"101310215", "万宁", "万宁", "海南"},
{"101310216", "陵水黎族自治县", "陵水黎族自治县", "海南"},
{"101310221", "乐东黎族自治县", "乐东黎族自治县", "海南"},
{"101310222", "五指山", "五指山", "海南"},
{"101310301", "三沙", "三沙", "海南"},
{"101270101", "成都", "成都", "四川"},
{"101270201", "攀枝花", "攀枝花", "四川"},
{"101270301", "自贡", "自贡", "四川"},
{"101270401", "绵阳", "绵阳", "四川"},
{"101270501", "南充", "南充", "四川"},
{"101270601", "达州", "达州", "四川"},
{"101270701", "遂宁", "遂宁", "四川"},
{"101270801", "广安", "广安", "四川"},
{"101270901", "巴中", "巴中", "四川"},
{"101271001", "泸州", "泸州", "四川"},
{"101271101", "宜宾", "宜宾", "四川"},
{"101271201", "内江", "内江", "四川"},
{"101271301", "资阳", "资阳", "四川"},
{"101271401", "乐山", "乐山", "四川"},
{"101271501", "眉山", "眉山", "四川"},
{"101271601", "凉山彝族自治州", "凉山彝族自治州", "四川"},
{"101271701", "雅安", "雅安", "四川"},
{"101271801", "甘孜藏族自治州", "甘孜藏族自治州", "四川"},
{"101271901", "阿坝藏族羌族自治州", "阿坝藏族羌族自治州", "四川"},
{"101272001", "德阳", "德阳", "四川"},
{"101272101", "广元", "广元", "四川"},
{"101260101", "贵阳", "贵阳", "贵州"}, {"101260101", "贵阳", "贵阳", "贵州"},
{"101260201", "遵义", "遵义", "贵州"},
{"101260301", "安顺", "安顺", "贵州"},
{"101260401", "黔南布依族苗族自治州", "黔南布依族苗族自治州", "贵州"},
{"101260501", "黔东南苗族侗族自治州", "黔东南苗族侗族自治州", "贵州"},
{"101260601", "铜仁", "铜仁", "贵州"},
{"101260701", "毕节", "毕节", "贵州"},
{"101260801", "六盘水", "六盘水", "贵州"},
{"101260901", "黔西南布依族苗族自治州", "黔西南布依族苗族自治州", "贵州"},
{"101290101", "昆明", "昆明", "云南"},
{"101290201", "大理白族自治州", "大理白族自治州", "云南"},
{"101290301", "红河哈尼族彝族自治州", "红河哈尼族彝族自治州", "云南"},
{"101290401", "曲靖", "曲靖", "云南"},
{"101290501", "保山", "保山", "云南"},
{"101290601", "文山壮族苗族自治州", "文山壮族苗族自治州", "云南"},
{"101290701", "玉溪", "玉溪", "云南"},
{"101290801", "楚雄彝族自治州", "楚雄彝族自治州", "云南"},
{"101290901", "普洱", "普洱", "云南"},
{"101291001", "昭通", "昭通", "云南"},
{"101291101", "临沧", "临沧", "云南"},
{"101291201", "怒江傈僳族自治州", "怒江傈僳族自治州", "云南"},
{"101291301", "迪庆藏族自治州", "迪庆藏族自治州", "云南"},
{"101291401", "丽江", "丽江", "云南"},
{"101291501", "德宏傣族景颇族自治州", "德宏傣族景颇族自治州", "云南"},
{"101291601", "西双版纳傣族自治州", "西双版纳傣族自治州", "云南"},
{"101110101", "西安", "西安", "陕西"},
{"101110200", "咸阳", "咸阳", "陕西"},
{"101110300", "延安", "延安", "陕西"},
{"101110401", "榆林", "榆林", "陕西"},
{"101110501", "渭南", "渭南", "陕西"},
{"101110601", "商洛", "商洛", "陕西"},
{"101110701", "安康", "安康", "陕西"},
{"101110801", "汉中", "汉中", "陕西"},
{"101110901", "宝鸡", "宝鸡", "陕西"},
{"101111001", "铜川", "铜川", "陕西"},
{"101160101", "兰州", "兰州", "甘肃"},
{"101160201", "定西", "定西", "甘肃"},
{"101160301", "平凉", "平凉", "甘肃"},
{"101160401", "庆阳", "庆阳", "甘肃"},
{"101160501", "武威", "武威", "甘肃"},
{"101160601", "金昌", "金昌", "甘肃"},
{"101160701", "张掖", "张掖", "甘肃"},
{"101160801", "酒泉", "酒泉", "甘肃"},
{"101160901", "天水", "天水", "甘肃"},
{"101161001", "陇南", "陇南", "甘肃"},
{"101161101", "临夏回族自治州", "临夏回族自治州", "甘肃"},
{"101161201", "甘南藏族自治州", "甘南藏族自治州", "甘肃"},
{"101161301", "白银", "白银", "甘肃"},
{"101161401", "嘉峪关", "嘉峪关", "甘肃"},
{"101150101", "西宁", "西宁", "青海"},
{"101150201", "海东", "海东", "青海"},
{"101150301", "黄南藏族自治州", "黄南藏族自治州", "青海"},
{"101150401", "海南藏族自治州", "海南藏族自治州", "青海"},
{"101150501", "果洛藏族自治州", "果洛藏族自治州", "青海"},
{"101150601", "玉树藏族自治州", "玉树藏族自治州", "青海"},
{"101150701", "海西蒙古族藏族自治州", "海西蒙古族藏族自治州", "青海"},
{"101150801", "海北藏族自治州", "海北藏族自治州", "青海"},
{"101080101", "呼和浩特", "呼和浩特", "内蒙古"},
{"101080201", "包头", "包头", "内蒙古"},
{"101080301", "乌海", "乌海", "内蒙古"},
{"101080401", "乌兰察布", "乌兰察布", "内蒙古"},
{"101080501", "通辽", "通辽", "内蒙古"},
{"101080601", "赤峰", "赤峰", "内蒙古"},
{"101080701", "鄂尔多斯", "鄂尔多斯", "内蒙古"},
{"101080801", "巴彦淖尔", "巴彦淖尔", "内蒙古"},
{"101080901", "锡林郭勒盟", "锡林郭勒盟", "内蒙古"},
{"101081001", "呼伦贝尔", "呼伦贝尔", "内蒙古"},
{"101081101", "兴安盟", "兴安盟", "内蒙古"},
{"101081201", "阿拉善盟", "阿拉善盟", "内蒙古"},
{"101300101", "南宁", "南宁", "广西"},
{"101300201", "崇左", "崇左", "广西"},
{"101300301", "柳州", "柳州", "广西"},
{"101300401", "来宾", "来宾", "广西"},
{"101300501", "桂林", "桂林", "广西"},
{"101300601", "梧州", "梧州", "广西"},
{"101300701", "贺州", "贺州", "广西"},
{"101300801", "贵港", "贵港", "广西"},
{"101300901", "玉林", "玉林", "广西"},
{"101301001", "百色", "百色", "广西"},
{"101301101", "钦州", "钦州", "广西"},
{"101301201", "河池", "河池", "广西"},
{"101301301", "北海", "北海", "广西"},
{"101301401", "防城港", "防城港", "广西"},
{"101140101", "拉萨", "拉萨", "西藏"},
{"101140201", "日喀则", "日喀则", "西藏"},
{"101140301", "山南", "山南", "西藏"},
{"101140401", "林芝", "林芝", "西藏"},
{"101140501", "昌都", "昌都", "西藏"},
{"101140601", "那曲", "那曲", "西藏"},
{"101140701", "阿里地区", "阿里地区", "西藏"},
{"101170101", "银川", "银川", "宁夏"},
{"101170201", "石嘴山", "石嘴山", "宁夏"},
{"101170301", "吴忠", "吴忠", "宁夏"},
{"101170401", "固原", "固原", "宁夏"},
{"101170501", "中卫", "中卫", "宁夏"},
{"101130101", "乌鲁木齐", "乌鲁木齐", "新疆"},
{"101130201", "克拉玛依", "克拉玛依", "新疆"},
{"101130301", "石河子", "石河子", "新疆"},
{"101130401", "昌吉回族自治州", "昌吉回族自治州", "新疆"},
{"101130501", "吐鲁番", "吐鲁番", "新疆"},
{"101130601", "巴音郭楞蒙古自治州", "巴音郭楞蒙古自治州", "新疆"},
{"101130701", "阿拉尔", "阿拉尔", "新疆"},
{"101130801", "阿克苏地区", "阿克苏地区", "新疆"},
{"101130901", "喀什地区", "喀什地区", "新疆"},
{"101131001", "伊犁哈萨克自治州", "伊犁哈萨克自治州", "新疆"},
{"101131101", "塔城地区", "塔城地区", "新疆"},
{"101131201", "哈密", "哈密", "新疆"},
{"101131301", "和田地区", "和田地区", "新疆"},
{"101131401", "阿勒泰地区", "阿勒泰地区", "新疆"},
{"101131501", "克孜勒苏柯尔克孜自治州", "克孜勒苏柯尔克孜自治州", "新疆"},
{"101131601", "博尔塔拉蒙古自治州", "博尔塔拉蒙古自治州", "新疆"},
{"101131701", "图木舒克", "图木舒克", "新疆"},
{"101131801", "五家渠", "五家渠", "新疆"},
{"101131901", "铁门关", "铁门关", "新疆"},
{"101131920", "昆玉", "昆玉", "新疆"},
{"101132101", "北屯", "北屯", "新疆"},
{"101132201", "双河", "双河", "新疆"},
{"101132301", "可克达拉", "可克达拉", "新疆"},
{"101132401", "胡杨河", "胡杨河", "新疆"},
{"101132501", "新星", "新星", "新疆"},
{"101132601", "白杨", "白杨", "新疆"},
{"101320101", "香港特别行政区", "香港特别行政区", "香港"},
{"101330101", "澳门特别行政区", "澳门特别行政区", "澳门"},
{"101231001", "宜兰县", "宜兰县", "台湾"},
{"101340101", "台北", "台北", "台湾"},
{"101340102", "桃园", "桃园", "台湾"},
{"101340103", "新竹", "新竹", "台湾"},
{"101340201", "高雄", "高雄", "台湾"},
{"101340202", "嘉义", "嘉义", "台湾"},
{"101340203", "台南", "台南", "台湾"},
{"101340204", "台东县", "台东县", "台湾"},
{"101340205", "屏东县", "屏东县", "台湾"},
{"101340401", "台中", "台中", "台湾"},
{"101340402", "苗栗县", "苗栗县", "台湾"},
{"101340403", "彰化县", "彰化县", "台湾"},
{"101340404", "南投县", "南投县", "台湾"},
{"101340405", "花莲县", "花莲县", "台湾"},
} }
var defaultCity = City{"101200101", "武汉", "武汉", "湖北"} var defaultCity = City{"101200101", "武汉", "武汉", "湖北"}
@@ -84,18 +84,32 @@
<img v-if="bing.url" :src="bingThumb" class="w-16 h-10 object-cover rounded flex-shrink-0"> <img v-if="bing.url" :src="bingThumb" class="w-16 h-10 object-cover rounded flex-shrink-0">
<div class="min-w-0"> <div class="min-w-0">
<div class="text-xs font-medium text-[var(--text-muted)] truncate">{{ bing.copyright || 'Bing 每日壁纸' }}</div> <div class="text-xs font-medium text-[var(--text-muted)] truncate">{{ bing.copyright || 'Bing 每日壁纸' }}</div>
<div class="text-[10px] text-[var(--text-weak)]">{{ bing.idx > 0 ? `${bing.idx} / ${bing.total}` : '' }}</div> <div class="text-[10px] text-[var(--text-weak)]">{{ bing.total > 0 ? `${bing.idx + 1} / ${bing.total}` : '' }}</div>
</div> </div>
</div> </div>
<div class="flex gap-1 flex-shrink-0"> <div class="flex gap-1 flex-shrink-0">
<button class="btn-sm" @click="onBingPrev">&#9664;</button> <button class="btn-sm" @click="onBingPrev">&#9664;</button>
<button class="btn-sm" @click="onBingNext">&#9654;</button> <button class="btn-sm" @click="onBingNext">&#9654;</button>
<button class="btn-sm" @click="onBingFav">{{ bing.fav ? '★' : '☆' }}</button> <button class="btn-sm" @click="onBingFav">{{ bing.fav ? '★' : '☆' }}</button>
<button class="btn-sm" @click="onBingDislike">👎</button>
</div> </div>
</div> </div>
<div class="flex justify-between items-center px-3.5 py-2 border-t border-[var(--card-divider)]"> <div class="flex justify-between items-center px-3.5 py-2 border-t border-[var(--card-divider)]">
<div><div class="text-xs font-medium text-[var(--text-muted)]">定时切换</div><div class="text-[10px] text-[var(--text-weak)] mt-px">每小时自动切换壁纸</div></div> <div><div class="text-xs font-medium text-[var(--text-muted)]">定时切换</div><div class="text-[10px] text-[var(--text-weak)] mt-px">每小时自动切换壁纸</div></div>
<ToggleSwitch v-model="s.bingAutoRefresh" /> <ToggleSwitch v-model="s.bingAutoRefresh" />
</div>
<!-- 市场选择 -->
<div class="px-3.5 py-2 border-t border-[var(--card-divider)]">
<div class="flex justify-between items-center cursor-pointer select-none" @click="showMarkets = !showMarkets">
<div class="text-xs font-medium text-[var(--text-muted)]">壁纸来源地区</div>
<span class="text-[10px] text-[var(--text-weak)]">{{ showMarkets ? '收起' : '展开' }} {{ selectedMarkets.length }}/{{ allMarkets.length }}</span>
</div>
<div v-if="showMarkets" class="flex flex-wrap gap-2 mt-2">
<label v-for="m in allMarkets" :key="m.code" class="flex items-center gap-1.5 text-xs text-[var(--text)] cursor-pointer">
<input type="checkbox" :checked="selectedMarkets.includes(m.code)" @change="toggleMarket(m.code)" class="accent-[var(--accent)]">
{{ m.label }}
</label>
</div>
</div> </div>
<!-- 收藏列表 --> <!-- 收藏列表 -->
<div v-if="bingFavs.length" class="px-3.5 py-2 border-t border-[var(--card-divider)]"> <div v-if="bingFavs.length" class="px-3.5 py-2 border-t border-[var(--card-divider)]">
@@ -185,9 +199,12 @@ const audioColors = [
] ]
// Bing // Bing
const bing = ref<BingState>({ date: '', title: '', copyright: '', url: '', fav: false, idx: 0, total: 0 }) const bing = ref<BingState>({ date: '', title: '', copyright: '', url: '', fav: false, idx: 0, total: 0, hidden: false, market: '' })
const bingThumb = ref('') const bingThumb = ref('')
const bingFavs = ref<{ idx: number; thumb: string }[]>([]) const bingFavs = ref<{ idx: number; thumb: string }[]>([])
const showMarkets = ref(false)
const allMarkets = ref<{ code: string; label: string }[]>([])
const selectedMarkets = ref<string[]>([])
async function loadBingInfo() { async function loadBingInfo() {
const raw = await go.getBingInfo() const raw = await go.getBingInfo()
@@ -199,23 +216,53 @@ async function loadBingInfo() {
} }
} }
async function onBingPrev() { bing.value = JSON.parse(await go.bingPrev()) } async function loadBingMarkets() {
const raw = await go.getBingMarkets()
const codes = JSON.parse(raw) as string[]
selectedMarkets.value = codes
allMarkets.value = [
{ code: 'zh-CN', label: '中国' },
{ code: 'en-US', label: '美国' },
{ code: 'ja-JP', label: '日本' },
{ code: 'de-DE', label: '德国' },
{ code: 'fr-FR', label: '法国' },
{ code: 'en-GB', label: '英国' },
{ code: 'ko-KR', label: '韩国' },
{ code: 'ru-RU', label: '俄罗斯' },
{ code: 'pt-BR', label: '巴西' },
{ code: 'es-ES', label: '西班牙' },
]
}
function toggleMarket(code: string) {
const idx = selectedMarkets.value.indexOf(code)
if (idx >= 0) {
selectedMarkets.value.splice(idx, 1)
} else {
selectedMarkets.value.push(code)
}
go.saveBingMarkets(JSON.stringify(selectedMarkets.value))
}
// 左 ◄ = 上一张(更旧), 右 ► = 下一张(更新)
async function onBingNext() { bing.value = JSON.parse(await go.bingNext()) } async function onBingNext() { bing.value = JSON.parse(await go.bingNext()) }
async function onBingPrev() { bing.value = JSON.parse(await go.bingPrev()) }
async function onBingFav() { bing.value = JSON.parse(await go.bingToggleFavorite()); await loadBingFavs() } async function onBingFav() { bing.value = JSON.parse(await go.bingToggleFavorite()); await loadBingFavs() }
async function onBingSetByIdx(idx: number) { bing.value = JSON.parse(await go.bingSetByIdx(idx)) } async function onBingSetByIdx(idx: number) { bing.value = JSON.parse(await go.bingSetByIdx(idx)) }
async function onBingDislike() { bing.value = JSON.parse(await go.bingToggleDislike()) }
async function loadBingFavs() { async function loadBingFavs() {
const raw = await go.getBingFavorites() const raw = await go.getBingFavorites()
const list = JSON.parse(raw) as string[] const list = JSON.parse(raw) as { date: string; copyright: string; filename: string; idx: number }[]
bingFavs.value = await Promise.all(list.map(async f => ({ bingFavs.value = await Promise.all(list.map(async f => ({
idx: parseInt(f.match(/(\d+)/)?.[1] || '0', 10), idx: f.idx,
thumb: await go.bingThumbDataURI(f) thumb: await go.bingThumbDataURI(f.filename)
}))) })))
} }
watch(() => props.s.bingAutoRefresh, v => go.saveBingAutoRefresh(v)) watch(() => props.s.bingAutoRefresh, v => go.saveBingAutoRefresh(v))
watch(() => props.s.wallpaperType, t => { watch(() => props.s.wallpaperType, t => {
if (t === 'bing') { loadBingInfo(); loadBingFavs() } if (t === 'bing') { loadBingInfo(); loadBingFavs(); loadBingMarkets() }
}, { immediate: true }) }, { immediate: true })
async function onPickImage() { async function onPickImage() {
@@ -17,6 +17,9 @@ export const go = {
getBingFavorites: (): Promise<string> => w.getBingFavorites(), getBingFavorites: (): Promise<string> => w.getBingFavorites(),
bingSetByIdx: (idx: number): Promise<string> => w.bingSetByIdx(idx), bingSetByIdx: (idx: number): Promise<string> => w.bingSetByIdx(idx),
bingThumbDataURI: (filename: string): Promise<string> => w.bingThumbDataURI(filename), bingThumbDataURI: (filename: string): Promise<string> => w.bingThumbDataURI(filename),
bingToggleDislike: (): Promise<string> => w.bingToggleDislike(),
getBingMarkets: (): Promise<string> => w.getBingMarkets(),
saveBingMarkets: (markets: string) => w.saveBingMarkets(markets),
pickSolidColor: (): Promise<string> => w.pickSolidColor(), pickSolidColor: (): Promise<string> => w.pickSolidColor(),
pickGradientColor: (): Promise<string> => w.pickGradientColor(), pickGradientColor: (): Promise<string> => w.pickGradientColor(),
addSavedColor: (c1: string, c2: string, gradient: boolean) => w.addSavedColor(c1, c2, gradient), addSavedColor: (c1: string, c2: string, gradient: boolean) => w.addSavedColor(c1, c2, gradient),
+3
View File
@@ -1,5 +1,6 @@
export interface WeatherData { export interface WeatherData {
current: string current: string
city: string
hourly: HourlyItem[] hourly: HourlyItem[]
daily: DailyItem[] daily: DailyItem[]
} }
@@ -95,4 +96,6 @@ export interface BingState {
fav: boolean fav: boolean
idx: number idx: number
total: number total: number
hidden: boolean
market?: string
} }
+2 -2
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
File diff suppressed because one or more lines are too long