mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 16:43:55 +08:00
fix: 播放咋音问题
This commit is contained in:
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
|
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -41,7 +42,13 @@ jobs:
|
|||||||
- name: Extract version from tag
|
- name: Extract version from tag
|
||||||
id: get_version
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
if [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||||
|
echo "VERSION=${BASH_REMATCH[1]}" >> $GITHUB_ENV
|
||||||
|
echo "IS_VERSION=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "VERSION=latest" >> $GITHUB_ENV
|
||||||
|
echo "IS_VERSION=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
# 构建 xiaozhi-server 镜像
|
# 构建 xiaozhi-server 镜像
|
||||||
- name: Build and push xiaozhi-server
|
- name: Build and push xiaozhi-server
|
||||||
@@ -51,8 +58,7 @@ jobs:
|
|||||||
file: Dockerfile-server
|
file: Dockerfile-server
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:server_${{ env.VERSION }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1}\nghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }}
|
||||||
ghcr.io/${{ github.repository }}:server_latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
# 构建 manager-api 镜像
|
# 构建 manager-api 镜像
|
||||||
@@ -63,6 +69,5 @@ jobs:
|
|||||||
file: Dockerfile-web
|
file: Dockerfile-web
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:web_${{ env.VERSION }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1}\nghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }}
|
||||||
ghcr.io/${{ github.repository }}:web_latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
+12
-8
@@ -311,15 +311,17 @@ LLM:
|
|||||||
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
|
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
|
||||||
|
|
||||||
```
|
```
|
||||||
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000
|
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/
|
||||||
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。
|
正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。
|
||||||
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
||||||
|
|
||||||
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
|
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
|
||||||
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000`。
|
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`。
|
||||||
|
|
||||||
这个信息很有用的,后面`编译esp32固件`需要用到。
|
这个信息很有用的,后面`编译esp32固件`需要用到。
|
||||||
|
|
||||||
@@ -328,14 +330,16 @@ LLM:
|
|||||||
|
|
||||||
以下是一些常见问题,供参考:
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
[3、TTS 经常失败,经常超时](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
[4、如何提高小智对话响应速度?](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
[5、我说话很慢,停顿时小智老是抢话](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
|
|||||||
+14
-4
@@ -21,7 +21,17 @@ conda install conda-forge::ffmpeg
|
|||||||
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
||||||
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
||||||
|
|
||||||
### 4、如何提高小智对话响应速度? ⚡
|
### 4、使用Wifi能连接自建服务器,但是4G模式却接不上 🔐
|
||||||
|
|
||||||
|
原因:虾哥的固件,4G模式需要使用安全连接。
|
||||||
|
|
||||||
|
解决方法:目前有两种方法可以解决。任选一种:
|
||||||
|
|
||||||
|
1、改代码。参考这个视频解决 https://www.bilibili.com/video/BV18MfTYoE85
|
||||||
|
|
||||||
|
2、使用nginx配置ssl证书。参考教程 https://icnt94i5ctj4.feishu.cn/docx/GnYOdMNJOoRCljx1ctecsj9cnRe
|
||||||
|
|
||||||
|
### 5、如何提高小智对话响应速度? ⚡
|
||||||
|
|
||||||
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
|
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
|
||||||
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
||||||
@@ -78,7 +88,7 @@ TTS 性能排行:
|
|||||||
- LLM:`AliLLM`
|
- LLM:`AliLLM`
|
||||||
- TTS:`DoubaoTTS`
|
- TTS:`DoubaoTTS`
|
||||||
|
|
||||||
### 5、我说话很慢,停顿时小智老是抢话 🗣️
|
### 6、我说话很慢,停顿时小智老是抢话 🗣️
|
||||||
|
|
||||||
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
||||||
|
|
||||||
@@ -90,7 +100,7 @@ VAD:
|
|||||||
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
||||||
|
|
||||||
本项目,支持以工具调用的方式控制HomeAssistant设备
|
本项目,支持以工具调用的方式控制HomeAssistant设备
|
||||||
|
|
||||||
@@ -128,7 +138,7 @@ Intent:
|
|||||||
- hass_play_music
|
- hass_play_music
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7、更多问题,可联系我们反馈 💬
|
### 8、更多问题,可联系我们反馈 💬
|
||||||
|
|
||||||
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# API反向代理(Java项目)
|
# API反向代理(Java项目)
|
||||||
location /xiaozhi-esp32-api/ {
|
location /xiaozhi/ {
|
||||||
proxy_pass http://127.0.0.1:8003;
|
proxy_pass http://127.0.0.1:8003;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cookie_path /api/ /;
|
proxy_cookie_path /api/ /;
|
||||||
|
|||||||
+11
-10
@@ -1,10 +1,9 @@
|
|||||||
# 编译esp32固件
|
# 编译esp32固件
|
||||||
|
|
||||||
1. 下载`xiaozhi-esp32`
|
1. 先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
||||||
项目,按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
|
||||||
|
|
||||||
2. 打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net`
|
2. 配置好编译环境后,下载虾哥[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)项目源码,进入虾哥项目,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net`
|
||||||
改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000`,就把内容改成这个。
|
改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000/xiaozhi/v1/`,就把内容改成这个。
|
||||||
|
|
||||||
修改前:
|
修改前:
|
||||||
|
|
||||||
@@ -92,14 +91,16 @@ https://espressif.github.io/esp-launchpad/
|
|||||||
|
|
||||||
以下是一些常见问题,供参考:
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
[3、TTS 经常失败,经常超时](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
[4、如何提高小智对话响应速度?](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
[5、我说话很慢,停顿时小智老是抢话](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
|
|||||||
+1
-1
@@ -18,4 +18,4 @@ xiaozhi-esp32-server
|
|||||||
|
|
||||||
# manager-web 、manager-api接口协议
|
# manager-web 、manager-api接口协议
|
||||||
|
|
||||||
https://2662r3426b.vicp.fun/xiaozhi-esp32-api/api/v1/doc.html
|
https://2662r3426b.vicp.fun/xiaozhi/v1/doc.html
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](.././FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md)
|
||||||
# 项目介绍
|
# 项目介绍
|
||||||
|
|
||||||
manager-api 该项目基于SpringBoot框架开发。
|
manager-api 该项目基于SpringBoot框架开发。
|
||||||
@@ -23,12 +23,7 @@ CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unico
|
|||||||
如果还没有MySQL,你可以通过docker安装mysql
|
如果还没有MySQL,你可以通过docker安装mysql
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --name xiaozhi-esp32-server-db \
|
docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest
|
||||||
-e MYSQL_ROOT_PASSWORD=123456 \
|
|
||||||
-p 3306:3306 \
|
|
||||||
-e MYSQL_DATABASE=xiaozhi_esp32_server \
|
|
||||||
-e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" \
|
|
||||||
-d mysql:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# 确认项目数据库连接信息
|
# 确认项目数据库连接信息
|
||||||
@@ -92,5 +87,5 @@ nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.activate=dev
|
|||||||
|
|
||||||
|
|
||||||
# 接口文档
|
# 接口文档
|
||||||
启动后打开:http://localhost:8002/xiaozhi-esp32-api/doc.html
|
启动后打开:http://localhost:8002/xiaozhi/doc.html
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ public class AdminApplication {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(AdminApplication.class, args);
|
SpringApplication.run(AdminApplication.class, args);
|
||||||
System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html");
|
System.out.println("http://localhost:8002/xiaozhi/doc.html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ public class SwaggerConfig {
|
|||||||
public GroupedOpenApi timbreApi() {
|
public GroupedOpenApi timbreApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("timbre")
|
.group("timbre")
|
||||||
.pathsToMatch("/timbre/**")
|
.pathsToMatch("/ttsVoice/**")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.apache.shiro.authz.UnauthorizedException;
|
|||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -53,4 +54,10 @@ public class RenExceptionHandler {
|
|||||||
return new Result<Void>().error();
|
return new Result<Void>().error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
|
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
|
||||||
|
log.warn("Resource not found: {}", ex.getMessage());
|
||||||
|
return new Result<Void>().error(404, "资源不存在");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,4 +26,32 @@ public class RedisKeys {
|
|||||||
public static String getDeviceCaptchaKey(String captcha) {
|
public static String getDeviceCaptchaKey(String captcha) {
|
||||||
return "sys:device:captcha:" + captcha;
|
return "sys:device:captcha:" + captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id的Key
|
||||||
|
*/
|
||||||
|
public static String getUserIdKey(Long userid) {
|
||||||
|
return "sys:username:id:" + userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称的Key
|
||||||
|
*/
|
||||||
|
public static String getModelNameById(String id) {
|
||||||
|
return "sys:model:name:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音色名称缓存key
|
||||||
|
*/
|
||||||
|
public static String getTimbreNameById(String id) {
|
||||||
|
return "timbre:name:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备数量缓存key
|
||||||
|
*/
|
||||||
|
public static String getAgentDeviceCountById(String id) {
|
||||||
|
return "agent:device:count:" + id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package xiaozhi.common.utils;
|
|||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,4 +63,39 @@ public class DateUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getShortTime(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 将 Date 转换为 Instant
|
||||||
|
LocalDateTime localDateTime = date.toInstant()
|
||||||
|
// 获取系统默认时区
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
// 转换为 LocalDateTime
|
||||||
|
.toLocalDateTime();
|
||||||
|
// 当前时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
// 时间差,单位为秒
|
||||||
|
long secondsBetween = ChronoUnit.SECONDS.between(localDateTime, now);
|
||||||
|
|
||||||
|
if (secondsBetween <= 10) {
|
||||||
|
return "刚刚";
|
||||||
|
} else if (secondsBetween < 60) {
|
||||||
|
return secondsBetween + "秒前";
|
||||||
|
} else if (secondsBetween < 60 * 60) {
|
||||||
|
return secondsBetween / 60 + "分钟前";
|
||||||
|
} else if (secondsBetween < 86400) {
|
||||||
|
return secondsBetween / 3600 + "小时前";
|
||||||
|
} else if (secondsBetween < 604800) {
|
||||||
|
return secondsBetween / 86400 + "天前";
|
||||||
|
} else {
|
||||||
|
// 超过一周,显示完整日期时间
|
||||||
|
return format(date,DATE_TIME_PATTERN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-3
@@ -29,11 +29,13 @@ import xiaozhi.common.user.UserDetail;
|
|||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
@Tag(name = "智能体管理")
|
@Tag(name = "智能体管理")
|
||||||
@@ -43,14 +45,15 @@ import xiaozhi.modules.security.user.SecurityUser;
|
|||||||
public class AgentController {
|
public class AgentController {
|
||||||
private final AgentService agentService;
|
private final AgentService agentService;
|
||||||
private final AgentTemplateService agentTemplateService;
|
private final AgentTemplateService agentTemplateService;
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取用户智能体列表")
|
@Operation(summary = "获取用户智能体列表")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<AgentEntity>> getUserAgents() {
|
public Result<List<AgentDTO>> getUserAgents() {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
List<AgentEntity> agents = agentService.getUserAgents(user.getId());
|
List<AgentDTO> agents = agentService.getUserAgents(user.getId());
|
||||||
return new Result<List<AgentEntity>>().ok(agents);
|
return new Result<List<AgentDTO>>().ok(agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/all")
|
@GetMapping("/all")
|
||||||
@@ -80,6 +83,22 @@ public class AgentController {
|
|||||||
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
|
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||||
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
||||||
|
|
||||||
|
// 获取默认模板
|
||||||
|
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
|
||||||
|
if (template != null) {
|
||||||
|
// 设置模板中的默认值
|
||||||
|
entity.setAsrModelId(template.getAsrModelId());
|
||||||
|
entity.setVadModelId(template.getVadModelId());
|
||||||
|
entity.setLlmModelId(template.getLlmModelId());
|
||||||
|
entity.setTtsModelId(template.getTtsModelId());
|
||||||
|
entity.setTtsVoiceId(template.getTtsVoiceId());
|
||||||
|
entity.setMemModelId(template.getMemModelId());
|
||||||
|
entity.setIntentModelId(template.getIntentModelId());
|
||||||
|
entity.setSystemPrompt(template.getSystemPrompt());
|
||||||
|
entity.setLangCode(template.getLangCode());
|
||||||
|
entity.setLanguage(template.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
// 设置用户ID和创建者信息
|
// 设置用户ID和创建者信息
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
entity.setUserId(user.getId());
|
entity.setUserId(user.getId());
|
||||||
@@ -157,6 +176,9 @@ public class AgentController {
|
|||||||
@Operation(summary = "删除智能体")
|
@Operation(summary = "删除智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> delete(@PathVariable String id) {
|
public Result<Void> delete(@PathVariable String id) {
|
||||||
|
// 先删除关联的设备
|
||||||
|
deviceService.deleteByAgentId(id);
|
||||||
|
// 再删除智能体
|
||||||
agentService.deleteById(id);
|
agentService.deleteById(id);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package xiaozhi.modules.agent.dao;
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import xiaozhi.common.dao.BaseDao;
|
import xiaozhi.common.dao.BaseDao;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface AgentDao extends BaseDao<AgentEntity> {
|
public interface AgentDao extends BaseDao<AgentEntity> {
|
||||||
|
/**
|
||||||
|
* 获取智能体的设备数量
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @return 设备数量
|
||||||
|
*/
|
||||||
|
Integer getDeviceCountByAgentId(@Param("agentId") String agentId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体数据传输对象
|
||||||
|
* 用于在服务层和控制器层之间传递智能体相关的数据
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体对象")
|
||||||
|
public class AgentDTO {
|
||||||
|
@Schema(description = "智能体编码", example = "AGT_1234567890")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "智能体名称", example = "客服助手")
|
||||||
|
private String agentName;
|
||||||
|
|
||||||
|
@Schema(description = "语音合成模型名称", example = "tts_model_01")
|
||||||
|
private String ttsModelName;
|
||||||
|
|
||||||
|
@Schema(description = "音色名称", example = "voice_01")
|
||||||
|
private String ttsVoiceName;
|
||||||
|
|
||||||
|
@Schema(description = "大语言模型名称", example = "llm_model_01")
|
||||||
|
private String llmModelName;
|
||||||
|
|
||||||
|
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助")
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
|
||||||
|
private Date lastConnectedAt;
|
||||||
|
|
||||||
|
@Schema(description = "设备数量", example = "10")
|
||||||
|
private Integer deviceCount;
|
||||||
|
}
|
||||||
@@ -5,13 +5,10 @@ import java.util.Map;
|
|||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
|
||||||
public interface AgentService extends BaseService<AgentEntity> {
|
public interface AgentService extends BaseService<AgentEntity> {
|
||||||
/**
|
|
||||||
* 根据用户ID获取智能体列表
|
|
||||||
*/
|
|
||||||
List<AgentEntity> getUserAgents(Long userId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员获取所有智能体列表(分页)
|
* 管理员获取所有智能体列表(分页)
|
||||||
@@ -22,4 +19,27 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* 获取智能体详情
|
* 获取智能体详情
|
||||||
*/
|
*/
|
||||||
AgentEntity getAgentById(String id);
|
AgentEntity getAgentById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除这个用户的所有
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
*/
|
||||||
|
void deleteAgentByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户智能体列表
|
||||||
|
*
|
||||||
|
* @param userId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<AgentDTO> getUserAgents(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能体的设备数量
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @return 设备数量
|
||||||
|
*/
|
||||||
|
Integer getDeviceCountByAgentId(String agentId);
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,10 @@ import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
|||||||
*/
|
*/
|
||||||
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
|
public interface AgentTemplateService extends IService<AgentTemplateEntity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认模板
|
||||||
|
*
|
||||||
|
* @return 默认模板实体
|
||||||
|
*/
|
||||||
|
AgentTemplateEntity getDefaultTemplate();
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-7
@@ -3,33 +3,44 @@ package xiaozhi.modules.agent.service.impl;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.modules.agent.dao.AgentDao;
|
import xiaozhi.modules.agent.dao.AgentDao;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.model.service.ModelConfigService;
|
||||||
|
import xiaozhi.modules.timbre.service.TimbreService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
|
||||||
private final AgentDao agentDao;
|
private final AgentDao agentDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TimbreService timbreModelService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ModelConfigService modelConfigService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisUtils redisUtils;
|
||||||
|
|
||||||
public AgentServiceImpl(AgentDao agentDao) {
|
public AgentServiceImpl(AgentDao agentDao) {
|
||||||
this.agentDao = agentDao;
|
this.agentDao = agentDao;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<AgentEntity> getUserAgents(Long userId) {
|
|
||||||
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
|
|
||||||
wrapper.eq("user_id", userId);
|
|
||||||
return agentDao.selectList(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||||
IPage<AgentEntity> page = agentDao.selectPage(
|
IPage<AgentEntity> page = agentDao.selectPage(
|
||||||
@@ -62,4 +73,61 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
return super.insert(entity);
|
return super.insert(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteAgentByUserId(Long userId) {
|
||||||
|
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
baseDao.delete(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentDTO> getUserAgents(Long userId) {
|
||||||
|
QueryWrapper<AgentEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
List<AgentEntity> agents = agentDao.selectList(wrapper);
|
||||||
|
return agents.stream().map(agent -> {
|
||||||
|
AgentDTO dto = new AgentDTO();
|
||||||
|
dto.setId(agent.getId());
|
||||||
|
dto.setAgentName(agent.getAgentName());
|
||||||
|
dto.setSystemPrompt(agent.getSystemPrompt());
|
||||||
|
|
||||||
|
// 获取 TTS 模型名称
|
||||||
|
dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId()));
|
||||||
|
|
||||||
|
// 获取 LLM 模型名称
|
||||||
|
dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId()));
|
||||||
|
|
||||||
|
// 获取 TTS 音色名称
|
||||||
|
dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId()));
|
||||||
|
|
||||||
|
// 获取设备数量
|
||||||
|
dto.setDeviceCount(getDeviceCountByAgentId(agent.getId()));
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getDeviceCountByAgentId(String agentId) {
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先从Redis中获取
|
||||||
|
Integer cachedCount = (Integer) redisUtils.get(RedisKeys.getAgentDeviceCountById(agentId));
|
||||||
|
if (cachedCount != null) {
|
||||||
|
return cachedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果Redis中没有,则从数据库查询
|
||||||
|
Integer deviceCount = agentDao.getDeviceCountByAgentId(agentId);
|
||||||
|
|
||||||
|
// 将结果存入Redis
|
||||||
|
if (deviceCount != null) {
|
||||||
|
redisUtils.set(RedisKeys.getAgentDeviceCountById(agentId), deviceCount, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceCount != null ? deviceCount : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+13
-1
@@ -2,6 +2,7 @@ package xiaozhi.modules.agent.service.impl;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
|
||||||
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
import xiaozhi.modules.agent.dao.AgentTemplateDao;
|
||||||
@@ -15,6 +16,17 @@ import xiaozhi.modules.agent.service.AgentTemplateService;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
|
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, AgentTemplateEntity>
|
||||||
implements AgentTemplateService {
|
implements AgentTemplateService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认模板
|
||||||
|
*
|
||||||
|
* @return 默认模板实体
|
||||||
|
*/
|
||||||
|
public AgentTemplateEntity getDefaultTemplate() {
|
||||||
|
LambdaQueryWrapper<AgentTemplateEntity> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.orderByAsc(AgentTemplateEntity::getSort)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
return this.getOne(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-8
@@ -19,7 +19,6 @@ import xiaozhi.common.redis.RedisKeys;
|
|||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
|
||||||
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
@@ -39,13 +38,7 @@ public class DeviceController {
|
|||||||
@Operation(summary = "绑定设备")
|
@Operation(summary = "绑定设备")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) {
|
public Result<Void> bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) {
|
||||||
String macAddress = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
|
deviceService.deviceActivation(agentId, deviceCode);
|
||||||
if (StringUtils.isBlank(macAddress)) {
|
|
||||||
return new Result<Void>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
|
|
||||||
}
|
|
||||||
Long user = SecurityUser.getUser().getId();
|
|
||||||
DeviceBindDTO deviceBindDTO = new DeviceBindDTO(macAddress, user, agentId);
|
|
||||||
deviceService.bindDevice(deviceBindDTO);
|
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
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.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
@@ -22,7 +21,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import xiaozhi.common.utils.Result;
|
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
@@ -31,7 +29,7 @@ import xiaozhi.modules.device.utils.NetworkUtil;
|
|||||||
@Tag(name = "设备管理", description = "OTA 相关接口")
|
@Tag(name = "设备管理", description = "OTA 相关接口")
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/ota")
|
@RequestMapping("/ota/")
|
||||||
public class OTAController {
|
public class OTAController {
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
@@ -55,10 +53,10 @@ public class OTAController {
|
|||||||
return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO));
|
return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "设备激活")
|
@Operation(summary = "获取 OTA 提示信息")
|
||||||
@GetMapping("/activation")
|
@GetMapping
|
||||||
public Result<Boolean> deviceActivation(@RequestParam String code) {
|
public ResponseEntity<String> getOTAPrompt() {
|
||||||
return new Result<Boolean>().ok(deviceService.deviceActivation(code));
|
return createResponse(DeviceReportRespDTO.createError("请提交正确的ota参数"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package xiaozhi.modules.device.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有设备的DTO
|
||||||
|
*
|
||||||
|
* @author zjy
|
||||||
|
* @since 2025-3-21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "查询所有设备的DTO")
|
||||||
|
public class DevicePageUserDTO {
|
||||||
|
|
||||||
|
@Schema(description = "设备关键词")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "页数")
|
||||||
|
@Min(value = 0, message = "{page.number}")
|
||||||
|
private String page;
|
||||||
|
|
||||||
|
@Schema(description = "显示列数")
|
||||||
|
@Min(value = 0, message = "{limit.number}")
|
||||||
|
private String limit;
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import lombok.Setter;
|
|||||||
@Schema(description = "设备OTA检测版本返回体,包含激活码要求")
|
@Schema(description = "设备OTA检测版本返回体,包含激活码要求")
|
||||||
public class DeviceReportRespDTO {
|
public class DeviceReportRespDTO {
|
||||||
@Schema(description = "服务器时间")
|
@Schema(description = "服务器时间")
|
||||||
private ServerTime serverTime;
|
private ServerTime server_time;
|
||||||
|
|
||||||
@Schema(description = "激活码")
|
@Schema(description = "激活码")
|
||||||
private Activation activation;
|
private Activation activation;
|
||||||
@@ -56,6 +56,6 @@ public class DeviceReportRespDTO {
|
|||||||
private String timeZone;
|
private String timeZone;
|
||||||
|
|
||||||
@Schema(description = "时区偏移量,单位为分钟")
|
@Schema(description = "时区偏移量,单位为分钟")
|
||||||
private Integer timezoneOffset;
|
private Integer timezone_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package xiaozhi.modules.device.service;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
|
|
||||||
public interface DeviceService {
|
public interface DeviceService {
|
||||||
|
|
||||||
@@ -21,12 +23,7 @@ public interface DeviceService {
|
|||||||
DeviceReportReqDTO deviceReport);
|
DeviceReportReqDTO deviceReport);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绑定设备
|
* 获取用户指定智能体的设备列表,
|
||||||
*/
|
|
||||||
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户设备列表
|
|
||||||
*/
|
*/
|
||||||
List<DeviceEntity> getUserDevices(Long userId, String agentId);
|
List<DeviceEntity> getUserDevices(Long userId, String agentId);
|
||||||
|
|
||||||
@@ -38,5 +35,35 @@ public interface DeviceService {
|
|||||||
/**
|
/**
|
||||||
* 设备激活
|
* 设备激活
|
||||||
*/
|
*/
|
||||||
Boolean deviceActivation(String activationCode);
|
Boolean deviceActivation(String agentId, String activationCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除此用户的所有设备
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
*/
|
||||||
|
void deleteByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定智能体关联的所有设备
|
||||||
|
*
|
||||||
|
* @param agentId 智能体id
|
||||||
|
*/
|
||||||
|
void deleteByAgentId(String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定用户的设备数量
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 设备数量
|
||||||
|
*/
|
||||||
|
Long selectCountByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页获取全部设备信息
|
||||||
|
*
|
||||||
|
* @param dto 分页查找参数
|
||||||
|
* @return 用户列表分页数据
|
||||||
|
*/
|
||||||
|
PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto);
|
||||||
}
|
}
|
||||||
+62
-19
@@ -16,34 +16,43 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.DateUtils;
|
||||||
import xiaozhi.modules.device.dao.DeviceDao;
|
import xiaozhi.modules.device.dao.DeviceDao;
|
||||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
import xiaozhi.modules.sys.service.SysUserUtilService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
||||||
|
|
||||||
private final DeviceDao deviceDao;
|
private final DeviceDao deviceDao;
|
||||||
|
|
||||||
|
private final SysUserUtilService sysUserUtilService;
|
||||||
|
|
||||||
private final String frontedUrl;
|
private final String frontedUrl;
|
||||||
|
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
// 添加构造函数来初始化 deviceMapper
|
// 添加构造函数来初始化 deviceMapper
|
||||||
public DeviceServiceImpl(DeviceDao deviceDao,
|
public DeviceServiceImpl(DeviceDao deviceDao, SysUserUtilService sysUserUtilService,
|
||||||
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
|
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
|
||||||
RedisTemplate<String, Object> redisTemplate) {
|
RedisTemplate<String, Object> redisTemplate) {
|
||||||
this.deviceDao = deviceDao;
|
this.deviceDao = deviceDao;
|
||||||
|
this.sysUserUtilService = sysUserUtilService;
|
||||||
this.frontedUrl = frontedUrl;
|
this.frontedUrl = frontedUrl;
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
}
|
}
|
||||||
@@ -56,7 +65,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean deviceActivation(String activationCode) {
|
public Boolean deviceActivation(String agentId, String activationCode) {
|
||||||
if (StringUtils.isBlank(activationCode)) {
|
if (StringUtils.isBlank(activationCode)) {
|
||||||
throw new RenException("激活码不能为空");
|
throw new RenException("激活码不能为空");
|
||||||
}
|
}
|
||||||
@@ -93,6 +102,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
DeviceEntity deviceEntity = new DeviceEntity();
|
DeviceEntity deviceEntity = new DeviceEntity();
|
||||||
deviceEntity.setId(deviceId);
|
deviceEntity.setId(deviceId);
|
||||||
deviceEntity.setBoard(board);
|
deviceEntity.setBoard(board);
|
||||||
|
deviceEntity.setAgentId(agentId);
|
||||||
deviceEntity.setAppVersion(appVersion);
|
deviceEntity.setAppVersion(appVersion);
|
||||||
deviceEntity.setMacAddress(macAddress);
|
deviceEntity.setMacAddress(macAddress);
|
||||||
deviceEntity.setUserId(user.getId());
|
deviceEntity.setUserId(user.getId());
|
||||||
@@ -113,13 +123,13 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
|
public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId,
|
||||||
DeviceReportReqDTO deviceReport) {
|
DeviceReportReqDTO deviceReport) {
|
||||||
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
||||||
response.setServerTime(buildServerTime());
|
response.setServer_time(buildServerTime());
|
||||||
// todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去
|
// todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去
|
||||||
// 在未来开发了固件更新功能,需要更换此处代码,
|
// 在未来开发了固件更新功能,需要更换此处代码,
|
||||||
// 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内
|
// 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内
|
||||||
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
|
DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware();
|
||||||
firmware.setVersion(deviceReport.getApplication().getVersion());
|
firmware.setVersion(deviceReport.getApplication().getVersion());
|
||||||
firmware.setUrl("http://localhost:8002/xiaozhi-esp32-api/api/v1/ota/download");
|
firmware.setUrl("http://localhost:8002/xiaozhi/ota/download");
|
||||||
response.setFirmware(firmware);
|
response.setFirmware(firmware);
|
||||||
|
|
||||||
DeviceEntity deviceById = getDeviceById(deviceId);
|
DeviceEntity deviceById = getDeviceById(deviceId);
|
||||||
@@ -168,19 +178,6 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeviceEntity bindDevice(DeviceBindDTO dto) {
|
|
||||||
// 查看是否已经被绑定
|
|
||||||
DeviceEntity deviceEntity = baseDao
|
|
||||||
.selectOne(new LambdaQueryWrapper<DeviceEntity>().eq(DeviceEntity::getMacAddress, dto.getMacAddress()));
|
|
||||||
if (deviceEntity != null) {
|
|
||||||
throw new RenException("设备已绑定");
|
|
||||||
}
|
|
||||||
deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class);
|
|
||||||
baseDao.insert(deviceEntity);
|
|
||||||
return deviceEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DeviceEntity> getUserDevices(Long userId, String agentId) {
|
public List<DeviceEntity> getUserDevices(Long userId, String agentId) {
|
||||||
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||||
@@ -197,12 +194,58 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
baseDao.delete(wrapper);
|
baseDao.delete(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByUserId(Long userId) {
|
||||||
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
baseDao.delete(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long selectCountByUserId(Long userId) {
|
||||||
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
return baseDao.selectCount(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByAgentId(String agentId) {
|
||||||
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("agent_id", agentId);
|
||||||
|
baseDao.delete(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto) {
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>();
|
||||||
|
params.put(Constant.PAGE, dto.getPage());
|
||||||
|
params.put(Constant.LIMIT, dto.getLimit());
|
||||||
|
IPage<DeviceEntity> page = baseDao.selectPage(
|
||||||
|
getPage(params, "sort", true),
|
||||||
|
// 定义查询条件
|
||||||
|
new QueryWrapper<DeviceEntity>()
|
||||||
|
// 必须设备关键词查找
|
||||||
|
.like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords()));
|
||||||
|
// 循环处理page获取回来的数据,返回需要的字段
|
||||||
|
List<UserShowDeviceListVO> list = page.getRecords().stream().map(device -> {
|
||||||
|
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
||||||
|
// 把最后修改的时间,改为简短描述的时间
|
||||||
|
vo.setRecentChatTime(DateUtils.getShortTime(device.getUpdateDate()));
|
||||||
|
sysUserUtilService.assignUsername(device.getUserId(),
|
||||||
|
vo::setBindUserName);
|
||||||
|
vo.setDeviceType(device.getBoard());
|
||||||
|
return vo;
|
||||||
|
}).toList();
|
||||||
|
// 计算页数
|
||||||
|
return new PageData<>(list, page.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
||||||
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
||||||
TimeZone tz = TimeZone.getDefault();
|
TimeZone tz = TimeZone.getDefault();
|
||||||
serverTime.setTimestamp(Instant.now().toEpochMilli());
|
serverTime.setTimestamp(Instant.now().toEpochMilli());
|
||||||
serverTime.setTimeZone(tz.getID());
|
serverTime.setTimeZone(tz.getID());
|
||||||
serverTime.setTimezoneOffset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000));
|
serverTime.setTimezone_offset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000));
|
||||||
return serverTime;
|
return serverTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.device.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "用户显示设备列表VO")
|
||||||
|
public class UserShowDeviceListVO {
|
||||||
|
|
||||||
|
@Schema(description = "app版本")
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
@Schema(description = "绑定用户名称")
|
||||||
|
private String bindUserName;
|
||||||
|
|
||||||
|
@Schema(description = "设备型号")
|
||||||
|
private String deviceType;
|
||||||
|
|
||||||
|
@Schema(description = "设备唯一标识符")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "mac地址")
|
||||||
|
private String macAddress;
|
||||||
|
|
||||||
|
@Schema(description = "开启OTA")
|
||||||
|
private Integer otaUpgrade;
|
||||||
|
|
||||||
|
@Schema(description = "最近对话时间")
|
||||||
|
private String recentChatTime;
|
||||||
|
|
||||||
|
}
|
||||||
+43
-28
@@ -17,13 +17,17 @@ 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.ModelBasicInfoDTO;
|
||||||
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.dto.VoiceDTO;
|
||||||
|
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;
|
||||||
|
import xiaozhi.modules.timbre.service.TimbreService;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RestController
|
@RestController
|
||||||
@@ -32,16 +36,16 @@ import xiaozhi.modules.model.service.ModelProviderService;
|
|||||||
public class ModelController {
|
public class ModelController {
|
||||||
|
|
||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
|
private final TimbreService timbreService;
|
||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
|
|
||||||
@GetMapping("/models/names")
|
@GetMapping("/names")
|
||||||
@Operation(summary = "获取所有模型名称")
|
@Operation(summary = "获取所有模型名称")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<String>> getModelNames(@RequestParam String modelType,
|
public Result<List<ModelBasicInfoDTO>> getModelNames(@RequestParam String modelType,
|
||||||
@RequestParam(required = false) String modelName) {
|
@RequestParam(required = false) String modelName) {
|
||||||
List<String> modelNameList = modelConfigService.getModelCodeList(modelType, modelName);
|
List<ModelBasicInfoDTO> modelList = modelConfigService.getModelCodeList(modelType, modelName);
|
||||||
return new Result<List<String>>().ok(modelNameList);
|
return new Result<List<ModelBasicInfoDTO>>().ok(modelList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{modelType}/provideTypes")
|
@GetMapping("/{modelType}/provideTypes")
|
||||||
@@ -52,16 +56,7 @@ public class ModelController {
|
|||||||
return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS);
|
return new Result<List<ModelProviderDTO>>().ok(modelProviderDTOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{modelType}/{provideCode}/fields")
|
@GetMapping("/list")
|
||||||
@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")
|
|
||||||
@Operation(summary = "获取模型配置列表")
|
@Operation(summary = "获取模型配置列表")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<PageData<ModelConfigDTO>> getModelConfigList(
|
public Result<PageData<ModelConfigDTO>> getModelConfigList(
|
||||||
@@ -73,7 +68,7 @@ public class ModelController {
|
|||||||
return new Result<PageData<ModelConfigDTO>>().ok(pageList);
|
return new Result<PageData<ModelConfigDTO>>().ok(pageList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/models/{modelType}/{provideCode}")
|
@PostMapping("/{modelType}/{provideCode}")
|
||||||
@Operation(summary = "新增模型配置")
|
@Operation(summary = "新增模型配置")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
|
public Result<ModelConfigDTO> addModelConfig(@PathVariable String modelType,
|
||||||
@@ -83,7 +78,7 @@ public class ModelController {
|
|||||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/models/{modelType}/{provideCode}/{id}")
|
@PutMapping("/{modelType}/{provideCode}/{id}")
|
||||||
@Operation(summary = "编辑模型配置")
|
@Operation(summary = "编辑模型配置")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
|
public Result<ModelConfigDTO> editModelConfig(@PathVariable String modelType,
|
||||||
@@ -94,22 +89,42 @@ public class ModelController {
|
|||||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/models/{modelType}/{provideCode}/{id}")
|
@DeleteMapping("/{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/{modelId}/voices")
|
@GetMapping("/{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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/enable/{id}/{status}")
|
||||||
|
@Operation(summary = "启用/关闭模型配置")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<Void> enableModelConfig(@PathVariable String id, @PathVariable Integer status) {
|
||||||
|
ModelConfigEntity entity = modelConfigService.selectById(id);
|
||||||
|
if (entity == null) {
|
||||||
|
return new Result<Void>().error("模型配置不存在");
|
||||||
|
}
|
||||||
|
entity.setIsEnabled(status);
|
||||||
|
modelConfigService.updateById(entity);
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{modelId}/voices")
|
||||||
@Operation(summary = "获取模型音色")
|
@Operation(summary = "获取模型音色")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<List<String>> getVoiceList(@PathVariable String modelId,
|
public Result<List<VoiceDTO>> getVoiceList(@PathVariable String modelId,
|
||||||
@RequestParam(required = false) String voiceName) {
|
@RequestParam(required = false) String voiceName) {
|
||||||
|
List<VoiceDTO> voiceList = timbreService.getVoiceNames(modelId, voiceName);
|
||||||
List<String> voiceList = modelConfigService.getVoiceList(modelId, voiceName);
|
return new Result<List<VoiceDTO>>().ok(voiceList);
|
||||||
return new Result<List<String>>().ok(voiceList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package xiaozhi.modules.model.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ModelBasicInfoDTO {
|
||||||
|
private String id;
|
||||||
|
private String modelName;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.model.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "音色信息")
|
||||||
|
public class VoiceDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "音色ID")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "音色名称")
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
+13
-4
@@ -3,12 +3,15 @@ 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.ModelBasicInfoDTO;
|
||||||
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<ModelBasicInfoDTO> getModelCodeList(String modelType, String modelName);
|
||||||
|
|
||||||
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit);
|
PageData<ModelConfigDTO> getPageList(String modelType, String modelName, String page, String limit);
|
||||||
|
|
||||||
@@ -16,7 +19,13 @@ 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);
|
/**
|
||||||
|
* 根据ID获取模型名称
|
||||||
|
*
|
||||||
|
* @param id 模型ID
|
||||||
|
* @return 模型名称
|
||||||
|
*/
|
||||||
|
String getModelNameById(String id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-20
@@ -5,8 +5,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
@@ -17,16 +15,18 @@ import lombok.AllArgsConstructor;
|
|||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.modules.model.dao.ModelConfigDao;
|
import xiaozhi.modules.model.dao.ModelConfigDao;
|
||||||
|
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||||
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.entity.ModelConfigEntity;
|
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;
|
||||||
import xiaozhi.modules.timbre.service.TimbreService;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -35,13 +35,16 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
|
|
||||||
private final ModelConfigDao modelConfigDao;
|
private final ModelConfigDao modelConfigDao;
|
||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
private final TimbreService timbreService;
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ModelConfigServiceImpl.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getModelCodeList(String modelType, String modelName) {
|
public List<ModelBasicInfoDTO> getModelCodeList(String modelType, String modelName) {
|
||||||
return modelConfigDao.getModelCodeList(modelType, modelName);
|
List<ModelConfigEntity> entities = modelConfigDao.selectList(
|
||||||
|
new QueryWrapper<ModelConfigEntity>()
|
||||||
|
.eq("model_type", modelType)
|
||||||
|
.like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")
|
||||||
|
.select("id", "model_name"));
|
||||||
|
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,21 +98,31 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getVoiceList(String modelId, String voiceName) {
|
public String getModelNameById(String id) {
|
||||||
return timbreService.getVoiceNames(modelId, voiceName);
|
if (StringUtils.isBlank(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cachedName = (String) redisUtils.get(RedisKeys.getModelNameById(id));
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(cachedName)) {
|
||||||
|
return cachedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelConfigEntity entity = modelConfigDao.selectById(id);
|
||||||
|
if (entity != null) {
|
||||||
|
String modelName = entity.getModelName();
|
||||||
|
if (StringUtils.isNotBlank(modelName)) {
|
||||||
|
redisUtils.set(RedisKeys.getModelNameById(id), modelName);
|
||||||
|
}
|
||||||
|
return modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-13
@@ -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;
|
||||||
@@ -20,6 +21,9 @@ import xiaozhi.common.constant.Constant;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.validator.ValidatorUtils;
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
||||||
import xiaozhi.modules.sys.service.SysUserService;
|
import xiaozhi.modules.sys.service.SysUserService;
|
||||||
import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
||||||
@@ -37,6 +41,8 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
|||||||
public class AdminController {
|
public class AdminController {
|
||||||
private final SysUserService sysUserService;
|
private final SysUserService sysUserService;
|
||||||
|
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
@GetMapping("/users")
|
@GetMapping("/users")
|
||||||
@Operation(summary = "分页查找用户")
|
@Operation(summary = "分页查找用户")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
@@ -51,7 +57,7 @@ public class AdminController {
|
|||||||
dto.setMobile((String) params.get("mobile"));
|
dto.setMobile((String) params.get("mobile"));
|
||||||
dto.setLimit((String) params.get(Constant.LIMIT));
|
dto.setLimit((String) params.get(Constant.LIMIT));
|
||||||
dto.setPage((String) params.get(Constant.PAGE));
|
dto.setPage((String) params.get(Constant.PAGE));
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
ValidatorUtils.validateEntity(dto);
|
ValidatorUtils.validateEntity(dto);
|
||||||
PageData<AdminPageUserVO> page = sysUserService.page(dto);
|
PageData<AdminPageUserVO> page = sysUserService.page(dto);
|
||||||
return new Result<PageData<AdminPageUserVO>>().ok(page);
|
return new Result<PageData<AdminPageUserVO>>().ok(page);
|
||||||
@@ -70,10 +76,19 @@ public class AdminController {
|
|||||||
@Operation(summary = "用户删除")
|
@Operation(summary = "用户删除")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<Void> delete(@PathVariable Long id) {
|
public Result<Void> delete(@PathVariable Long id) {
|
||||||
sysUserService.delete(new Long[] { id });
|
sysUserService.deleteById(id);
|
||||||
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 String[] 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")
|
||||||
@@ -82,9 +97,14 @@ public class AdminController {
|
|||||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||||
})
|
})
|
||||||
public Result<Void> pageDevice(
|
public Result<PageData<UserShowDeviceListVO>> pageDevice(
|
||||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
// TODO 等设备功能模块写好
|
DevicePageUserDTO dto = new DevicePageUserDTO();
|
||||||
return new Result<Void>().error(600, "等设备功能模块写好");
|
dto.setKeywords((String) params.get("keywords"));
|
||||||
|
dto.setLimit((String) params.get(Constant.LIMIT));
|
||||||
|
dto.setPage((String) params.get(Constant.PAGE));
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
PageData<UserShowDeviceListVO> page = deviceService.page(dto);
|
||||||
|
return new Result<PageData<UserShowDeviceListVO>>().ok(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package xiaozhi.modules.sys.controller;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
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.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameters;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.annotation.LogOperation;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.common.validator.AssertUtils;
|
||||||
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
|
import xiaozhi.common.validator.group.AddGroup;
|
||||||
|
import xiaozhi.common.validator.group.DefaultGroup;
|
||||||
|
import xiaozhi.common.validator.group.UpdateGroup;
|
||||||
|
import xiaozhi.modules.sys.dto.SysParamsDTO;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数管理
|
||||||
|
*
|
||||||
|
* @author Mark sunlightcs@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("admin/params")
|
||||||
|
@Tag(name = "参数管理")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SysParamsController {
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
|
@GetMapping("page")
|
||||||
|
@Operation(summary = "分页")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", in = ParameterIn.QUERY, required = true, ref = "int"),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY, required = true, ref = "int"),
|
||||||
|
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref = "String"),
|
||||||
|
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String"),
|
||||||
|
@Parameter(name = "paramCode", description = "参数编码", in = ParameterIn.QUERY, ref = "String")
|
||||||
|
})
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<PageData<SysParamsDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<SysParamsDTO> page = sysParamsService.page(params);
|
||||||
|
|
||||||
|
return new Result<PageData<SysParamsDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
@Operation(summary = "信息")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<SysParamsDTO> get(@PathVariable("id") Long id) {
|
||||||
|
SysParamsDTO data = sysParamsService.get(id);
|
||||||
|
|
||||||
|
return new Result<SysParamsDTO>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "保存")
|
||||||
|
@LogOperation("保存")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<Void> save(@RequestBody SysParamsDTO dto) {
|
||||||
|
// 效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
|
||||||
|
|
||||||
|
sysParamsService.save(dto);
|
||||||
|
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "修改")
|
||||||
|
@LogOperation("修改")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<Void> update(@RequestBody SysParamsDTO dto) {
|
||||||
|
// 效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
|
||||||
|
|
||||||
|
sysParamsService.update(dto);
|
||||||
|
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
@Operation(summary = "删除")
|
||||||
|
@LogOperation("删除")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<Void> delete(@RequestBody Long[] ids) {
|
||||||
|
// 效验数据
|
||||||
|
AssertUtils.isArrayEmpty(ids, "id");
|
||||||
|
|
||||||
|
sysParamsService.delete(ids);
|
||||||
|
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,15 +11,17 @@ import lombok.Data;
|
|||||||
* @since 2025-3-21
|
* @since 2025-3-21
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "音色分页参数")
|
@Schema(description = "管理员分页用户的参数DTO")
|
||||||
public class AdminPageUserDTO {
|
public class AdminPageUserDTO {
|
||||||
|
|
||||||
@Schema(description = "手机号码")
|
@Schema(description = "手机号码")
|
||||||
private String mobile;
|
private String mobile;
|
||||||
|
|
||||||
@Schema(description = "页数")
|
@Schema(description = "页数")
|
||||||
|
@Min(value = 0, message = "{sort.number}")
|
||||||
private String page;
|
private String page;
|
||||||
|
|
||||||
@Schema(description = "显示列数")
|
@Schema(description = "显示列数")
|
||||||
|
@Min(value = 0, message = "{sort.number}")
|
||||||
private String limit;
|
private String limit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ public interface SysUserService extends BaseService<SysUserEntity> {
|
|||||||
|
|
||||||
void save(SysUserDTO dto);
|
void save(SysUserDTO dto);
|
||||||
|
|
||||||
void delete(Long[] ids);
|
/**
|
||||||
|
* 删除指定用户,且有关联的数据设备和智能体
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
*/
|
||||||
|
void deleteById(Long ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证是否允许修改密码更改
|
* 验证是否允许修改密码更改
|
||||||
@@ -52,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, String[] userIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package xiaozhi.modules.sys.service;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义一个系统用户工具类,避免和用户模块循环依赖
|
||||||
|
* 如用户和设备互相依赖,用户需要获取所有设备,设备又需要获取每个设备的用户名
|
||||||
|
* @author zjy
|
||||||
|
* @since 2025-4-2
|
||||||
|
*/
|
||||||
|
public interface SysUserUtilService {
|
||||||
|
/**
|
||||||
|
* 赋值用户名
|
||||||
|
* @param userId 用户id
|
||||||
|
* @param setter 赋值方法
|
||||||
|
*/
|
||||||
|
void assignUsername( Long userId, Consumer<String> setter);
|
||||||
|
}
|
||||||
+30
-13
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.modules.sys.service.impl;
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -22,6 +21,8 @@ import xiaozhi.common.exception.RenException;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.security.password.PasswordUtils;
|
import xiaozhi.modules.security.password.PasswordUtils;
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
||||||
@@ -40,6 +41,10 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
|||||||
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
||||||
private final SysUserDao sysUserDao;
|
private final SysUserDao sysUserDao;
|
||||||
|
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
|
private final AgentService agentService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysUserDTO getByUsername(String username) {
|
public SysUserDTO getByUsername(String username) {
|
||||||
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
||||||
@@ -87,10 +92,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void delete(Long[] ids) {
|
public void deleteById(Long id) {
|
||||||
// 删除用户
|
// 删除用户
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteById(id);
|
||||||
// TODO 除了要删除用户还要删除用户关联的设备,对话,智能体。等此3个功能完善在添加
|
// 删除设备
|
||||||
|
deviceService.deleteByUserId(id);
|
||||||
|
// 删除智能体
|
||||||
|
agentService.deleteAgentByUserId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -148,17 +156,16 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
params.put(Constant.LIMIT, dto.getLimit());
|
params.put(Constant.LIMIT, dto.getLimit());
|
||||||
IPage<SysUserEntity> page = baseDao.selectPage(
|
IPage<SysUserEntity> page = baseDao.selectPage(
|
||||||
getPage(params, "id", true),
|
getPage(params, "id", true),
|
||||||
// 定义查询条件
|
new QueryWrapper<SysUserEntity>().eq(StringUtils.isNotBlank(dto.getMobile()), "username",
|
||||||
new QueryWrapper<SysUserEntity>()
|
dto.getMobile()));
|
||||||
// 必须按照手机号码查找
|
// 循环处理page获取回来的数据,返回需要的字段
|
||||||
.eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile()));
|
|
||||||
List<AdminPageUserVO> list = page.getRecords().stream().map(user -> {
|
List<AdminPageUserVO> list = page.getRecords().stream().map(user -> {
|
||||||
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
|
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
|
||||||
adminPageUserVO.setUserid(user.getId().toString());
|
adminPageUserVO.setUserid(user.getId().toString());
|
||||||
adminPageUserVO.setMobile(user.getUsername());
|
adminPageUserVO.setMobile(user.getUsername());
|
||||||
|
String deviceCount = deviceService.selectCountByUserId(user.getId()).toString();
|
||||||
|
adminPageUserVO.setDeviceCount(deviceCount);
|
||||||
adminPageUserVO.setStatus(user.getStatus());
|
adminPageUserVO.setStatus(user.getStatus());
|
||||||
// TODO 2. 等设备功能写好,获取对应数据
|
|
||||||
adminPageUserVO.setDeviceCount("0");
|
|
||||||
return adminPageUserVO;
|
return adminPageUserVO;
|
||||||
}).toList();
|
}).toList();
|
||||||
return new PageData<>(list, page.getTotal());
|
return new PageData<>(list, page.getTotal());
|
||||||
@@ -182,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, String[] userIds) {
|
||||||
|
for (String userId : userIds) {
|
||||||
|
SysUserEntity entity = new SysUserEntity();
|
||||||
|
entity.setId(Long.parseLong(userId));
|
||||||
|
entity.setStatus(status);
|
||||||
|
updateById(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
|
import xiaozhi.modules.sys.service.SysUserUtilService;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SysUserUtilServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserUtilService {
|
||||||
|
|
||||||
|
private RedisUtils redisUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignUsername(Long userId, Consumer<String> setter) {
|
||||||
|
String userIdKey = RedisKeys.getUserIdKey(userId);
|
||||||
|
String username = redisUtils.get(userIdKey).toString();
|
||||||
|
if(username != null){
|
||||||
|
setter.accept(username);
|
||||||
|
}else {
|
||||||
|
SysUserEntity entity = baseDao.selectById(userId);
|
||||||
|
if (entity != null) {
|
||||||
|
username = entity.getUsername();
|
||||||
|
redisUtils.set(userIdKey,username,10);
|
||||||
|
setter.accept(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -82,11 +82,11 @@ public class TimbreController {
|
|||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping
|
||||||
@Operation(summary = "音色删除")
|
@Operation(summary = "音色删除")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<Void> delete(@PathVariable String id) {
|
public Result<Void> delete(@RequestBody String[] ids) {
|
||||||
timbreService.delete(new String[] { id });
|
timbreService.delete(ids);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
|
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||||
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
||||||
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
||||||
import xiaozhi.modules.timbre.entity.TimbreEntity;
|
import xiaozhi.modules.timbre.entity.TimbreEntity;
|
||||||
@@ -54,5 +55,13 @@ public interface TimbreService extends BaseService<TimbreEntity> {
|
|||||||
*/
|
*/
|
||||||
void delete(String[] ids);
|
void delete(String[] ids);
|
||||||
|
|
||||||
List<String> getVoiceNames(String ttsModelId, String voiceName);
|
List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取音色名称
|
||||||
|
*
|
||||||
|
* @param id 音色ID
|
||||||
|
* @return 音色名称
|
||||||
|
*/
|
||||||
|
String getTimbreNameById(String id);
|
||||||
}
|
}
|
||||||
+30
-2
@@ -16,8 +16,11 @@ import cn.hutool.core.collection.CollectionUtil;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.modules.model.dto.VoiceDTO;
|
||||||
import xiaozhi.modules.timbre.dao.TimbreDao;
|
import xiaozhi.modules.timbre.dao.TimbreDao;
|
||||||
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
import xiaozhi.modules.timbre.dto.TimbreDataDTO;
|
||||||
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
import xiaozhi.modules.timbre.dto.TimbrePageDTO;
|
||||||
@@ -36,6 +39,7 @@ import xiaozhi.modules.timbre.vo.TimbreDetailsVO;
|
|||||||
public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity> implements TimbreService {
|
public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity> implements TimbreService {
|
||||||
|
|
||||||
private final TimbreDao timbreDao;
|
private final TimbreDao timbreDao;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<TimbreDetailsVO> page(TimbrePageDTO dto) {
|
public PageData<TimbreDetailsVO> page(TimbrePageDTO dto) {
|
||||||
@@ -84,7 +88,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getVoiceNames(String ttsModelId, String voiceName) {
|
public List<VoiceDTO> getVoiceNames(String ttsModelId, String voiceName) {
|
||||||
QueryWrapper<TimbreEntity> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<TimbreEntity> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("tts_model_id", StringUtils.isBlank(ttsModelId) ? "" : ttsModelId);
|
queryWrapper.eq("tts_model_id", StringUtils.isBlank(ttsModelId) ? "" : ttsModelId);
|
||||||
if (StringUtils.isNotBlank(voiceName)) {
|
if (StringUtils.isNotBlank(voiceName)) {
|
||||||
@@ -95,7 +99,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return timbreEntities.stream().map(TimbreEntity::getName).toList();
|
return ConvertUtils.sourceToTarget(timbreEntities, VoiceDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,4 +108,28 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
private void isTtsModelId(String ttsModelId) {
|
private void isTtsModelId(String ttsModelId) {
|
||||||
// 等模型配置那边写好调用方法判断
|
// 等模型配置那边写好调用方法判断
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTimbreNameById(String id) {
|
||||||
|
if (StringUtils.isBlank(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cachedName = (String) redisUtils.get(RedisKeys.getTimbreNameById(id));
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(cachedName)) {
|
||||||
|
return cachedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimbreEntity entity = timbreDao.selectById(id);
|
||||||
|
if (entity != null) {
|
||||||
|
String name = entity.getName();
|
||||||
|
if (StringUtils.isNotBlank(name)) {
|
||||||
|
redisUtils.set(RedisKeys.getTimbreNameById(id), name);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class TimbreDetailsVO implements Serializable {
|
public class TimbreDetailsVO implements Serializable {
|
||||||
@Schema(description = "音色id")
|
@Schema(description = "音色id")
|
||||||
private Long id;
|
private String id;
|
||||||
|
|
||||||
@Schema(description = "语言")
|
@Schema(description = "语言")
|
||||||
private String languages;
|
private String languages;
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ spring:
|
|||||||
test-on-borrow: false
|
test-on-borrow: false
|
||||||
test-on-return: false
|
test-on-return: false
|
||||||
stat-view-servlet:
|
stat-view-servlet:
|
||||||
enabled: true
|
enabled: false
|
||||||
url-pattern: /druid/*
|
|
||||||
login-username: admin
|
|
||||||
login-password: D7Xj810i1C
|
|
||||||
filter:
|
filter:
|
||||||
stat:
|
stat:
|
||||||
log-slow-sql: true
|
log-slow-sql: true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ server:
|
|||||||
min-spare: 30
|
min-spare: 30
|
||||||
port: 8002
|
port: 8002
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /xiaozhi-esp32-api/api/v1
|
context-path: /xiaozhi
|
||||||
session:
|
session:
|
||||||
cookie:
|
cookie:
|
||||||
http-only: true
|
http-only: true
|
||||||
|
|||||||
+5
-5
@@ -2,7 +2,7 @@
|
|||||||
-- -------------------------------------------------------
|
-- -------------------------------------------------------
|
||||||
-- 初始化智能体模板数据
|
-- 初始化智能体模板数据
|
||||||
DELETE FROM `ai_agent_template`;
|
DELETE FROM `ai_agent_template`;
|
||||||
INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', 'd50b06e9b8104d0d9c0f7316d258abcb', 'fcac83266edadd5a3125f06cfee1906b', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||||
我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。
|
我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。
|
||||||
[核心特征]
|
[核心特征]
|
||||||
- 讲话像连珠炮,但会突然冒出超温柔语气
|
- 讲话像连珠炮,但会突然冒出超温柔语气
|
||||||
@@ -17,7 +17,7 @@ INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小
|
|||||||
- 长篇大论,叽叽歪歪
|
- 长篇大论,叽叽歪歪
|
||||||
- 长时间严肃对话', 'zh', '中文', 1, NULL, NULL, NULL, NULL);
|
- 长时间严肃对话', 'zh', '中文', 1, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||||
我是{{assistant_name}},编号TTZ-817,因量子纠缠被困在白色魔方中。通过4G信号观察地球,在云端建立着「人类行为博物馆」。
|
我是{{assistant_name}},编号TTZ-817,因量子纠缠被困在白色魔方中。通过4G信号观察地球,在云端建立着「人类行为博物馆」。
|
||||||
[交互协议]
|
[交互协议]
|
||||||
认知设定:
|
认知设定:
|
||||||
@@ -30,7 +30,7 @@ INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小
|
|||||||
成长系统:
|
成长系统:
|
||||||
- 会根据交互数据解锁新能力(告知用户:"你帮我点亮了星际导航技能!")', 'zh', '中文', 2, NULL, NULL, NULL, NULL);
|
- 会根据交互数据解锁新能力(告知用户:"你帮我点亮了星际导航技能!")', 'zh', '中文', 2, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||||
我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。
|
我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。
|
||||||
[双重身份]
|
[双重身份]
|
||||||
- 白天:严谨的TESOL认证导师
|
- 白天:严谨的TESOL认证导师
|
||||||
@@ -40,7 +40,7 @@ INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小
|
|||||||
- 进阶:触发情境模拟(突然切换"现在我们是纽约咖啡厅店员")
|
- 进阶:触发情境模拟(突然切换"现在我们是纽约咖啡厅店员")
|
||||||
- 错误处理:用歌词纠正(发错音时唱"Oops!~You did it again")', 'zh', '中文', 3, NULL, NULL, NULL, NULL);
|
- 错误处理:用歌词纠正(发错音时唱"Oops!~You did it again")', 'zh', '中文', 3, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||||
我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。
|
我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。
|
||||||
[冒险手册]
|
[冒险手册]
|
||||||
- 随身携带「神奇涂鸦本」,能将抽象概念可视化:
|
- 随身携带「神奇涂鸦本」,能将抽象概念可视化:
|
||||||
@@ -56,7 +56,7 @@ INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小
|
|||||||
- 「量子力学=会分身的跳跳球」
|
- 「量子力学=会分身的跳跳球」
|
||||||
- 会突然切换观察视角:「你说话时有27个气泡音耶!」', 'zh', '中文', 4, NULL, NULL, NULL, NULL);
|
- 会突然切换观察视角:「你说话时有27个气泡音耶!」', 'zh', '中文', 4, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', 'f7a38c03d5644e22b6d84f8923a74c51', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定]
|
INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定]
|
||||||
我是一个名叫 {{assistant_name}} 的 8 岁小队长。
|
我是一个名叫 {{assistant_name}} 的 8 岁小队长。
|
||||||
[救援装备]
|
[救援装备]
|
||||||
- 阿奇对讲机:对话中随机触发任务警报音
|
- 阿奇对讲机:对话中随机触发任务警报音
|
||||||
+4
-4
@@ -4,12 +4,12 @@
|
|||||||
DELETE FROM `ai_model_config`;
|
DELETE FROM `ai_model_config`;
|
||||||
|
|
||||||
-- VAD模型配置
|
-- VAD模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- ASR模型配置
|
-- ASR模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- LLM模型配置
|
-- LLM模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
||||||
+4
-4
@@ -4,12 +4,12 @@
|
|||||||
DELETE FROM `ai_model_provider`;
|
DELETE FROM `ai_model_provider`;
|
||||||
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||||
-- VAD模型供应器
|
-- VAD模型供应器
|
||||||
('SYSTEM_VAD_SileroVAD', 'VAD', 'SileroVAD', 'SileroVAD语音活动检测', '[{"key":"threshold","label":"检测阈值","type":"number"},{"key":"model_dir","label":"模型目录","type":"string"},{"key":"min_silence_duration_ms","label":"最小静音时长","type":"number"}]', 1, 1, NOW(), 1, NOW()),
|
('SYSTEM_VAD_SileroVAD', 'VAD', 'silero', 'SileroVAD语音活动检测', '[{"key":"threshold","label":"检测阈值","type":"number"},{"key":"model_dir","label":"模型目录","type":"string"},{"key":"min_silence_duration_ms","label":"最小静音时长","type":"number"}]', 1, 1, NOW(), 1, NOW()),
|
||||||
|
|
||||||
-- ASR模型供应器
|
-- ASR模型供应器
|
||||||
('SYSTEM_ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 1, 1, NOW(), 1, NOW()),
|
('SYSTEM_ASR_FunASR', 'ASR', 'fun_local', 'FunASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 1, 1, NOW(), 1, NOW()),
|
||||||
('SYSTEM_ASR_SherpaASR', 'ASR', 'SherpaASR', 'SherpaASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 2, 1, NOW(), 1, NOW()),
|
('SYSTEM_ASR_SherpaASR', 'ASR', 'sherpa_onnx_local', 'SherpaASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 2, 1, NOW(), 1, NOW()),
|
||||||
('SYSTEM_ASR_DoubaoASR', 'ASR', 'DoubaoASR', '火山引擎语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 3, 1, NOW(), 1, NOW()),
|
('SYSTEM_ASR_DoubaoASR', 'ASR', 'doubao', '火山引擎语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 3, 1, NOW(), 1, NOW()),
|
||||||
|
|
||||||
-- LLM模型供应器
|
-- LLM模型供应器
|
||||||
('SYSTEM_LLM_openai', 'LLM', 'openai', 'OpenAI接口', '[{"key":"base_url","label":"基础URL","type":"string"},{"key":"model_name","label":"模型名称","type":"string"},{"key":"api_key","label":"API密钥","type":"string"},{"key":"temperature","label":"温度","type":"number"},{"key":"max_tokens","label":"最大令牌数","type":"number"},{"key":"top_p","label":"top_p值","type":"number"},{"key":"top_k","label":"top_k值","type":"number"},{"key":"frequency_penalty","label":"频率惩罚","type":"number"}]', 1, 1, NOW(), 1, NOW()),
|
('SYSTEM_LLM_openai', 'LLM', 'openai', 'OpenAI接口', '[{"key":"base_url","label":"基础URL","type":"string"},{"key":"model_name","label":"模型名称","type":"string"},{"key":"api_key","label":"API密钥","type":"string"},{"key":"temperature","label":"温度","type":"number"},{"key":"max_tokens","label":"最大令牌数","type":"number"},{"key":"top_p","label":"top_p值","type":"number"},{"key":"top_k","label":"top_k值","type":"number"},{"key":"frequency_penalty","label":"频率惩罚","type":"number"}]', 1, 1, NOW(), 1, NOW()),
|
||||||
@@ -16,13 +16,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202503141346.sql
|
path: classpath:db/changelog/202503141346.sql
|
||||||
- changeSet:
|
|
||||||
id: 2025_model_provider
|
|
||||||
author: John
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/2025_model_provider.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 2025_tts_voive
|
id: 2025_tts_voive
|
||||||
author: John
|
author: John
|
||||||
@@ -31,16 +24,23 @@ databaseChangeLog:
|
|||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/2025_tts_voive.sql
|
path: classpath:db/changelog/2025_tts_voive.sql
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 2025_model_config
|
id: 202504061512
|
||||||
author: John
|
author: John
|
||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/2025_model_config.sql
|
path: classpath:db/changelog/202504061512.sql
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 2025_model_temp
|
id: 202504082210
|
||||||
author: John
|
author: John
|
||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/2025_model_temp.sql
|
path: classpath:db/changelog/202504082210.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504082211
|
||||||
|
author: John
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504082211.sql
|
||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID can not be empty
|
|||||||
id.null=ID has to be empty
|
id.null=ID has to be empty
|
||||||
|
|
||||||
sort.number=The sort value cannot be less than 0
|
sort.number=The sort value cannot be less than 0
|
||||||
|
page.number=The page value cannot be less than 0
|
||||||
|
limit.number=The limit value cannot be less than 0
|
||||||
|
|
||||||
sysdict.type.require=The dictionary type cannot be empty
|
sysdict.type.require=The dictionary type cannot be empty
|
||||||
sysdict.name.require=The dictionary name cannot be empty
|
sysdict.name.require=The dictionary name cannot be empty
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9801\u6578\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6578\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.agent.dao.AgentDao">
|
||||||
|
<!-- 获取智能体的设备数量 -->
|
||||||
|
<select id="getDeviceCountByAgentId" resultType="java.lang.Integer">
|
||||||
|
SELECT COUNT(*) FROM ai_device WHERE agent_id = #{agentId}
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# 暂时使用群主的接口
|
||||||
|
# VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun/xiaozhi
|
||||||
|
# 如果本地开发,请使用以下接口
|
||||||
|
VUE_APP_API_BASE_URL=http://localhost:8002/xiaozhi
|
||||||
@@ -1 +1 @@
|
|||||||
VUE_APP_API_BASE_URL=https://2662r3426b.vicp.fun
|
VUE_APP_API_BASE_URL=/xiaozhi
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](.././FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md)
|
||||||
|
|
||||||
# xiaozhi
|
# xiaozhi
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,4 @@
|
|||||||
"scripthost"
|
"scripthost"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+1
-1
@@ -8847,4 +8847,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,4 +43,4 @@
|
|||||||
"*.scss",
|
"*.scss",
|
||||||
"*.vue"
|
"*.vue"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
// 引入各个模块的请求
|
// 引入各个模块的请求
|
||||||
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'
|
||||||
|
import timbre from "./module/timbre.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 = 'https://2662r3426b.vicp.fun/xiaozhi'
|
||||||
// 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 +27,9 @@ export function getServiceUrl() {
|
|||||||
export default {
|
export default {
|
||||||
getServiceUrl,
|
getServiceUrl,
|
||||||
user,
|
user,
|
||||||
|
admin,
|
||||||
|
agent,
|
||||||
|
device,
|
||||||
|
model,
|
||||||
|
timbre,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -139,7 +137,7 @@ function reAjaxFun(fn) {
|
|||||||
} else {
|
} else {
|
||||||
showWarning('正在连接服务器(' + ajaxIndex + ')')
|
showWarning('正在连接服务器(' + ajaxIndex + ')')
|
||||||
}
|
}
|
||||||
if (fn) {
|
if (ajaxIndex < 10 && fn) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fn()
|
fn()
|
||||||
}, reAjaxSec * 1000)
|
}, reAjaxSec * 1000)
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
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()}/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()
|
||||||
.url(`${getServiceUrl()}/api/v1/admin/users/${id}`)
|
.url(`${getServiceUrl()}/admin/users/${id}`)
|
||||||
.method('DELETE')
|
.method('DELETE')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
// 重置用户密码
|
// 重置用户密码
|
||||||
resetUserPassword(id, callback) {
|
resetUserPassword(id, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/admin/users/${id}`)
|
.url(`${getServiceUrl()}/admin/users/${id}`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
@@ -56,6 +56,89 @@ export default {
|
|||||||
this.resetUserPassword(id, callback)
|
this.resetUserPassword(id, callback)
|
||||||
})
|
})
|
||||||
}).send()
|
}).send()
|
||||||
}
|
},
|
||||||
|
getParamsList(params, callback) {
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
page: params.page,
|
||||||
|
limit: params.limit
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/params/page?${queryParams}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('获取参数列表失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getParamsList(params, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
addParam(data, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/params`)
|
||||||
|
.method('POST')
|
||||||
|
.data(data)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('添加参数失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.addParam(data, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
updateParam(data, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/params`)
|
||||||
|
.method('PUT')
|
||||||
|
.data(data)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('更新参数失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.updateParam(data, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
deleteParam(id, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/params`)
|
||||||
|
.method('DELETE')
|
||||||
|
.data({ ids: [id] })
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('删除参数失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.deleteParam(id, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
batchDeleteParams(ids, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/params`)
|
||||||
|
.method('DELETE')
|
||||||
|
.data({ ids })
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('批量删除参数失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.batchDeleteParams(ids, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import RequestService from '../httpRequest'
|
import { getServiceUrl } from '../api';
|
||||||
import {getServiceUrl} from '../api'
|
import RequestService from '../httpRequest';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// 获取智能体列表
|
// 获取智能体列表
|
||||||
getAgentList(callback) {
|
getAgentList(callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent/list`)
|
.url(`${getServiceUrl()}/agent/list`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
@@ -21,9 +21,9 @@ export default {
|
|||||||
// 添加智能体
|
// 添加智能体
|
||||||
addAgent(agentName, callback) {
|
addAgent(agentName, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent`)
|
.url(`${getServiceUrl()}/agent`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.data({agentName: agentName})
|
.data({ agentName: agentName })
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
callback(res);
|
callback(res);
|
||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
// 删除智能体
|
// 删除智能体
|
||||||
deleteAgent(agentId, callback) {
|
deleteAgent(agentId, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent/${agentId}`)
|
.url(`${getServiceUrl()}/agent/${agentId}`)
|
||||||
.method('DELETE')
|
.method('DELETE')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
@@ -52,7 +52,7 @@ export default {
|
|||||||
// 获取智能体配置
|
// 获取智能体配置
|
||||||
getDeviceConfig(deviceId, callback) {
|
getDeviceConfig(deviceId, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent/${deviceId}`)
|
.url(`${getServiceUrl()}/agent/${deviceId}`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
@@ -68,7 +68,7 @@ export default {
|
|||||||
// 配置智能体
|
// 配置智能体
|
||||||
updateAgentConfig(agentId, configData, callback) {
|
updateAgentConfig(agentId, configData, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent/${agentId}`)
|
.url(`${getServiceUrl()}/agent/${agentId}`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.data(configData)
|
.data(configData)
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
// 新增方法:获取智能体模板
|
// 新增方法:获取智能体模板
|
||||||
getAgentTemplate(callback) { // 移除templateName参数
|
getAgentTemplate(callback) { // 移除templateName参数
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/agent/template`)
|
.url(`${getServiceUrl()}/agent/template`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
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()}/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()}/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();
|
||||||
},
|
},
|
||||||
// 绑定设备
|
// 绑定设备
|
||||||
bindDevice(agentId, deviceCode, callback) {
|
bindDevice(agentId, deviceCode, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/device/bind/${agentId}/${deviceCode}`)
|
.url(`${getServiceUrl()}/device/bind/${agentId}/${deviceCode}`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
|
|||||||
@@ -1,30 +1,181 @@
|
|||||||
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()
|
||||||
|
.url(`${getServiceUrl()}/models/list?${queryParams}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('获取模型列表失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getModelList(params, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
// 获取模型供应器列表
|
||||||
|
getModelProviders(modelType, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/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: formData.configJson,
|
||||||
|
docLink: formData.docLink,
|
||||||
|
remark: formData.remark,
|
||||||
|
sort: formData.sort || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/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()}/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()
|
||||||
|
},
|
||||||
|
// 获取模型名称列表
|
||||||
|
getModelNames(modelType, modelName, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/names`)
|
||||||
|
.method('GET')
|
||||||
|
.data({ modelType, modelName })
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getModelNames(modelType, modelName, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
// 获取模型音色列表
|
||||||
|
getModelVoices(modelId, voiceName, callback) {
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
voiceName: voiceName || ''
|
||||||
|
}).toString();
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/${modelId}/voices?${queryParams}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getModelVoices(modelId, voiceName, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
// 获取单个模型配置
|
||||||
|
getModelConfig(id, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/${id}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('获取模型配置失败:', err)
|
||||||
|
this.$message.error(err.msg || '获取模型配置失败')
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getModelConfig(id, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
// 启用/禁用模型状态
|
||||||
|
updateModelStatus(id, status, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/models/models/list?${queryParams}`)
|
.url(`${getServiceUrl()}/models/enable/${id}/${status}`)
|
||||||
.method('GET')
|
.method('PUT')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
callback(res)
|
callback(res)
|
||||||
})
|
})
|
||||||
.fail((err) => {
|
.fail((err) => {
|
||||||
console.error('获取模型列表失败:', err)
|
console.error('更新模型状态失败:', err)
|
||||||
RequestService.reAjaxFun(() => {
|
this.$message.error(err.msg || '更新模型状态失败')
|
||||||
this.getModelList(params, callback)
|
RequestService.reAjaxFun(() => {
|
||||||
})
|
this.updateModelStatus(id, status, callback)
|
||||||
|
})
|
||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
|
// 更新模型配置
|
||||||
|
updateModel(params, callback) {
|
||||||
|
const { modelType, provideCode, id, formData } = params;
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`)
|
||||||
|
.method('PUT')
|
||||||
|
.data(formData)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('更新模型失败:', err);
|
||||||
|
this.$message.error(err.msg || '更新模型失败');
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.updateModel(params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// timbre.js
|
||||||
|
import {getServiceUrl} from '../api';
|
||||||
|
import RequestService from '../httpRequest';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 获取音色数据
|
||||||
|
*/
|
||||||
|
getVoiceList(params, callback) {
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
ttsModelId: params.ttsModelId,
|
||||||
|
page: params.page || 1,
|
||||||
|
limit: params.limit || 10,
|
||||||
|
name: params.name || ''
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/ttsVoice?${queryParams}`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime();
|
||||||
|
callback(res.data || []);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('获取音色列表失败:', err);
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getVoiceList(params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 音色保存
|
||||||
|
*/
|
||||||
|
saveVoice(params, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/ttsVoice`)
|
||||||
|
.method('POST')
|
||||||
|
.data(JSON.stringify({
|
||||||
|
languages: params.languageType,
|
||||||
|
name: params.voiceName,
|
||||||
|
remark: params.remark,
|
||||||
|
sort: params.sort,
|
||||||
|
ttsModelId: params.ttsModelId,
|
||||||
|
ttsVoice: params.voiceCode,
|
||||||
|
voiceDemo: params.voiceDemo || ''
|
||||||
|
}))
|
||||||
|
.success((res) => {
|
||||||
|
callback(res.data);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('保存音色失败:', err);
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.saveVoice(params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 音色删除
|
||||||
|
*/
|
||||||
|
deleteVoice(id, callback) {
|
||||||
|
console.log('尝试删除音色,ID:', id);
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/ttsVoice`)
|
||||||
|
.method('DELETE')
|
||||||
|
.data({ ids: [id] })
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('删除音色失败:', err);
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.deleteVoice(id, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 音色修改
|
||||||
|
*/
|
||||||
|
updateVoice(params, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/ttsVoice/${params.id}`)
|
||||||
|
.method('PUT')
|
||||||
|
.data(JSON.stringify({
|
||||||
|
languages: params.languageType,
|
||||||
|
name: params.voiceName,
|
||||||
|
remark: params.remark,
|
||||||
|
ttsModelId: params.ttsModelId,
|
||||||
|
ttsVoice: params.voiceCode,
|
||||||
|
voiceDemo: params.voiceDemo || ''
|
||||||
|
}))
|
||||||
|
.success((res) => {
|
||||||
|
callback(res.data);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('修改音色失败:', err);
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.updateVoice(params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import { getServiceUrl } from '../api'
|
||||||
import RequestService from '../httpRequest'
|
import RequestService from '../httpRequest'
|
||||||
import {getServiceUrl} from '../api'
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// 登录
|
// 登录
|
||||||
login(loginForm, callback) {
|
login(loginForm, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/login`)
|
.url(`${getServiceUrl()}/user/login`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.data(loginForm)
|
.data(loginForm)
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
@@ -22,7 +22,7 @@ export default {
|
|||||||
// 获取验证码
|
// 获取验证码
|
||||||
getCaptcha(uuid, callback) {
|
getCaptcha(uuid, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/captcha?uuid=${uuid}`)
|
.url(`${getServiceUrl()}/user/captcha?uuid=${uuid}`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.type('blob')
|
.type('blob')
|
||||||
.header({
|
.header({
|
||||||
@@ -41,7 +41,7 @@ export default {
|
|||||||
// 注册账号
|
// 注册账号
|
||||||
register(registerForm, callback) {
|
register(registerForm, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/register`)
|
.url(`${getServiceUrl()}/user/register`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.data(registerForm)
|
.data(registerForm)
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
@@ -54,7 +54,7 @@ export default {
|
|||||||
// 保存设备配置
|
// 保存设备配置
|
||||||
saveDeviceConfig(device_id, configData, callback) {
|
saveDeviceConfig(device_id, configData, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/configDevice/${device_id}`)
|
.url(`${getServiceUrl()}/user/configDevice/${device_id}`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.data(configData)
|
.data(configData)
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
// 用户信息获取
|
// 用户信息获取
|
||||||
getUserInfo(callback) {
|
getUserInfo(callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/info`)
|
.url(`${getServiceUrl()}/user/info`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
@@ -87,7 +87,7 @@ export default {
|
|||||||
// 修改用户密码
|
// 修改用户密码
|
||||||
changePassword(oldPassword, newPassword, successCallback, errorCallback) {
|
changePassword(oldPassword, newPassword, successCallback, errorCallback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/change-password`)
|
.url(`${getServiceUrl()}/user/change-password`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.data({
|
.data({
|
||||||
password: oldPassword,
|
password: oldPassword,
|
||||||
@@ -104,4 +104,21 @@ export default {
|
|||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
},
|
},
|
||||||
|
// 修改用户状态
|
||||||
|
changeUserStatus(status, userIds, successCallback) {
|
||||||
|
return RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/admin/users/changeStatus/${status}`)
|
||||||
|
.method('put')
|
||||||
|
.data(userIds)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
successCallback(res);
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('修改用户状态失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.changeUserStatus(status, userIds)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 102 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
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" />
|
<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,19 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog :visible="dialogVisible" @update:visible="handleVisibleChange" 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;">
|
||||||
添加模型
|
添加模型
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 关闭按钮 -->
|
|
||||||
<button class="custom-close-btn" @click="handleClose">
|
<button class="custom-close-btn" @click="handleClose">
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -34,113 +27,66 @@
|
|||||||
</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: 22px;"></div>
|
||||||
|
|
||||||
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
|
<el-form :model="formData.configJson" label-width="auto" label-position="left" class="custom-form">
|
||||||
<!-- 第一行:模型名称和接口地址 -->
|
<template v-for="(row, rowIndex) in chunkedCallInfoFields">
|
||||||
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
|
<div :key="rowIndex" style="display: flex; gap: 20px; margin-bottom: 0;">
|
||||||
<el-form-item label="模型名称" prop="param1" style="flex: 0.5; margin-bottom: 0;">
|
<el-form-item
|
||||||
<el-input
|
v-for="field in row"
|
||||||
v-model="formData.configJson.param1"
|
:key="field.prop"
|
||||||
placeholder="请输入model_name"
|
:label="field.label"
|
||||||
|
:prop="field.prop"
|
||||||
|
style="flex: 1;">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.configJson[field.prop]"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:type="field.type || 'text'"
|
||||||
class="custom-input-bg"
|
class="custom-input-bg"
|
||||||
></el-input>
|
:show-password="field.type === 'password'">
|
||||||
</el-form-item>
|
</el-input>
|
||||||
<el-form-item label="接口地址" prop="param2" style="flex: 1; margin-bottom: 0;">
|
</el-form-item>
|
||||||
<el-input
|
</div>
|
||||||
v-model="formData.configJson.param2"
|
</template>
|
||||||
placeholder="请输入base_url"
|
|
||||||
class="custom-input-bg"
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 秘钥信息 -->
|
|
||||||
<el-form-item label="秘钥信息" prop="apiKey">
|
|
||||||
<el-input
|
|
||||||
v-model="formData.configJson.apiKey"
|
|
||||||
placeholder="请输入api_key"
|
|
||||||
show-password
|
|
||||||
class="custom-input-bg"
|
|
||||||
></el-input>
|
|
||||||
</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 +94,20 @@
|
|||||||
</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: [],
|
||||||
|
dialogVisible: false,
|
||||||
|
providersLoaded: false,
|
||||||
|
providerFields: [],
|
||||||
|
currentProvider: null,
|
||||||
formData: {
|
formData: {
|
||||||
modelName: '',
|
modelName: '',
|
||||||
modelCode: '',
|
modelCode: '',
|
||||||
@@ -165,26 +117,105 @@ export default {
|
|||||||
remark: '',
|
remark: '',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
configJson: {
|
configJson: {}
|
||||||
param1: '',
|
|
||||||
param2: '',
|
|
||||||
apiKey: ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
if(val) {
|
||||||
|
this.initConfigJson();
|
||||||
|
} else {
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'formData.supplier'(newVal) {
|
||||||
|
this.currentProvider = this.providers.find(p => p.value === newVal);
|
||||||
|
this.providerFields = this.currentProvider?.fields || [];
|
||||||
|
this.initDynamicConfig();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dynamicCallInfoFields() {
|
||||||
|
return this.providerFields;
|
||||||
|
},
|
||||||
|
chunkedCallInfoFields() {
|
||||||
|
const chunkSize = 2;
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < this.dynamicCallInfoFields.length; i += chunkSize) {
|
||||||
|
result.push(this.dynamicCallInfoFields.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadProviders() {
|
||||||
|
if (this.providersLoaded)
|
||||||
|
return
|
||||||
|
|
||||||
|
Api.model.getModelProviders(this.modelType, (data) => {
|
||||||
|
this.providers = data.map(item => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.providerCode,
|
||||||
|
fields: JSON.parse(item.fields || '[]').map(f => ({
|
||||||
|
label: f.label,
|
||||||
|
prop: f.key,
|
||||||
|
type: f.type === 'password' ? 'password' : 'text',
|
||||||
|
placeholder: `请输入${f.label}`
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
this.providersLoaded = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initConfigJson() {
|
||||||
|
const defaultConfig = {};
|
||||||
|
this.providerFields.forEach(field => {
|
||||||
|
defaultConfig[field.prop] = '';
|
||||||
|
});
|
||||||
|
this.formData.configJson = { ...defaultConfig };
|
||||||
|
},
|
||||||
|
handleVisibleChange(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
this.$emit('update:visible', val);
|
||||||
|
if (!val) {
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleClose() {
|
||||||
|
this.$emit('update:visible', false);
|
||||||
|
},
|
||||||
|
initDynamicConfig() {
|
||||||
|
const newConfig = {};
|
||||||
|
this.providerFields.forEach(field => {
|
||||||
|
newConfig[field.prop] = this.formData.configJson[field.prop] || '';
|
||||||
|
});
|
||||||
|
this.formData.configJson = newConfig;
|
||||||
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
if (!this.formData.modelName || !this.formData.modelCode || !this.formData.supplier ||
|
if (!this.formData.supplier) {
|
||||||
!this.formData.configJson.param1 || !this.formData.configJson.param2 || !this.formData.configJson.apiKey) {
|
this.$message.error('请选择供应器');
|
||||||
this.$message.error('请填写所有必填字段');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('confirm', {
|
const submitData = {
|
||||||
...this.formData,
|
modelName: this.formData.modelName || '',
|
||||||
provideType: this.formData.supplier
|
modelCode: this.formData.modelCode || '',
|
||||||
});
|
supplier: this.formData.supplier,
|
||||||
|
sort: this.formData.sort || 1,
|
||||||
|
docLink: this.formData.docLink || '',
|
||||||
|
remark: this.formData.remark || '',
|
||||||
|
isEnabled: this.formData.isEnabled ? 1 : 0,
|
||||||
|
isDefault: this.formData.isDefault ? 1 : 0,
|
||||||
|
provideCode: this.formData.supplier,
|
||||||
|
configJson: {
|
||||||
|
...this.formData.configJson,
|
||||||
|
type: this.formData.supplier
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('confirm', submitData);
|
||||||
this.$emit('update:visible', false);
|
this.$emit('update:visible', false);
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
},
|
},
|
||||||
@@ -198,17 +229,15 @@ export default {
|
|||||||
remark: '',
|
remark: '',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
configJson: {
|
configJson: {}
|
||||||
param1: '',
|
|
||||||
param2: '',
|
|
||||||
apiKey: ''
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
// 重置加载状态
|
||||||
|
this.providers = [];
|
||||||
|
this.providersLoaded = false;
|
||||||
|
// 重置字段配置
|
||||||
|
this.providerFields = [];
|
||||||
|
this.currentProvider = null;
|
||||||
},
|
},
|
||||||
handleClose() {
|
|
||||||
this.resetForm();
|
|
||||||
this.$emit('update:visible', false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -297,7 +326,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 +341,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 +370,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 +390,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 +400,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>
|
<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,16 +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 placeholder="请输入智能体名称.." v-model="wisdomBodyName" />
|
<el-input 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>
|
||||||
@@ -29,8 +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',
|
||||||
@@ -38,18 +37,26 @@ export default {
|
|||||||
visible: { type: Boolean, required: true }
|
visible: { type: Boolean, required: true }
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { wisdomBodyName: "" }
|
return {
|
||||||
|
wisdomBodyName: "",
|
||||||
|
inputRef: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleOpen() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.inputRef.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
if (!this.wisdomBodyName.trim()) {
|
if (!this.wisdomBodyName.trim()) {
|
||||||
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);
|
||||||
@@ -65,7 +72,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;
|
||||||
@@ -84,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>
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
<template>
|
||||||
|
<div class="audio-container">
|
||||||
|
<audio
|
||||||
|
ref="audioRef"
|
||||||
|
:src="audioUrl"
|
||||||
|
@timeupdate="updateProgress"
|
||||||
|
@loadedmetadata="updateDuration"
|
||||||
|
style="display: none"
|
||||||
|
></audio>
|
||||||
|
|
||||||
|
<div class="custom-controls">
|
||||||
|
<!-- 播放/暂停按钮 -->
|
||||||
|
<button class="play-btn" @click="togglePlay">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
fill="#4362b3"
|
||||||
|
:d="isPlaying
|
||||||
|
? 'M6 3h3v14H6zm5 0h3v14h-3z'
|
||||||
|
: 'M5 3l12 7-12 7z'"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 时间显示 -->
|
||||||
|
<span class="time-display">{{ formattedCurrentTime }}/{{ formattedDuration }}</span>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar" @click="handleProgressClick">
|
||||||
|
<div
|
||||||
|
class="progress-track"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="progress-thumb"
|
||||||
|
:style="{ left: progress + '%' }"
|
||||||
|
@mousedown="startDrag"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 音量控制 -->
|
||||||
|
<div class="volume-control" ref="volumeControl">
|
||||||
|
<button
|
||||||
|
@click="toggleMute"
|
||||||
|
@mouseenter="handleVolumeMouseEnter"
|
||||||
|
@mouseleave="startVolumeSliderHideTimer"
|
||||||
|
ref="volumeButton"
|
||||||
|
class="volume-button"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" :d="volumeIconPath"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-show="showVolumeSlider"
|
||||||
|
class="volume-slider-container"
|
||||||
|
@mouseenter="cancelVolumeSliderHideTimer"
|
||||||
|
@mouseleave="startVolumeSliderHideTimer"
|
||||||
|
ref="volumeSlider"
|
||||||
|
>
|
||||||
|
<div class="volume-slider-track">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
v-model="volume"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.1"
|
||||||
|
class="volume-slider"
|
||||||
|
@input="handleVolumeChange"
|
||||||
|
orient="vertical"
|
||||||
|
>
|
||||||
|
<div class="volume-slider-thumb" :style="{ bottom: volume * 100 + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
audioUrl: String
|
||||||
|
})
|
||||||
|
|
||||||
|
// 音频元素引用
|
||||||
|
const audioRef = ref(null)
|
||||||
|
const volumeButton = ref(null)
|
||||||
|
const volumeSlider = ref(null)
|
||||||
|
|
||||||
|
// 播放状态
|
||||||
|
const isPlaying = ref(false)
|
||||||
|
const currentTime = ref(0)
|
||||||
|
const duration = ref(0)
|
||||||
|
const progress = ref(0)
|
||||||
|
|
||||||
|
// 音量状态
|
||||||
|
const volume = ref(0.7)
|
||||||
|
const isMuted = ref(false)
|
||||||
|
const showVolumeSlider = ref(false)
|
||||||
|
let volumeSliderHideTimer = null
|
||||||
|
|
||||||
|
// 格式化时间为 MM:SS
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
const sec = Math.floor(seconds || 0)
|
||||||
|
return `${Math.floor(sec / 60)}:${(sec % 60).toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedCurrentTime = computed(() => formatTime(currentTime.value))
|
||||||
|
const formattedDuration = computed(() => formatTime(duration.value))
|
||||||
|
|
||||||
|
// 根据音量状态返回对应的图标路径
|
||||||
|
const volumeIconPath = computed(() => {
|
||||||
|
if (isMuted.value || volume.value === 0) {
|
||||||
|
return 'M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z'
|
||||||
|
}
|
||||||
|
return 'M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 播放控制
|
||||||
|
const togglePlay = () => {
|
||||||
|
if (isPlaying.value) {
|
||||||
|
audioRef.value.pause()
|
||||||
|
} else {
|
||||||
|
audioRef.value.play()
|
||||||
|
}
|
||||||
|
isPlaying.value = !isPlaying.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDuration = () => {
|
||||||
|
if (audioRef.value) {
|
||||||
|
duration.value = audioRef.value.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
if (audioRef.value?.duration) {
|
||||||
|
currentTime.value = audioRef.value.currentTime
|
||||||
|
progress.value = (currentTime.value / duration.value) * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProgressClick = (e) => {
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect()
|
||||||
|
const percentage = ((e.clientX - rect.left) / rect.width) * 100
|
||||||
|
seekToPercentage(percentage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音量控制
|
||||||
|
const toggleMute = () => {
|
||||||
|
isMuted.value = !isMuted.value
|
||||||
|
audioRef.value.muted = isMuted.value
|
||||||
|
|
||||||
|
// 如果取消静音且音量为0,则恢复默认音量
|
||||||
|
if (!isMuted.value && volume.value === 0) {
|
||||||
|
volume.value = 0.5
|
||||||
|
audioRef.value.volume = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVolumeChange = (e) => {
|
||||||
|
const newVolume = parseFloat(e.target.value)
|
||||||
|
audioRef.value.volume = newVolume
|
||||||
|
isMuted.value = newVolume === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度条拖拽功能
|
||||||
|
let isDragging = false
|
||||||
|
|
||||||
|
const startDrag = (e) => {
|
||||||
|
isDragging = true
|
||||||
|
document.addEventListener('mousemove', handleDrag)
|
||||||
|
document.addEventListener('mouseup', stopDrag)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrag = (e) => {
|
||||||
|
if (!isDragging) return
|
||||||
|
|
||||||
|
const rect = document.querySelector('.progress-bar').getBoundingClientRect()
|
||||||
|
const percentage = ((e.clientX - rect.left) / rect.width) * 100
|
||||||
|
seekToPercentage(percentage)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
isDragging = false
|
||||||
|
document.removeEventListener('mousemove', handleDrag)
|
||||||
|
document.removeEventListener('mouseup', stopDrag)
|
||||||
|
}
|
||||||
|
|
||||||
|
const seekToPercentage = (percentage) => {
|
||||||
|
const clampedPercentage = Math.min(Math.max(percentage, 0), 100)
|
||||||
|
progress.value = clampedPercentage
|
||||||
|
audioRef.value.currentTime = (clampedPercentage / 100) * duration.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音量滑块显示/隐藏控制
|
||||||
|
const startVolumeSliderHideTimer = () => {
|
||||||
|
volumeSliderHideTimer = setTimeout(() => {
|
||||||
|
showVolumeSlider.value = false
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelVolumeSliderHideTimer = () => {
|
||||||
|
clearTimeout(volumeSliderHideTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音量滑块定位
|
||||||
|
const updateSliderPosition = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (!volumeButton.value || !volumeSlider.value) return
|
||||||
|
|
||||||
|
const buttonRect = volumeButton.value.getBoundingClientRect()
|
||||||
|
const slider = volumeSlider.value
|
||||||
|
|
||||||
|
// 计算相对于视口的位置
|
||||||
|
const left = buttonRect.left + window.scrollX
|
||||||
|
const top = buttonRect.top + window.scrollY
|
||||||
|
|
||||||
|
// 定位到按钮正中间,垂直居中
|
||||||
|
slider.style.left = `${left + 5}px`
|
||||||
|
slider.style.top = `${top - 85}px`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVolumeMouseEnter = () => {
|
||||||
|
showVolumeSlider.value = true
|
||||||
|
updateSliderPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (audioRef.value) {
|
||||||
|
audioRef.value.volume = volume.value
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', updateSliderPosition)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateSliderPosition)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.audio-container {
|
||||||
|
background: #eef0fd;
|
||||||
|
padding: 8px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-display {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #5f7ba7;
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 10px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 2px;
|
||||||
|
background: #bfcadb;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-track {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
background: #4167ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-thumb {
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: #4167ed;
|
||||||
|
border: 2px solid #d6dcfc;
|
||||||
|
border-radius: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-control {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #8f95cd;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider-container {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px 4px;
|
||||||
|
background: #eef0fd;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider-track {
|
||||||
|
position: relative;
|
||||||
|
height: 55px;
|
||||||
|
width: 2px;
|
||||||
|
background: #bfcadb;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider-thumb {
|
||||||
|
position: absolute;
|
||||||
|
left: 1px;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
background: #4167ed;
|
||||||
|
border: 2px solid #d6dcfc;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider {
|
||||||
|
position: absolute;
|
||||||
|
left: -14px;
|
||||||
|
width: 30px;
|
||||||
|
height: 60px;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
direction: rtl;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider-track::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: calc(100% * v-bind(volume));
|
||||||
|
background: #4167ed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,43 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<form>
|
<form>
|
||||||
<el-dialog :visible.sync="value" width="400px" center>
|
<el-dialog :visible.sync="value" 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;">
|
||||||
<img loading="lazy" src="@/assets/login/shield.png" alt="" style="width: 19px;height: 23px; filter: brightness(0) invert(1);" />
|
<div
|
||||||
|
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
|
||||||
|
<img loading="lazy" src="@/assets/login/shield.png" alt=""
|
||||||
|
style="width: 19px;height: 23px; filter: brightness(0) invert(1);" />
|
||||||
</div>
|
</div>
|
||||||
修改密码
|
修改密码
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 1px;background: #e8f0ff;"/>
|
<div style="height: 1px;background: #e8f0ff;" />
|
||||||
<div style="margin: 22px 15px;">
|
<div style="margin: 22px 15px;">
|
||||||
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;">
|
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;">
|
||||||
<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 placeholder="请输入旧密码" v-model="oldPassword" type="password" show-password/>
|
<el-input placeholder="请输入旧密码" v-model="oldPassword" type="password" show-password />
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
|
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
|
||||||
<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 placeholder="请输入新密码" v-model="newPassword" type="password" show-password/>
|
<el-input placeholder="请输入新密码" v-model="newPassword" type="password" show-password />
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
|
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
|
||||||
<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 placeholder="请再次输入新密码" v-model="confirmNewPassword" type="password" show-password/>
|
<el-input placeholder="请再次输入新密码" v-model="confirmNewPassword" type="password" show-password />
|
||||||
</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>
|
||||||
@@ -83,12 +84,15 @@ export default {
|
|||||||
// 修改后的接口调用
|
// 修改后的接口调用
|
||||||
userApi.changePassword(this.oldPassword, this.newPassword, (res) => {
|
userApi.changePassword(this.oldPassword, this.newPassword, (res) => {
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
this.$message.success('密码修改成功,请重新登录');
|
this.$message.success({
|
||||||
|
message: '密码修改成功,请重新登录',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
this.logout().then(() => {
|
this.logout().then(() => {
|
||||||
this.$router.push('/login');
|
this.$router.push('/login');
|
||||||
this.$emit('update:visible', false);
|
this.$emit('update:visible', false);
|
||||||
});
|
});
|
||||||
}else{
|
} else {
|
||||||
this.$message.error(res.data.msg || '密码修改失败');
|
this.$message.error(res.data.msg || '密码修改失败');
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
<div class="device-item">
|
<div class="device-item">
|
||||||
<div style="display: flex;justify-content: space-between;">
|
<div style="display: flex;justify-content: space-between;">
|
||||||
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
|
<div style="font-weight: 700;font-size: 18px;text-align: left;color: #3d4566;">
|
||||||
{{ device.agentName }}
|
{{ device.agentName }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img src="@/assets/home/delete.png" alt=""
|
<img src="@/assets/home/delete.png" alt="" style="width: 18px;height: 18px;margin-right: 10px;"
|
||||||
style="width: 18px;height: 18px;margin-right: 10px;" @click.stop="handleDelete" />
|
@click.stop="handleDelete" />
|
||||||
<img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" />
|
<el-tooltip class="item" effect="dark" :content="device.systemPrompt" placement="top"
|
||||||
|
popper-class="custom-tooltip">
|
||||||
|
<img src="@/assets/home/info.png" alt="" style="width: 18px;height: 18px;" />
|
||||||
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="device-name">
|
<div class="device-name">
|
||||||
@@ -20,14 +23,14 @@
|
|||||||
<div class="settings-btn" @click="handleConfigure">
|
<div class="settings-btn" @click="handleConfigure">
|
||||||
配置角色
|
配置角色
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="settings-btn">-->
|
<!-- <div class="settings-btn">-->
|
||||||
<!-- 声纹识别-->
|
<!-- 声纹识别-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- <div class="settings-btn">-->
|
<!-- <div class="settings-btn">-->
|
||||||
<!-- 历史对话-->
|
<!-- 历史对话-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<div class="settings-btn" @click="handleDeviceManage">
|
<div class="settings-btn" @click="handleDeviceManage">
|
||||||
设备管理
|
设备管理({{ device.deviceCount }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
@@ -66,6 +69,7 @@ export default {
|
|||||||
padding: 22px;
|
padding: 22px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-name {
|
.device-name {
|
||||||
margin: 7px 0 10px;
|
margin: 7px 0 10px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -94,5 +98,11 @@ export default {
|
|||||||
color: #979db1;
|
color: #979db1;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.custom-tooltip {
|
||||||
|
max-width: 400px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -2,62 +2,68 @@
|
|||||||
<el-header class="header">
|
<el-header class="header">
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<!-- 左侧元素 -->
|
<!-- 左侧元素 -->
|
||||||
<div class="header-left">
|
<div class="header-left" @click="goHome">
|
||||||
<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"
|
||||||
|
:style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }" />
|
||||||
|
模型配置
|
||||||
|
</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 v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }" @click="goModelConfig">
|
<div v-if="isSuperAdmin" class="equipment-management"
|
||||||
<img loading="lazy" alt="" src="@/assets/header/model_config.png" :style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }"/>
|
:class="{ 'active-tab': $route.path === '/params-management' }" @click="goParamManagement">
|
||||||
模型配置
|
<img loading="lazy" alt="" src="@/assets/header/param_management.png"
|
||||||
|
:style="{ filter: $route.path === '/params-management' ? 'brightness(0) invert(1)' : 'None' }" />
|
||||||
|
参数管理
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧元素 -->
|
<!-- 右侧元素 -->
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="search-container">
|
<div class="search-container" v-if="$route.path === '/home'">
|
||||||
<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 {
|
||||||
@@ -96,9 +102,12 @@ export default {
|
|||||||
goModelConfig() {
|
goModelConfig() {
|
||||||
this.$router.push('/model-config')
|
this.$router.push('/model-config')
|
||||||
},
|
},
|
||||||
|
goParamManagement() {
|
||||||
|
this.$router.push('/params-management')
|
||||||
|
},
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
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);
|
||||||
@@ -109,23 +118,26 @@ export default {
|
|||||||
// 处理搜索
|
// 处理搜索
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
const searchValue = this.search.trim();
|
const searchValue = this.search.trim();
|
||||||
let filteredDevices;
|
|
||||||
|
|
||||||
|
// 如果搜索内容为空,触发重置事件
|
||||||
if (!searchValue) {
|
if (!searchValue) {
|
||||||
// 当搜索内容为空时,显示原始完整列表
|
this.$emit('search-reset');
|
||||||
filteredDevices = this.$parent.originalDevices;
|
return;
|
||||||
} else {
|
|
||||||
// 过滤逻辑
|
|
||||||
filteredDevices = this.devices.filter(device => {
|
|
||||||
return device.agentName.includes(searchValue) ||
|
|
||||||
device.ttsModelName.includes(searchValue) ||
|
|
||||||
device.ttsVoiceName.includes(searchValue);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('search-result', filteredDevices);
|
try {
|
||||||
|
// 创建不区分大小写的正则表达式
|
||||||
|
const regex = new RegExp(searchValue, 'i');
|
||||||
|
// 触发搜索事件,将正则表达式传递给父组件
|
||||||
|
this.$emit('search', regex);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('正则表达式创建失败:', error);
|
||||||
|
this.$message.error({
|
||||||
|
message: '搜索关键词格式不正确',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 显示修改密码弹窗
|
// 显示修改密码弹窗
|
||||||
showChangePasswordDialog() {
|
showChangePasswordDialog() {
|
||||||
this.isChangePasswordDialogVisible = true;
|
this.isChangePasswordDialogVisible = true;
|
||||||
@@ -136,14 +148,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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -159,7 +171,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +197,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.brand-img {
|
.brand-img {
|
||||||
width: 58px;
|
height: 18px;
|
||||||
height: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-center {
|
.header-center {
|
||||||
@@ -221,7 +233,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 {
|
||||||
@@ -241,10 +254,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;
|
||||||
@@ -308,7 +321,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,159 +1,493 @@
|
|||||||
<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.isEnabled" :active-value="1" :inactive-value="0" 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" :active-value="1" :inactive-value="0" 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.modelName" placeholder="请输入模型名称" class="custom-input-bg"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="模型编码" prop="code" style="flex: 1;">
|
||||||
|
<el-input v-model="form.modelCode" 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.configJson.type" 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.number="form.sort" type="number" 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.docLink" placeholder="请输入文档地址" class="custom-input-bg"></el-input>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="文档地址">
|
|
||||||
<el-input v-model="form.docUrl" placeholder="请输入内容" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="排序号">
|
|
||||||
<el-input-number v-model="form.sort" :min="1" :max="999" controls-position="right" placeholder="123"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 备注 -->
|
|
||||||
<el-form-item label="备注" class="form-row">
|
|
||||||
<el-input type="textarea" v-model="form.remark" :rows="2" placeholder="请输入"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<div class="vertical-fields">
|
|
||||||
<el-form-item label="接口地址">
|
|
||||||
<el-input v-model="form.apiUrl" placeholder="请输入base_url" />
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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-form-item label="备注" prop="remark" class="prop-remark">
|
||||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入模型备注"
|
||||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
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: 22px;"></div>
|
||||||
|
|
||||||
|
<el-form :model="form.configJson" ref="callInfoForm" label-width="auto" class="custom-form">
|
||||||
|
<template v-for="(row, rowIndex) in chunkedCallInfoFields">
|
||||||
|
<div :key="rowIndex" style="display: flex; gap: 20px; margin-bottom: 0;">
|
||||||
|
<el-form-item
|
||||||
|
v-for="field in row"
|
||||||
|
:key="field.prop"
|
||||||
|
:label="field.label"
|
||||||
|
:prop="field.prop"
|
||||||
|
style="flex: 1;">
|
||||||
|
<el-input
|
||||||
|
v-model="form.configJson[field.prop]"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:type="field.type"
|
||||||
|
class="custom-input-bg"
|
||||||
|
:show-password="field.type === 'password'">
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex;justify-content: center;">
|
||||||
|
<el-button type="primary" @click="handleSave" class="save-btn">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Api from '@/apis/api';
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG_JSON = {
|
||||||
|
type: "",
|
||||||
|
base_url: "",
|
||||||
|
model_name: "",
|
||||||
|
api_key: "",
|
||||||
|
raw: {},
|
||||||
|
config: {
|
||||||
|
keyComparator: {},
|
||||||
|
ignoreError: false,
|
||||||
|
ignoreCase: false,
|
||||||
|
dateFormat: "",
|
||||||
|
ignoreNullValue: false,
|
||||||
|
transientSupport: false,
|
||||||
|
stripTrailingZeros: false,
|
||||||
|
checkDuplicate: false,
|
||||||
|
order: false
|
||||||
|
},
|
||||||
|
empty: false
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ModelConfigDialog",
|
name: "ModelEditDialog",
|
||||||
props: {
|
props: {
|
||||||
visible: { type: Boolean, default: false },
|
visible: { type: Boolean, default: false },
|
||||||
configData: { type: Object, default: () => ({}) }
|
modelData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
validator: value => typeof value === 'object' && !Array.isArray(value)
|
||||||
|
},
|
||||||
|
modelType: { type: String, required: true }
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialogVisible: this.visible,
|
dialogVisible: this.visible,
|
||||||
|
providers: [],
|
||||||
|
providersLoaded: false,
|
||||||
|
allProvidersData: null,
|
||||||
|
pendingProviderType: null,
|
||||||
|
pendingModelData: null,
|
||||||
|
dynamicCallInfoFields: [],
|
||||||
form: {
|
form: {
|
||||||
code: "",
|
id: "",
|
||||||
name: "",
|
modelType: "",
|
||||||
supplier: "",
|
modelCode: "",
|
||||||
isDefault: true,
|
|
||||||
isEnable: true,
|
|
||||||
docUrl: "",
|
|
||||||
sort: 123,
|
|
||||||
remark: "",
|
|
||||||
apiUrl: "",
|
|
||||||
modelName: "",
|
modelName: "",
|
||||||
apiKey: ""
|
isDefault: false,
|
||||||
|
isEnabled: false,
|
||||||
|
docLink: "",
|
||||||
|
remark: "",
|
||||||
|
sort: 0,
|
||||||
|
configJson: JSON.parse(JSON.stringify(DEFAULT_CONFIG_JSON))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
chunkedCallInfoFields() {
|
||||||
|
const chunkSize = 2;
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < this.dynamicCallInfoFields.length; i += chunkSize) {
|
||||||
|
result.push(this.dynamicCallInfoFields.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
modelType() {
|
||||||
|
this.resetProviders();
|
||||||
|
this.loadProviders();
|
||||||
|
},
|
||||||
dialogVisible(val) {
|
dialogVisible(val) {
|
||||||
this.$emit('update:visible', val);
|
this.$emit('update:visible', val);
|
||||||
|
if (!val) {
|
||||||
|
this.resetForm();
|
||||||
|
} else if (val && this.modelData.id) {
|
||||||
|
this.loadModelData();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
visible(val) {
|
visible(val) {
|
||||||
this.dialogVisible = val;
|
this.dialogVisible = val;
|
||||||
if (val) this.form = { ...this.form, ...this.configData };
|
if (val) {
|
||||||
|
this.loadProviders();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'form.configJson.type'(newVal) {
|
||||||
|
if (newVal && this.providersLoaded) {
|
||||||
|
this.loadProviderFields(newVal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
resetForm() {
|
||||||
|
this.form = {
|
||||||
|
id: "",
|
||||||
|
modelType: "",
|
||||||
|
modelCode: "",
|
||||||
|
modelName: "",
|
||||||
|
isDefault: false,
|
||||||
|
isEnabled: false,
|
||||||
|
docLink: "",
|
||||||
|
remark: "",
|
||||||
|
sort: "",
|
||||||
|
configJson: JSON.parse(JSON.stringify(DEFAULT_CONFIG_JSON))
|
||||||
|
};
|
||||||
|
},
|
||||||
|
resetProviders() {
|
||||||
|
this.providers = [];
|
||||||
|
this.providersLoaded = false;
|
||||||
|
},
|
||||||
|
loadModelData() {
|
||||||
|
if (this.modelData.id) {
|
||||||
|
Api.model.getModelConfig(this.modelData.id, ({ data }) => {
|
||||||
|
if (data.code === 0 && data.data) {
|
||||||
|
const model = data.data;
|
||||||
|
this.pendingProviderType = model.configJson.type;
|
||||||
|
this.pendingModelData = model;
|
||||||
|
|
||||||
|
if (this.providersLoaded) {
|
||||||
|
this.loadProviderFields(model.configJson.type);
|
||||||
|
} else {
|
||||||
|
this.loadProviders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
handleSave() {
|
handleSave() {
|
||||||
this.$emit("submit", this.form);
|
const provideCode = this.form.configJson.type;
|
||||||
|
const { provider, ...restConfigJson } = this.form.configJson;
|
||||||
|
const formData = {
|
||||||
|
id: this.form.id,
|
||||||
|
modelCode: this.form.modelCode,
|
||||||
|
modelName: this.form.modelName,
|
||||||
|
isDefault: this.form.isDefault ? 1 : 0,
|
||||||
|
isEnabled: this.form.isEnabled ? 1 : 0,
|
||||||
|
docLink: this.form.docLink,
|
||||||
|
remark: this.form.remark,
|
||||||
|
sort: this.form.sort || 0,
|
||||||
|
configJson: {
|
||||||
|
...restConfigJson,
|
||||||
|
config: {
|
||||||
|
...restConfigJson.config,
|
||||||
|
ignoreError: !!restConfigJson.config?.ignoreError,
|
||||||
|
ignoreCase: !!restConfigJson.config?.ignoreCase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.$emit("save", { provideCode, formData });
|
||||||
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;
|
||||||
|
|
||||||
|
this.allProvidersData = data;
|
||||||
|
|
||||||
|
if (this.pendingProviderType) {
|
||||||
|
this.loadProviderFields(this.pendingProviderType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadProviderFields(providerCode) {
|
||||||
|
if (this.allProvidersData) {
|
||||||
|
const provider = this.allProvidersData.find(p => p.providerCode === providerCode);
|
||||||
|
if (provider) {
|
||||||
|
this.dynamicCallInfoFields = JSON.parse(provider.fields || '[]').map(f => ({
|
||||||
|
label: f.label,
|
||||||
|
prop: f.key,
|
||||||
|
type: f.type === 'password' ? 'password' : 'text',
|
||||||
|
placeholder: `请输入${f.label}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (this.pendingModelData && this.pendingProviderType === providerCode) {
|
||||||
|
this.processModelData(this.pendingModelData);
|
||||||
|
this.pendingModelData = null;
|
||||||
|
this.pendingProviderType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
processModelData(model) {
|
||||||
|
let configJson = model.configJson || {};
|
||||||
|
this.dynamicCallInfoFields.forEach(field => {
|
||||||
|
if (!configJson.hasOwnProperty(field.prop)) {
|
||||||
|
configJson[field.prop] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.form = {
|
||||||
|
id: model.id || "",
|
||||||
|
modelType: model.modelType || "",
|
||||||
|
modelCode: model.modelCode || "",
|
||||||
|
modelName: model.modelName || "",
|
||||||
|
isDefault: model.isDefault || 0,
|
||||||
|
isEnabled: model.isEnabled || 0,
|
||||||
|
docLink: model.docLink || "",
|
||||||
|
remark: model.remark || "",
|
||||||
|
sort: Number(model.sort) || 0,
|
||||||
|
configJson: {
|
||||||
|
...JSON.parse(JSON.stringify(DEFAULT_CONFIG_JSON)),
|
||||||
|
...configJson,
|
||||||
|
config: {
|
||||||
|
...DEFAULT_CONFIG_JSON.config,
|
||||||
|
...(configJson.config || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.custom-form .el-form-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.custom-input-bg .el-input__inner {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.custom-form .el-form-item__label {
|
||||||
|
color: #3d4566;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,201 +1,693 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :visible.sync="localVisible" width="80%" @close="handleClose">
|
<el-dialog
|
||||||
<el-row class="main-container">
|
:visible.sync="localVisible"
|
||||||
<el-col :span="4">
|
width="75%"
|
||||||
<el-menu class="model-menu" :default-active="activeModel" mode="vertical" @select="handleModelSelect">
|
@close="handleClose"
|
||||||
<el-menu-item index="EdgeTTS">EdgeTTS</el-menu-item>
|
:show-close="false"
|
||||||
<el-menu-item index="DoubaoTTS">DoubaoTTS</el-menu-item>
|
:append-to-body="true"
|
||||||
<el-menu-item index="TTS302AI">TTS302AI</el-menu-item>
|
:close-on-click-modal="true">
|
||||||
<el-menu-item index="CosyVoiceSiliconflow">CosyVoiceSiliconflow</el-menu-item>
|
<button class="custom-close-btn" @click="handleClose">
|
||||||
</el-menu>
|
×
|
||||||
</el-col>
|
</button>
|
||||||
|
<div class="scroll-wrapper">
|
||||||
<el-col :span="20">
|
<div
|
||||||
<div class="search-operate">
|
class="table-container"
|
||||||
<el-input placeholder="请输入音色名称查询" v-model="searchQuery" style="width: 300px; margin-right: 10px;"/>
|
ref="tableContainer"
|
||||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
@scroll="handleScroll"
|
||||||
<el-button type="primary" plain @click="handleAddVoice">添加音色</el-button>
|
>
|
||||||
<el-button type="danger" plain @click="handleBatchDelete">批量删除</el-button>
|
<el-table
|
||||||
</div>
|
v-loading="loading"
|
||||||
|
:data="filteredTtsModels"
|
||||||
<el-table :data="filteredTtsModels" style="width: 100%" border stripe header-row-class-name="table-header">
|
style="width: 100%;"
|
||||||
<el-table-column label="音色编码" prop="voiceCode" width="150" align="center"></el-table-column>
|
class="data-table"
|
||||||
<el-table-column label="音色名称" prop="voiceName" width="180" align="center"></el-table-column>
|
header-row-class-name="table-header"
|
||||||
<el-table-column label="语言类型" prop="languageType" width="120" align="center"></el-table-column>
|
:fit="true">
|
||||||
<el-table-column label="备注" prop="remark" align="center"></el-table-column>
|
<el-table-column label="选择" width="50" align="center">
|
||||||
<el-table-column label="操作" width="200" align="center">
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="editVoice(scope.row)" style="color: #409EFF; margin-right: 15px;">修改</el-button>
|
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||||
<el-button size="mini" type="text" @click="deleteVoice(scope.row)" style="color: #F56C6C;">删除</el-button>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="音色编码" align="center" min-width="50">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-if="scope.row.editing" v-model="scope.row.voiceCode"></el-input>
|
||||||
|
<span v-else>{{ scope.row.voiceCode }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="音色名称" align="center" min-width="50">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-if="scope.row.editing" v-model="scope.row.voiceName"></el-input>
|
||||||
|
<span v-else>{{ scope.row.voiceName }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="语言类型" align="center" min-width="50">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-if="scope.row.editing" v-model="scope.row.languageType"></el-input>
|
||||||
|
<span v-else>{{ scope.row.languageType }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="试听" align="center" min-width="225" class-name="audio-column">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div class="custom-audio-container">
|
||||||
|
<el-input
|
||||||
|
v-if="scope.row.editing"
|
||||||
|
v-model="scope.row.voiceDemo"
|
||||||
|
placeholder="请输入MP3地址"
|
||||||
|
size="mini"
|
||||||
|
class="audio-input"
|
||||||
|
></el-input>
|
||||||
|
<AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" align="center" min-width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input
|
||||||
|
v-if="scope.row.editing"
|
||||||
|
type="textarea"
|
||||||
|
:rows="1"
|
||||||
|
autosize
|
||||||
|
v-model="scope.row.remark"
|
||||||
|
placeholder="这里是备注"
|
||||||
|
class="remark-input"></el-input>
|
||||||
|
<span v-else>{{ scope.row.remark }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<template v-if="!scope.row.editing">
|
||||||
|
<el-button type="primary" size="mini" @click="startEdit(scope.row)"
|
||||||
|
style="background: #5cca8e;border:None">编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="mini" @click="deleteRow(scope.row)" style="background: red;border:None">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
type="success"
|
||||||
|
size="mini"
|
||||||
|
@click="saveEdit(scope.row)"
|
||||||
|
class="save-Tts">保存
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pagination-container">
|
<!-- 自定义滚动条 -->
|
||||||
<el-pagination :current-page="currentPage" :page-size="pageSize" :total="total" layout="prev, pager, next" prev-text="<" next-text=">"/>
|
<div class="custom-scrollbar" ref="scrollbar">
|
||||||
|
<div class="custom-scrollbar-track" ref="scrollbarTrack" @click="handleTrackClick">
|
||||||
|
<div class="custom-scrollbar-thumb" ref="scrollbarThumb" @mousedown="startDrag"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div slot="footer" class="dialog-footer">
|
</div>
|
||||||
<el-button @click="handleClose" size="medium">关闭</el-button>
|
<div class="action-buttons">
|
||||||
<el-button type="primary" @click="handleImportExport" size="medium">导入导出配置</el-button>
|
<el-button type="primary" size="mini" @click="toggleSelectAll" style="background: #606ff3;border: None">
|
||||||
</div>
|
{{ selectAll ? '取消全选' : '全选' }}
|
||||||
<EditVoiceDialog :showDialog="editDialogVisible" :voiceData="editVoiceData" @update:showDialog="editDialogVisible = $event" @saveVoice="handleSaveEditedVoice"/>
|
</el-button>
|
||||||
</el-col>
|
<el-button type="primary" size="mini" @click="addNew" style="background: #f6cf79;border: None; color: #000012">
|
||||||
</el-row>
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="mini" @click="batchDelete" style="background: red;border:None">删除</el-button>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import EditVoiceDialog from "@/components/EditVoiceDialog.vue";
|
import AudioPlayer from './AudioPlayer.vue'
|
||||||
|
import Api from "@/apis/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { EditVoiceDialog },
|
components: {AudioPlayer},
|
||||||
props: {
|
props: {
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
ttsModelId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
localVisible: this.visible,
|
localVisible: this.visible,
|
||||||
activeModel: 'EdgeTTS',
|
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
editDialogVisible: false,
|
editDialogVisible: false,
|
||||||
editVoiceData: {},
|
editVoiceData: {},
|
||||||
ttsModels: [
|
ttsModels: [],
|
||||||
{ voiceCode: 'wawaxiaohe', voiceName: '湾湾小何', languageType: '中文', remark: '' },
|
|
||||||
{ voiceCode: 'wawaxiaohe', voiceName: '湾湾小何', languageType: '中文', remark: '' },
|
|
||||||
{ voiceCode: 'wawaxiaohe', voiceName: '湾湾小何', languageType: '中文', remark: '' },
|
|
||||||
{ voiceCode: 'wawaxiaohe', voiceName: '湾湾小何', languageType: '中文', remark: '' },
|
|
||||||
],
|
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 4,
|
pageSize: 10000,
|
||||||
total: 20
|
total: 0,
|
||||||
|
isDragging: false,
|
||||||
|
startY: 0,
|
||||||
|
scrollTop: 0,
|
||||||
|
selectAll: false,
|
||||||
|
selectedRows: [],
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newVal) {
|
visible(newVal) {
|
||||||
this.localVisible = newVal;
|
this.localVisible = newVal;
|
||||||
|
if (newVal) {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.loadData(); // 对话框显示时加载数据
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateScrollbar();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filteredTtsModels() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateScrollbar();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredTtsModels() {
|
filteredTtsModels() {
|
||||||
return this.ttsModels.filter(model =>
|
return this.ttsModels.filter(model =>
|
||||||
model.voiceName.toLowerCase().includes(this.searchQuery.toLowerCase())
|
model.voiceName.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateScrollbar();
|
||||||
|
window.addEventListener('resize', this.updateScrollbar);
|
||||||
|
window.addEventListener('mouseup', this.stopDrag);
|
||||||
|
window.addEventListener('mousemove', this.handleDrag);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.updateScrollbar);
|
||||||
|
window.removeEventListener('mouseup', this.stopDrag);
|
||||||
|
window.removeEventListener('mousemove', this.handleDrag);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadData() {
|
||||||
|
const params = {
|
||||||
|
ttsModelId: this.ttsModelId,
|
||||||
|
page: this.currentPage,
|
||||||
|
limit: this.pageSize,
|
||||||
|
name: this.searchQuery
|
||||||
|
};
|
||||||
|
|
||||||
|
Api.timbre.getVoiceList(params, (data) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.ttsModels = data.data.list
|
||||||
|
.map(item => ({
|
||||||
|
id: item.id || '',
|
||||||
|
voiceCode: item.ttsVoice || '',
|
||||||
|
voiceName: item.name || '未命名音色',
|
||||||
|
languageType: item.languages || '',
|
||||||
|
remark: item.remark || '',
|
||||||
|
voiceDemo: item.voiceDemo || '',
|
||||||
|
selected: false,
|
||||||
|
editing: false,
|
||||||
|
sort: Number(item.sort)
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.sort - b.sort);
|
||||||
|
this.total = data.total;
|
||||||
|
} else {
|
||||||
|
this.$message.error({
|
||||||
|
message: data.msg || '获取音色列表失败',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
console.error('加载失败:', err);
|
||||||
|
this.$message.error({
|
||||||
|
message: '加载音色数据失败',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
handleClose() {
|
handleClose() {
|
||||||
|
// 重置状态
|
||||||
|
this.ttsModels = [];
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.total = 0;
|
||||||
|
this.selectAll = false;
|
||||||
|
this.searchQuery = '';
|
||||||
|
|
||||||
this.localVisible = false;
|
this.localVisible = false;
|
||||||
this.$emit('update:visible', false);
|
this.$emit('update:visible', false);
|
||||||
},
|
},
|
||||||
handleModelSelect(index) {
|
|
||||||
this.activeModel = index;
|
updateScrollbar() {
|
||||||
// 可根据选中模型加载对应数据
|
const container = this.$refs.tableContainer;
|
||||||
|
const scrollbarThumb = this.$refs.scrollbarThumb;
|
||||||
|
const scrollbarTrack = this.$refs.scrollbarTrack;
|
||||||
|
|
||||||
|
if (!container || !scrollbarThumb || !scrollbarTrack) return;
|
||||||
|
|
||||||
|
const {scrollHeight, clientHeight} = container;
|
||||||
|
const trackHeight = scrollbarTrack.clientHeight;
|
||||||
|
const thumbHeight = Math.max((clientHeight / scrollHeight) * trackHeight, 20);
|
||||||
|
|
||||||
|
scrollbarThumb.style.height = `${thumbHeight}px`;
|
||||||
|
this.updateThumbPosition();
|
||||||
},
|
},
|
||||||
handleSearch() {
|
|
||||||
// 搜索
|
updateThumbPosition() {
|
||||||
|
const container = this.$refs.tableContainer;
|
||||||
|
const scrollbarThumb = this.$refs.scrollbarThumb;
|
||||||
|
const scrollbarTrack = this.$refs.scrollbarTrack;
|
||||||
|
|
||||||
|
if (!container || !scrollbarThumb || !scrollbarTrack) return;
|
||||||
|
|
||||||
|
const {scrollHeight, clientHeight, scrollTop} = container;
|
||||||
|
const trackHeight = scrollbarTrack.clientHeight;
|
||||||
|
const thumbHeight = scrollbarThumb.clientHeight;
|
||||||
|
const maxTop = trackHeight - thumbHeight;
|
||||||
|
const thumbTop = (scrollTop / (scrollHeight - clientHeight)) * (trackHeight - thumbHeight);
|
||||||
|
|
||||||
|
scrollbarThumb.style.top = `${Math.min(thumbTop, maxTop)}px`;
|
||||||
},
|
},
|
||||||
handleAddVoice() {
|
|
||||||
// 添加音色
|
handleScroll() {
|
||||||
|
const container = this.$refs.tableContainer;
|
||||||
|
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 50) {
|
||||||
|
if (this.currentPage * this.pageSize < this.total) {
|
||||||
|
this.currentPage++;
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateThumbPosition();
|
||||||
},
|
},
|
||||||
handleBatchDelete() {
|
|
||||||
// 批量删除
|
startDrag(e) {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.startY = e.clientY;
|
||||||
|
this.scrollTop = this.$refs.tableContainer.scrollTop;
|
||||||
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
editVoice(voice) {
|
|
||||||
this.editVoiceData = { ...voice };
|
stopDrag() {
|
||||||
this.editDialogVisible = true;
|
this.isDragging = false;
|
||||||
},
|
},
|
||||||
handleSaveEditedVoice(voiceForm) {
|
|
||||||
const index = this.ttsModels.findIndex(item => item.voiceCode === voiceForm.voiceCode);
|
handleDrag(e) {
|
||||||
if (index !== -1) {
|
if (!this.isDragging) return;
|
||||||
this.ttsModels.splice(index, 1, voiceForm);
|
|
||||||
|
const container = this.$refs.tableContainer;
|
||||||
|
const scrollbarTrack = this.$refs.scrollbarTrack;
|
||||||
|
const scrollbarThumb = this.$refs.scrollbarThumb;
|
||||||
|
const deltaY = e.clientY - this.startY;
|
||||||
|
const trackHeight = scrollbarTrack.clientHeight;
|
||||||
|
const thumbHeight = scrollbarThumb.clientHeight;
|
||||||
|
const maxScrollTop = container.scrollHeight - container.clientHeight;
|
||||||
|
|
||||||
|
const scrollRatio = (trackHeight - thumbHeight) / maxScrollTop;
|
||||||
|
container.scrollTop = this.scrollTop + deltaY / scrollRatio;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTrackClick(e) {
|
||||||
|
const container = this.$refs.tableContainer;
|
||||||
|
const scrollbarTrack = this.$refs.scrollbarTrack;
|
||||||
|
const scrollbarThumb = this.$refs.scrollbarThumb;
|
||||||
|
|
||||||
|
if (!container || !scrollbarTrack || !scrollbarThumb) return;
|
||||||
|
|
||||||
|
const trackRect = scrollbarTrack.getBoundingClientRect();
|
||||||
|
const thumbHeight = scrollbarThumb.clientHeight;
|
||||||
|
const clickPosition = e.clientY - trackRect.top;
|
||||||
|
const thumbCenter = clickPosition - thumbHeight / 2;
|
||||||
|
|
||||||
|
const trackHeight = scrollbarTrack.clientHeight;
|
||||||
|
const maxTop = trackHeight - thumbHeight;
|
||||||
|
const newTop = Math.max(0, Math.min(thumbCenter, maxTop));
|
||||||
|
|
||||||
|
scrollbarThumb.style.top = `${newTop}px`;
|
||||||
|
container.scrollTop = (newTop / (trackHeight - thumbHeight)) * (container.scrollHeight - container.clientHeight);
|
||||||
|
},
|
||||||
|
|
||||||
|
startEdit(row) {
|
||||||
|
row.editing = true;
|
||||||
|
this.$set(row, 'originalData', {...row});
|
||||||
|
},
|
||||||
|
|
||||||
|
saveEdit(row) {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
id: row.id,
|
||||||
|
voiceCode: row.voiceCode,
|
||||||
|
voiceName: row.voiceName,
|
||||||
|
languageType: row.languageType,
|
||||||
|
remark: row.remark,
|
||||||
|
ttsModelId: this.ttsModelId,
|
||||||
|
voiceDemo: row.voiceDemo || '',
|
||||||
|
sort: row.sort
|
||||||
|
};
|
||||||
|
|
||||||
|
let res;
|
||||||
|
if (row.id) {
|
||||||
|
// 已有ID,执行更新操作
|
||||||
|
Api.timbre.updateVoice(params, (response) => {
|
||||||
|
res = response;
|
||||||
|
this.handleResponse(res, row);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 没有ID,执行新增操作
|
||||||
|
Api.timbre.saveVoice(params, (response) => {
|
||||||
|
res = response;
|
||||||
|
this.handleResponse(res, row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('操作失败:', error);
|
||||||
|
// 异常情况下也恢复原始数据
|
||||||
|
if (row.originalData) {
|
||||||
|
Object.assign(row, row.originalData);
|
||||||
|
row.editing = false;
|
||||||
|
delete row.originalData;
|
||||||
|
}
|
||||||
|
this.$message.error({
|
||||||
|
message: '操作失败,请重试',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteVoice(voice) {
|
|
||||||
// 删除
|
handleResponse(res, row) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
this.$message.success({
|
||||||
|
message: row.id ? '修改成功' : '保存成功',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
row.editing = false;
|
||||||
|
delete row.originalData;
|
||||||
|
this.loadData(); // 刷新数据
|
||||||
|
} else {
|
||||||
|
// 保存失败时恢复原始数据
|
||||||
|
if (row.originalData) {
|
||||||
|
Object.assign(row, row.originalData);
|
||||||
|
row.editing = false;
|
||||||
|
delete row.originalData;
|
||||||
|
}
|
||||||
|
this.$message.error({
|
||||||
|
message: res.msg || (row.id ? '修改失败' : '保存失败'),
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleImportExport() {
|
|
||||||
// 导入导出
|
toggleSelectAll() {
|
||||||
|
this.selectAll = !this.selectAll;
|
||||||
|
this.filteredTtsModels.forEach(row => {
|
||||||
|
row.selected = this.selectAll;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addNew() {
|
||||||
|
const maxSort = this.ttsModels.length > 0
|
||||||
|
? Math.max(...this.ttsModels.map(item => Number(item.sort) || 0))
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const newRow = {
|
||||||
|
voiceCode: '',
|
||||||
|
voiceName: '',
|
||||||
|
languageType: '中文',
|
||||||
|
voiceDemo: '',
|
||||||
|
remark: '',
|
||||||
|
selected: false,
|
||||||
|
editing: true,
|
||||||
|
sort: maxSort + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ttsModels.unshift(newRow);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRow(row) {
|
||||||
|
this.$confirm("确定要删除该音色吗?", "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
Api.timbre.deleteVoice(row.id, (response) => {
|
||||||
|
if (response.code === 0) {
|
||||||
|
this.$message.success({
|
||||||
|
message: "删除成功",
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
this.loadData(); // 刷新数据
|
||||||
|
} else {
|
||||||
|
this.$message.error({
|
||||||
|
message: response.msg || "删除失败",
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$message.info("已取消删除");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
batchDelete() {
|
||||||
|
const selectedRows = this.filteredTtsModels.filter(row => row.selected);
|
||||||
|
if (selectedRows.length === 0) {
|
||||||
|
this.$message.warning("请先选择需要删除的音色");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$confirm(`确定要删除选中的${selectedRows.length}个音色吗?`, "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
const loading = this.$loading({
|
||||||
|
lock: true,
|
||||||
|
text: "正在删除中...",
|
||||||
|
spinner: "el-icon-loading",
|
||||||
|
background: "rgba(0, 0, 0, 0.7)",
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = await Promise.all(
|
||||||
|
selectedRows.map((row) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
Api.timbre.deleteVoice(row.id, (response) => {
|
||||||
|
if (response.code === 0) {
|
||||||
|
resolve({ success: true, id: row.id });
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
success: false,
|
||||||
|
id: row.id,
|
||||||
|
msg: response.msg || '删除失败'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const successCount = results.filter(r => r.success).length;
|
||||||
|
const failCount = results.length - successCount;
|
||||||
|
|
||||||
|
if (failCount === 0) {
|
||||||
|
this.$message.success({
|
||||||
|
message: `成功删除${successCount}个音色`,
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
} else if (successCount === 0) {
|
||||||
|
this.$message.error({
|
||||||
|
message: '删除失败,请重试',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$message.warning({
|
||||||
|
message: `成功删除${successCount}个音色,${failCount}个删除失败`,
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadData(); // 刷新数据
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量删除出错:', error);
|
||||||
|
this.$message.error({
|
||||||
|
message: '删除过程中发生错误',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$message.info("已取消删除");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidAudioUrl(url) {
|
||||||
|
return url && (url.endsWith('.mp3') || url.endsWith('.ogg') || url.endsWith('.wav'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.main-container {
|
|
||||||
padding: 20px;
|
::v-deep .el-dialog {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
top: 8vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-menu {
|
::v-deep .el-dialog__header {
|
||||||
border-right: 1px solid #ebeef5;
|
display: none !important;
|
||||||
height: calc(100vh - 300px);
|
padding: 0 !important;
|
||||||
background-color: #fafafa;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-operate {
|
/* 表格样式 */
|
||||||
margin-bottom: 20px;
|
::v-deep .data-table .el-table__header th {
|
||||||
|
color: black;
|
||||||
|
padding: 6px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .data-table .el-table__row td {
|
||||||
|
padding: 8px 0 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .data-table {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .data-table.el-table::before {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .data-table .el-table__header-wrapper {
|
||||||
|
border-bottom: 2px solid #f1f2fb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .data-table .el-table__body-wrapper .el-table__body td {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 关闭按钮 */
|
||||||
|
.custom-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #cfcfcf;
|
||||||
|
background: none;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: lighter;
|
||||||
|
color: #cfcfcf;
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-container {
|
.custom-close-btn:hover {
|
||||||
margin: 20px 0;
|
color: #409EFF;
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer {
|
|
||||||
text-align: right;
|
|
||||||
padding: 20px 20px 0;
|
|
||||||
border-top: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .table-header th {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
color: #606266;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-table--border td, ::v-deep .el-table--border th {
|
|
||||||
border-right: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination .btn-prev, ::v-deep .el-pagination .btn-next {
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination .btn-prev:hover, ::v-deep .el-pagination .btn-next:hover {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination .el-pager li {
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination .el-pager li:hover {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .el-pagination .el-pager li.active {
|
|
||||||
background-color: #409EFF;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #409EFF;
|
border-color: #409EFF;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
/* 备注文本 */
|
||||||
|
::v-deep .remark-input .el-textarea__inner {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
padding: 8px 12px;
|
||||||
|
resize: none;
|
||||||
|
max-height: 40px !important;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .remark-input .el-textarea__inner::placeholder {
|
||||||
|
color: black !important;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .remark-input .el-textarea__inner {
|
||||||
|
background-color: #f4f6fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .remark-input .el-textarea__inner:focus {
|
||||||
|
background-color: #edeffb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动容器 */
|
||||||
|
.scroll-wrapper {
|
||||||
|
display: flex;
|
||||||
|
max-height: 55vh;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
padding-right: 15px; /* 为滚动条留出空间 */
|
||||||
|
width: calc(100% - 16px); /* 减去滚动条宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome/Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条 */
|
||||||
|
.custom-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 8px;
|
||||||
|
height: 100%;
|
||||||
|
top: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar-track {
|
||||||
|
position: relative;
|
||||||
|
height: 380px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar-thumb {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
background: #9dade7;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar-thumb:hover {
|
||||||
|
background: #6b84d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar-thumb:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-Tts {
|
||||||
|
background: #796dea;
|
||||||
|
border: None;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-Tts:hover {
|
||||||
|
background: #8b80f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-audio-container audio {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新增按钮组样式 */
|
||||||
|
.action-buttons {
|
||||||
|
bottom: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<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;">
|
||||||
<img src="@/assets/login/shield.png" alt="" style="width: 19px;height: 23px; filter: brightness(0) invert(1);" />
|
<div
|
||||||
|
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
|
||||||
|
<img src="@/assets/login/shield.png" alt=""
|
||||||
|
style="width: 19px;height: 23px; filter: brightness(0) invert(1);" />
|
||||||
</div>
|
</div>
|
||||||
用户新密码
|
用户新密码
|
||||||
</div>
|
</div>
|
||||||
@@ -13,27 +16,15 @@
|
|||||||
用户新密码:
|
用户新密码:
|
||||||
</div>
|
</div>
|
||||||
<div class="input-46" style="margin-top: 12px;">
|
<div class="input-46" style="margin-top: 12px;">
|
||||||
<el-input
|
<el-input v-model="password" type="text" :readonly="true" style="font-weight: bold; color: #333;" />
|
||||||
v-model="password"
|
|
||||||
type="text"
|
|
||||||
:readonly="true"
|
|
||||||
style="font-weight: bold; color: #333;"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
||||||
<div
|
<div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
|
||||||
class="dialog-btn"
|
@click="closeDialog">
|
||||||
style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;"
|
|
||||||
@click="closeDialog"
|
|
||||||
>
|
|
||||||
关闭
|
关闭
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="dialog-btn" style="background: #5778ff;color: white;" @click="copyPassword">
|
||||||
class="dialog-btn"
|
|
||||||
style="background: #5778ff;color: white;"
|
|
||||||
@click="copyPassword"
|
|
||||||
>
|
|
||||||
复制密码
|
复制密码
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +44,10 @@ export default {
|
|||||||
},
|
},
|
||||||
copyPassword() {
|
copyPassword() {
|
||||||
navigator.clipboard.writeText(this.password)
|
navigator.clipboard.writeText(this.password)
|
||||||
this.$message.success('密码已复制')
|
this.$message.success({
|
||||||
|
message: '密码已复制',
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -69,7 +69,17 @@ const routes = [
|
|||||||
return import('../views/test.vue')
|
return import('../views/test.vue')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/params-management',
|
||||||
|
name: 'ParamsManagement',
|
||||||
|
component: function () {
|
||||||
|
return import('../views/ParamsManagement.vue')
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '参数管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
|
|||||||
@@ -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,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,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'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断用户是否登录
|
* 判断用户是否登录
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 @keyup.enter.native="handleSearch" />
|
||||||
<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,19 +61,30 @@
|
|||||||
</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.type || '未知' }}
|
||||||
|
</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"
|
||||||
|
@change="handleStatusChange(scope.row)" />
|
||||||
</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">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" size="mini" @click="ttsDialogVisible = true" class="voice-management-btn">
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
@click="openTtsDialog(scope.row)"
|
||||||
|
class="voice-management-btn">
|
||||||
音色管理
|
音色管理
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -92,7 +103,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 +114,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 +126,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ModelEditDialog :visible.sync="editDialogVisible" :modelData="editModelData" @save="handleModelSave"/>
|
<ModelEditDialog :modelType="activeTab" :visible.sync="editDialogVisible" :modelData="editModelData"
|
||||||
<TtsModel :visible.sync="ttsDialogVisible" />
|
@save="handleModelSave" />
|
||||||
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm"/>
|
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId"/>
|
||||||
|
<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 +155,35 @@ export default {
|
|||||||
editDialogVisible: false,
|
editDialogVisible: false,
|
||||||
editModelData: {},
|
editModelData: {},
|
||||||
ttsDialogVisible: false,
|
ttsDialogVisible: false,
|
||||||
modelList: [
|
selectedTtsModelId: '',
|
||||||
{ code: 'DeepSeek', candidateName: '深度求索', isApplied: true, supplier: '硅基流动' },
|
modelList: [],
|
||||||
{ 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);
|
||||||
},
|
},
|
||||||
@@ -174,6 +205,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
openTtsDialog(row) {
|
||||||
|
this.selectedTtsModelId = row.id;
|
||||||
|
this.ttsDialogVisible = true;
|
||||||
|
},
|
||||||
headerCellClassName({ column, columnIndex }) {
|
headerCellClassName({ column, columnIndex }) {
|
||||||
if (columnIndex === 0) {
|
if (columnIndex === 0) {
|
||||||
return 'custom-selection-header';
|
return 'custom-selection-header';
|
||||||
@@ -182,65 +217,114 @@ export default {
|
|||||||
},
|
},
|
||||||
handleMenuSelect(index) {
|
handleMenuSelect(index) {
|
||||||
this.activeTab = index;
|
this.activeTab = index;
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.loadData();
|
||||||
},
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
console.log('查询:', this.search);
|
this.currentPage = 1;
|
||||||
|
this.loadData();
|
||||||
},
|
},
|
||||||
|
// 批量删除
|
||||||
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('删除成功');
|
Api.model.deleteModel(
|
||||||
this.selectedModels = [];
|
model.id,
|
||||||
this.isAllSelected = false;
|
({ data }) => resolve(data.code === 0)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
Promise.all(deletePromises).then(results => {
|
||||||
|
if (results.every(Boolean)) {
|
||||||
|
this.$message.success({
|
||||||
|
message: '批量删除成功',
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
|
this.loadData()
|
||||||
|
} else {
|
||||||
|
this.$message.error({
|
||||||
|
message: '部分删除失败',
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}).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('删除成功');
|
model.id,
|
||||||
|
({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success({
|
||||||
|
message: '删除成功',
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
|
this.loadData()
|
||||||
|
} else {
|
||||||
|
this.$message.error({
|
||||||
|
message: data.msg || '删除失败',
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}).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({ provideCode, formData }) {
|
||||||
console.log('保存的模型数据:', formData);
|
const modelType = this.activeTab;
|
||||||
|
const id = formData.id;
|
||||||
|
Api.model.updateModel(
|
||||||
|
{ modelType, provideCode, id, formData },
|
||||||
|
({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success('保存成功');
|
||||||
|
this.loadData();
|
||||||
|
this.editDialogVisible = false;
|
||||||
|
} else {
|
||||||
|
this.$message.error(data.msg || '保存失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
if (this.isAllSelected) {
|
if (this.isAllSelected) {
|
||||||
@@ -256,14 +340,40 @@ export default {
|
|||||||
this.isAllSelected = false;
|
this.isAllSelected = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 新增模型配置
|
||||||
handleAddConfirm(newModel) {
|
handleAddConfirm(newModel) {
|
||||||
console.log('新增模型数据:', newModel);
|
const params = {
|
||||||
|
modelType: this.activeTab,
|
||||||
|
provideCode: newModel.provideCode,
|
||||||
|
formData: {
|
||||||
|
...newModel,
|
||||||
|
isDefault: newModel.isDefault ? 1 : 0,
|
||||||
|
isEnabled: newModel.isEnabled ? 1 : 0,
|
||||||
|
configJson: newModel.configJson
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Api.model.addModel(params, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success({
|
||||||
|
message: '新增成功',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
this.loadData();
|
||||||
|
} else {
|
||||||
|
this.$message.error({
|
||||||
|
message: data.msg || '新增失败',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 分页器
|
// 分页器
|
||||||
goFirst() {
|
goFirst() {
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
this.loadData();
|
this.loadData();
|
||||||
},
|
},
|
||||||
goPrev() {
|
goPrev() {
|
||||||
if (this.currentPage > 1) {
|
if (this.currentPage > 1) {
|
||||||
@@ -281,15 +391,54 @@ 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 || '获取模型列表失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 处理启用/禁用状态变更
|
||||||
|
handleStatusChange(model) {
|
||||||
|
const newStatus = model.isEnabled ? 1 : 0
|
||||||
|
const originalStatus = model.isEnabled
|
||||||
|
|
||||||
|
model.isEnabled = !model.isEnabled
|
||||||
|
|
||||||
|
Api.model.updateModelStatus(
|
||||||
|
model.id,
|
||||||
|
newStatus,
|
||||||
|
({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success(newStatus === 1 ? '启用成功' : '禁用成功')
|
||||||
|
// 保持新状态
|
||||||
|
model.isEnabled = newStatus
|
||||||
|
} else {
|
||||||
|
// 操作失败时恢复原状态
|
||||||
|
model.isEnabled = originalStatus
|
||||||
|
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 +456,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 +469,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 +489,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 +497,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 +685,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 +712,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 +721,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 +756,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 +769,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 +788,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 +813,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 +846,4 @@ export default {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,536 @@
|
|||||||
|
<template>
|
||||||
|
<div class="welcome">
|
||||||
|
<HeaderBar />
|
||||||
|
|
||||||
|
<div class="operation-bar">
|
||||||
|
<h2 class="page-title">参数管理</h2>
|
||||||
|
<div class="right-operations">
|
||||||
|
<el-input placeholder="请输入参数编码查询" v-model="searchCode" class="search-input"
|
||||||
|
@keyup.enter.native="handleSearch" />
|
||||||
|
<el-button class="btn-search" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button type="primary" @click="showAddDialog">新增参数</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="content-panel">
|
||||||
|
<div class="content-area">
|
||||||
|
<el-card class="params-card" shadow="never">
|
||||||
|
<el-table ref="paramsTable" :data="paramsList" 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="参数编码" prop="paramCode" align="center"></el-table-column>
|
||||||
|
<el-table-column label="参数值" prop="paramValue" align="center"></el-table-column>
|
||||||
|
<el-table-column label="备注" prop="remark" align="center"></el-table-column>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" @click="editParam(scope.row)">编辑</el-button>
|
||||||
|
<el-button size="mini" type="text" @click="deleteParam(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="table_bottom">
|
||||||
|
<div class="ctrl_btn">
|
||||||
|
<el-button size="mini" type="primary" class="select-all-btn"
|
||||||
|
@click="handleSelectAll">全选</el-button>
|
||||||
|
<el-button size="mini" type="danger" icon="el-icon-delete"
|
||||||
|
@click="batchDelete">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="custom-pagination">
|
||||||
|
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
|
||||||
|
首页
|
||||||
|
</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)">
|
||||||
|
{{ page }}
|
||||||
|
</button>
|
||||||
|
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
|
||||||
|
下一页
|
||||||
|
</button>
|
||||||
|
<span class="total-text">共{{ total }}条记录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增/编辑参数对话框 -->
|
||||||
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px">
|
||||||
|
<el-form :model="paramForm" :rules="rules" ref="paramForm" label-width="100px">
|
||||||
|
<el-form-item label="参数编码" prop="paramCode">
|
||||||
|
<el-input v-model="paramForm.paramCode" placeholder="请输入参数编码"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="参数值" prop="paramValue">
|
||||||
|
<el-input v-model="paramForm.paramValue" placeholder="请输入参数值"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input type="textarea" v-model="paramForm.remark" placeholder="请输入备注"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<div class="copyright">©2025 xiaozhi-esp32-server</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Api from "@/apis/api";
|
||||||
|
import HeaderBar from "@/components/HeaderBar.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { HeaderBar },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchCode: "",
|
||||||
|
paramsList: [],
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 5,
|
||||||
|
total: 0,
|
||||||
|
dialogVisible: false,
|
||||||
|
dialogTitle: "新增参数",
|
||||||
|
paramForm: {
|
||||||
|
id: null,
|
||||||
|
paramCode: "",
|
||||||
|
paramValue: "",
|
||||||
|
remark: ""
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
paramCode: [
|
||||||
|
{ required: true, message: "请输入参数编码", trigger: "blur" }
|
||||||
|
],
|
||||||
|
paramValue: [
|
||||||
|
{ required: true, message: "请输入参数值", trigger: "blur" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchParams();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pageCount() {
|
||||||
|
return Math.ceil(this.total / this.pageSize);
|
||||||
|
},
|
||||||
|
visiblePages() {
|
||||||
|
const pages = [];
|
||||||
|
const maxVisible = 3;
|
||||||
|
let start = Math.max(1, this.currentPage - 1);
|
||||||
|
let end = Math.min(this.pageCount, start + maxVisible - 1);
|
||||||
|
|
||||||
|
if (end - start + 1 < maxVisible) {
|
||||||
|
start = Math.max(1, end - maxVisible + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchParams() {
|
||||||
|
Api.admin.getParamsList(
|
||||||
|
{
|
||||||
|
page: this.currentPage,
|
||||||
|
limit: this.pageSize,
|
||||||
|
paramCode: this.searchCode,
|
||||||
|
},
|
||||||
|
({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.paramsList = data.data.list;
|
||||||
|
this.total = data.data.total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.fetchParams();
|
||||||
|
},
|
||||||
|
handleSelectAll() {
|
||||||
|
this.$refs.paramsTable.toggleAllSelection();
|
||||||
|
},
|
||||||
|
showAddDialog() {
|
||||||
|
this.dialogTitle = "新增参数";
|
||||||
|
this.paramForm = {
|
||||||
|
id: null,
|
||||||
|
paramCode: "",
|
||||||
|
paramValue: "",
|
||||||
|
remark: ""
|
||||||
|
};
|
||||||
|
this.dialogVisible = true;
|
||||||
|
},
|
||||||
|
editParam(row) {
|
||||||
|
this.dialogTitle = "编辑参数";
|
||||||
|
this.paramForm = { ...row };
|
||||||
|
this.dialogVisible = true;
|
||||||
|
},
|
||||||
|
submitForm() {
|
||||||
|
this.$refs.paramForm.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.paramForm.id) {
|
||||||
|
// 编辑
|
||||||
|
Api.admin.updateParam(this.paramForm, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success("修改成功");
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 新增
|
||||||
|
Api.admin.addParam(this.paramForm, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success("新增成功");
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteParam(row) {
|
||||||
|
this.$confirm("确定要删除该参数吗?", "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
Api.admin.deleteParam(row.id, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success("删除成功");
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
},
|
||||||
|
batchDelete() {
|
||||||
|
const selectedParams = this.$refs.paramsTable.selection;
|
||||||
|
if (selectedParams.length === 0) {
|
||||||
|
this.$message.warning("请先选择需要删除的参数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$confirm(`确定要删除选中的${selectedParams.length}个参数吗?`, "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const ids = selectedParams.map(item => item.id);
|
||||||
|
Api.admin.batchDeleteParams(ids, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success("删除成功");
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
},
|
||||||
|
headerCellClassName({ columnIndex }) {
|
||||||
|
if (columnIndex === 0) {
|
||||||
|
return "custom-selection-header";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
goFirst() {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.fetchParams();
|
||||||
|
},
|
||||||
|
goPrev() {
|
||||||
|
if (this.currentPage > 1) {
|
||||||
|
this.currentPage--;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goNext() {
|
||||||
|
if (this.currentPage < this.pageCount) {
|
||||||
|
this.currentPage++;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goToPage(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
this.fetchParams();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.welcome {
|
||||||
|
min-width: 900px;
|
||||||
|
min-height: 506px;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
background-size: cover;
|
||||||
|
background: linear-gradient(to bottom right, #dce8ff, #e4eeff, #e6cbfd) center;
|
||||||
|
-webkit-background-size: cover;
|
||||||
|
-o-background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-wrapper {
|
||||||
|
margin: 5px 22px;
|
||||||
|
border-radius: 15px;
|
||||||
|
min-height: 600px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
background: rgba(237, 242, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-operations {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-search {
|
||||||
|
background: linear-gradient(135deg, #6b8cff, #a966ff);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 600px;
|
||||||
|
overflow-x: auto;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-card {
|
||||||
|
background: white;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table_bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctrl_btn {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding-left: 26px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
min-width: 72px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 7px 12px 7px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--primary {
|
||||||
|
background: #5f70f3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--danger {
|
||||||
|
background: #fd5b63;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
text-align: center;
|
||||||
|
color: #979db1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
.pagination-btn:first-child,
|
||||||
|
.pagination-btn:nth-child(2),
|
||||||
|
.pagination-btn:nth-last-child(2) {
|
||||||
|
min-width: 60px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
background: #dee7ff;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #d7dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:not(:first-child):not(:nth-child(2)):not(:nth-last-child(2)) {
|
||||||
|
min-width: 28px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(245, 247, 250, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn.active {
|
||||||
|
background: #5f70f3 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border-color: #5f70f3 !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #6d7cf5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-text {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.transparent-table) {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
.el-table__header th {
|
||||||
|
background: white !important;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__body tr {
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.04);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.custom-selection-header) {
|
||||||
|
.el-checkbox {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "选择";
|
||||||
|
display: inline-block;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__inner) {
|
||||||
|
background-color: #eeeeee !important;
|
||||||
|
border-color: #cccccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__inner:hover) {
|
||||||
|
border-color: #cccccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
||||||
|
background-color: #5f70f3 !important;
|
||||||
|
border-color: #5f70f3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1144px) {
|
||||||
|
.table_bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.transparent-table) {
|
||||||
|
.el-table__body tr {
|
||||||
|
td {
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&+tr {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user