新增: 自动升级体系 + WorkerW重建检测 + 知识卡片优化
自动升级: - 版本变量注入 (-ldflags -X main.version) - 远程 version.json 检查 + 弹窗提示(非强制右下角/强制居中) - updater.exe 嵌入主程序, 运行时释放到临时目录 - 下载+SHA256校验+备份+替换+回滚 全流程 - 托盘菜单"检查更新" 稳定性: - WorkerW 重建自动检测(每10s)并重新嵌入 - 左键托盘点击防抖 - 设置窗口已打开时正确前置 优化: - 知识卡片prompt改为面向开发者的硬核内容 - 配置目录统一到 ~/.u-desktop/ - 设置窗口WebView2数据目录改为configDir下
This commit is contained in:
@@ -4,28 +4,41 @@ function parseArg(data: any) {
|
||||
return typeof data === 'string' ? JSON.parse(data) : data
|
||||
}
|
||||
|
||||
// JSON 去重:相同数据不触发 Vue 重渲染
|
||||
const lastJSON = new Map<string, string>()
|
||||
function shouldUpdate(key: string, data: any): boolean {
|
||||
const json = JSON.stringify(data)
|
||||
if (lastJSON.get(key) === json) return false
|
||||
lastJSON.set(key, json)
|
||||
return true
|
||||
}
|
||||
|
||||
export function registerGoBridge() {
|
||||
const w = window as any
|
||||
|
||||
w.updateHoroscopeFromGo = (data: any) => {
|
||||
state.horoscope = parseArg(data)
|
||||
const parsed = parseArg(data)
|
||||
if (shouldUpdate('horoscope', parsed)) state.horoscope = parsed
|
||||
}
|
||||
|
||||
w.updateAINewsFromGo = (items: any) => {
|
||||
const parsed = parseArg(items)
|
||||
if (parsed?.length) state.aiNews = parsed
|
||||
if (parsed?.length && shouldUpdate('ainews', parsed)) state.aiNews = parsed
|
||||
}
|
||||
|
||||
w.updateKnowledgeFromGo = (data: any) => {
|
||||
state.knowledge = parseArg(data)
|
||||
const parsed = parseArg(data)
|
||||
if (shouldUpdate('knowledge', parsed)) state.knowledge = parsed
|
||||
}
|
||||
|
||||
w.updateWeatherFromGo = (data: any) => {
|
||||
state.weather = parseArg(data)
|
||||
const parsed = parseArg(data)
|
||||
if (shouldUpdate('weather', parsed)) state.weather = parsed
|
||||
}
|
||||
|
||||
w.updatePhotoFromGo = (data: any) => {
|
||||
state.photo = parseArg(data)
|
||||
const parsed = parseArg(data)
|
||||
if (parsed?.src && shouldUpdate('photo', parsed)) state.photo = parsed
|
||||
}
|
||||
|
||||
w.setCardVisible = (card: string, visible: boolean) => {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="knowledge-card">
|
||||
<div class="knowledge-header">
|
||||
💡 知识卡片
|
||||
<span class="knowledge-label">知识卡片</span>
|
||||
<span class="knowledge-keyword-tag" v-if="state.knowledge.keyword">#{{ state.knowledge.keyword }}</span>
|
||||
</div>
|
||||
<div class="knowledge-content">{{ state.knowledge.content || '请设置知识关键字' }}</div>
|
||||
<div class="knowledge-divider"></div>
|
||||
<div class="knowledge-body" v-if="state.knowledge.content">
|
||||
<span class="knowledge-quote">"</span>
|
||||
<span class="knowledge-text">{{ state.knowledge.content }}</span>
|
||||
</div>
|
||||
<div class="knowledge-placeholder" v-else>请在设置中配置知识关键字</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,19 +18,49 @@ import { state } from '../composables/useOverlayState'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.knowledge-card {
|
||||
display: flex; flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.knowledge-header {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.knowledge-label {
|
||||
font-size: 11px; color: var(--text-faint);
|
||||
margin-bottom: 10px; display: flex;
|
||||
align-items: center; gap: 6px;
|
||||
letter-spacing: 0; font-weight: 700;
|
||||
font-weight: 700; letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.knowledge-keyword-tag {
|
||||
background: rgba(255,255,255,0.09);
|
||||
padding: 3px 8px; border-radius: 6px;
|
||||
font-size: 10px; color: var(--text-soft);
|
||||
background: rgba(255,216,107,0.12);
|
||||
color: var(--accent-warm);
|
||||
padding: 2px 9px; border-radius: 4px;
|
||||
font-size: 10px; font-weight: 600;
|
||||
border: 1px solid rgba(255,216,107,0.15);
|
||||
}
|
||||
.knowledge-content {
|
||||
font-size: 16px; color: var(--text-main);
|
||||
line-height: 1.55; text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
.knowledge-divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, var(--accent-warm), rgba(255,216,107,0.08), transparent);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.knowledge-body {
|
||||
position: relative;
|
||||
padding-left: 4px;
|
||||
}
|
||||
.knowledge-quote {
|
||||
position: absolute; top: -10px; left: -6px;
|
||||
font-size: 52px; line-height: 1;
|
||||
color: var(--accent-warm); opacity: 0.18;
|
||||
font-family: Georgia, serif; pointer-events: none;
|
||||
}
|
||||
.knowledge-text {
|
||||
font-size: 15px; color: var(--text-main);
|
||||
line-height: 1.7; text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
display: -webkit-box; -webkit-line-clamp: 8;
|
||||
-webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.knowledge-placeholder {
|
||||
font-size: 13px; color: var(--text-faint);
|
||||
font-style: italic; padding-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -68,10 +68,7 @@ import PhotoCard from './PhotoCard.vue'
|
||||
width: 100%; min-width: 0; text-align: right;
|
||||
padding: 22px 28px 24px;
|
||||
}
|
||||
#card-knowledge :deep(.knowledge-content) {
|
||||
display: -webkit-box; -webkit-line-clamp: 8;
|
||||
-webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
#card-knowledge { overflow: hidden; }
|
||||
#layout-multi :deep(#card-photo) {
|
||||
grid-area: photo;
|
||||
position: relative; top: auto; left: auto;
|
||||
|
||||
@@ -35,9 +35,7 @@ html, body {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(145deg, rgba(255,255,255,0.075), rgba(255,255,255,0.025)), var(--card-bg);
|
||||
backdrop-filter: blur(28px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.2);
|
||||
background: linear-gradient(145deg, rgba(255,255,255,0.075), rgba(255,255,255,0.025)), var(--card-bg-strong);
|
||||
border: 1px solid var(--card-line);
|
||||
border-radius: 8px;
|
||||
padding: 22px 24px;
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
</div>
|
||||
<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">AI 将根据关键字生成知识小卡片</div></div>
|
||||
<input type="text" v-model="s.knowledgeKeyword" @input="debounced(go.saveKnowledgeKeyword, s.knowledgeKeyword)"
|
||||
<input type="text" v-model="s.knowledgeKeyword" @input="debounced('kw', go.saveKnowledgeKeyword, s.knowledgeKeyword)"
|
||||
placeholder="如: 历史、科学、冷知识"
|
||||
class="bg-[var(--input-bg)] border border-[var(--input-border)] rounded-md text-[var(--text)] text-[11px] py-1 px-2 outline-none w-[140px] focus:border-[var(--input-border-focus)]">
|
||||
</div>
|
||||
<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>
|
||||
<input type="text" v-model="s.knowledgePrompt" @input="debounced(go.saveKnowledgePrompt, s.knowledgePrompt)"
|
||||
<input type="text" v-model="s.knowledgePrompt" @input="debounced('kp', go.saveKnowledgePrompt, s.knowledgePrompt)"
|
||||
placeholder="如: 偏实践、给出判断标准"
|
||||
class="bg-[var(--input-bg)] border border-[var(--input-border)] rounded-md text-[var(--text)] text-[11px] py-1 px-2 outline-none w-[140px] focus:border-[var(--input-border-focus)]">
|
||||
</div>
|
||||
@@ -76,12 +76,10 @@ function onDocClick(e: MouseEvent) {
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onDocClick)
|
||||
// Find initial city
|
||||
for (const p of Object.keys(props.s.citiesByProv)) {
|
||||
for (const c of (props.s.citiesByProv as any)[p]) {
|
||||
if (c.id === props.s.city) { activeProv.value = p; selectedCityName.value = c.name; break }
|
||||
}
|
||||
if (activeProv.value) break
|
||||
const entries = Object.entries(props.s.citiesByProv as Record<string, { id: string; name: string }[]>)
|
||||
for (const [p, cs] of entries) {
|
||||
const found = cs.find(c => c.id === props.s.city)
|
||||
if (found) { activeProv.value = p; selectedCityName.value = found.name; break }
|
||||
}
|
||||
})
|
||||
onUnmounted(() => document.removeEventListener('click', onDocClick))
|
||||
|
||||
@@ -29,12 +29,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue'
|
||||
import { go } from '../composables/useGoBridge'
|
||||
import ToggleSwitch from './ToggleSwitch.vue'
|
||||
import type { SettingsData } from '@shared/types'
|
||||
|
||||
const props = defineProps<{ s: SettingsData }>()
|
||||
|
||||
watch(
|
||||
() => [props.s.photoCard, props.s.photoFrameMode] as [boolean, boolean],
|
||||
([photoCard, photoFrameMode]) => {
|
||||
go.saveToggles({ photoCard, photoFrameMode })
|
||||
}
|
||||
)
|
||||
|
||||
async function onPickDir() {
|
||||
const dir = await go.pickPhotoDir()
|
||||
if (dir) props.s.photoDir = dir
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div v-if="s.theme === 'text'" class="flex justify-between items-center px-3.5 py-2 border-t border-[var(--card-divider)]">
|
||||
<div class="text-xs font-medium text-[var(--text-muted)]">自定义文字</div>
|
||||
<input type="text" v-model="s.wallpaperText" @input="debounced(go.saveWallpaperText, s.wallpaperText)"
|
||||
<input type="text" v-model="s.wallpaperText" @input="debounced('wt', go.saveWallpaperText, s.wallpaperText)"
|
||||
placeholder="输入显示文字"
|
||||
class="bg-[var(--input-bg)] border border-[var(--input-border)] rounded-md text-[var(--text)] text-[11px] py-1 px-2 outline-none w-[140px] focus:border-[var(--input-border-focus)]">
|
||||
</div>
|
||||
@@ -135,7 +135,10 @@ async function loadBingInfo() {
|
||||
const raw = await go.getBingInfo()
|
||||
const st = JSON.parse(raw) as BingState
|
||||
bing.value = st
|
||||
if (st.url) bingThumb.value = st.url.replace('_1920x1080', '_400x240')
|
||||
if (st.url) {
|
||||
const t = st.url.replace('_1920x1080', '_400x240')
|
||||
bingThumb.value = t !== st.url ? t : await go.bingThumbDataURI(st.url.split('/').pop() || '')
|
||||
}
|
||||
}
|
||||
|
||||
async function onBingPrev() { bing.value = JSON.parse(await go.bingPrev()) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const timers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
export function debounced(fn: (v: string) => void, val: string, ms = 500) {
|
||||
clearTimeout(timers.get(fn.name))
|
||||
timers.set(fn.name, setTimeout(() => fn(val), ms))
|
||||
export function debounced(key: string, fn: (v: string) => void, val: string, ms = 500) {
|
||||
clearTimeout(timers.get(key))
|
||||
timers.set(key, setTimeout(() => fn(val), ms))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user