feat(用户): 实现用户信息获取及配置写入功能
添加获取用户信息的API接口和Pinia存储 在登录流程中增加用户信息获取和配置写入操作 新增opencode配置写入的IPC通信功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { app, BrowserWindow, shell, ipcMain, Menu } from 'electron';
|
import { app, BrowserWindow, shell, ipcMain, Menu } from 'electron';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import started from 'electron-squirrel-startup';
|
import started from 'electron-squirrel-startup';
|
||||||
@@ -225,6 +226,48 @@ function registerIpcHandlers() {
|
|||||||
// Bonjour
|
// Bonjour
|
||||||
ipcMain.handle('bonjour:get-services', () => getDiscoveredServices());
|
ipcMain.handle('bonjour:get-services', () => getDiscoveredServices());
|
||||||
|
|
||||||
|
// opencode 配置写入
|
||||||
|
ipcMain.handle('opencode:write-config', async (_e, { modelInfo, deviceHost, devicePort }) => {
|
||||||
|
const configDir = path.join(os.homedir(), '.config', 'opencode');
|
||||||
|
const configPath = path.join(configDir, 'opencode.json');
|
||||||
|
|
||||||
|
await fs.promises.mkdir(configDir, { recursive: true });
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
$schema: 'https://opencode.ai/config.json',
|
||||||
|
provider: {
|
||||||
|
zhiju: {
|
||||||
|
name: 'Zhiju AI',
|
||||||
|
env: ['ZHIJU_API_KEY'],
|
||||||
|
options: {
|
||||||
|
baseURL: `http://${deviceHost}:${modelInfo.port}/v1`,
|
||||||
|
apiKey: `${modelInfo.apiKey}`,
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
[modelInfo.model_name]: {
|
||||||
|
name: modelInfo.model_name,
|
||||||
|
family: 'openai',
|
||||||
|
status: modelInfo.status || 'beta',
|
||||||
|
capabilities: modelInfo.capabilities || {
|
||||||
|
reasoning: false,
|
||||||
|
attachment: true,
|
||||||
|
toolcall: true,
|
||||||
|
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
||||||
|
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||||
|
},
|
||||||
|
limit: modelInfo.limit || { context: 128000, output: 4096 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
model: `zhiju/${modelInfo.model_name}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
console.log('[opencode] config written to:', configPath);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// 窗口控制
|
// 窗口控制
|
||||||
ipcMain.on('window:minimize', (event) => {
|
ipcMain.on('window:minimize', (event) => {
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
|||||||
@@ -26,3 +26,7 @@ contextBridge.exposeInMainWorld('bonjour', {
|
|||||||
return () => ipcRenderer.removeListener('bonjour:services-updated', listener);
|
return () => ipcRenderer.removeListener('bonjour:services-updated', listener);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('opencodeConfig', {
|
||||||
|
write: (params) => ipcRenderer.invoke('opencode:write-config', params),
|
||||||
|
});
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ import { ref, watch } from 'vue';
|
|||||||
import { User } from '@element-plus/icons-vue';
|
import { User } from '@element-plus/icons-vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { useSparkStore } from '@/stores/spark';
|
import { useSparkStore } from '@/stores/spark';
|
||||||
import { loginAction } from '@/http/api.js';
|
import { loginAction, getUserInfoAction } from '@/http/api.js';
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
|
||||||
const sparkStore = useSparkStore();
|
const sparkStore = useSparkStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -115,13 +117,38 @@ async function handleLogin() {
|
|||||||
const device = sparkStore.devices.find((d) => d.name === form.value.sparkDevice);
|
const device = sparkStore.devices.find((d) => d.name === form.value.sparkDevice);
|
||||||
if (device) sparkStore.selectDevice(device);
|
if (device) sparkStore.selectDevice(device);
|
||||||
|
|
||||||
const url = sparkStore.selectedDeviceUrl;
|
const selectedDevice = sparkStore.selectedDevice;
|
||||||
console.log('[Login] spark device:', device);
|
console.log('[Login] spark device:', selectedDevice);
|
||||||
console.log('[Login] target url:', url);
|
console.log('[Login] target url:', sparkStore.selectedDeviceUrl);
|
||||||
|
|
||||||
await loginAction({ email: form.value.username, password: form.value.password });
|
await loginAction({ email: form.value.username, password: form.value.password });
|
||||||
ElMessage.success(`登录成功 | ${url ?? '未选择设备'}`);
|
|
||||||
emit('login-success', { username: form.value.username, device });
|
// 登录成功后获取用户信息并保存
|
||||||
|
const userRes = await getUserInfoAction();
|
||||||
|
const modelInfo = userRes.data?.xuanjian_model_info;
|
||||||
|
userStore.setUserInfo({ nickname: userRes.data?.nickname, email: userRes.data?.email });
|
||||||
|
|
||||||
|
// 写入 opencode 配置文件
|
||||||
|
if (modelInfo && selectedDevice) {
|
||||||
|
try {
|
||||||
|
const deviceHost = selectedDevice.host;
|
||||||
|
console.log('[Config] modelInfo:', modelInfo);
|
||||||
|
console.log('[Config] deviceHost:', deviceHost, 'port:', selectedDevice.port);
|
||||||
|
await window.opencodeConfig.write({
|
||||||
|
modelInfo,
|
||||||
|
deviceHost: selectedDevice.host,
|
||||||
|
devicePort: selectedDevice.port,
|
||||||
|
});
|
||||||
|
console.log('[Config] 写入成功');
|
||||||
|
} catch (configErr) {
|
||||||
|
console.error('[Config] 写入失败:', configErr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[Config] 跳过写入,modelInfo:', modelInfo, 'selectedDevice:', selectedDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success(`登录成功 | ${sparkStore.selectedDeviceUrl ?? '未选择设备'}`);
|
||||||
|
emit('login-success', { username: form.value.username, device: selectedDevice });
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ElMessage.error('登录失败,请重试');
|
ElMessage.error('登录失败,请重试');
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export const getHealthAction = () => getAction(url.health);
|
|||||||
// 用户登录
|
// 用户登录
|
||||||
export const loginAction = (data) => postAction(url.user.login, { email: data.email, password: encryptPassword(data.password) });
|
export const loginAction = (data) => postAction(url.user.login, { email: data.email, password: encryptPassword(data.password) });
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
export const getUserInfoAction = () => getAction(url.user.getUserInfo);
|
||||||
|
|
||||||
// 会话
|
// 会话
|
||||||
export const createSessionAction = (data) => postAction(url.session.create, data);
|
export const createSessionAction = (data) => postAction(url.session.create, data);
|
||||||
export const getSessionAction = (id) => getAction(url.session.detail(id));
|
export const getSessionAction = (id) => getAction(url.session.detail(id));
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const url = {
|
|||||||
// 用户
|
// 用户
|
||||||
user: {
|
user: {
|
||||||
login: '/v1/user/login',
|
login: '/v1/user/login',
|
||||||
|
// 获取用户信息接口
|
||||||
|
getUserInfo: '/v1/user/info',
|
||||||
},
|
},
|
||||||
|
|
||||||
// SSE 事件流
|
// SSE 事件流
|
||||||
|
|||||||
25
src/renderer/stores/user.js
Normal file
25
src/renderer/stores/user.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'user_info';
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', () => {
|
||||||
|
const userInfo = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null'));
|
||||||
|
|
||||||
|
const nickname = computed(() => userInfo.value?.nickname || '');
|
||||||
|
const email = computed(() => userInfo.value?.email || '');
|
||||||
|
const isLoggedIn = computed(() => !!userInfo.value);
|
||||||
|
|
||||||
|
function setUserInfo(info) {
|
||||||
|
userInfo.value = info;
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearUserInfo() {
|
||||||
|
userInfo.value = null;
|
||||||
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
|
localStorage.removeItem('Authorization');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { userInfo, nickname, email, isLoggedIn, setUserInfo, clearUserInfo };
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user