Merge pull request #672 from xinnan-tech/hot-fix

合并多个提交
This commit is contained in:
欣南科技
2025-04-05 22:14:30 +08:00
committed by GitHub
52 changed files with 1208 additions and 1107 deletions
@@ -25,7 +25,8 @@ public interface AgentService extends BaseService<AgentEntity> {
/** /**
* 删除这个用户的所有 * 删除这个用户的所有
*
* @param userId * @param userId
*/ */
void deleteAgentByUserId(String userId); void deleteAgentByUserId(Long userId);
} }
@@ -65,7 +65,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
} }
@Override @Override
public void deleteAgentByUserId(String userId) { public void deleteAgentByUserId(Long userId) {
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>(); UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId); wrapper.eq("user_id", userId);
baseDao.delete(wrapper); baseDao.delete(wrapper);
@@ -17,11 +17,12 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.Result; import xiaozhi.common.utils.Result;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
import xiaozhi.modules.model.dto.ModelConfigDTO; import xiaozhi.modules.model.dto.ModelConfigDTO;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO; import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
@@ -52,15 +53,6 @@ public class ModelController {
return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS); return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS);
} }
@GetMapping("/{modelType}/{provideCode}/fields")
@Operation(summary = "获取模型供应器字段")
@RequiresPermissions("sys:role:superAdmin")
public Result<List<ModelProviderFieldDTO>> getModelProviderFields(@PathVariable String modelType,
@PathVariable String provideCode) {
List<ModelProviderFieldDTO> fieldList = modelProviderService.getFieldList(modelType, provideCode);
return new Result<List<ModelProviderFieldDTO>>().ok(fieldList);
}
@GetMapping("/models/list") @GetMapping("/models/list")
@Operation(summary = "获取模型配置列表") @Operation(summary = "获取模型配置列表")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
@@ -94,15 +86,23 @@ public class ModelController {
return new Result<ModelConfigDTO>().ok(modelConfigDTO); return new Result<ModelConfigDTO>().ok(modelConfigDTO);
} }
@DeleteMapping("/models/{modelType}/{provideCode}/{id}") @DeleteMapping("/models/{id}")
@Operation(summary = "删除模型配置") @Operation(summary = "删除模型配置")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<Void> deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, public Result<Void> deleteModelConfig(@PathVariable String id) {
@PathVariable String id) { modelConfigService.delete(id);
modelConfigService.delete(modelType, provideCode, id);
return new Result<>(); return new Result<>();
} }
@GetMapping("/models/{id}")
@Operation(summary = "获取模型配置")
@RequiresPermissions("sys:role:superAdmin")
public Result<ModelConfigDTO> getModelConfig(@PathVariable String id) {
ModelConfigEntity item = modelConfigService.selectById(id);
ModelConfigDTO modelConfigDTO = ConvertUtils.sourceToTarget(item, ModelConfigDTO.class);
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
}
@GetMapping("/models/{modelId}/voices") @GetMapping("/models/{modelId}/voices")
@Operation(summary = "获取模型音色") @Operation(summary = "获取模型音色")
@RequiresPermissions("sys:role:normal") @RequiresPermissions("sys:role:normal")
@@ -2,6 +2,7 @@ package xiaozhi.modules.model.dto;
import java.io.Serial; import java.io.Serial;
import cn.hutool.json.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -28,7 +29,7 @@ public class ModelConfigBodyDTO {
private Integer isEnabled; private Integer isEnabled;
@Schema(description = "模型配置(JSON格式)") @Schema(description = "模型配置(JSON格式)")
private String configJson; private JSONObject configJson;
@Schema(description = "官方文档链接") @Schema(description = "官方文档链接")
private String docLink; private String docLink;
@@ -3,6 +3,7 @@ package xiaozhi.modules.model.dto;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import cn.hutool.json.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -32,7 +33,7 @@ public class ModelConfigDTO implements Serializable {
private Integer isEnabled; private Integer isEnabled;
@Schema(description = "模型配置(JSON格式)") @Schema(description = "模型配置(JSON格式)")
private String configJson; private JSONObject configJson;
@Schema(description = "官方文档链接") @Schema(description = "官方文档链接")
private String docLink; private String docLink;
@@ -1,19 +0,0 @@
package xiaozhi.modules.model.dto;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "模型供应器字段")
public class ModelProviderFieldDTO implements Serializable {
@Schema(description = "字段名")
private String key;
@Schema(description = "字段标签")
private String label;
@Schema(description = "字段类型")
private String type;
}
@@ -9,11 +9,12 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import cn.hutool.json.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@Data @Data
@TableName("ai_model_config") @TableName(value = "ai_model_config", autoResultMap = true)
@Schema(description = "模型配置表") @Schema(description = "模型配置表")
public class ModelConfigEntity { public class ModelConfigEntity {
@@ -38,7 +39,7 @@ public class ModelConfigEntity {
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
@Schema(description = "模型配置(JSON格式)") @Schema(description = "模型配置(JSON格式)")
private String configJson; private JSONObject configJson;
@Schema(description = "官方文档链接") @Schema(description = "官方文档链接")
private String docLink; private String docLink;
@@ -3,10 +3,12 @@ package xiaozhi.modules.model.service;
import java.util.List; import java.util.List;
import xiaozhi.common.page.PageData; import xiaozhi.common.page.PageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
import xiaozhi.modules.model.dto.ModelConfigDTO; import xiaozhi.modules.model.dto.ModelConfigDTO;
import xiaozhi.modules.model.entity.ModelConfigEntity;
public interface ModelConfigService { public interface ModelConfigService extends BaseService<ModelConfigEntity> {
List<String> getModelCodeList(String modelType, String modelName); List<String> getModelCodeList(String modelType, String modelName);
@@ -16,7 +18,7 @@ public interface ModelConfigService {
ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO); ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO);
void delete(String modelType, String provideCode, String id); void delete(String id);
List<String> getVoiceList(String modelName, String voiceName); List<String> getVoiceList(String modelName, String voiceName);
} }
@@ -3,7 +3,6 @@ package xiaozhi.modules.model.service;
import java.util.List; import java.util.List;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
public interface ModelProviderService { public interface ModelProviderService {
@@ -19,6 +18,4 @@ public interface ModelProviderService {
void delete(); void delete();
List<ModelProviderDTO> getList(String modelType, String provideCode); List<ModelProviderDTO> getList(String modelType, String provideCode);
List<ModelProviderFieldDTO> getFieldList(String modelType, String provideCode);
} }
@@ -91,16 +91,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
} }
@Override @Override
public void delete(String modelType, String provideCode, String id) { public void delete(String id) {
// 先验证有没有供应器
if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) {
throw new RenException("modelType和provideCode不能为空");
}
List<ModelProviderDTO> providerList = modelProviderService.getList(modelType, provideCode);
if (CollectionUtil.isEmpty(providerList)) {
throw new RenException("供应器不存在");
}
modelConfigDao.deleteById(id); modelConfigDao.deleteById(id);
} }
@@ -10,10 +10,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dao.ModelProviderDao;
import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.dto.ModelProviderDTO;
import xiaozhi.modules.model.dto.ModelProviderFieldDTO;
import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.entity.ModelProviderEntity;
import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.model.service.ModelProviderService;
@@ -56,15 +54,4 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper); List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class); return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
} }
@Override
public List<ModelProviderFieldDTO> getFieldList(String modelType, String providerCode) {
List<String> modelProviderEntities = modelProviderDao.getFieldList(modelType, providerCode);
if (modelProviderEntities == null || modelProviderEntities.isEmpty()) {
return null;
}
String fields = modelProviderEntities.getFirst();
List<ModelProviderFieldDTO> fieldList = JsonUtils.parseArray(fields, ModelProviderFieldDTO.class);
return fieldList;
}
} }
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -79,6 +80,15 @@ public class AdminController {
return new Result<>(); return new Result<>();
} }
@PutMapping("/users/changeStatus/{status}")
@Operation(summary = "批量修改用户状态")
@RequiresPermissions("sys:role:superAdmin")
@Parameter(name = "status", description = "用户状态", required = true)
public Result<Void> changeStatus(@PathVariable Integer status, @RequestBody Long[] userIds) {
sysUserService.changeStatus(status, userIds);
return new Result<Void>();
}
@GetMapping("/device/all") @GetMapping("/device/all")
@Operation(summary = "分页查找设备") @Operation(summary = "分页查找设备")
@RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
@@ -57,4 +57,12 @@ public interface SysUserService extends BaseService<SysUserEntity> {
* @return 用户列表分页数据 * @return 用户列表分页数据
*/ */
PageData<AdminPageUserVO> page(AdminPageUserDTO dto); PageData<AdminPageUserVO> page(AdminPageUserDTO dto);
/**
* 批量修改用户状态
*
* @param status 用户状态
* @param userIds 用户ID数组
*/
void changeStatus(Integer status, Long[] userIds);
} }
@@ -98,7 +98,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
// 删除设备 // 删除设备
deviceService.deleteByUserId(id); deviceService.deleteByUserId(id);
// 删除智能体 // 删除智能体
agentService.deleteById(id); agentService.deleteAgentByUserId(id);
} }
@Override @Override
@@ -189,10 +189,20 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
*/ */
private String generatePassword() { private String generatePassword() {
StringBuilder password = new StringBuilder(); StringBuilder password = new StringBuilder();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 12; i++) {
int randomIndex = random.nextInt(CHARACTERS.length()); password.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
password.append(CHARACTERS.charAt(randomIndex));
} }
return password.toString(); return password.toString();
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void changeStatus(Integer status, Long[] userIds) {
for (Long userId : userIds) {
SysUserEntity entity = new SysUserEntity();
entity.setId(userId);
entity.setStatus(status);
updateById(entity);
}
}
} }
@@ -2,6 +2,22 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xiaozhi.modules.model.dao.ModelConfigDao"> <mapper namespace="xiaozhi.modules.model.dao.ModelConfigDao">
<resultMap id="ModelConfigResultMap" type="xiaozhi.modules.model.entity.ModelConfigEntity">
<id column="id" property="id"/>
<result column="model_type" property="modelType"/>
<result column="model_code" property="modelCode"/>
<result column="model_name" property="modelName"/>
<result column="is_default" property="isDefault"/>
<result column="is_enabled" property="isEnabled"/>
<result column="config_json" property="configJson" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
<result column="doc_link" property="docLink"/>
<result column="remark" property="remark"/>
<result column="sort" property="sort"/>
<result column="updater" property="updater"/>
<result column="update_date" property="updateDate"/>
<result column="creator" property="creator"/>
<result column="create_date" property="createDate"/>
</resultMap>
<!-- 获取模型供应器字段 --> <!-- 获取模型供应器字段 -->
<select id="getModelCodeList" resultType="String"> <select id="getModelCodeList" resultType="String">
select model_name from ai_model_config where model_type = #{modelType} select model_name from ai_model_config where model_type = #{modelType}
+4 -1
View File
@@ -1 +1,4 @@
VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun # 暂时使用群主的接口
VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun/xiaozhi-esp32-api
# 如果本地开发,请使用以下接口
# VUE_APP_API_BASE_URL=http://localhost:8002/xiaozhi-esp32-api
+1 -1
View File
@@ -1 +1 @@
VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun VUE_APP_API_BASE_URL=/xiaozhi-esp32-api
+11 -12
View File
@@ -1,13 +1,12 @@
module.exports = { module.exports = {
presets: [ presets: [
['@vue/cli-plugin-babel/preset', { ['@vue/cli-plugin-babel/preset', {
useBuiltIns: 'usage', useBuiltIns: 'usage',
corejs: 3 corejs: 3
}] }]
], ],
plugins: [ plugins: [
'@babel/plugin-syntax-dynamic-import', // 确保支持动态导入 (Lazy Loading) '@babel/plugin-syntax-dynamic-import', // 确保支持动态导入 (Lazy Loading)
'@babel/plugin-transform-runtime' '@babel/plugin-transform-runtime'
] ]
} }
+1 -1
View File
@@ -16,4 +16,4 @@
"scripthost" "scripthost"
] ]
} }
} }
+1 -1
View File
@@ -8847,4 +8847,4 @@
} }
} }
} }
} }
+1 -1
View File
@@ -43,4 +43,4 @@
"*.scss", "*.scss",
"*.vue" "*.vue"
] ]
} }
+3 -4
View File
@@ -1,11 +1,10 @@
<template> <template>
<div id="app"> <div id="app">
<router-view/> <router-view />
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
#app { #app {
font-family: Avenir, Helvetica, Arial, sans-serif; font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@@ -29,7 +28,7 @@ nav {
.copyright { .copyright {
text-align: center; text-align: center;
color:rgb(0, 0, 0); color: rgb(0, 0, 0);
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
margin-top: auto; margin-top: auto;
@@ -40,10 +39,10 @@ nav {
transform: translateX(-50%); transform: translateX(-50%);
width: 100%; width: 100%;
} }
.el-message { .el-message {
top: 45px !important; top: 45px !important;
} }
</style> </style>
<script> <script>
</script> </script>
+12 -8
View File
@@ -1,22 +1,22 @@
// 引入各个模块的请求 // 引入各个模块的请求
import user from './module/user.js'
import admin from './module/admin.js' import admin from './module/admin.js'
import agent from './module/agent.js'
import device from './module/device.js'
import model from './module/model.js'
import user from './module/user.js'
/** /**
* 接口地址 * 接口地址
* 当前8002端口接口还没开发完成,暂时用 apifoxmock 接口代替 * 开发时自动读取使用.env.development文件
* 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求 * 编译时自动读取使用.env.production文件
*
*/ */
const DEV_API_SERVICE = 'https://2662r3426b.vicp.fun/xiaozhi-esp32-api' const DEV_API_SERVICE = process.env.VUE_APP_API_BASE_URL
// 8002开发完成完成后使用这个
// const DEV_API_SERVICE = '/xiaozhi-esp32-api'
/** /**
* 根据开发环境返回接口url * 根据开发环境返回接口url
* @returns {string} * @returns {string}
*/ */
export function getServiceUrl() { export function getServiceUrl() {
// return '/xiaozhi-esp32-api'
return DEV_API_SERVICE return DEV_API_SERVICE
} }
@@ -25,4 +25,8 @@ export function getServiceUrl() {
export default { export default {
getServiceUrl, getServiceUrl,
user, user,
admin,
agent,
device,
model,
} }
+8 -10
View File
@@ -1,7 +1,7 @@
import {goToPage, showDanger, showWarning, isNotNull} from '../utils/index'
import Constant from '../utils/constant'
import Fly from 'flyio/dist/npm/fly'; import Fly from 'flyio/dist/npm/fly';
import store from '../store/index' import store from '../store/index';
import Constant from '../utils/constant';
import { goToPage, isNotNull, showDanger, showWarning } from '../utils/index';
const fly = new Fly() const fly = new Fly()
// 设置超时 // 设置超时
@@ -22,11 +22,11 @@ function sendRequest() {
_failCallback: null, _failCallback: null,
_method: 'GET', _method: 'GET',
_data: {}, _data: {},
_header: {'content-type': 'application/json; charset=utf-8'}, _header: { 'content-type': 'application/json; charset=utf-8' },
_url: '', _url: '',
_responseType: undefined, // 新增响应类型字段 _responseType: undefined, // 新增响应类型字段
'send'() { 'send'() {
if(isNotNull(store.getters.getToken)){ if (isNotNull(store.getters.getToken)) {
this._header.Authorization = 'Bearer ' + (JSON.parse(store.getters.getToken)).token this._header.Authorization = 'Bearer ' + (JSON.parse(store.getters.getToken)).token
} }
@@ -40,7 +40,7 @@ function sendRequest() {
if (error) { if (error) {
return return
} }
if (this._sucCallback) { if (this._sucCallback) {
this._sucCallback(res) this._sucCallback(res)
} }
@@ -100,15 +100,13 @@ function sendRequest() {
*/ */
// 在错误处理函数中添加日志 // 在错误处理函数中添加日志
function httpHandlerError(info, callBack) { function httpHandlerError(info, callBack) {
console.log('httpHandlerError', info)
/** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */ /** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */
let networkError = false let networkError = false
if (info.status === 200) { if (info.status === 200) {
if (info.data.code === 'success' || info.data.code === 0 || info.data.code === undefined) { if (info.data.code === 'success' || info.data.code === 0 || info.data.code === undefined) {
return networkError return networkError
}else if (info.data.code === 401) { } else if (info.data.code === 401) {
console.log('触发 401,清除 Token 并跳转登录页');
store.commit('clearAuth'); store.commit('clearAuth');
goToPage(Constant.PAGE.LOGIN, true); goToPage(Constant.PAGE.LOGIN, true);
return true return true
+20 -20
View File
@@ -1,30 +1,30 @@
import RequestService from '../httpRequest' import { getServiceUrl } from '../api';
import {getServiceUrl} from '../api' import RequestService from '../httpRequest';
export default { export default {
// 用户列表 // 用户列表
getUserList(params, callback) { getUserList(params, callback) {
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
page: params.page, page: params.page,
limit: params.limit, limit: params.limit,
mobile: params.mobile mobile: params.mobile
}).toString(); }).toString();
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/admin/users?${queryParams}`) .url(`${getServiceUrl()}/api/v1/admin/users?${queryParams}`)
.method('GET') .method('GET')
.success((res) => { .success((res) => {
RequestService.clearRequestTime() RequestService.clearRequestTime()
callback(res) callback(res)
})
.fail((err) => {
console.error('请求失败:', err)
RequestService.reAjaxFun(() => {
this.getUserList(callback)
}) })
.fail((err) => { }).send()
console.error('请求失败:', err) },
RequestService.reAjaxFun(() => {
this.getUserList(callback)
})
}).send()
},
// 删除用户 // 删除用户
deleteUser(id, callback) { deleteUser(id, callback) {
RequestService.sendRequest() RequestService.sendRequest()
+3 -3
View File
@@ -1,5 +1,5 @@
import RequestService from '../httpRequest' import { getServiceUrl } from '../api';
import {getServiceUrl} from '../api' import RequestService from '../httpRequest';
export default { export default {
@@ -23,7 +23,7 @@ export default {
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/agent`) .url(`${getServiceUrl()}/api/v1/agent`)
.method('POST') .method('POST')
.data({agentName: agentName}) .data({ agentName: agentName })
.success((res) => { .success((res) => {
RequestService.clearRequestTime(); RequestService.clearRequestTime();
callback(res); callback(res);
+24 -24
View File
@@ -1,38 +1,38 @@
import RequestService from '../httpRequest' import { getServiceUrl } from '../api';
import {getServiceUrl} from '../api' import RequestService from '../httpRequest';
export default { export default {
// 已绑设备 // 已绑设备
getAgentBindDevices(agentId, callback) { getAgentBindDevices(agentId, callback) {
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/device/bind/${agentId}`) .url(`${getServiceUrl()}/api/v1/device/bind/${agentId}`)
.method('GET') .method('GET')
.success((res) => { .success((res) => {
RequestService.clearRequestTime(); RequestService.clearRequestTime();
callback(res); callback(res);
}) })
.fail((err) => { .fail((err) => {
console.error('获取设备列表失败:', err); console.error('获取设备列表失败:', err);
RequestService.reAjaxFun(() => { RequestService.reAjaxFun(() => {
this.getAgentBindDevices(agentId, callback); this.getAgentBindDevices(agentId, callback);
}); });
}).send(); }).send();
}, },
// 解绑设备 // 解绑设备
unbindDevice(device_id, callback) { unbindDevice(device_id, callback) {
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/device/unbind`) .url(`${getServiceUrl()}/api/v1/device/unbind`)
.method('POST') .method('POST')
.data({ deviceId: device_id }) .data({ deviceId: device_id })
.success((res) => { .success((res) => {
RequestService.clearRequestTime(); RequestService.clearRequestTime();
callback(res); callback(res);
}) })
.fail((err) => { .fail((err) => {
console.error('解绑设备失败:', err); console.error('解绑设备失败:', err);
RequestService.reAjaxFun(() => { RequestService.reAjaxFun(() => {
this.unbindDevice(device_id, callback); this.unbindDevice(device_id, callback);
}); });
}).send(); }).send();
}, },
// 绑定设备 // 绑定设备
+89 -23
View File
@@ -1,30 +1,96 @@
import RequestService from '../httpRequest' import { getServiceUrl } from '../api';
import {getServiceUrl} from '../api' import RequestService from '../httpRequest';
export default { export default {
// 获取模型配置列表 // 获取模型配置列表
getModelList(params, callback) { getModelList(params, callback) {
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
modelType: params.modelType, modelType: params.modelType,
modelName: params.modelName || '', modelName: params.modelName || '',
page: params.page || 0, page: params.page || 0,
limit: params.limit || 10 limit: params.limit || 10
}).toString(); }).toString();
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/models/models/list?${queryParams}`) .url(`${getServiceUrl()}/api/v1/models/models/list?${queryParams}`)
.method('GET') .method('GET')
.success((res) => { .success((res) => {
RequestService.clearRequestTime() RequestService.clearRequestTime()
callback(res) callback(res)
})
.fail((err) => {
console.error('获取模型列表失败:', err)
RequestService.reAjaxFun(() => {
this.getModelList(params, callback)
}) })
.fail((err) => { }).send()
console.error('获取模型列表失败:', err) },
RequestService.reAjaxFun(() => { // 获取模型供应器列表
this.getModelList(params, callback) getModelProviders(modelType, callback) {
}) RequestService.sendRequest()
}).send() .url(`${getServiceUrl()}/api/v1/models/${modelType}/provideTypes`)
}, .method('GET')
.success((res) => {
RequestService.clearRequestTime()
callback(res.data?.data || [])
})
.fail((err) => {
console.error('获取供应器列表失败:', err)
this.$message.error('获取供应器列表失败')
RequestService.reAjaxFun(() => {
this.getModelProviders(modelType, callback)
})
}).send()
},
// 新增模型配置
addModel(params, callback) {
const { modelType, provideCode, formData } = params;
const postData = {
modelCode: formData.modelCode,
modelName: formData.modelName,
isDefault: formData.isDefault ? 1 : 0,
isEnabled: formData.isEnabled ? 1 : 0,
configJson: JSON.stringify(formData.configJson),
docLink: formData.docLink,
remark: formData.remark,
sort: formData.sort || 0
};
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/models/models/${modelType}/${provideCode}`)
.method('POST')
.data(postData)
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail((err) => {
console.error('新增模型失败:', err)
this.$message.error(err.msg || '新增模型失败')
RequestService.reAjaxFun(() => {
this.addModel(params, callback)
})
}).send()
},
// 删除模型配置
deleteModel(id, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/models/models/${id}`)
.method('DELETE')
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail((err) => {
console.error('删除模型失败:', err)
this.$message.error(err.msg || '删除模型失败')
RequestService.reAjaxFun(() => {
this.deleteModel(id, callback)
})
}).send()
},
} }
-54
View File
@@ -1,54 +0,0 @@
import RequestService from '../httpRequest'
import {getServiceUrl} from '../api'
export default {
/**
* 设备激活接口
* @param {string} code 激活码
* @param {function} callback 回调函数
*/
activateDevice(code, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/ota/activation`)
.method('GET')
.query({code})
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail((err) => {
console.error('设备激活失败:', err)
RequestService.reAjaxFun(() => {
this.activateDevice(code, callback)
})
}).send()
},
/**
* 检查OTA版本和设备激活状态
* @param {object} deviceInfo 设备信息对象
* @param {string} deviceId 设备唯一标识
* @param {string} clientId 客户端标识
* @param {function} callback 回调函数
*/
checkOtaVersion(deviceInfo, deviceId, clientId, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/ota`)
.method('POST')
.header({
'Device-Id': deviceId,
'Client-Id': clientId
})
.data(deviceInfo)
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail((err) => {
console.error('检查OTA版本失败:', err)
RequestService.reAjaxFun(() => {
this.checkOtaVersion(deviceInfo, deviceId, clientId, callback)
})
}).send()
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { getServiceUrl } from '../api'
import RequestService from '../httpRequest' import RequestService from '../httpRequest'
import {getServiceUrl} from '../api'
export default { export default {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

@@ -1,98 +0,0 @@
<template>
<el-dialog :visible.sync="visible" width="400px" center>
<div style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;">
<div style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
<img src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
</div>
激活设备
</div>
<div style="height: 1px;background: #e8f0ff;" />
<div style="margin: 22px 15px;">
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;">
<div style="color: red;display: inline-block;">*</div> 设备激活码
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请输入设备播报的激活码.." v-model="activateCode" />
</div>
</div>
<div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm">
确定
</div>
<div class="dialog-btn"
style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
@click="cancel">
取消
</div>
</div>
</el-dialog>
</template>
<script>
import userApi from '@/apis/module/user';
export default {
name: 'ActivateDeviceDialog',
props: {
visible: { type: Boolean, required: true }
},
data() {
return { activateCode: "" }
},
methods: {
confirm() {
if (!this.activateCode.trim()) {
this.$message.error('请输入激活码');
return;
}
userApi.activateDevice(this.activateCode, (res) => {
this.$message.success('激活成功,请重新唤醒设备即可完成激活!');
this.$emit('confirm', res);
this.$emit('update:visible', false);
console.log("res: ", res)
this.activateCode = "";
});
},
cancel() {
this.$emit('update:visible', false)
this.activateCode = ""
}
}
}
</script>
<style scoped>
.input-46 {
border: 1px solid #e4e6ef;
background: #f6f8fb;
border-radius: 15px;
}
.dialog-btn {
cursor: pointer;
flex: 1;
border-radius: 23px;
background: #5778ff;
height: 40px;
font-weight: 500;
font-size: 12px;
color: #fff;
line-height: 40px;
text-align: center;
}
::v-deep .el-dialog {
border-radius: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
::v-deep .el-dialog__headerbtn {
display: none;
}
::v-deep .el-dialog__body {
padding: 4px 6px;
}
::v-deep .el-dialog__header{
padding: 10px;
}
</style>
@@ -1,7 +1,9 @@
<template> <template>
<el-dialog :visible.sync="visible" width="400px" center > <el-dialog :visible.sync="visible" width="400px" center>
<div style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;"> <div
<div style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;"> style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;">
<div
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
<img src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" /> <img src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
</div> </div>
添加设备 添加设备
@@ -13,16 +15,14 @@
<span style="font-size: 11px"> 验证码</span> <span style="font-size: 11px"> 验证码</span>
</div> </div>
<div class="input-46" style="margin-top: 12px;"> <div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请输入设备播报的6位数验证码.." v-model="deviceCode" @keyup.enter.native="confirm"/> <el-input placeholder="请输入设备播报的6位数验证码.." v-model="deviceCode" @keyup.enter.native="confirm" />
</div> </div>
</div> </div>
<div style="display: flex;margin: 15px 15px;gap: 7px;"> <div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm"> <div class="dialog-btn" @click="confirm">
确定 确定
</div> </div>
<div class="dialog-btn" <div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" @click="cancel">
style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
@click="cancel">
取消 取消
</div> </div>
</div> </div>
@@ -30,6 +30,8 @@
</template> </template>
<script> <script>
import Api from '@/apis/api';
export default { export default {
name: 'AddDeviceDialog', name: 'AddDeviceDialog',
props: { props: {
@@ -49,31 +51,25 @@ export default {
return; return;
} }
this.loading = true; this.loading = true;
import('@/apis/module/device').then(({ default: deviceApi }) => { Api.device.bindDevice(
deviceApi.bindDevice( this.agentId,
this.agentId, this.deviceCode, ({ data }) => {
this.deviceCode, ({data}) => {
this.loading = false;
if (data.code === 0) {
this.$emit('refresh');
this.$message.success({
message: '设备绑定成功',
showClose: true
});
this.closeDialog();
} else {
this.$message.error({
message: data.msg || '绑定失败',
showClose: true
});
}
}
);
}).catch((err) => {
this.loading = false; this.loading = false;
console.error('API模块加载失败:', err); if (data.code === 0) {
this.$message.error('绑定服务不可用'); this.$emit('refresh');
}); this.$message.success({
message: '设备绑定成功',
showClose: true
});
this.closeDialog();
} else {
this.$message.error({
message: data.msg || '绑定失败',
showClose: true
});
}
}
);
}, },
closeDialog() { closeDialog() {
this.$emit('update:visible', false); this.$emit('update:visible', false);
@@ -89,7 +85,6 @@ export default {
</script> </script>
<style scoped> <style scoped>
.input-46 { .input-46 {
border: 1px solid #e4e6ef; border: 1px solid #e4e6ef;
background: #f6f8fb; background: #f6f8fb;
@@ -113,14 +108,16 @@ export default {
border-radius: 15px; border-radius: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
::v-deep .el-dialog__headerbtn { ::v-deep .el-dialog__headerbtn {
display: none; display: none;
} }
::v-deep .el-dialog__body { ::v-deep .el-dialog__body {
padding: 4px 6px; padding: 4px 6px;
} }
::v-deep .el-dialog__header{
::v-deep .el-dialog__header {
padding: 10px; padding: 10px;
} }
</style> </style>
@@ -1,13 +1,6 @@
<template> <template>
<el-dialog <el-dialog :visible.sync="visible" width="975px" center custom-class="custom-dialog" :show-close="false"
:visible.sync="visible" class="center-dialog">
width="975px"
center
custom-class="custom-dialog"
:show-close="false"
class="center-dialog"
>
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;"> <div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
<div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;"> <div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
添加模型 添加模型
@@ -34,113 +27,62 @@
</div> </div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div> <div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form"> <el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
<!-- 第一行模型名称和模型编码 -->
<div style="display: flex; gap: 20px; margin-bottom: 0;"> <div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="模型名称" prop="modelName" style="flex: 1;"> <el-form-item label="模型名称" prop="modelName" style="flex: 1;">
<el-input <el-input v-model="formData.modelName" placeholder="请输入模型名称" class="custom-input-bg"></el-input>
v-model="formData.modelName"
placeholder="请输入模型名称"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="模型编码" prop="modelCode" style="flex: 1;"> <el-form-item label="模型编码" prop="modelCode" style="flex: 1;">
<el-input <el-input v-model="formData.modelCode" placeholder="请输入模型编码" class="custom-input-bg"></el-input>
v-model="formData.modelCode"
placeholder="请输入模型编码"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
</div> </div>
<!-- 第二行供应器和排序号 -->
<div style="display: flex; gap: 20px; margin-bottom: 0;"> <div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="供应器" prop="supplier" style="flex: 1;"> <el-form-item label="供应器" prop="supplier" style="flex: 1;">
<el-select <el-select v-model="formData.supplier" placeholder="请选择" class="custom-select custom-input-bg"
v-model="formData.supplier" style="width: 100%;" @focus="loadProviders" filterable>
placeholder="请选择" <el-option v-for="item in providers" :key="item.value" :label="item.label" :value="item.value" />
class="custom-select custom-input-bg"
style="width: 100%;"
>
<el-option label="硅基流动" value="硅基流动"></el-option>
<el-option label="智脑科技" value="智脑科技"></el-option>
<el-option label="云智科技" value="云智科技"></el-option>
<el-option label="其他" value="其他"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="排序号" prop="sortOrder" style="flex: 1;"> <el-form-item label="排序号" prop="sortOrder" style="flex: 1;">
<el-input <el-input v-model="formData.sort" placeholder="请输入排序号" class="custom-input-bg"></el-input>
v-model="formData.sort"
placeholder="请输入排序号"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
</div> </div>
<!-- 文档地址 -->
<el-form-item label="文档地址" prop="docLink" style="margin-bottom: 27px;"> <el-form-item label="文档地址" prop="docLink" style="margin-bottom: 27px;">
<el-input <el-input v-model="formData.docLink" placeholder="请输入文档地址" class="custom-input-bg"></el-input>
v-model="formData.docLink"
placeholder="请输入文档地址"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
<!-- 备注 -->
<el-form-item label="备注" prop="remark" class="prop-remark"> <el-form-item label="备注" prop="remark" class="prop-remark">
<el-input <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入模型备注"
v-model="formData.remark" class="custom-input-bg"></el-input>
type="textarea"
:rows="3"
placeholder="请输入模型备注"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 调用信息部分 -->
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">调用信息</div> <div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">调用信息</div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 15px;"></div> <div style="height: 2px; background: #e9e9e9; margin-bottom: 15px;"></div>
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form"> <el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
<!-- 第一行模型名称和接口地址 -->
<div style="display: flex; gap: 10px; margin-bottom: 15px;"> <div style="display: flex; gap: 10px; margin-bottom: 15px;">
<el-form-item label="模型名称" prop="param1" style="flex: 0.5; margin-bottom: 0;"> <el-form-item label="模型名称" prop="param1" style="flex: 0.5; margin-bottom: 0;">
<el-input <el-input v-model="formData.configJson.param1" placeholder="请输入model_name"
v-model="formData.configJson.param1" class="custom-input-bg"></el-input>
placeholder="请输入model_name"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="接口地址" prop="param2" style="flex: 1; margin-bottom: 0;"> <el-form-item label="接口地址" prop="param2" style="flex: 1; margin-bottom: 0;">
<el-input <el-input v-model="formData.configJson.param2" placeholder="请输入base_url" class="custom-input-bg"></el-input>
v-model="formData.configJson.param2"
placeholder="请输入base_url"
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
</div> </div>
<!-- 秘钥信息 -->
<el-form-item label="秘钥信息" prop="apiKey"> <el-form-item label="秘钥信息" prop="apiKey">
<el-input <el-input v-model="formData.configJson.apiKey" placeholder="请输入api_key" show-password
v-model="formData.configJson.apiKey" class="custom-input-bg"></el-input>
placeholder="请输入api_key"
show-password
class="custom-input-bg"
></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<!-- 保存按钮 -->
<div style="display: flex;justify-content: center;"> <div style="display: flex;justify-content: center;">
<el-button <el-button type="primary" @click="confirm" class="save-btn">
type="primary"
@click="confirm"
class="save-btn"
>
保存 保存
</el-button> </el-button>
</div> </div>
@@ -148,14 +90,17 @@
</template> </template>
<script> <script>
import Api from '@/apis/api';
export default { export default {
name: 'AddModelDialog', name: 'AddModelDialog',
props: { props: {
visible: {type: Boolean, required: true}, visible: { type: Boolean, required: true },
modelType: {type: String, required: true} modelType: { type: String, required: true }
}, },
data() { data() {
return { return {
providers: [],
providersLoaded: false,
formData: { formData: {
modelName: '', modelName: '',
modelCode: '', modelCode: '',
@@ -174,16 +119,29 @@ export default {
} }
}, },
methods: { methods: {
loadProviders() {
if (this.providersLoaded)
return
Api.model.getModelProviders(this.modelType, (data) => {
this.providers = data.map(item => ({
label: item.name,
value: item.providerCode
}))
this.providersLoaded = true
})
},
confirm() { confirm() {
if (!this.formData.modelName || !this.formData.modelCode || !this.formData.supplier || if (!this.formData.modelName || !this.formData.modelCode || !this.formData.supplier ||
!this.formData.configJson.param1 || !this.formData.configJson.param2 || !this.formData.configJson.apiKey) { !this.formData.configJson.param1 || !this.formData.configJson.param2 || !this.formData.configJson.apiKey) {
this.$message.error('请填写所有必填字段'); this.$message.error('请填写所有必填字段');
return; return;
} }
this.$emit('confirm', { this.$emit('confirm', {
...this.formData, ...this.formData,
provideType: this.formData.supplier provideType: this.formData.supplier,
configJson: this.formData.configJson
}); });
this.$emit('update:visible', false); this.$emit('update:visible', false);
this.resetForm(); this.resetForm();
@@ -204,6 +162,9 @@ export default {
apiKey: '' apiKey: ''
} }
}; };
// 重置加载状态
this.providers = [];
this.providersLoaded = false;
}, },
handleClose() { handleClose() {
this.resetForm(); this.resetForm();
@@ -297,7 +258,7 @@ export default {
} }
.custom-form .el-form-item { .custom-form .el-form-item {
margin-bottom: 20px; /* 统一设置所有表单项的间距 */ margin-bottom: 20px;
} }
.custom-form .el-form-item__label { .custom-form .el-form-item__label {
@@ -312,13 +273,12 @@ export default {
margin-top: -4px; margin-top: -4px;
} }
/* 修改placeholder颜色 */
.custom-input-bg .el-input__inner::-webkit-input-placeholder, .custom-input-bg .el-input__inner::-webkit-input-placeholder,
.custom-input-bg .el-textarea__inner::-webkit-input-placeholder { .custom-input-bg .el-textarea__inner::-webkit-input-placeholder {
color: #9c9f9e; color: #9c9f9e;
} }
/* 输入框背景色 */
.custom-input-bg .el-input__inner, .custom-input-bg .el-input__inner,
.custom-input-bg .el-textarea__inner { .custom-input-bg .el-textarea__inner {
background-color: #f6f8fc; background-color: #f6f8fc;
@@ -342,13 +302,12 @@ export default {
} }
/* 修改开关样式 */
.custom-switch .el-switch__core { .custom-switch .el-switch__core {
border-radius: 20px; border-radius: 20px;
height: 23px; height: 23px;
background-color: #c0ccda; background-color: #c0ccda;
width: 35px; width: 35px;
padding: 0 20px; /* 调整左右内边距 */ padding: 0 20px;
} }
.custom-switch .el-switch__core:after { .custom-switch .el-switch__core:after {
@@ -363,7 +322,7 @@ export default {
.custom-switch.is-checked .el-switch__core { .custom-switch.is-checked .el-switch__core {
border-color: #b5bcf0; border-color: #b5bcf0;
background-color: #cfd7fa; background-color: #cfd7fa;
padding: 0 20px; /* 确保启用状态也有相同的间隔 */ padding: 0 20px;
} }
.custom-switch.is-checked .el-switch__core:after { .custom-switch.is-checked .el-switch__core:after {
@@ -373,15 +332,11 @@ export default {
} }
/* 调整flex布局的gap */
[style*="display: flex"] { [style*="display: flex"] {
gap: 20px; /* 扩大flex项间距 */ gap: 20px;
} }
/* 调整输入框高度 */
.custom-input-bg .el-input__inner { .custom-input-bg .el-input__inner {
height: 32px; /* 固定输入框高度 */ height: 32px;
} }
</style> </style>
@@ -1,7 +1,9 @@
<template> <template>
<el-dialog :visible.sync="visible" width="400px" center @open="handleOpen"> <el-dialog :visible.sync="visible" width="400px" center @open="handleOpen">
<div style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;"> <div
<div style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;"> style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;">
<div
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
<img loading="lazy" src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" /> <img loading="lazy" src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
</div> </div>
添加智能体 添加智能体
@@ -12,20 +14,14 @@
<div style="color: red;display: inline-block;">*</div> 智慧体名称 <div style="color: red;display: inline-block;">*</div> 智慧体名称
</div> </div>
<div class="input-46" style="margin-top: 12px;"> <div class="input-46" style="margin-top: 12px;">
<el-input <el-input ref="inputRef" placeholder="请输入智能体名称.." v-model="wisdomBodyName" @keyup.enter.native="confirm" />
ref="inputRef"
placeholder="请输入智能体名称.."
v-model="wisdomBodyName"
@keyup.enter.native="confirm" />
</div> </div>
</div> </div>
<div style="display: flex;margin: 15px 15px;gap: 7px;"> <div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm"> <div class="dialog-btn" @click="confirm">
确定 确定
</div> </div>
<div class="dialog-btn" <div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" @click="cancel">
style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
@click="cancel">
取消 取消
</div> </div>
</div> </div>
@@ -33,7 +29,7 @@
</template> </template>
<script> <script>
import userApi from '@/apis/module/agent'; import Api from '@/apis/api';
export default { export default {
name: 'AddWisdomBodyDialog', name: 'AddWisdomBodyDialog',
@@ -57,10 +53,10 @@ export default {
this.$message.error('请输入智能体名称'); this.$message.error('请输入智能体名称');
return; return;
} }
userApi.addAgent(this.wisdomBodyName, (res) => { Api.agent.addAgent(this.wisdomBodyName, (res) => {
this.$message.success({ this.$message.success({
message: '添加成功', message: '添加成功',
showClose: true showClose: true
}); });
this.$emit('confirm', res); this.$emit('confirm', res);
this.$emit('update:visible', false); this.$emit('update:visible', false);
@@ -94,17 +90,21 @@ export default {
line-height: 40px; line-height: 40px;
text-align: center; text-align: center;
} }
::v-deep .el-dialog { ::v-deep .el-dialog {
border-radius: 15px; border-radius: 15px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
} }
::v-deep .el-dialog__headerbtn { ::v-deep .el-dialog__headerbtn {
display: none; display: none;
} }
::v-deep .el-dialog__body { ::v-deep .el-dialog__body {
padding: 4px 6px; padding: 4px 6px;
} }
::v-deep .el-dialog__header{
::v-deep .el-dialog__header {
padding: 10px; padding: 10px;
} }
</style> </style>
+35 -34
View File
@@ -3,61 +3,61 @@
<div class="header-container"> <div class="header-container">
<!-- 左侧元素 --> <!-- 左侧元素 -->
<div class="header-left"> <div class="header-left">
<img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" class="logo-img"/> <img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" class="logo-img" />
<img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" class="brand-img"/> <img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" class="brand-img" />
</div> </div>
<!-- 中间导航菜单 --> <!-- 中间导航菜单 -->
<div class="header-center"> <div class="header-center">
<div class="equipment-management" :class="{ 'active-tab': $route.path === '/home' }" @click="goHome"> <div class="equipment-management" :class="{ 'active-tab': $route.path === '/home' }" @click="goHome">
<img loading="lazy" alt="" src="@/assets/header/robot.png" :style="{ filter: $route.path === '/home' ? 'brightness(0) invert(1)' : 'None' }"/> <img loading="lazy" alt="" src="@/assets/header/robot.png"
:style="{ filter: $route.path === '/home' ? 'brightness(0) invert(1)' : 'None' }" />
智能体管理 智能体管理
</div> </div>
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement"> <div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
<img loading="lazy" alt="" src="@/assets/header/user_management.png" :style="{ filter: $route.path === '/user-management' ? 'brightness(0) invert(1)' : 'None' }"/> @click="goModelConfig">
用户管理 <img loading="lazy" alt="" src="@/assets/header/model_config.png"
</div> :style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }" />
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }" @click="goModelConfig">
<img loading="lazy" alt="" src="@/assets/header/model_config.png" :style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }"/>
模型配置 模型配置
</div> </div>
<div v-if="isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement">
<img loading="lazy" alt="" src="@/assets/header/user_management.png"
:style="{ filter: $route.path === '/user-management' ? 'brightness(0) invert(1)' : 'None' }" />
用户管理
</div>
</div> </div>
<!-- 右侧元素 --> <!-- 右侧元素 -->
<div class="header-right"> <div class="header-right">
<div class="search-container"> <div class="search-container">
<el-input <el-input v-model="search" placeholder="输入名称搜索.." class="custom-search-input"
v-model="search" @keyup.enter.native="handleSearch">
placeholder="输入名称搜索.."
class="custom-search-input"
@keyup.enter.native="handleSearch"
>
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i> <i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i>
</el-input> </el-input>
</div> </div>
<img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img"/> <img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img" />
<el-dropdown trigger="click" class="user-dropdown"> <el-dropdown trigger="click" class="user-dropdown">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ userInfo.username || '加载中...' }}<i class="el-icon-arrow-down el-icon--right"></i> {{ userInfo.username || '加载中...' }}<i class="el-icon-arrow-down el-icon--right"></i>
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-plus" @click.native="">个人中心</el-dropdown-item> <el-dropdown-item @click.native="showChangePasswordDialog">修改密码</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-plus" @click.native="showChangePasswordDialog">修改密码</el-dropdown-item> <el-dropdown-item @click.native="handleLogout">退出登录</el-dropdown-item>
<el-dropdown-item icon="el-icon-circle-plus-outline" @click.native="handleLogout">退出登录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div> </div>
</div> </div>
<!-- 修改密码弹窗 --> <!-- 修改密码弹窗 -->
<ChangePasswordDialog v-model="isChangePasswordDialogVisible"/> <ChangePasswordDialog v-model="isChangePasswordDialogVisible" />
</el-header> </el-header>
</template> </template>
<script> <script>
import userApi from '@/apis/module/user'; import userApi from '@/apis/module/user';
import ChangePasswordDialog from './ChangePasswordDialog.vue'; // 引入修改密码弹窗组件
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import ChangePasswordDialog from './ChangePasswordDialog.vue'; // 引入修改密码弹窗组件
export default { export default {
@@ -98,7 +98,7 @@ export default {
}, },
// 获取用户信息 // 获取用户信息
fetchUserInfo() { fetchUserInfo() {
userApi.getUserInfo(({data}) => { userApi.getUserInfo(({ data }) => {
this.userInfo = data.data this.userInfo = data.data
if (data.data.superAdmin !== undefined) { if (data.data.superAdmin !== undefined) {
this.$store.commit('setUserInfo', data.data); this.$store.commit('setUserInfo', data.data);
@@ -139,14 +139,14 @@ export default {
// 调用 Vuex 的 logout action // 调用 Vuex 的 logout action
await this.logout(); await this.logout();
this.$message.success({ this.$message.success({
message:'退出登录成功', message: '退出登录成功',
showClose:true showClose: true
}); });
} catch (error) { } catch (error) {
console.error('退出登录失败:', error); console.error('退出登录失败:', error);
this.$message.error({ this.$message.error({
message:'退出登录失败,请重试', message: '退出登录失败,请重试',
showClose:true showClose: true
}); });
} }
}, },
@@ -162,7 +162,8 @@ export default {
background: #f6fcfe66; background: #f6fcfe66;
border: 1px solid #fff; border: 1px solid #fff;
height: 53px !important; height: 53px !important;
min-width: 900px; /* 设置最小宽度防止过度压缩 */ min-width: 900px;
/* 设置最小宽度防止过度压缩 */
overflow: hidden; overflow: hidden;
} }
@@ -187,8 +188,7 @@ export default {
} }
.brand-img { .brand-img {
width: 58px; height: 18px;
height: 12px;
} }
.header-center { .header-center {
@@ -224,7 +224,8 @@ export default {
align-items: center; align-items: center;
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer; cursor: pointer;
flex-shrink: 0; /* 防止导航按钮被压缩 */ flex-shrink: 0;
/* 防止导航按钮被压缩 */
} }
.equipment-management.active-tab { .equipment-management.active-tab {
@@ -244,10 +245,10 @@ export default {
max-width: 220px; max-width: 220px;
} }
.custom-search-input >>> .el-input__inner { .custom-search-input>>>.el-input__inner {
height: 30px; height: 30px;
border-radius: 15px; border-radius: 15px;
background-color: #e2e5f8; background-color: #fff;
border: 1px solid #e4e6ef; border: 1px solid #e4e6ef;
padding-left: 15px; padding-left: 15px;
font-size: 12px; font-size: 12px;
@@ -311,7 +312,7 @@ export default {
max-width: 145px; max-width: 145px;
} }
.custom-search-input >>> .el-input__inner { .custom-search-input>>>.el-input__inner {
padding-left: 10px; padding-left: 10px;
font-size: 11px; font-size: 11px;
} }
@@ -1,92 +1,104 @@
<template> <template>
<el-dialog :visible.sync="dialogVisible" width="800px" center> <el-dialog :visible.sync="dialogVisible" width="975px" center custom-class="custom-dialog" :show-close="false"
<el-form :model="form" ref="form" label-width="70px"> class="center-dialog">
<el-row :gutter="20" class="form-row"> <div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
<el-col :span="12"> <div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
<el-form-item label="模型编码"> 修改模型
<el-input v-model="form.code" placeholder="请输入内容" /> </div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="模型名称">
<el-input v-model="form.name" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
<!-- 供应商 --> <button class="custom-close-btn" @click="dialogVisible = false">
<el-row :gutter="20" class="form-row"> ×
<el-col :span="12"> </button>
<el-form-item label="供应器">
<el-select v-model="form.supplier" placeholder="请选择"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<el-option label="openai" value="openai" /> <div style="font-size: 20px; font-weight: bold; color: #3d4566;">模型信息</div>
<el-option label="dify" value="dify" /> <div style="display: flex; align-items: center; gap: 20px;">
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">是否启用</span>
<el-switch v-model="form.isEnable" class="custom-switch"></el-switch>
</div>
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">设为默认</span>
<el-switch v-model="form.isDefault" class="custom-switch"></el-switch>
</div>
</div>
</div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="form" ref="form" label-width="100px" label-position="left" class="custom-form">
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="模型名称" prop="name" style="flex: 1;">
<el-input v-model="form.name" placeholder="请输入模型名称" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="模型编码" prop="code" style="flex: 1;">
<el-input v-model="form.code" placeholder="请输入模型编码" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="供应器" prop="supplier" style="flex: 1;">
<el-select v-model="form.supplier" placeholder="请选择" class="custom-select custom-input-bg"
style="width: 100%;" @focus="loadProviders" filterable>
<el-option v-for="item in providers" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> <el-form-item label="排序号" prop="sort" style="flex: 1;">
<el-col :span="12"> <el-input v-model="form.sort" placeholder="请输入排序号" class="custom-input-bg"></el-input>
<el-form-item>
<div style="display: flex; align-items: center;">
<span class="el-form-item__label" style="width: 70px;">设成默认</span>
<el-switch v-model="form.isDefault" style="margin-right: 20px;" />
<span class="el-form-item__label" style="width: 70px;">是否启用</span>
<el-switch v-model="form.isEnable" />
</div>
</el-form-item> </el-form-item>
</el-col> </div>
</el-row>
<!-- 文档 --> <el-form-item label="文档地址" prop="docUrl" style="margin-bottom: 27px;">
<el-row :gutter="20" class="form-row"> <el-input v-model="form.docUrl" placeholder="请输入文档地址" class="custom-input-bg"></el-input>
<el-col :span="12"> </el-form-item>
<el-form-item label="文档地址">
<el-input v-model="form.docUrl" placeholder="请输入内容" /> <el-form-item label="备注" prop="remark" class="prop-remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入模型备注"
class="custom-input-bg"></el-input>
</el-form-item>
</el-form>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">调用信息</div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 15px;"></div>
<el-form :model="form" label-width="100px" label-position="left" class="custom-form">
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
<el-form-item label="模型名称" prop="modelName" style="flex: 0.5; margin-bottom: 0;">
<el-input v-model="form.modelName" placeholder="请输入model_name" class="custom-input-bg"></el-input>
</el-form-item> </el-form-item>
</el-col> <el-form-item label="接口地址" prop="apiUrl" style="flex: 1; margin-bottom: 0;">
<el-col :span="12"> <el-input v-model="form.apiUrl" placeholder="请输入base_url" class="custom-input-bg"></el-input>
<el-form-item label="排序号">
<el-input-number v-model="form.sort" :min="1" :max="999" controls-position="right" placeholder="123"
/>
</el-form-item> </el-form-item>
</el-col> </div>
</el-row>
<!-- 备注 --> <el-form-item label="秘钥信息" prop="apiKey">
<el-form-item label="备注" class="form-row"> <el-input v-model="form.apiKey" placeholder="请输入api_key" show-password class="custom-input-bg"></el-input>
<el-input type="textarea" v-model="form.remark" :rows="2" placeholder="请输入" </el-form-item>
/> </el-form>
</el-form-item> </div>
<div class="vertical-fields"> <div style="display: flex;justify-content: center;">
<el-form-item label="接口地址"> <el-button type="primary" @click="handleSave" class="save-btn">
<el-input v-model="form.apiUrl" placeholder="请输入base_url" /> 保存
</el-form-item> </el-button>
<el-form-item label="模型名称">
<el-input v-model="form.modelName" placeholder="请输入model_name" />
</el-form-item>
<el-form-item label="密钥信息">
<el-input v-model="form.apiKey" placeholder="请输入api_key" show-password/>
</el-form-item>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleSave">保存</el-button>
<el-button @click="dialogVisible = false">关闭</el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import Api from '@/apis/api';
export default { export default {
name: "ModelConfigDialog", name: "ModelConfigDialog",
props: { props: {
visible: { type: Boolean, default: false }, visible: { type: Boolean, default: false },
configData: { type: Object, default: () => ({}) } configData: { type: Object, default: () => ({}) },
modelType: { type: String, required: true }
}, },
data() { data() {
return { return {
dialogVisible: this.visible, dialogVisible: this.visible,
providers: [],
providersLoaded: false,
form: { form: {
code: "", code: "",
name: "", name: "",
@@ -103,57 +115,213 @@ export default {
}; };
}, },
watch: { watch: {
modelType() {
this.resetProviders()
},
dialogVisible(val) { dialogVisible(val) {
this.$emit('update:visible', val); this.$emit('update:visible', val);
if (!val) {
this.form = {
code: "",
name: "",
supplier: "",
};
}
}, },
visible(val) { visible(val) {
this.dialogVisible = val; this.dialogVisible = val;
if (val) this.form = { ...this.form, ...this.configData }; if (val) {
} this.form = JSON.parse(JSON.stringify({
...this.form,
...this.configData
}));
this.resetProviders();
}
},
}, },
methods: { methods: {
resetProviders() {
this.providers = []
this.providersLoaded = false
},
handleSave() { handleSave() {
this.$emit("submit", this.form); this.$emit("submit", this.form);
this.dialogVisible = false; this.dialogVisible = false;
} },
loadProviders() {
if (this.providersLoaded) return
Api.model.getModelProviders(this.modelType, (data) => {
this.providers = data.map(item => ({
label: item.name,
value: item.providerCode
}))
this.providersLoaded = true
})
},
} }
}; };
</script> </script>
<style scoped> <style scoped>
.dialog-footer { .custom-dialog {
margin-top: -30px; position: relative;
text-align: center; border-radius: 20px;
} overflow: hidden;
.el-form-item { background: white;
margin-bottom: 18px; padding-bottom: 17px;
}
.el-input-number {
width: 100%;
}
.form-row {
margin-bottom: 12px;
} }
.vertical-fields { .custom-dialog .el-dialog__header {
margin-top: 8px; padding: 0;
border-bottom: none;
} }
.dialog-footer { .center-dialog {
margin-top: -10px; display: flex;
text-align: center; align-items: center;
justify-content: center;
} }
.el-input-number {
.center-dialog .el-dialog {
margin: 4% 0 auto !important;
display: flex;
flex-direction: column;
}
.custom-close-btn {
position: absolute;
top: 20px;
right: 20px;
width: 35px;
height: 35px;
border-radius: 50%;
border: 2px solid #cfcfcf;
background: none;
font-size: 30px;
font-weight: lighter;
color: #cfcfcf;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
padding: 0;
outline: none;
}
.custom-close-btn:hover {
color: #409EFF;
border-color: #409EFF;
}
.custom-select .el-input__suffix {
background: #e6e8ea;
right: 6px;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
top: 9px;
}
.custom-select .el-input__suffix-inner {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
} }
.el-input__inner { .custom-select .el-icon-arrow-up:before {
height: 36px; content: "";
line-height: 36px; display: inline-block;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 7px solid #c0c4cc;
position: relative;
top: -2px;
transform: rotate(180deg);
} }
.el-form-item__label { .custom-form .el-form-item {
text-align: left; margin-bottom: 20px;
padding-right: 10px; }
.custom-form .el-form-item__label {
color: #3d4566;
font-weight: normal;
text-align: right;
padding-right: 20px;
}
.custom-form .el-form-item.prop-remark .el-form-item__label {
margin-top: -4px;
}
.custom-input-bg .el-input__inner::-webkit-input-placeholder,
.custom-input-bg .el-textarea__inner::-webkit-input-placeholder {
color: #9c9f9e;
}
.custom-input-bg .el-input__inner,
.custom-input-bg .el-textarea__inner {
background-color: #f6f8fc;
}
.save-btn {
background: #e6f0fd;
color: #237ff4;
border: 1px solid #b3d1ff;
width: 150px;
height: 40px;
font-size: 16px;
transition: all 0.3s ease;
}
.save-btn:hover {
background: linear-gradient(to right, #237ff4, #9c40d5);
color: white;
border: none;
}
.custom-switch .el-switch__core {
border-radius: 20px;
height: 23px;
background-color: #c0ccda;
width: 35px;
padding: 0 20px;
}
.custom-switch .el-switch__core:after {
width: 15px;
height: 15px;
background-color: white;
top: 3px;
left: 4px;
transition: all .3s;
}
.custom-switch.is-checked .el-switch__core {
border-color: #b5bcf0;
background-color: #cfd7fa;
padding: 0 20px;
}
.custom-switch.is-checked .el-switch__core:after {
left: 100%;
margin-left: -18px;
background-color: #1b47ee;
}
[style*="display: flex"] {
gap: 20px;
}
.custom-input-bg .el-input__inner {
height: 32px;
} }
</style> </style>
+6 -6
View File
@@ -1,11 +1,11 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'; import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css'; import 'element-ui/lib/theme-chalk/index.css';
import './styles/global.scss' import 'normalize.css/normalize.css'; // A modern alternative to CSS resets
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './styles/global.scss';
Vue.use(ElementUI); Vue.use(ElementUI);
+5 -5
View File
@@ -56,11 +56,11 @@ const routes = [
} }
}, },
{ {
path: '/model-config', path: '/model-config',
name: 'ModelConfig', name: 'ModelConfig',
component: function () { component: function () {
return import('../views/ModelConfig.vue') return import('../views/ModelConfig.vue')
} }
}, },
{ {
path: '/test', path: '/test',
+5 -5
View File
@@ -1,7 +1,7 @@
import Vue from 'vue' import { goToPage } from "@/utils";
import Vuex from 'vuex' import Vue from 'vue';
import Constant from '../utils/constant' import Vuex from 'vuex';
import {goToPage} from "@/utils"; import Constant from '../utils/constant';
Vue.use(Vuex) Vue.use(Vuex)
@@ -52,7 +52,7 @@ export default new Vuex.Store({
logout({ commit }) { logout({ commit }) {
return new Promise((resolve) => { return new Promise((resolve) => {
commit('clearAuth') commit('clearAuth')
goToPage(Constant.PAGE.LOGIN,true); goToPage(Constant.PAGE.LOGIN, true);
window.location.reload(); // 彻底重置状态 window.location.reload(); // 彻底重置状态
}) })
} }
+1 -1
View File
@@ -1,6 +1,6 @@
// 覆盖 autofill 样式 // 覆盖 autofill 样式
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus, input:-webkit-autofill:focus,
textarea:-webkit-autofill, textarea:-webkit-autofill,
textarea:-webkit-autofill:hover, textarea:-webkit-autofill:hover,
+1 -1
View File
@@ -1,6 +1,6 @@
import { Message } from 'element-ui'
import router from '../router' import router from '../router'
import Constant from '../utils/constant' import Constant from '../utils/constant'
import { Message } from 'element-ui'
/** /**
* 判断用户是否登录 * 判断用户是否登录
+47 -61
View File
@@ -15,7 +15,8 @@
<el-table-column label="最近对话" prop="lastConversation" width="140"></el-table-column> <el-table-column label="最近对话" prop="lastConversation" width="140"></el-table-column>
<el-table-column label="备注" width="180"> <el-table-column label="备注" width="180">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-if="scope.row.isEdit" v-model="scope.row.remark" size="mini" @blur="stopEditRemark(scope.$index)"></el-input> <el-input v-if="scope.row.isEdit" v-model="scope.row.remark" size="mini"
@blur="stopEditRemark(scope.$index)"></el-input>
<span v-else> <span v-else>
<i v-if="!scope.row.remark" class="el-icon-edit" @click="startEditRemark(scope.$index, scope.row)"></i> <i v-if="!scope.row.remark" class="el-icon-edit" @click="startEditRemark(scope.$index, scope.row)"></i>
<span v-else @click="startEditRemark(scope.$index, scope.row)"> <span v-else @click="startEditRemark(scope.$index, scope.row)">
@@ -26,7 +27,8 @@
</el-table-column> </el-table-column>
<el-table-column label="OTA升级" width="120"> <el-table-column label="OTA升级" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66" inactive-color="#ff4949"></el-switch> <el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66"
inactive-color="#ff4949"></el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="80"> <el-table-column label="操作" width="80">
@@ -37,31 +39,26 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination class="pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange"
class="pagination" :current-page="currentPage" :page-sizes="[5, 10, 20, 50]" :page-size="pageSize"
@size-change="handleSizeChange" layout="total, sizes, prev, pager, next, jumper" :total="deviceList.length"></el-pagination>
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="deviceList.length"
></el-pagination>
</div> </div>
<div class="copyright"> <div class="copyright">
©2025 xiaozhi-esp32-server ©2025 xiaozhi-esp32-server
</div> </div>
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId" @refresh="fetchBindDevices(currentAgentId)" /> <AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId"
@refresh="fetchBindDevices(currentAgentId)" />
</el-main> </el-main>
</div> </div>
</template> </template>
<script> <script>
import HeaderBar from "@/components/HeaderBar.vue"; import Api from '@/apis/api';
import AddDeviceDialog from "@/components/AddDeviceDialog.vue"; import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
export default { export default {
components: {HeaderBar, AddDeviceDialog }, components: { HeaderBar, AddDeviceDialog },
data() { data() {
return { return {
addDeviceDialogVisible: false, addDeviceDialogVisible: false,
@@ -82,12 +79,9 @@ export default {
}, },
mounted() { mounted() {
const agentId = this.$route.query.agentId; const agentId = this.$route.query.agentId;
import('@/apis/module/device').then(({ default: deviceApi }) => { if (agentId) {
this.deviceApi = deviceApi; this.fetchBindDevices(agentId);
if (agentId) { }
this.fetchBindDevices(agentId);
}
});
}, },
methods: { methods: {
handleAddDevice() { handleAddDevice() {
@@ -100,26 +94,22 @@ export default {
this.deviceList[index].isEdit = false; this.deviceList[index].isEdit = false;
}, },
handleUnbind(device_id) { handleUnbind(device_id) {
if (!this.deviceApi) {
this.$message.error('功能模块加载失败');
return;
}
this.$confirm('确认要解绑该设备吗?', '警告', { this.$confirm('确认要解绑该设备吗?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.deviceApi.unbindDevice(device_id, ({ data }) => { Api.device.unbindDevice(device_id, ({ data }) => {
if (data.code === 0) { if (data.code === 0) {
this.$message.success({ this.$message.success({
message: '设备解绑成功', message: '设备解绑成功',
showClose: true showClose: true
}); });
this.fetchBindDevices(this.$route.query.agentId); this.fetchBindDevices(this.$route.query.agentId);
} else { } else {
this.$message.error({ this.$message.error({
message: data.msg || '设备解绑失败', message: data.msg || '设备解绑失败',
showClose: true showClose: true
}); });
} }
}); });
@@ -133,38 +123,33 @@ export default {
}, },
fetchBindDevices(agentId) { fetchBindDevices(agentId) {
this.loading = true; this.loading = true;
import('@/apis/module/device').then(({ default: deviceApi }) => { Api.device.getAgentBindDevices(agentId, ({ data }) => {
deviceApi.getAgentBindDevices(agentId, ({ data }) => { this.loading = false;
this.loading = false; if (data.code === 0) {
if (data.code === 0) { // 格式化日期并按照绑定时间降序排列
// 格式化日期并按照绑定时间降序排列 this.deviceList = data.data.map(device => {
this.deviceList = data.data.map(device => { // 格式化绑定时间
// 格式化绑定时间 const bindDate = new Date(device.createDate);
const bindDate = new Date(device.createDate); const formattedBindTime = `${bindDate.getFullYear()}-${(bindDate.getMonth() + 1).toString().padStart(2, '0')}-${bindDate.getDate().toString().padStart(2, '0')} ${bindDate.getHours().toString().padStart(2, '0')}:${bindDate.getMinutes().toString().padStart(2, '0')}:${bindDate.getSeconds().toString().padStart(2, '0')}`;
const formattedBindTime = `${bindDate.getFullYear()}-${(bindDate.getMonth()+1).toString().padStart(2, '0')}-${bindDate.getDate().toString().padStart(2, '0')} ${bindDate.getHours().toString().padStart(2, '0')}:${bindDate.getMinutes().toString().padStart(2, '0')}:${bindDate.getSeconds().toString().padStart(2, '0')}`; return {
return { device_id: device.id,
device_id: device.id, model: device.board,
model: device.board, firmwareVersion: device.appVersion,
firmwareVersion: device.appVersion, macAddress: device.macAddress,
macAddress: device.macAddress, bindTime: formattedBindTime, // 使用格式化后的时间
bindTime: formattedBindTime, // 使用格式化后的时间 lastConversation: device.lastConnectedAt,
lastConversation: device.lastConnectedAt, remark: device.alias,
remark: device.alias, isEdit: false,
isEdit: false, otaSwitch: device.autoUpdate === 1,
otaSwitch: device.autoUpdate === 1, // 添加原始时间用于排序
// 添加原始时间用于排序 rawBindTime: new Date(device.createDate).getTime()
rawBindTime: new Date(device.createDate).getTime() };
}; })
})
// 按照绑定时间降序排序 // 按照绑定时间降序排序
.sort((a, b) => a.rawBindTime - b.rawBindTime); .sort((a, b) => a.rawBindTime - b.rawBindTime);
} else { } else {
this.$message.error(data.msg || '获取设备列表失败'); this.$message.error(data.msg || '获取设备列表失败');
} }
});
}).catch(error => {
console.error('模块加载失败:', error);
this.$message.error('功能模块加载失败');
}); });
}, },
} }
@@ -179,7 +164,7 @@ export default {
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-image: url("@/assets/home/background.png"); background: linear-gradient(145deg, #e6eeff, #eff0ff);
background-size: cover; background-size: cover;
background-position: center; background-position: center;
-webkit-background-size: cover; -webkit-background-size: cover;
@@ -207,6 +192,7 @@ export default {
font-size: 14px; font-size: 14px;
gap: 8px; gap: 8px;
margin-bottom: 15px; margin-bottom: 15px;
&:hover { &:hover {
background: #3a8ee6; background: #3a8ee6;
} }
+167 -80
View File
@@ -2,26 +2,26 @@
<div class="welcome"> <div class="welcome">
<HeaderBar /> <HeaderBar />
<div class="operation-bar"> <div class="operation-bar">
<h2 class="page-title">模型配置</h2> <h2 class="page-title">模型配置</h2>
<div class="right-operations"> <div class="right-operations">
<el-button plain size="small" @click="handleImport" style="background: #7b9de5; color: white;"> <el-button plain size="small" @click="handleImport" style="background: #7b9de5; color: white;">
<img loading="lazy" alt="" src="@/assets/model/inner_conf.png"> <img loading="lazy" alt="" src="@/assets/model/inner_conf.png">
导入配置 导入配置
</el-button> </el-button>
<el-button plain size="small" @click="handleExport" style="background: #71c9d1; color: white;"> <el-button plain size="small" @click="handleExport" style="background: #71c9d1; color: white;">
<img loading="lazy" alt="" src="@/assets/model/output_conf.png"> <img loading="lazy" alt="" src="@/assets/model/output_conf.png">
导出配置 导出配置
</el-button> </el-button>
</div>
</div> </div>
</div>
<!-- 主体内容 --> <!-- 主体内容 -->
<div class="main-wrapper"> <div class="main-wrapper">
<div class="content-panel"> <div class="content-panel">
<!-- 左侧导航 --> <!-- 左侧导航 -->
<el-menu :default-active="activeTab" class="nav-panel" @select="handleMenuSelect" <el-menu :default-active="activeTab" class="nav-panel" @select="handleMenuSelect"
style="background-size: cover; background-position: center;"> style="background-size: cover; background-position: center;">
<el-menu-item index="vad"> <el-menu-item index="vad">
<span class="menu-text">语言活动检测</span> <span class="menu-text">语言活动检测</span>
</el-menu-item> </el-menu-item>
@@ -46,14 +46,14 @@
<div class="content-area"> <div class="content-area">
<div class="title-bar"> <div class="title-bar">
<div class="title-wrapper"> <div class="title-wrapper">
<h2 class="model-title">大语言模型LLM</h2> <h2 class="model-title">{{ modelTypeText }}</h2>
<el-button type="primary" size="small" @click="addModel" class="add-btn"> <el-button type="primary" size="small" @click="addModel" class="add-btn">
添加 添加
</el-button> </el-button>
</div> </div>
<div class="action-group"> <div class="action-group">
<div class="search-group"> <div class="search-group">
<el-input placeholder="请输入模型名称查询" v-model="search" size="small" class="search-input" clearable/> <el-input placeholder="请输入模型名称查询" v-model="search" size="small" class="search-input" clearable />
<el-button type="primary" size="small" class="search-btn" @click="handleSearch"> <el-button type="primary" size="small" class="search-btn" @click="handleSearch">
查询 查询
</el-button> </el-button>
@@ -61,14 +61,21 @@
</div> </div>
</div> </div>
<el-table ref="modelTable" style="width: 100%" :header-cell-style="{background: 'transparent'}" :data="modelList" class="data-table" header-row-class-name="table-header" :header-cell-class-name="headerCellClassName" @selection-change="handleSelectionChange"> <el-table ref="modelTable" style="width: 100%" :header-cell-style="{ background: 'transparent' }"
:data="modelList" class="data-table" header-row-class-name="table-header"
:header-cell-class-name="headerCellClassName" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center"></el-table-column> <el-table-column type="selection" width="55" align="center"></el-table-column>
<el-table-column label="模型名称" prop="candidateName" align="center"></el-table-column> <el-table-column label="模型名称" prop="modelName" align="center"></el-table-column>
<el-table-column label="模型编码" prop="code" align="center"></el-table-column> <el-table-column label="模型编码" prop="modelCode" align="center"></el-table-column>
<el-table-column label="提供商" prop="supplier" align="center"></el-table-column> <el-table-column label="提供商" align="center">
<template slot-scope="scope">
{{ scope.row.configJson?.provider || '未知' }}
</template>
</el-table-column>
<el-table-column label="是否启用" align="center"> <el-table-column label="是否启用" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch v-model="scope.row.isApplied" class="custom-switch" :active-color="null" :inactive-color="null"/> <el-switch v-model="scope.row.isEnabled" class="custom-switch" :active-value="1" :inactive-value="0"
:active-color="null" :inactive-color="null" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="activeTab === 'tts'" label="音色管理" align="center"> <el-table-column v-if="activeTab === 'tts'" label="音色管理" align="center">
@@ -92,7 +99,9 @@
<div class="table-footer"> <div class="table-footer">
<div class="batch-actions"> <div class="batch-actions">
<el-button size="mini" @click="selectAll" style="width: 75px; background: #606ff3">{{ isAllSelected ? '取消全选' : '全选' }}</el-button> <el-button size="mini" @click="selectAll" style="width: 75px; background: #606ff3">{{ isAllSelected ?
'取消全选' : '全选'
}}</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete"> <el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">
删除 删除
</el-button> </el-button>
@@ -101,7 +110,8 @@
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button> <button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</button> <button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</button>
<button v-for="page in visiblePages" :key="page" class="pagination-btn" :class="{ active: page === currentPage }" @click="goToPage(page)"> <button v-for="page in visiblePages" :key="page" class="pagination-btn"
:class="{ active: page === currentPage }" @click="goToPage(page)">
{{ page }} {{ page }}
</button> </button>
@@ -112,22 +122,24 @@
</div> </div>
</div> </div>
<ModelEditDialog :visible.sync="editDialogVisible" :modelData="editModelData" @save="handleModelSave"/> <ModelEditDialog :modelType="activeTab" :visible.sync="editDialogVisible" :modelData="editModelData"
@save="handleModelSave" />
<TtsModel :visible.sync="ttsDialogVisible" /> <TtsModel :visible.sync="ttsDialogVisible" />
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm"/> <AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm" />
</div> </div>
<div class="copyright"> <div class="copyright">
©2025 xiaozhi-esp32-server ©2025 xiaozhi-esp32-server
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import Api from "@/apis/api";
import AddModelDialog from "@/components/AddModelDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue"; import HeaderBar from "@/components/HeaderBar.vue";
import ModelEditDialog from "@/components/ModelEditDialog.vue"; import ModelEditDialog from "@/components/ModelEditDialog.vue";
import TtsModel from "@/components/TtsModel.vue"; import TtsModel from "@/components/TtsModel.vue";
import AddModelDialog from "@/components/AddModelDialog.vue";
export default { export default {
components: { HeaderBar, ModelEditDialog, TtsModel, AddModelDialog }, components: { HeaderBar, ModelEditDialog, TtsModel, AddModelDialog },
@@ -139,20 +151,34 @@ export default {
editDialogVisible: false, editDialogVisible: false,
editModelData: {}, editModelData: {},
ttsDialogVisible: false, ttsDialogVisible: false,
modelList: [ modelList: [],
{ code: 'DeepSeek', candidateName: '深度求索', isApplied: true, supplier: '硅基流动' },
{ code: 'SmartAssist', candidateName: '智能助手', isApplied: false, supplier: '智脑科技' },
{ code: 'CogEngine', candidateName: '认知引擎', isApplied: true, supplier: '云智科技' },
],
currentPage: 1, currentPage: 1,
pageSize: 4, pageSize: 5,
total: 20, total: 0,
selectedModels: [], selectedModels: [],
isAllSelected: false isAllSelected: false
}; };
}, },
created() {
this.loadData();
},
computed: { computed: {
modelTypeText() {
const map = {
vad: '语言活动检测模型(VAD)',
asr: '语音识别模型(ASR)',
llm: '大语言模型(LLM',
intent: '意图识别模型(Intent)',
tts: '语音合成模型(TTS)',
memory: '记忆模型(Memory)'
}
return map[this.activeTab] || '模型配置'
},
pageCount() { pageCount() {
return Math.ceil(this.total / this.pageSize); return Math.ceil(this.total / this.pageSize);
}, },
@@ -182,64 +208,92 @@ export default {
}, },
handleMenuSelect(index) { handleMenuSelect(index) {
this.activeTab = index; this.activeTab = index;
this.currentPage = 1;
this.loadData();
}, },
handleSearch() { handleSearch() {
// TODO: 查询
console.log('查询:', this.search); console.log('查询:', this.search);
}, },
// 批量删除
batchDelete() { batchDelete() {
if (this.selectedModels.length === 0) { if (this.selectedModels.length === 0) {
this.$message.warning('请先选择要删除的模型'); this.$message.warning('请先选择要删除的模型')
return; return
} }
this.$confirm('确定要删除选中的模型吗?', '提示', { this.$confirm('确定要删除选中的模型吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
const selectedIds = this.selectedModels.map(model => model.code); const deletePromises = this.selectedModels.map(model =>
this.modelList = this.modelList.filter(model => !selectedIds.includes(model.code)); new Promise(resolve => {
this.$message.success('删除成功'); // TODO: 删除获取model.id
this.selectedModels = []; Api.model.deleteModel(
this.isAllSelected = false; model.id,
({ data }) => resolve(data.code === 0)
)
})
)
Promise.all(deletePromises).then(results => {
if (results.every(Boolean)) {
this.$message.success('批量删除成功')
this.loadData()
} else {
this.$message.error('部分删除失败')
}
})
}).catch(() => { }).catch(() => {
this.$message.info('已取消删除'); this.$message.info('已取消删除')
}); })
}, },
addModel() { addModel() {
this.addDialogVisible = true; this.addDialogVisible = true;
}, },
editModel(model) { editModel(model) {
this.editModelData = { this.editModelData = JSON.parse(JSON.stringify(model));
code: model.code,
name: model.candidateName,
supplier: model.supplier,
};
this.editDialogVisible = true; this.editDialogVisible = true;
}, },
// 删除单个模型
deleteModel(model) { deleteModel(model) {
this.$confirm(`确定要删除模型 ${model.candidateName} 吗?`, '提示', { this.$confirm('确定要删除模型吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.modelList = this.modelList.filter(item => item.code !== model.code); Api.model.deleteModel(
this.$message.success('删除成功'); this.activeTab,
model.configJson?.provider || '', // 从configJson获取provider
model.id,
({ data }) => {
if (data.code === 0) {
this.$message.success('删除成功')
this.loadData()
} else {
this.$message.error(data.msg || '删除失败')
}
}
)
}).catch(() => { }).catch(() => {
this.$message.info('已取消删除'); this.$message.info('已取消删除')
}); })
}, },
handleCurrentChange(page) { handleCurrentChange(page) {
this.currentPage = page; this.currentPage = page;
this.$refs.modelTable.clearSelection(); this.$refs.modelTable.clearSelection();
console.log('当前页码:', page);
}, },
handleImport() { handleImport() {
// TODO: 导入配置
console.log('导入配置'); console.log('导入配置');
}, },
handleExport() { handleExport() {
// TODO: 导出配置
console.log('导出配置'); console.log('导出配置');
}, },
handleModelSave(formData) { handleModelSave(formData) {
// TODO: 保存模型数据
console.log('保存的模型数据:', formData); console.log('保存的模型数据:', formData);
}, },
selectAll() { selectAll() {
@@ -256,14 +310,33 @@ export default {
this.isAllSelected = false; this.isAllSelected = false;
} }
}, },
// 新增模型配置
handleAddConfirm(newModel) { handleAddConfirm(newModel) {
console.log('新增模型数据:', newModel); const params = {
modelType: this.activeTab,
provideCode: newModel.supplier,
formData: {
...newModel,
isDefault: newModel.isDefault ? 1 : 0,
isEnabled: newModel.isEnabled ? 1 : 0
}
};
Api.model.addModel(params, ({ data }) => {
if (data.code === 0) {
this.$message.success('新增成功');
this.loadData();
} else {
this.$message.error(data.msg || '新增失败');
}
});
}, },
// 分页器 // 分页器
goFirst() { goFirst() {
this.currentPage = 1; this.currentPage = 1;
this.loadData(); this.loadData();
}, },
goPrev() { goPrev() {
if (this.currentPage > 1) { if (this.currentPage > 1) {
@@ -281,15 +354,31 @@ export default {
this.currentPage = page; this.currentPage = page;
this.loadData(); this.loadData();
}, },
// 获取模型配置列表
loadData() { loadData() {
console.log('加载数据,当前页:', this.currentPage); const params = {
modelType: this.activeTab,
modelName: this.search,
page: this.currentPage,
limit: this.pageSize
};
Api.model.getModelList(params, ({ data }) => {
if (data.code === 0) {
this.modelList = data.data.list;
this.total = data.data.total;
} else {
this.$message.error(data.msg || '获取模型列表失败');
}
});
} }
}, },
}; };
</script> </script>
<style scoped> <style scoped>
::v-deep .el-table tr{ ::v-deep .el-table tr {
background: transparent; background: transparent;
} }
@@ -307,12 +396,12 @@ export default {
} }
.main-wrapper { .main-wrapper {
margin: 5px 60px; margin: 5px 20px;
border-radius: 15px; border-radius: 15px;
min-height: 600px; min-height: 600px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: relative; position: relative;
background: rgba(237,242,255,0.5); background: rgba(237, 242, 255, 0.5);
} }
.operation-bar { .operation-bar {
@@ -320,7 +409,6 @@ export default {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 16px 24px; padding: 16px 24px;
border-bottom: 1px solid #ebeef5;
} }
.page-title { .page-title {
@@ -341,6 +429,7 @@ export default {
height: 100%; height: 100%;
border-radius: 15px; border-radius: 15px;
background: transparent; background: transparent;
border: 1px solid #fff;
} }
.nav-panel { .nav-panel {
@@ -348,12 +437,10 @@ export default {
height: 100%; height: 100%;
border-right: 1px solid #ebeef5; border-right: 1px solid #ebeef5;
background: background:
linear-gradient( linear-gradient(120deg,
120deg,
rgba(107, 140, 255, 0.3) 0%, rgba(107, 140, 255, 0.3) 0%,
rgba(169, 102, 255, 0.3) 25%, rgba(169, 102, 255, 0.3) 25%,
transparent 60% transparent 60%),
),
url("../assets/model/model.png") no-repeat center / cover; url("../assets/model/model.png") no-repeat center / cover;
padding: 16px 0; padding: 16px 0;
flex-shrink: 0; flex-shrink: 0;
@@ -538,7 +625,8 @@ export default {
} }
::v-deep .el-table .custom-selection-header .cell .el-checkbox__inner { ::v-deep .el-table .custom-selection-header .cell .el-checkbox__inner {
display: none !important; /* 使表头复选框不可见 */ display: none !important;
/* 使表头复选框不可见 */
} }
::v-deep .el-table .custom-selection-header .cell::before { ::v-deep .el-table .custom-selection-header .cell::before {
@@ -564,6 +652,7 @@ export default {
} }
::v-deep .data-table { ::v-deep .data-table {
&.el-table::before, &.el-table::before,
&.el-table::after, &.el-table::after,
&.el-table__inner-wrapper::before { &.el-table__inner-wrapper::before {
@@ -572,14 +661,14 @@ export default {
} }
::v-deep .data-table .el-table__header-wrapper { ::v-deep .data-table .el-table__header-wrapper {
border-bottom: 1px solid rgb(224,227,237); border-bottom: 1px solid rgb(224, 227, 237);
} }
::v-deep .data-table .el-table__body td { ::v-deep .data-table .el-table__body td {
border-bottom: 1px solid rgb(224,227,237) !important; border-bottom: 1px solid rgb(224, 227, 237) !important;
} }
.el-button img{ .el-button img {
height: 1em; height: 1em;
vertical-align: middle; vertical-align: middle;
padding-right: 2px; padding-right: 2px;
@@ -607,7 +696,8 @@ export default {
} }
.voice-management-btn:hover { .voice-management-btn:hover {
background: #8aa2e0; /* 悬停时颜色加深 */ background: #8aa2e0;
/* 悬停时颜色加深 */
transform: scale(1.05); transform: scale(1.05);
} }
@@ -619,7 +709,8 @@ export default {
padding-right: 15px !important; padding-right: 15px !important;
} }
.edit-btn, .delete-btn { .edit-btn,
.delete-btn {
margin: 0 8px; margin: 0 8px;
color: #7079aa !important; color: #7079aa !important;
} }
@@ -637,7 +728,6 @@ export default {
margin-top: 15px; margin-top: 15px;
/* 导航按钮样式 (首页、上一页、下一页) */ /* 导航按钮样式 (首页、上一页、下一页) */
.pagination-btn:first-child, .pagination-btn:first-child,
.pagination-btn:nth-child(2), .pagination-btn:nth-child(2),
.pagination-btn:nth-last-child(2) { .pagination-btn:nth-last-child(2) {
@@ -663,7 +753,6 @@ export default {
} }
/* 数字按钮样式 */ /* 数字按钮样式 */
.pagination-btn:not(:first-child):not(:nth-child(2)):not(:nth-last-child(2)) { .pagination-btn:not(:first-child):not(:nth-child(2)):not(:nth-last-child(2)) {
min-width: 28px; min-width: 28px;
height: 32px; height: 32px;
@@ -697,6 +786,4 @@ export default {
margin-left: 10px; margin-left: 10px;
} }
} }
</style> </style>
+146 -131
View File
@@ -5,7 +5,8 @@
<div class="operation-bar"> <div class="operation-bar">
<h2 class="page-title">用户管理</h2> <h2 class="page-title">用户管理</h2>
<div class="right-operations"> <div class="right-operations">
<el-input placeholder="请输入手机号码查询" v-model="searchPhone" class="search-input" @keyup.enter.native="handleSearch"/> <el-input placeholder="请输入手机号码查询" v-model="searchPhone" class="search-input"
@keyup.enter.native="handleSearch" />
<el-button class="btn-search" @click="handleSearch">搜索</el-button> <el-button class="btn-search" @click="handleSearch">搜索</el-button>
</div> </div>
</div> </div>
@@ -14,7 +15,8 @@
<div class="content-panel"> <div class="content-panel">
<div class="content-area"> <div class="content-area">
<el-card class="user-card" shadow="never"> <el-card class="user-card" shadow="never">
<el-table ref="userTable" :data="userList" class="transparent-table" :header-cell-class-name="headerCellClassName"> <el-table ref="userTable" :data="userList" class="transparent-table"
:header-cell-class-name="headerCellClassName">
<el-table-column label="选择" type="selection" align="center" width="120"></el-table-column> <el-table-column label="选择" type="selection" align="center" width="120"></el-table-column>
<el-table-column label="用户Id" prop="userid" align="center"></el-table-column> <el-table-column label="用户Id" prop="userid" align="center"></el-table-column>
<el-table-column label="手机号码" prop="mobile" align="center"></el-table-column> <el-table-column label="手机号码" prop="mobile" align="center"></el-table-column>
@@ -22,14 +24,12 @@
<el-table-column label="状态" prop="status" align="center"></el-table-column> <el-table-column label="状态" prop="status" align="center"></el-table-column>
<el-table-column label="操作" align="center"> <el-table-column label="操作" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" @click="resetPassword(scope.row)" style="color: #989fdd">重置密码</el-button> <el-button size="mini" type="text" @click="resetPassword(scope.row)">重置密码</el-button>
<el-button size="mini" type="text" <el-button size="mini" type="text" v-if="scope.row.status === '正常'"
v-if="scope.row.status === '正常'"
@click="disableUser(scope.row)">禁用账户</el-button> @click="disableUser(scope.row)">禁用账户</el-button>
<el-button size="mini" type="text" <el-button size="mini" type="text" v-if="scope.row.status === '禁用'"
v-if="scope.row.status === '禁用'"
@click="restoreUser(scope.row)">恢复账号</el-button> @click="restoreUser(scope.row)">恢复账号</el-button>
<el-button size="mini" type="text" @click="deleteUser(scope.row)" style="color: #989fdd">删除用户</el-button> <el-button size="mini" type="text" @click="deleteUser(scope.row)">删除用户</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -38,18 +38,24 @@
<div class="ctrl_btn"> <div class="ctrl_btn">
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">全选</el-button> <el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">全选</el-button>
<el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">启用</el-button> <el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">启用</el-button>
<el-button size="mini" type="warning" @click="batchDisable"><i class="el-icon-remove-outline rotated-icon"></i>禁用</el-button> <el-button size="mini" type="warning" @click="batchDisable"><i
class="el-icon-remove-outline rotated-icon"></i>禁用</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">删除</el-button> <el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">删除</el-button>
</div> </div>
<div class="custom-pagination"> <div class="custom-pagination">
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">首页</button> <button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">上一页</button> 首页
</button>
<button v-for="page in visiblePages" :key="page" class="pagination-btn" :class="{ active: page === currentPage }" @click="goToPage(page)"> <button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">
{{ page }} 上一页
</button> </button>
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">下一页</button> <button v-for="page in visiblePages" :key="page" class="pagination-btn"
:class="{ active: page === currentPage }" @click="goToPage(page)">
{{ page }}
</button> <button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
下一页
</button>
<span class="total-text">{{ total }}条记录</span> <span class="total-text">{{ total }}条记录</span>
</div> </div>
</div> </div>
@@ -58,29 +64,27 @@
</div> </div>
</div> </div>
<div class="copyright"> <div class="copyright">©2025 xiaozhi-esp32-server</div>
©2025 xiaozhi-esp32-server <view-password-dialog :visible.sync="showViewPassword" :password="currentPassword" />
</div>
<view-password-dialog :visible.sync="showViewPassword" :password="currentPassword"/>
</div> </div>
</template> </template>
<script> <script>
import Api from "@/apis/api";
import HeaderBar from "@/components/HeaderBar.vue"; import HeaderBar from "@/components/HeaderBar.vue";
import adminApi from '@/apis/module/admin'; import ViewPasswordDialog from "@/components/ViewPasswordDialog.vue";
import ViewPasswordDialog from '@/components/ViewPasswordDialog.vue'
export default { export default {
components: { HeaderBar, ViewPasswordDialog }, components: { HeaderBar, ViewPasswordDialog },
data() { data() {
return { return {
showViewPassword: false, showViewPassword: false,
currentPassword: '', currentPassword: "",
searchPhone: '', searchPhone: "",
userList: [], userList: [],
currentPage: 1, currentPage: 1,
pageSize: 5, pageSize: 5,
total: 0 total: 0,
}; };
}, },
created() { created() {
@@ -104,27 +108,30 @@ export default {
pages.push(i); pages.push(i);
} }
return pages; return pages;
} },
}, },
methods: { methods: {
fetchUsers() { fetchUsers() {
adminApi.getUserList({ Api.admin.getUserList(
page: this.currentPage, {
limit: this.pageSize, page: this.currentPage,
mobile: this.searchPhone limit: this.pageSize,
}, ({ data }) => { mobile: this.searchPhone,
if (data.code === 0) { },
this.userList = data.data.list.map(user => ({ ({ data }) => {
...user, if (data.code === 0) {
status: user.status === '1' ? '正常' : '禁用' this.userList = data.data.list.map((user) => ({
})); ...user,
this.total = data.data.total; status: user.status === "1" ? "正常" : "禁用",
} }));
}); this.total = data.data.total;
}
}
);
}, },
handleSearch() { handleSearch() {
this.currentPage = 1; this.currentPage = 1;
this.fetchUsers(); this.fetchUsers();
}, },
handleSelectAll() { handleSelectAll() {
this.$refs.userTable.toggleAllSelection(); this.$refs.userTable.toggleAllSelection();
@@ -132,118 +139,122 @@ export default {
batchDelete() { batchDelete() {
const selectedUsers = this.$refs.userTable.selection; const selectedUsers = this.$refs.userTable.selection;
if (selectedUsers.length === 0) { if (selectedUsers.length === 0) {
this.$message.warning('请先选择需要删除的用户'); this.$message.warning("请先选择需要删除的用户");
return; return;
} }
this.$confirm(`确定要删除选中的${selectedUsers.length}个用户吗?`, '警告', { this.$confirm(`确定要删除选中的${selectedUsers.length}个用户吗?`, "警告", {
confirmButtonText: '确定', confirmButtonText: "确定",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'warning' type: "warning",
}).then(async () => { })
const loading = this.$loading({ .then(async () => {
lock: true, const loading = this.$loading({
text: '正在删除中...', lock: true,
spinner: 'el-icon-loading', text: "正在删除中...",
background: 'rgba(0, 0, 0, 0.7)' spinner: "el-icon-loading",
}); background: "rgba(0, 0, 0, 0.7)",
});
try { try {
const results = await Promise.all( const results = await Promise.all(
selectedUsers.map(user => { selectedUsers.map((user) => {
return new Promise((resolve) => { return new Promise((resolve) => {
adminApi.deleteUser(user.userid, ({data}) => { Api.admin.deleteUser(user.userid, ({ data }) => {
if (data.code === 0) { if (data.code === 0) {
resolve({success: true, userid: user.userid}); resolve({ success: true, userid: user.userid });
} else { } else {
resolve({success: false, userid: user.userid, msg: data.msg}); resolve({ success: false, userid: user.userid, msg: data.msg });
} }
});
}); });
}); })
}) );
);
const successCount = results.filter(r => r.success).length; const successCount = results.filter((r) => r.success).length;
const failCount = results.length - successCount; const failCount = results.length - successCount;
if (failCount === 0) { if (failCount === 0) {
this.$message.success(`成功删除${successCount}个用户`); this.$message.success(`成功删除${successCount}个用户`);
} else if (successCount === 0) { } else if (successCount === 0) {
this.$message.error(`删除失败,请重试`); this.$message.error(`删除失败,请重试`);
} else { } else {
this.$message.warning(`成功删除${successCount}个用户,${failCount}个删除失败`); this.$message.warning(
`成功删除${successCount}个用户,${failCount}个删除失败`
);
}
this.fetchUsers();
} catch (error) {
this.$message.error("删除过程中发生错误");
} finally {
loading.close();
} }
})
this.fetchUsers(); .catch(() => {
} catch (error) { this.$message.info("已取消删除");
this.$message.error('删除过程中发生错误'); });
} finally {
loading.close();
}
}).catch(() => {
this.$message.info('已取消删除');
});
}, },
batchEnable() { batchEnable() {
const selectedUsers = this.$refs.userTable.selection; const selectedUsers = this.$refs.userTable.selection;
if (selectedUsers.length === 0) { if (selectedUsers.length === 0) {
this.$message.warning('请先选择需要启用的用户'); this.$message.warning("请先选择需要启用的用户");
return; return;
} }
selectedUsers.forEach(user => { selectedUsers.forEach((user) => {
user.status = '正常'; user.status = "正常";
}); });
this.$message.success('启用操作成功'); this.$message.success("启用操作成功");
}, },
batchDisable() { batchDisable() {
this.userList.forEach(user => { this.userList.forEach((user) => {
user.status = '禁用'; user.status = "禁用";
}); });
this.$message.success('状态已更新为禁用'); this.$message.success("状态已更新为禁用");
}, },
resetPassword(row) { resetPassword(row) {
this.$confirm('重置后将会生成新密码,是否继续?', '提示', { this.$confirm("重置后将会生成新密码,是否继续?", "提示", {
confirmButtonText: '确定', confirmButtonText: "确定",
cancelButtonText: '取消' cancelButtonText: "取消",
}).then(() => { }).then(() => {
adminApi.resetUserPassword(row.userid, ({ data }) => { Api.admin.resetUserPassword(row.userid, ({ data }) => {
if (data.code === 0) { if (data.code === 0) {
this.currentPassword = data.data this.currentPassword = data.data;
this.showViewPassword = true this.showViewPassword = true;
this.$message.success('密码已重置,请通知用户使用新密码登录') this.$message.success("密码已重置,请通知用户使用新密码登录");
} }
}) });
}) });
}, },
disableUser(row) { disableUser(row) {
row.status = '禁用'; row.status = "禁用";
console.log('禁用用户:', row);
}, },
restoreUser(row) { restoreUser(row) {
row.status = '正常'; row.status = "正常";
console.log('恢复用户:', row);
}, },
deleteUser(row) { deleteUser(row) {
this.$confirm('确定要删除该用户吗?', '警告', { this.$confirm("确定要删除该用户吗?", "警告", {
confirmButtonText: '确定', confirmButtonText: "确定",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'warning' type: "warning",
}).then(() => { })
adminApi.deleteUser(row.userid, ({data}) => { .then(() => {
if (data.code === 0) { Api.admin.deleteUser(row.userid, ({ data }) => {
this.$message.success('删除成功') if (data.code === 0) {
this.fetchUsers() this.$message.success("删除成功");
} else { this.fetchUsers();
this.$message.error(data.msg || '删除失败') } else {
} this.$message.error(data.msg || "删除失败");
}) }
}).catch(() => {}) });
})
.catch(() => { });
}, },
headerCellClassName({columnIndex}) { headerCellClassName({ columnIndex }) {
if (columnIndex === 0) { if (columnIndex === 0) {
return 'custom-selection-header' return "custom-selection-header";
} }
return '' return "";
}, },
goFirst() { goFirst() {
this.currentPage = 1; this.currentPage = 1;
@@ -252,20 +263,20 @@ export default {
goPrev() { goPrev() {
if (this.currentPage > 1) { if (this.currentPage > 1) {
this.currentPage--; this.currentPage--;
this.fetchUsers(); this.fetchUsers();
} }
}, },
goNext() { goNext() {
if (this.currentPage < this.pageCount) { if (this.currentPage < this.pageCount) {
this.currentPage++; this.currentPage++;
this.fetchUsers(); this.fetchUsers();
} }
}, },
goToPage(page) { goToPage(page) {
this.currentPage = page; this.currentPage = page;
this.fetchUsers(); this.fetchUsers();
}, },
} },
}; };
</script> </script>
@@ -289,7 +300,7 @@ export default {
min-height: 600px; min-height: 600px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: relative; position: relative;
background: rgba(237,242,255,0.5); background: rgba(237, 242, 255, 0.5);
} }
.operation-bar { .operation-bar {
@@ -297,7 +308,6 @@ export default {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 16px 24px; padding: 16px 24px;
border-bottom: 1px solid #ebeef5;
} }
.page-title { .page-title {
@@ -316,7 +326,7 @@ export default {
} }
.btn-search { .btn-search {
background: linear-gradient(135deg, #6B8CFF, #A966FF); background: linear-gradient(135deg, #6b8cff, #a966ff);
border: none; border: none;
color: white; color: white;
} }
@@ -328,6 +338,7 @@ export default {
height: 100%; height: 100%;
border-radius: 15px; border-radius: 15px;
background: transparent; background: transparent;
border: 1px solid #fff;
} }
.content-area { .content-area {
@@ -355,6 +366,7 @@ export default {
display: flex; display: flex;
gap: 8px; gap: 8px;
padding-left: 26px; padding-left: 26px;
.el-button { .el-button {
min-width: 72px; min-width: 72px;
height: 32px; height: 32px;
@@ -429,7 +441,7 @@ export default {
padding: 0 12px; padding: 0 12px;
border-radius: 4px; border-radius: 4px;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
background: #DEE7FF; background: #dee7ff;
color: #606266; color: #606266;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
@@ -481,6 +493,7 @@ export default {
:deep(.transparent-table) { :deep(.transparent-table) {
background: white; background: white;
.el-table__header th { .el-table__header th {
background: white !important; background: white !important;
color: black; color: black;
@@ -492,6 +505,7 @@ export default {
.el-table__body tr { .el-table__body tr {
background-color: white; background-color: white;
td { td {
border-top: 1px solid rgba(0, 0, 0, 0.04); border-top: 1px solid rgba(0, 0, 0, 0.04);
border-bottom: 1px solid rgba(0, 0, 0, 0.04); border-bottom: 1px solid rgba(0, 0, 0, 0.04);
@@ -505,7 +519,7 @@ export default {
} }
&::after { &::after {
content: '选择'; content: "选择";
display: inline-block; display: inline-block;
color: black; color: black;
font-weight: bold; font-weight: bold;
@@ -534,17 +548,18 @@ export default {
align-items: center; align-items: center;
margin-top: 40px; margin-top: 40px;
} }
:deep(.transparent-table) { :deep(.transparent-table) {
.el-table__body tr { .el-table__body tr {
td { td {
padding-top: 16px; padding-top: 16px;
padding-bottom: 16px; padding-bottom: 16px;
} }
& + tr {
&+tr {
margin-top: 10px; margin-top: 10px;
} }
} }
} }
} }
</style>
</style>
+67 -72
View File
@@ -1,56 +1,54 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<!-- 公共头部 --> <!-- 公共头部 -->
<HeaderBar :devices="devices" @search="handleSearch" @search-reset="handleSearchReset" /> <HeaderBar :devices="devices" @search="handleSearch" @search-reset="handleSearchReset" />
<el-main style="padding: 20px;display: flex;flex-direction: column;"> <el-main style="padding: 20px;display: flex;flex-direction: column;">
<div> <div>
<!-- 首页内容 --> <!-- 首页内容 -->
<div class="add-device"> <div class="add-device">
<div class="add-device-bg"> <div class="add-device-bg">
<div class="hellow-text" style="margin-top: 30px;"> <div class="hellow-text" style="margin-top: 30px;">
小智 小智
</div>
<div class="hellow-text">
让我们度过
<div style="display: inline-block;color: #5778FF;">
美好的一天
</div> </div>
<div class="hellow-text"> </div>
让我们度过 <div class="hi-hint">
<div style="display: inline-block;color: #5778FF;"> Hello, Let's have a wonderful day!
美好的一天 </div>
</div> <div class="add-device-btn" @click="showAddDialog">
<div class="left-add">
添加智能体
</div> </div>
<div class="hi-hint"> <div style="width: 23px;height: 13px;background: #5778ff;margin-left: -10px;" />
Hello, Let's have a wonderful day! <div class="right-add">
</div> <i class="el-icon-right" style="font-size: 20px;color: #fff;" />
<div class="add-device-btn" @click="showAddDialog">
<div class="left-add">
添加智能体
</div>
<div style="width: 23px;height: 13px;background: #5778ff;margin-left: -10px;" />
<div class="right-add">
<i class="el-icon-right" style="font-size: 20px;color: #fff;" />
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="device-list-container">
<DeviceItem v-for="(item,index) in devices" :key="index" :device="item"
@configure="goToRoleConfig"
@deviceManage="handleDeviceManage"
@delete="handleDeleteAgent"
/>
</div>
</div> </div>
<div class="copyright"> <div class="device-list-container">
©2025 xiaozhi-esp32-server <DeviceItem v-for="(item, index) in devices" :key="index" :device="item" @configure="goToRoleConfig"
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" />
</div> </div>
<AddWisdomBodyDialog :visible.sync="addDeviceDialogVisible" @confirm="handleWisdomBodyAdded" /> </div>
</el-main> <div class="copyright">
©2025 xiaozhi-esp32-server
</div>
<AddWisdomBodyDialog :visible.sync="addDeviceDialogVisible" @confirm="handleWisdomBodyAdded" />
</el-main>
</div> </div>
</template> </template>
<script> <script>
import DeviceItem from '@/components/DeviceItem.vue' import Api from '@/apis/api';
import AddWisdomBodyDialog from '@/components/AddWisdomBodyDialog.vue' import AddWisdomBodyDialog from '@/components/AddWisdomBodyDialog.vue';
import HeaderBar from '@/components/HeaderBar.vue' import DeviceItem from '@/components/DeviceItem.vue';
import HeaderBar from '@/components/HeaderBar.vue';
export default { export default {
name: 'HomePage', name: 'HomePage',
@@ -78,7 +76,6 @@ export default {
this.$router.push('/role-config') this.$router.push('/role-config')
}, },
handleWisdomBodyAdded(res) { handleWisdomBodyAdded(res) {
console.log('新增智能体响应', res);
this.fetchAgentList(); this.fetchAgentList();
this.addDeviceDialogVisible = false; this.addDeviceDialogVisible = false;
}, },
@@ -111,14 +108,12 @@ export default {
}, },
// 获取智能体列表 // 获取智能体列表
fetchAgentList() { fetchAgentList() {
import('@/apis/module/agent').then(({ default: userApi }) => { Api.agent.getAgentList(({ data }) => {
userApi.getAgentList(({data}) => { this.originalDevices = data.data.map(item => ({
this.originalDevices = data.data.map(item => ({ ...item,
...item, agentId: item.id // 字段映射
agentId: item.id // 字段映射 }));
})); this.handleSearchReset(); // 重置搜索状态
this.handleSearchReset(); // 重置搜索状态
});
}); });
}, },
// 删除智能体 // 删除智能体
@@ -128,23 +123,21 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
import('@/apis/module/agent').then(({ default: userApi }) => { Api.agent.deleteAgent(agentId, (res) => {
userApi.deleteAgent(agentId, (res) => { if (res.data.code === 0) {
if (res.data.code === 0) { this.$message.success({
this.$message.success({ message: '删除成功',
message: '删除成功', showClose: true
showClose: true });
}); this.fetchAgentList(); // 刷新列表
this.fetchAgentList(); // 刷新列表 } else {
} else { this.$message.error({
this.$message.error({ message: res.data.msg || '删除失败',
message: res.data.msg || '删除失败', showClose: true
showClose: true });
}); }
}
});
}); });
}).catch(() => {}); }).catch(() => { });
} }
} }
} }
@@ -157,7 +150,7 @@ export default {
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-image: url("@/assets/home/background.png"); background: linear-gradient(145deg, #e6eeff, #eff0ff);
background-size: cover; background-size: cover;
/* 确保背景图像覆盖整个元素 */ /* 确保背景图像覆盖整个元素 */
background-position: center; background-position: center;
@@ -167,18 +160,18 @@ export default {
-o-background-size: cover; -o-background-size: cover;
/* 兼容老版本Opera浏览器 */ /* 兼容老版本Opera浏览器 */
} }
.add-device { .add-device {
height: 195px; height: 195px;
border-radius: 15px; border-radius: 15px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: linear-gradient( background: linear-gradient(269.62deg,
269.62deg,
#e0e6fd 0%, #e0e6fd 0%,
#cce7ff 49.69%, #cce7ff 49.69%,
#d3d3fe 100% #d3d3fe 100%);
);
} }
.add-device-bg { .add-device-bg {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -193,6 +186,7 @@ export default {
/* 兼容老版本WebKit浏览器 */ /* 兼容老版本WebKit浏览器 */
-o-background-size: cover; -o-background-size: cover;
box-sizing: border-box; box-sizing: border-box;
/* 兼容老版本Opera浏览器 */ /* 兼容老版本Opera浏览器 */
.hellow-text { .hellow-text {
margin-left: 75px; margin-left: 75px;
@@ -252,7 +246,8 @@ export default {
/* 在 DeviceItem.vue 的样式中 */ /* 在 DeviceItem.vue 的样式中 */
.device-item { .device-item {
margin: 0 !important; /* 避免冲突 */ margin: 0 !important;
/* 避免冲突 */
width: auto !important; width: auto !important;
} }
@@ -262,7 +257,7 @@ export default {
margin-top: auto; margin-top: auto;
padding-top: 30px; padding-top: 30px;
color: #979db1; color: #979db1;
text-align: center; /* 居中显示 */ text-align: center;
/* 居中显示 */
} }
</style> </style>
+20 -28
View File
@@ -2,20 +2,18 @@
<div class="welcome"> <div class="welcome">
<el-container style="height: 100%;"> <el-container style="height: 100%;">
<el-header> <el-header>
<div <div style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;">
style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;"> <img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" style="width: 45px;height: 45px;" />
<img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" style="width: 45px;height: 45px;"/> <img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" style="height: 18px;" />
<img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" style="width: 70px;height: 13px;"/>
</div> </div>
</el-header> </el-header>
<div class="login-person"> <div class="login-person">
<img loading="lazy" alt="" src="@/assets/login/login-person.png" style="width: 100%;"/> <img loading="lazy" alt="" src="@/assets/login/login-person.png" style="width: 100%;" />
</div> </div>
<el-main style="position: relative;"> <el-main style="position: relative;">
<div class="login-box" @keyup.enter="login"> <div class="login-box" @keyup.enter="login">
<div <div style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;">
style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;"> <img loading="lazy" alt="" src="@/assets/login/hi.png" style="width: 34px;height: 34px;" />
<img loading="lazy" alt="" src="@/assets/login/hi.png" style="width: 34px;height: 34px;"/>
<div class="login-text">登录</div> <div class="login-text">登录</div>
<div class="login-welcome"> <div class="login-welcome">
WELCOME TO LOGIN WELCOME TO LOGIN
@@ -23,27 +21,23 @@
</div> </div>
<div style="padding: 0 30px;"> <div style="padding: 0 30px;">
<div class="input-box"> <div class="input-box">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/username.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/username.png" />
<el-input v-model="form.username" placeholder="请输入用户名"/> <el-input v-model="form.username" placeholder="请输入用户名" />
</div> </div>
<div class="input-box"> <div class="input-box">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png" />
<el-input v-model="form.password" placeholder="请输入密码" type="password"/> <el-input v-model="form.password" placeholder="请输入密码" type="password" />
</div> </div>
<div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;"> <div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
<div class="input-box" style="width: calc(100% - 130px); margin-top: 0;"> <div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png" />
<el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;"/> <el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;" />
</div> </div>
<img loading="lazy" v-if="captchaUrl" <img loading="lazy" v-if="captchaUrl" :src="captchaUrl" alt="验证码"
:src="captchaUrl" style="width: 150px; height: 40px; cursor: pointer;" @click="fetchCaptcha" />
alt="验证码"
style="width: 150px; height: 40px; cursor: pointer;"
@click="fetchCaptcha"
/>
</div> </div>
<div <div
style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;"> style="font-weight: 400;font-size: 14px;text-align: left;color: #5778ff;display: flex;justify-content: space-between;margin-top: 20px;">
<div style="cursor: pointer;" @click="goToRegister">新用户注册</div> <div style="cursor: pointer;" @click="goToRegister">新用户注册</div>
</div> </div>
</div> </div>
@@ -66,8 +60,8 @@
</template> </template>
<script> <script>
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
import Api from '@/apis/api'; import Api from '@/apis/api';
import { getUUID, goToPage, showDanger, showSuccess } from '@/utils';
export default { export default {
@@ -90,9 +84,8 @@ export default {
}, },
methods: { methods: {
fetchCaptcha() { fetchCaptcha() {
console.log(this.$store.getters.getToken)
if (this.$store.getters.getToken) { if (this.$store.getters.getToken) {
if (this.$route.path !== '/home'){ if (this.$route.path !== '/home') {
this.$router.push('/home') this.$router.push('/home')
} }
} else { } else {
@@ -100,7 +93,7 @@ export default {
Api.user.getCaptcha(this.captchaUuid, (res) => { Api.user.getCaptcha(this.captchaUuid, (res) => {
if (res.status === 200) { if (res.status === 200) {
const blob = new Blob([res.data], {type: res.data.type}); const blob = new Blob([res.data], { type: res.data.type });
this.captchaUrl = URL.createObjectURL(blob); this.captchaUrl = URL.createObjectURL(blob);
} else { } else {
showDanger('验证码加载失败,点击刷新'); showDanger('验证码加载失败,点击刷新');
@@ -133,7 +126,7 @@ export default {
} }
this.form.captchaId = this.captchaUuid this.form.captchaId = this.captchaUuid
Api.user.login(this.form, ({data}) => { Api.user.login(this.form, ({ data }) => {
if (data.code === 0) { if (data.code === 0) {
showSuccess('登录成功!'); showSuccess('登录成功!');
this.$store.commit('setToken', JSON.stringify(data.data)); this.$store.commit('setToken', JSON.stringify(data.data));
@@ -156,5 +149,4 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './auth.scss'; // 添加这行引用 @import './auth.scss'; // 添加这行引用</style>
</style>
+20 -24
View File
@@ -4,16 +4,18 @@
<!-- 保持相同的头部 --> <!-- 保持相同的头部 -->
<el-header> <el-header>
<div style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;"> <div style="display: flex;align-items: center;margin-top: 15px;margin-left: 10px;gap: 10px;">
<img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" style="width: 45px;height: 45px;"/> <img loading="lazy" alt="" src="@/assets/xiaozhi-logo.png" style="width: 45px;height: 45px;" />
<img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" style="width: 70px;height: 13px;"/> <img loading="lazy" alt="" src="@/assets/xiaozhi-ai.png" style="height: 18px;" />
</div> </div>
</el-header> </el-header>
<div class="login-person">
<img loading="lazy" alt="" src="@/assets/login/login-person.png" style="width: 100%;" />
</div>
<el-main style="position: relative;"> <el-main style="position: relative;">
<div class="login-box"> <div class="login-box">
<!-- 修改标题部分 --> <!-- 修改标题部分 -->
<div style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;"> <div style="display: flex;align-items: center;gap: 20px;margin-bottom: 39px;padding: 0 30px;">
<img loading="lazy" alt="" src="@/assets/login/hi.png" style="width: 34px;height: 34px;"/> <img loading="lazy" alt="" src="@/assets/login/hi.png" style="width: 34px;height: 34px;" />
<div class="login-text">注册</div> <div class="login-text">注册</div>
<div class="login-welcome"> <div class="login-welcome">
WELCOME TO REGISTER WELCOME TO REGISTER
@@ -23,34 +25,30 @@
<div style="padding: 0 30px;"> <div style="padding: 0 30px;">
<!-- 用户名输入框 --> <!-- 用户名输入框 -->
<div class="input-box"> <div class="input-box">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/username.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/username.png" />
<el-input v-model="form.username" placeholder="请输入用户名"/> <el-input v-model="form.username" placeholder="请输入用户名" />
</div> </div>
<!-- 密码输入框 --> <!-- 密码输入框 -->
<div class="input-box"> <div class="input-box">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png" />
<el-input v-model="form.password" placeholder="请输入密码" type="password"/> <el-input v-model="form.password" placeholder="请输入密码" type="password" />
</div> </div>
<!-- 新增确认密码 --> <!-- 新增确认密码 -->
<div class="input-box"> <div class="input-box">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/password.png" />
<el-input v-model="form.confirmPassword" placeholder="请确认密码" type="password"/> <el-input v-model="form.confirmPassword" placeholder="请确认密码" type="password" />
</div> </div>
<!-- 验证码部分保持相同 --> <!-- 验证码部分保持相同 -->
<div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;"> <div style="display: flex; align-items: center; margin-top: 20px; width: 100%; gap: 10px;">
<div class="input-box" style="width: calc(100% - 130px); margin-top: 0;"> <div class="input-box" style="width: calc(100% - 130px); margin-top: 0;">
<img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png"/> <img loading="lazy" alt="" class="input-icon" src="@/assets/login/shield.png" />
<el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;"/> <el-input v-model="form.captcha" placeholder="请输入验证码" style="flex: 1;" />
</div> </div>
<img loading="lazy" v-if="captchaUrl" <img loading="lazy" v-if="captchaUrl" :src="captchaUrl" alt="验证码"
:src="captchaUrl" style="width: 150px; height: 40px; cursor: pointer;" @click="fetchCaptcha" />
alt="验证码"
style="width: 150px; height: 40px; cursor: pointer;"
@click="fetchCaptcha"
/>
</div> </div>
<!-- 修改底部链接 --> <!-- 修改底部链接 -->
@@ -83,8 +81,8 @@
</template> </template>
<script> <script>
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
import Api from '@/apis/api'; import Api from '@/apis/api';
import { getUUID, goToPage, showDanger, showSuccess } from '@/utils';
export default { export default {
name: 'register', name: 'register',
@@ -109,7 +107,7 @@ export default {
this.form.captchaId = getUUID(); this.form.captchaId = getUUID();
Api.user.getCaptcha(this.form.captchaId, (res) => { Api.user.getCaptcha(this.form.captchaId, (res) => {
if (res.status === 200) { if (res.status === 200) {
const blob = new Blob([res.data], {type: res.data.type}); const blob = new Blob([res.data], { type: res.data.type });
this.captchaUrl = URL.createObjectURL(blob); this.captchaUrl = URL.createObjectURL(blob);
} else { } else {
@@ -146,8 +144,7 @@ export default {
return; return;
} }
Api.user.register(this.form, ({data}) => { Api.user.register(this.form, ({ data }) => {
console.log(data)
if (data.code === 0) { if (data.code === 0) {
showSuccess('注册成功!') showSuccess('注册成功!')
goToPage('/login') goToPage('/login')
@@ -169,5 +166,4 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './auth.scss'; // 修改为导入新建的SCSS文件 @import './auth.scss'; // 修改为导入新建的SCSS文件</style>
</style>
+67 -73
View File
@@ -1,27 +1,28 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<HeaderBar/> <HeaderBar />
<el-main style="padding: 16px;display: flex;flex-direction: column;"> <el-main style="padding: 16px;display: flex;flex-direction: column;">
<div style="border-radius: 16px;background: #fafcfe; border: 1px solid #e8f0ff;"> <div style="border-radius: 16px;background: #fafcfe; border: 1px solid #e8f0ff;">
<div <div
style="padding: 15px 24px;font-weight: 700;font-size: 19px;text-align: left;color: #3d4566;display: flex;gap: 13px;align-items: center;"> style="padding: 15px 24px;font-weight: 700;font-size: 19px;text-align: left;color: #3d4566;display: flex;gap: 13px;align-items: center;">
<div <div
style="width: 37px;height: 37px;background: #5778ff;border-radius: 50%;display: flex;align-items: center;justify-content: center;"> style="width: 37px;height: 37px;background: #5778ff;border-radius: 50%;display: flex;align-items: center;justify-content: center;">
<img loading="lazy" src="@/assets/home/setting-user.png" alt="" style="width: 19px;height: 19px;"/> <img loading="lazy" src="@/assets/home/setting-user.png" alt="" style="width: 19px;height: 19px;" />
</div> </div>
{{ form.agentName }} {{ form.agentName }}
</div> </div>
<div style="height: 1px;background: #e8f0ff;"/> <div style="height: 1px;background: #e8f0ff;" />
<el-form ref="form" :model="form" label-width="72px"> <el-form ref="form" :model="form" label-width="72px">
<div style="padding: 16px 24px;max-width: 792px;"> <div style="padding: 16px 24px;max-width: 792px;">
<el-form-item label="助手昵称:"> <el-form-item label="助手昵称:">
<div class="input-46" style="width: 100%; max-width: 412px;"> <div class="input-46" style="width: 100%; max-width: 412px;">
<el-input v-model="form.agentName"/> <el-input v-model="form.agentName" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="角色模版:"> <el-form-item label="角色模版:">
<div style="display: flex;gap: 8px;"> <div style="display: flex;gap: 8px;">
<div v-for="template in templates" :key="template" class="template-item" :class="{ 'template-loading': loadingTemplate }" @click="selectTemplate(template)"> <div v-for="template in templates" :key="template" class="template-item"
:class="{ 'template-loading': loadingTemplate }" @click="selectTemplate(template)">
{{ template }} {{ template }}
</div> </div>
</div> </div>
@@ -30,32 +31,31 @@
<div style="display: flex;gap: 8px;align-items: center;"> <div style="display: flex;gap: 8px;align-items: center;">
<div class="input-46" style="flex:1.4;"> <div class="input-46" style="flex:1.4;">
<el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;"> <el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;">
<el-option v-for="item in options" :key="item.value" :label="item.label" <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
:value="item.value">
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
<div class="audio-box"> <div class="audio-box">
<audio src="http://music.163.com/song/media/outer/url?id=447925558.mp3" controls <audio src="http://music.163.com/song/media/outer/url?id=447925558.mp3" controls
style="height: 100%;width: 100%;"/> style="height: 100%;width: 100%;" />
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="角色介绍:"> <el-form-item label="角色介绍:">
<div class="textarea-box"> <div class="textarea-box">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" <el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.systemPrompt"
v-model="form.systemPrompt" maxlength="2000" show-word-limit/> maxlength="2000" show-word-limit />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="记忆体:"> <el-form-item label="记忆体:">
<div class="textarea-box"> <div class="textarea-box">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" <el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.langCode"
v-model="form.langCode" maxlength="1000"/> maxlength="1000" />
<div class="prompt-bottom" @click="clearMemory"> <div class="prompt-bottom" @click="clearMemory">
<div style="display: flex;gap: 8px;align-items: center;"> <div style="display: flex;gap: 8px;align-items: center;">
<div style="color: #979db1;font-size: 11px;">当前记忆每次对话后重新生成</div> <div style="color: #979db1;font-size: 11px;">当前记忆每次对话后重新生成</div>
<div class="clear-btn"> <div class="clear-btn">
<i class="el-icon-delete-solid" style="font-size: 11px;"/> <i class="el-icon-delete-solid" style="font-size: 11px;" />
清除 清除
</div> </div>
</div> </div>
@@ -65,7 +65,7 @@
</el-form-item> </el-form-item>
<el-form-item v-for="model in models" :key="model.label" :label="model.label" class="model-item"> <el-form-item v-for="model in models" :key="model.label" :label="model.label" class="model-item">
<el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="select-field"> <el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="select-field">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"/> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="" class="lh-form-item" style="margin-top: -25px;"> <el-form-item label="" class="lh-form-item" style="margin-top: -25px;">
@@ -83,7 +83,7 @@
重制 重制
</div> </div>
<div class="clear-text"> <div class="clear-text">
<img loading="lazy" src="@/assets/home/red-info.png" alt="" style="width: 19px;height: 19px;"/> <img loading="lazy" src="@/assets/home/red-info.png" alt="" style="width: 19px;height: 19px;" />
保存配置后需要重启设备新的配置才会生效 保存配置后需要重启设备新的配置才会生效
</div> </div>
</div> </div>
@@ -96,11 +96,13 @@
</template> </template>
<script> <script>
import Api from '@/apis/api';
import HeaderBar from "@/components/HeaderBar.vue"; import HeaderBar from "@/components/HeaderBar.vue";
export default { export default {
name: 'RoleConfigPage', name: 'RoleConfigPage',
components: {HeaderBar}, components: { HeaderBar },
data() { data() {
return { return {
form: { form: {
@@ -121,16 +123,16 @@ export default {
} }
}, },
options: [ options: [
{value: '选项1', label: '黄金糕'}, { value: '选项1', label: '黄金糕' },
{value: '选项2', label: '双皮奶'} { value: '选项2', label: '双皮奶' }
], ],
models: [ models: [
{label: '大语言模型(LLM)', key: 'llmModelId'}, { label: '大语言模型(LLM)', key: 'llmModelId' },
{label: '语音识别(ASR)', key: 'asrModelId'}, { label: '语音识别(ASR)', key: 'asrModelId' },
{label: '语音活动检测模型(VAD)', key: 'vadModelId'}, { label: '语音活动检测(VAD)', key: 'vadModelId' },
{label: '语音合成模型(TTS)', key: 'ttsModelId'}, { label: '语音合成(TTS)', key: 'ttsModelId' },
{label: '意图识别模型(Intent)', key: 'intentModelId'}, { label: '意图识别(Intent)', key: 'intentModelId' },
{label: '记忆模型(Memory)', key: 'memModelId'} { label: '记忆(Memory)', key: 'memModelId' }
], ],
templates: ['湾湾小何', '星际游子', '英语老师', '好奇男孩', '汪汪队长'], templates: ['湾湾小何', '星际游子', '英语老师', '好奇男孩', '汪汪队长'],
loadingTemplate: false loadingTemplate: false
@@ -153,14 +155,12 @@ export default {
language: this.form.language, language: this.form.language,
sort: this.form.sort sort: this.form.sort
}; };
import('@/apis/module/agent').then(({default: agentApi}) => { Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
agentApi.updateAgentConfig(this.$route.query.agentId, configData, ({data}) => { if (data.code === 0) {
if (data.code === 0) { this.$message.success('配置保存成功');
this.$message.success('配置保存成功'); } else {
} else { this.$message.error(data.msg || '配置保存失败');
this.$message.error(data.msg || '配置保存失败'); }
}
});
}); });
}, },
resetConfig() { resetConfig() {
@@ -192,31 +192,29 @@ export default {
}) })
}, },
selectTemplate(templateName) { selectTemplate(templateName) {
if (this.loadingTemplate) return; if (this.loadingTemplate) return;
this.loadingTemplate = true; this.loadingTemplate = true;
import('@/apis/module/agent').then(({default: agentApi}) => { Api.agent.getAgentTemplate((response) => { // 移除参数传递
agentApi.getAgentTemplate((response) => { // 移除参数传递 this.loadingTemplate = false;
this.loadingTemplate = false; if (response.data.code === 0) {
if (response.data.code === 0) { // 在客户端过滤匹配的模板
// 在客户端过滤匹配的模板 const matchedTemplate = response.data.data.find(
const matchedTemplate = response.data.data.find( t => t.agentName === templateName
t => t.agentName === templateName );
); if (matchedTemplate) {
if (matchedTemplate) { this.applyTemplateData(matchedTemplate);
this.applyTemplateData(matchedTemplate); this.$message.success(`${templateName}」模板已应用`);
this.$message.success(`${templateName}」模板已应用`); } else {
} else { this.$message.warning(`未找到「${templateName}」模板`);
this.$message.warning(`未找到「${templateName}」模板`); }
} } else {
} else { this.$message.error(response.data.msg || '获取模板失败');
this.$message.error(response.data.msg || '获取模板失败'); }
} }).catch((error) => {
}); this.loadingTemplate = false;
}).catch((error) => { this.$message.error('模板加载失败');
this.loadingTemplate = false; console.error('接口异常:', error);
this.$message.error('模板加载失败'); });
console.error('接口异常:', error);
});
}, },
applyTemplateData(templateData) { applyTemplateData(templateData) {
this.form = { this.form = {
@@ -235,9 +233,8 @@ export default {
} }
}; };
}, },
fetchAgentConfig(agentId) { fetchAgentConfig(agentId) {
import('@/apis/module/agent').then(({default: agentApi}) => { Api.agent.getDeviceConfig(agentId, ({ data }) => {
agentApi.getDeviceConfig(agentId, ({data}) => {
if (data.code === 0) { if (data.code === 0) {
this.form = { this.form = {
...this.form, ...this.form,
@@ -255,17 +252,15 @@ export default {
this.$message.error(data.msg || '获取配置失败'); this.$message.error(data.msg || '获取配置失败');
} }
}); });
}); },
// 清空记忆体内容
clearMemory() {
this.form.langCode = "";
this.$message.success("记忆体已清空");
},
}, },
// 清空记忆体内容
clearMemory() {
this.form.langCode = "";
this.$message.success("记忆体已清空");
},
},
mounted() { mounted() {
const agentId = this.$route.query.agentId; const agentId = this.$route.query.agentId;
console.log('agentId2222',agentId);
if (agentId) { if (agentId) {
this.fetchAgentConfig(agentId); this.fetchAgentConfig(agentId);
} }
@@ -280,7 +275,7 @@ export default {
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-image: url("@/assets/home/background.png"); background: linear-gradient(145deg, #e6eeff, #eff0ff);
background-size: cover; background-size: cover;
/* 确保背景图像覆盖整个元素 */ /* 确保背景图像覆盖整个元素 */
background-position: center; background-position: center;
@@ -299,7 +294,7 @@ export default {
padding-bottom: 2px; padding-bottom: 2px;
} }
.select-field{ .select-field {
width: 100%; width: 100%;
max-width: 720px; max-width: 720px;
border: 1px solid #e4e6ef; border: 1px solid #e4e6ef;
@@ -397,4 +392,3 @@ export default {
background: #f6f8fb; background: #f6f8fb;
} }
</style> </style>
+7 -8
View File
@@ -3,13 +3,12 @@
<h1 class="title">XiaoZhi ESP32 Server 测试助手</h1> <h1 class="title">XiaoZhi ESP32 Server 测试助手</h1>
<div class="chat-container" ref="chatContainer"> <div class="chat-container" ref="chatContainer">
<div v-for="(message, index) in messages" :key="index" <div v-for="(message, index) in messages" :key="index"
:class="['message', message.role === 'user' ? 'user' : 'assistant']"> :class="['message', message.role === 'user' ? 'user' : 'assistant']">
<span>{{ message.content }}</span> <span>{{ message.content }}</span>
</div> </div>
</div> </div>
<div class="controls"> <div class="controls">
<button @click="toggleRecording" :disabled="wsStatus !== 'connected'" <button @click="toggleRecording" :disabled="wsStatus !== 'connected'" :class="{ recording: isRecording }">
:class="{ recording: isRecording }">
{{ isRecording ? '停止录音' : '开始录音' }} {{ isRecording ? '停止录音' : '开始录音' }}
</button> </button>
<p>WebSocket: {{ wsStatus }}</p> <p>WebSocket: {{ wsStatus }}</p>
@@ -18,8 +17,8 @@
</template> </template>
<script> <script>
import Recorder from 'opus-recorder';
import { OpusDecoder } from 'opus-decoder'; import { OpusDecoder } from 'opus-decoder';
import Recorder from 'opus-recorder';
export default { export default {
name: 'TestPage', name: 'TestPage',
@@ -89,8 +88,8 @@ export default {
console.log('收到音频帧,大小:', event.data.byteLength, '字节'); console.log('收到音频帧,大小:', event.data.byteLength, '字节');
const opusFrame = new Uint8Array(event.data); const opusFrame = new Uint8Array(event.data);
const frameHead = Array.from(opusFrame.slice(0, 8)) const frameHead = Array.from(opusFrame.slice(0, 8))
.map(b => b.toString(16).padStart(2, '0')) .map(b => b.toString(16).padStart(2, '0'))
.join(' '); .join(' ');
console.log('音频帧前8字节:', frameHead); console.log('音频帧前8字节:', frameHead);
try { try {
@@ -258,8 +257,8 @@ export default {
const frames = this.stripOggContainer(data); const frames = this.stripOggContainer(data);
frames.forEach((frame, index) => { frames.forEach((frame, index) => {
const frameHead = Array.from(new Uint8Array(frame.slice(0, 8))) const frameHead = Array.from(new Uint8Array(frame.slice(0, 8)))
.map(b => b.toString(16).padStart(2, '0')) .map(b => b.toString(16).padStart(2, '0'))
.join(' '); .join(' ');
console.log(`${index} 大小: ${frame.byteLength} 字节,前8字节: ${frameHead}`); console.log(`${index} 大小: ${frame.byteLength} 字节,前8字节: ${frameHead}`);
if (frame.byteLength < 50 || frame.byteLength > 300) { if (frame.byteLength < 50 || frame.byteLength > 300) {
+4 -11
View File
@@ -15,20 +15,13 @@ module.exports = defineConfig({
devServer: { devServer: {
port: 8001, // 指定端口为 8001 port: 8001, // 指定端口为 8001
proxy: { proxy: {
'/xiaozhi-esp32-api': {
target: process.env.VUE_APP_API_BASE_URL, // 后端 API 的基础 URL
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/xiaozhi-esp32-api': '/xiaozhi-esp32-api',
},
},
}, },
client: { client: {
overlay: false, // 不显示 webpack 错误覆盖层 overlay: false, // 不显示 webpack 错误覆盖层
}, },
}, },
chainWebpack: config => { chainWebpack: config => {
// 修改 HTML 插件配置,动态插入 CDN 链接 // 修改 HTML 插件配置,动态插入 CDN 链接
config.plugin('html') config.plugin('html')
.tap(args => { .tap(args => {
@@ -50,7 +43,7 @@ module.exports = defineConfig({
} }
return args; return args;
}); });
// 代码分割优化 // 代码分割优化
config.optimization.splitChunks({ config.optimization.splitChunks({
chunks: 'all', chunks: 'all',
@@ -72,12 +65,12 @@ module.exports = defineConfig({
}, },
} }
}); });
// 启用优化设置 // 启用优化设置
config.optimization.usedExports(true); config.optimization.usedExports(true);
config.optimization.concatenateModules(true); config.optimization.concatenateModules(true);
config.optimization.minimize(true); config.optimization.minimize(true);
}, },
configureWebpack: config => { configureWebpack: config => {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
// 开启多线程编译 // 开启多线程编译