From 8b145d79d38efb3e4424b7eab66df5a8db42a00c Mon Sep 17 00:00:00 2001 From: houakang Date: Sun, 12 Apr 2026 12:51:40 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(=E7=99=BB=E5=BD=95):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AE=BE=E5=A4=87=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E5=AE=9E=E7=8E=B0=E7=99=BB=E5=BD=95=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在登录弹窗中增加设备选择下拉框,并修改postAction以支持baseURL参数 实现用户登录接口调用,根据选择的设备进行登录请求 --- src/renderer/components/LoginDialog.vue | 30 +++++++++++++++++++++---- src/renderer/http/api.js | 3 +++ src/renderer/http/manage.js | 12 +++++----- src/renderer/http/url.js | 5 +++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/LoginDialog.vue b/src/renderer/components/LoginDialog.vue index 804b442..9700dc5 100644 --- a/src/renderer/components/LoginDialog.vue +++ b/src/renderer/components/LoginDialog.vue @@ -11,6 +11,16 @@

登录后体验更多功能

+ + + + + @@ -56,6 +66,10 @@ import { ref, watch } from 'vue'; import { User } from '@element-plus/icons-vue'; import { ElMessage } from 'element-plus'; +import { useSparkStore } from '@/stores/spark'; +import { loginAction } from '@/http/api.js'; + +const sparkStore = useSparkStore(); const props = defineProps({ modelValue: { @@ -78,11 +92,13 @@ const loading = ref(false); const agreed = ref(false); const form = ref({ + sparkDevice: sparkStore.selectedDevice?.name || '', username: '', password: '', }); const rules = { + sparkDevice: [{ required: true, message: '请选择设备', trigger: 'change' }], username: [{ required: true, message: '请输入账号', trigger: 'blur' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }], }; @@ -96,10 +112,16 @@ async function handleLogin() { if (!valid) return; loading.value = true; try { - // TODO: 替换为真实登录接口 - await new Promise((r) => setTimeout(r, 800)); - ElMessage.success('登录成功'); - emit('login-success', { username: form.value.username }); + const device = sparkStore.devices.find((d) => d.name === form.value.sparkDevice); + if (device) sparkStore.selectDevice(device); + + const url = sparkStore.selectedDeviceUrl; + console.log('[Login] spark device:', device); + console.log('[Login] target url:', url); + + await loginAction({ email: form.value.username, password: form.value.password }, url); + ElMessage.success(`登录成功 | ${url ?? '未选择设备'}`); + emit('login-success', { username: form.value.username, device }); visible.value = false; } catch (err) { ElMessage.error('登录失败,请重试'); diff --git a/src/renderer/http/api.js b/src/renderer/http/api.js index fa3d73b..7c75b73 100644 --- a/src/renderer/http/api.js +++ b/src/renderer/http/api.js @@ -4,6 +4,9 @@ import url, { getBaseUrl } from './url.js'; // 健康检查 export const getHealthAction = () => getAction(url.health); +// 用户登录 +export const loginAction = (data, sparkBaseUrl) => postAction(url.user.login, data, {}, sparkBaseUrl); + // 会话 export const createSessionAction = (data) => postAction(url.session.create, data); export const getSessionAction = (id) => getAction(url.session.detail(id)); diff --git a/src/renderer/http/manage.js b/src/renderer/http/manage.js index 63cbd3d..5d639e7 100644 --- a/src/renderer/http/manage.js +++ b/src/renderer/http/manage.js @@ -1,17 +1,17 @@ -import request from './index.js' +import request from './index.js'; export function getAction(url, params) { - return request({ url, method: 'GET', params }) + return request({ url, method: 'GET', params }); } -export function postAction(url, data, headers = {}) { - return request({ url, method: 'POST', data, headers }) +export function postAction(url, data, headers = {}, baseURL) { + return request({ url, method: 'POST', data, headers, ...(baseURL ? { baseURL } : {}) }); } export function putAction(url, data) { - return request({ url, method: 'PUT', data }) + return request({ url, method: 'PUT', data }); } export function deleteAction(url, params) { - return request({ url, method: 'DELETE', params }) + return request({ url, method: 'DELETE', params }); } diff --git a/src/renderer/http/url.js b/src/renderer/http/url.js index d3b6b74..476cf4e 100644 --- a/src/renderer/http/url.js +++ b/src/renderer/http/url.js @@ -22,6 +22,11 @@ const url = { list: (sessionId) => `/session/${sessionId}/message`, }, + // 用户 + user: { + login: '/v1/user/login', + }, + // SSE 事件流 event: '/event', }; From 9710da8b3de7db7e6ae6a147ced3f8936091fdb1 Mon Sep 17 00:00:00 2001 From: houakang Date: Sun, 12 Apr 2026 12:51:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86):?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0Spark=E8=AE=BE=E5=A4=87=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E4=B8=8E=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现Bonjour服务发现过滤和Spark设备状态管理,包括: - 在BonjourView中添加对polygence-spark类型设备的过滤 - 创建spark store用于管理设备列表和选中设备状态 - 支持设备URL自动生成和持久化存储 --- src/renderer/stores/spark.js | 55 ++++++++++++++++++++++ src/renderer/views/bonjour/BonjourView.vue | 13 ++++- 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/renderer/stores/spark.js diff --git a/src/renderer/stores/spark.js b/src/renderer/stores/spark.js new file mode 100644 index 0000000..ebad1c5 --- /dev/null +++ b/src/renderer/stores/spark.js @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; + +const STORAGE_KEY = 'selected_spark_device'; + +export const useSparkStore = defineStore('spark', () => { + // 运行时发现的设备列表(动态,不持久化) + const devices = ref([]); + + // 当前选中的设备,启动时从 localStorage 恢复 + const selectedDevice = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null')); + + // 选中设备的完整 URL,优先取 IPv4 地址 + const selectedDeviceUrl = computed(() => { + if (!selectedDevice.value) return null; + const { addresses, port, referer } = selectedDevice.value; + // 优先找 IPv4(不含冒号的地址) + const ipv4 = addresses?.find((a) => !a.includes(':')); + // 兜底用 referer.address(mDNS 响应来源 IP) + const ip = ipv4 || referer?.address; + return ip ? `http://${ip}:${port}` : null; + }); + + // 更新设备列表(由 BonjourView 调用) + function setDevices(list) { + devices.value = list; + + // 如果之前选中的设备还在列表里,用最新数据刷新它 + if (selectedDevice.value) { + const fresh = list.find((d) => d.name === selectedDevice.value.name); + if (fresh) selectDevice(fresh); + } + } + + // 选中某台设备,并持久化到 localStorage + function selectDevice(device) { + selectedDevice.value = device; + localStorage.setItem(STORAGE_KEY, JSON.stringify(device)); + } + + // 清除选中 + function clearSelectedDevice() { + selectedDevice.value = null; + localStorage.removeItem(STORAGE_KEY); + } + + return { + devices, + selectedDevice, + selectedDeviceUrl, + setDevices, + selectDevice, + clearSelectedDevice, + }; +}); diff --git a/src/renderer/views/bonjour/BonjourView.vue b/src/renderer/views/bonjour/BonjourView.vue index 244c283..14f1d6e 100644 --- a/src/renderer/views/bonjour/BonjourView.vue +++ b/src/renderer/views/bonjour/BonjourView.vue @@ -1,15 +1,22 @@