2525 <circle v-if="!privacyMode" cx="12" cy="12" r="3" />
2626 </svg>
2727 </div>
28+
29+ <!-- 设置按钮(仅桌面端) -->
30+ <div
31+ v-if="isDesktopEnv"
32+ class="w-16 h-12 flex items-center justify-center cursor-pointer transition-colors text-gray-500 hover:text-gray-700"
33+ @click="openDesktopSettings"
34+ title="设置"
35+ >
36+ <svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37+ <path
38+ stroke-linecap="round"
39+ stroke-linejoin="round"
40+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
41+ />
42+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
43+ </svg>
44+ </div>
2845 </div>
2946 </div>
3047
15431560 </div>
15441561 </div>
15451562 </div>
1563+
1564+ <!-- 桌面端设置弹窗 -->
1565+ <div
1566+ v-if="desktopSettingsOpen"
1567+ class="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
1568+ @click.self="closeDesktopSettings"
1569+ >
1570+ <div class="w-full max-w-md bg-white rounded-lg shadow-lg">
1571+ <div class="px-5 py-4 border-b border-gray-200 flex items-center justify-between">
1572+ <div class="text-base font-medium text-gray-900">设置</div>
1573+ <button
1574+ type="button"
1575+ class="text-gray-500 hover:text-gray-700"
1576+ @click="closeDesktopSettings"
1577+ title="关闭"
1578+ >
1579+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1580+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
1581+ </svg>
1582+ </button>
1583+ </div>
1584+
1585+ <div class="px-5 py-4 space-y-4">
1586+ <div class="flex items-center justify-between gap-4">
1587+ <div class="min-w-0">
1588+ <div class="text-sm font-medium text-gray-900">开机自启动</div>
1589+ <div class="text-xs text-gray-500">系统登录后自动启动桌面端</div>
1590+ </div>
1591+ <input
1592+ type="checkbox"
1593+ class="h-4 w-4"
1594+ :checked="desktopAutoLaunch"
1595+ :disabled="desktopAutoLaunchLoading"
1596+ @change="onDesktopAutoLaunchToggle"
1597+ />
1598+ </div>
1599+ <div v-if="desktopAutoLaunchError" class="text-xs text-red-600 whitespace-pre-wrap">
1600+ {{ desktopAutoLaunchError }}
1601+ </div>
1602+
1603+ <div class="flex items-center justify-between gap-4">
1604+ <div class="min-w-0">
1605+ <div class="text-sm font-medium text-gray-900">启动后自动开启实时获取</div>
1606+ <div class="text-xs text-gray-500">进入聊天页后自动打开“实时开关”(默认关闭)</div>
1607+ </div>
1608+ <input
1609+ type="checkbox"
1610+ class="h-4 w-4"
1611+ :checked="desktopAutoRealtime"
1612+ @change="onDesktopAutoRealtimeToggle"
1613+ />
1614+ </div>
1615+
1616+ <div class="flex items-center justify-between gap-4">
1617+ <div class="min-w-0">
1618+ <div class="text-sm font-medium text-gray-900">有数据时默认进入聊天页</div>
1619+ <div class="text-xs text-gray-500">有已解密账号时,打开应用默认跳转到 /chat(默认关闭)</div>
1620+ </div>
1621+ <input
1622+ type="checkbox"
1623+ class="h-4 w-4"
1624+ :checked="desktopDefaultToChatWhenData"
1625+ @change="onDesktopDefaultToChatToggle"
1626+ />
1627+ </div>
1628+ </div>
1629+
1630+ <div class="px-5 py-4 border-t border-gray-200 flex items-center justify-end gap-2">
1631+ <button
1632+ type="button"
1633+ class="text-sm px-3 py-2 rounded-md bg-white border border-gray-200 hover:bg-gray-50"
1634+ @click="closeDesktopSettings"
1635+ >
1636+ 关闭
1637+ </button>
1638+ </div>
1639+ </div>
1640+ </div>
1641+
15461642 </div>
15471643</template>
15481644
@@ -1607,6 +1703,103 @@ const selectedContact = ref(null)
16071703// 隐私模式
16081704const privacyMode = ref(false)
16091705
1706+ // 桌面端设置(仅 Electron 环境可见)
1707+ const isDesktopEnv = ref(false)
1708+ const desktopSettingsOpen = ref(false)
1709+
1710+ const DESKTOP_SETTING_AUTO_REALTIME_KEY = 'desktop.settings.autoRealtime'
1711+ const DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY = 'desktop.settings.defaultToChatWhenData'
1712+
1713+ const desktopAutoRealtime = ref(false)
1714+ const desktopDefaultToChatWhenData = ref(false)
1715+
1716+ const desktopAutoLaunch = ref(false)
1717+ const desktopAutoLaunchLoading = ref(false)
1718+ const desktopAutoLaunchError = ref('')
1719+
1720+ const readLocalBool = (key) => {
1721+ if (!process.client) return false
1722+ try {
1723+ return localStorage.getItem(key) === 'true'
1724+ } catch {
1725+ return false
1726+ }
1727+ }
1728+
1729+ const writeLocalBool = (key, value) => {
1730+ if (!process.client) return
1731+ try {
1732+ localStorage.setItem(key, value ? 'true' : 'false')
1733+ } catch {}
1734+ }
1735+
1736+ // 尽量早读本地设置,避免首次加载联系人时拿不到 autoRealtime 选项
1737+ if (process.client) {
1738+ desktopAutoRealtime.value = readLocalBool(DESKTOP_SETTING_AUTO_REALTIME_KEY)
1739+ desktopDefaultToChatWhenData.value = readLocalBool(DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY)
1740+ }
1741+
1742+ const refreshDesktopAutoLaunch = async () => {
1743+ if (!process.client || typeof window === 'undefined') return
1744+ if (!window.wechatDesktop?.getAutoLaunch) return
1745+ desktopAutoLaunchLoading.value = true
1746+ desktopAutoLaunchError.value = ''
1747+ try {
1748+ desktopAutoLaunch.value = !!(await window.wechatDesktop.getAutoLaunch())
1749+ } catch (e) {
1750+ desktopAutoLaunchError.value = e?.message || '读取开机自启动状态失败'
1751+ } finally {
1752+ desktopAutoLaunchLoading.value = false
1753+ }
1754+ }
1755+
1756+ const setDesktopAutoLaunch = async (enabled) => {
1757+ if (!process.client || typeof window === 'undefined') return
1758+ if (!window.wechatDesktop?.setAutoLaunch) return
1759+ desktopAutoLaunchLoading.value = true
1760+ desktopAutoLaunchError.value = ''
1761+ try {
1762+ desktopAutoLaunch.value = !!(await window.wechatDesktop.setAutoLaunch(!!enabled))
1763+ } catch (e) {
1764+ desktopAutoLaunchError.value = e?.message || '设置开机自启动失败'
1765+ await refreshDesktopAutoLaunch()
1766+ } finally {
1767+ desktopAutoLaunchLoading.value = false
1768+ }
1769+ }
1770+
1771+ const openDesktopSettings = async () => {
1772+ desktopSettingsOpen.value = true
1773+ await refreshDesktopAutoLaunch()
1774+ }
1775+
1776+ const closeDesktopSettings = () => {
1777+ desktopSettingsOpen.value = false
1778+ }
1779+
1780+ const onDesktopAutoLaunchToggle = async (ev) => {
1781+ const checked = !!ev?.target?.checked
1782+ await setDesktopAutoLaunch(checked)
1783+ }
1784+
1785+ const onDesktopAutoRealtimeToggle = async (ev) => {
1786+ const checked = !!ev?.target?.checked
1787+ desktopAutoRealtime.value = checked
1788+ writeLocalBool(DESKTOP_SETTING_AUTO_REALTIME_KEY, checked)
1789+ if (checked) {
1790+ // 开启后尝试立即启用实时模式(不可用则静默忽略)
1791+ try {
1792+ await tryEnableRealtimeAuto()
1793+ } catch {}
1794+ }
1795+ }
1796+
1797+ const onDesktopDefaultToChatToggle = (ev) => {
1798+ const checked = !!ev?.target?.checked
1799+ desktopDefaultToChatWhenData.value = checked
1800+ writeLocalBool(DESKTOP_SETTING_DEFAULT_TO_CHAT_KEY, checked)
1801+ }
1802+
16101803// 联系人数据
16111804const contacts = ref([])
16121805
@@ -3406,6 +3599,9 @@ const applyRouteSelection = async () => {
34063599
34073600// 默认选择第一个联系人
34083601onMounted(() => {
3602+ if (process.client && typeof window !== 'undefined') {
3603+ isDesktopEnv.value = !!window.wechatDesktop
3604+ }
34093605 loadContacts()
34103606 loadSearchHistory()
34113607})
@@ -3436,6 +3632,8 @@ const loadContacts = async () => {
34363632 } finally {
34373633 isLoadingContacts.value = false
34383634 }
3635+
3636+ await tryEnableRealtimeAuto()
34393637}
34403638
34413639const loadSessionsForSelectedAccount = async () => {
@@ -4546,23 +4744,26 @@ const startRealtimeStream = () => {
45464744 }
45474745}
45484746
4549- const toggleRealtime = async () => {
4747+ const toggleRealtime = async (opts = {}) => {
4748+ const silent = !!opts?.silent
45504749 if (!process.client || typeof window === 'undefined') return
45514750 if (!selectedAccount.value) return
45524751
45534752 if (!realtimeEnabled.value) {
45544753 await fetchRealtimeStatus()
45554754 if (!realtimeAvailable.value) {
4556- window.alert(realtimeStatusError.value || '实时模式不可用:缺少密钥或 db_storage 路径。')
4557- return
4755+ if (!silent) {
4756+ window.alert(realtimeStatusError.value || '实时模式不可用:缺少密钥或 db_storage 路径。')
4757+ }
4758+ return false
45584759 }
45594760 realtimeEnabled.value = true
45604761 startRealtimeStream()
45614762 queueRealtimeSessionsRefresh()
45624763 if (selectedContact.value?.username) {
45634764 await refreshSelectedMessages()
45644765 }
4565- return
4766+ return true
45664767 }
45674768
45684769 realtimeEnabled.value = false
@@ -4571,6 +4772,19 @@ const toggleRealtime = async () => {
45714772 if (selectedContact.value?.username) {
45724773 await refreshSelectedMessages()
45734774 }
4775+ return true
4776+ }
4777+
4778+ const tryEnableRealtimeAuto = async () => {
4779+ if (!process.client || typeof window === 'undefined') return
4780+ if (!isDesktopEnv.value) return
4781+ if (!desktopAutoRealtime.value) return
4782+ if (realtimeEnabled.value) return
4783+ if (!selectedAccount.value) return
4784+
4785+ try {
4786+ await toggleRealtime({ silent: true })
4787+ } catch {}
45744788}
45754789
45764790watch(selectedAccount, async () => {
0 commit comments