@@ -1,32 +1,46 @@
< script setup lang = "ts" >
import { onMounted , ref } from 'vue ';
import { ElMessage , ElMessageBox } from 'element-plus' ;
import type { AiAppDto , AiModelDto , AppShortcutDto } from '@/api/channel/types ';
import { Delete , Edit , Plus , Refresh , View } from '@element-plus/icons-vue' ;
import type { AiAppDto , AiModelDto } from '@/api/channel/type s';
import { ElMessage , ElMessageBox } from 'element-plu s';
import { onMounted , ref } from 'vue' ;
import {
getAppList ,
createApp ,
updateApp ,
deleteApp ,
getModelList ,
createModel ,
updateModel ,
deleteModel ,
clearPremiumModelCache ,
createApp ,
createModel ,
deleteApp ,
deleteModel ,
getAppList ,
getAppShortcutList ,
getModelList ,
updateApp ,
updateModel ,
} from '@/api/channel' ;
// ==================== 应用管理 ====================
const appList = ref < AiAppDto [ ] > ( [ ] ) ;
const appLoading = ref ( false ) ;
const selectedAppId = ref < string > ( '' ) ;
const selectedAppIds = ref < string [ ] > ( [ ] ) ;
// 快捷号池列表
const shortcutList = ref < AppShortcutDto [ ] > ( [ ] ) ;
const shortcutLoading = ref ( false ) ;
// 应用对话框
const appDialogVisible = ref ( false ) ;
const appDialogTitle = ref ( '' ) ;
const appForm = ref < Partial < AiAppDto > > ( { } ) ;
const selectedShortcutId = ref < string > ( '' ) ;
const appDetailDialogVisible = ref ( false ) ;
const appDetailData = ref < AiAppDto | null > ( null ) ;
// 批量应用号池对话框
const batchApplyDialogVisible = ref ( false ) ;
const batchApplyShortcutId = ref < string > ( '' ) ;
// 号池列表对话框
const poolListDialogVisible = ref ( false ) ;
// 获取应用列表
async function fetchAppList ( ) {
appLoading . value = true ;
@@ -51,6 +65,21 @@ async function fetchAppList() {
}
}
// 获取快捷号池列表
async function fetchShortcutList ( ) {
shortcutLoading . value = true ;
try {
const res = await getAppShortcutList ( ) ;
shortcutList . value = res . data ;
}
catch ( error : any ) {
ElMessage . error ( error . message || '获取快捷号池列表失败' ) ;
}
finally {
shortcutLoading . value = false ;
}
}
// 选择应用
function handleSelectApp ( appId : string ) {
selectedAppId . value = appId ;
@@ -66,6 +95,7 @@ function handleViewAppDetail(app: AiAppDto) {
// 打开应用对话框
function openAppDialog ( type : 'create' | 'edit' , row ? : AiAppDto ) {
appDialogTitle . value = type === 'create' ? '创建应用' : '编辑应用' ;
selectedShortcutId . value = '' ;
if ( type === 'create' ) {
appForm . value = {
name : '' ,
@@ -81,6 +111,85 @@ function openAppDialog(type: 'create' | 'edit', row?: AiAppDto) {
appDialogVisible . value = true ;
}
// 选择快捷号池
function handleSelectShortcut ( ) {
const shortcut = shortcutList . value . find ( s => s . id === selectedShortcutId . value ) ;
if ( shortcut ) {
appForm . value = {
... appForm . value ,
endpoint : shortcut . endpoint ,
extraUrl : shortcut . extraUrl || '' ,
apiKey : shortcut . apiKey ,
// 不修改名称和排序,保持应用原有的配置
} ;
ElMessage . success ( '已自动填入号池配置( 终结点和API Key) ' ) ;
}
}
// 打开批量应用号池对话框
function openBatchApplyDialog ( ) {
if ( selectedAppIds . value . length === 0 ) {
ElMessage . warning ( '请先选择要批量操作的应用' ) ;
return ;
}
batchApplyShortcutId . value = '' ;
batchApplyDialogVisible . value = true ;
}
// 批量应用号池
async function handleBatchApply ( ) {
if ( ! batchApplyShortcutId . value ) {
ElMessage . warning ( '请选择号池' ) ;
return ;
}
const shortcut = shortcutList . value . find ( s => s . id === batchApplyShortcutId . value ) ;
if ( ! shortcut ) {
ElMessage . error ( '未找到选择的号池' ) ;
return ;
}
try {
// 批量更新应用( 只更新终结点和API Key, 不修改应用名称)
const promises = selectedAppIds . value . map ( appId => {
// 获取当前应用信息,保留原有名称和排序
const currentApp = appList . value . find ( a => a . id === appId ) ;
return updateApp ( {
id : appId ,
name : currentApp ? . name || '' , // 保持原有应用名称
endpoint : shortcut . endpoint ,
extraUrl : shortcut . extraUrl ,
apiKey : shortcut . apiKey ,
orderNum : currentApp ? . orderNum || 0 , // 保持原有排序
} ) ;
} ) ;
await Promise . all ( promises ) ;
ElMessage . success ( ` 成功应用到 ${ selectedAppIds . value . length } 个应用( 终结点和API Key已更新) ` ) ;
batchApplyDialogVisible . value = false ;
selectedAppIds . value = [ ] ;
fetchAppList ( ) ;
}
catch ( error : any ) {
ElMessage . error ( error . message || '批量应用失败' ) ;
}
}
// 处理应用选择变化
function handleSelectionChange ( selection : AiAppDto [ ] ) {
selectedAppIds . value = selection . map ( item => item . id ) ;
}
// 切换应用选择
function toggleAppSelection ( appId : string ) {
const index = selectedAppIds . value . indexOf ( appId ) ;
if ( index > - 1 ) {
selectedAppIds . value . splice ( index , 1 ) ;
} else {
selectedAppIds . value . push ( appId ) ;
}
}
// 保存应用
async function saveApp ( ) {
try {
@@ -244,6 +353,7 @@ async function handleClearCache() {
// 初始化
onMounted ( ( ) => {
fetchAppList ( ) ;
fetchShortcutList ( ) ;
} ) ;
< / script >
@@ -254,9 +364,26 @@ onMounted(() => {
< div class = "app-list-panel" >
< div class = "panel-header" >
< h3 > 应用列表 < / h3 >
< el-button type = "primary" size = "small" :icon = "Plus" @click ="openAppDialog('create')" >
新建
< / el -button >
< div class = "header-actions" >
< el-button
v-if = "selectedAppIds.length > 0"
type = "warning"
size = "small"
@click ="openBatchApplyDialog"
>
批量应用号池 ( { { selectedAppIds . length } } )
< / el-button >
< el-button
type = "info"
size = "small"
@click ="poolListDialogVisible = true"
>
查看号池列表
< / el-button >
< el-button type = "primary" size = "small" :icon = "Plus" @click ="openAppDialog('create')" >
新建
< / el -button >
< / div >
< / div >
< el-scrollbar class = "app-list-scrollbar" >
@@ -266,10 +393,16 @@ onMounted(() => {
:key = "app.id"
class = "app-item"
: class = "{ active: selectedAppId === app.id }"
@click ="handleSelectApp(app.id)"
>
< div class = "app-item-content" >
< div class = "app-name" > { { app . name } } < / div >
< el-checkbox
:model-value = "selectedAppIds.includes(app.id)"
@change ="() => toggleAppSelection(app.id)"
@click.stop
/ >
< div class = "app-item-content" @click ="handleSelectApp(app.id)" >
< div class = "app-name" >
{ { app . name } }
< / div >
< div class = "app-actions" >
< el-button
link
@@ -381,20 +514,55 @@ onMounted(() => {
<!-- 应用详情对话框 -- >
< el-dialog v-model = "appDetailDialogVisible" title="应用详情" width="600px" >
< el -descriptions v-if = "appDetailData" :column="1" border >
< el-descriptions-item label = "应用名称" > { { appDetailData . name } } < / el-descriptions-item >
< el-descriptions-item label = "终结点" > { { appDetailData . endpoint } } < / el-descriptions-item >
< el-descriptions-item label = "额外URL" > { { appDetailData . extraUrl || '-' } } < / el-descriptions-item >
< el-descriptions-item label = "应用名称" >
{ { appDetailData . name } }
< / el-descriptions-item >
< el-descriptions-item label = "终结点" >
{ { appDetailData . endpoint } }
< / el-descriptions-item >
< el-descriptions-item label = "额外URL" >
{ { appDetailData . extraUrl || '-' } }
< / el-descriptions-item >
< el-descriptions-item label = "API Key" >
< el-input :model-value = "appDetailData.apiKey" type = "textarea" readonly / >
< / el-descriptions-item >
< el-descriptions-item label = "排序" > { { appDetailData . orderNum } } < / el-descriptions-item >
< el-descriptions-item label = "创建时间" > { { appDetailData . creationTime } } < / el-descriptions-item >
< el-descriptions-item label = "排序" >
{ { appDetailData . orderNum } }
< / el-descriptions-item >
< el-descriptions-item label = "创建时间" >
{ { appDetailData . creationTime } }
< / el-descriptions-item >
< / el-descriptions >
< / el-dialog >
<!-- 应用编辑对话框 -- >
< el-dialog v-model = "appDialogVisible" :title="appDialogTitle" width="60 0px" >
< el-dialog v-model = "appDialogVisible" :title="appDialogTitle" width="65 0px" >
< el -form :model = "appForm" label -width = " 120px " >
< el-form-item label = "选择号池" >
< el-select
v-model = "selectedShortcutId"
placeholder = "选择号池自动填入配置"
style = "width: 100%"
clearable
@change ="handleSelectShortcut"
>
< el-option
v-for = "shortcut in shortcutList"
:key = "shortcut.id"
:label = "shortcut.name"
:value = "shortcut.id"
>
< div style = "display: flex; justify-content: space-between; align-items: center" >
< span > { { shortcut . name } } < / span >
< span style = "font-size: 12px; color: #999; margin-left: 10px" > { { shortcut . endpoint } } < / span >
< / div >
< / el-option >
< / el-select >
< div style = "font-size: 12px; color: #999; margin-top: 5px" >
选择号池可自动填入终结点和 API Key
< / div >
< / el-form-item >
< el-divider / >
< el-form-item label = "应用名称" required >
< el-input v-model = "appForm.name" placeholder="请输入应用名称" / >
< / el-form-item >
@@ -412,8 +580,98 @@ onMounted(() => {
< / el-form-item >
< / el-form >
< template # footer >
< el-button @click ="appDialogVisible = false" > 取消 < / el -button >
< el-button type = "primary" @click ="saveApp" > 保存 < / el -button >
< el-button @click ="appDialogVisible = false" >
取消
< / el -button >
< el-button type = "primary" @click ="saveApp" >
保存
< / el -button >
< / template >
< / el-dialog >
<!-- 批量应用号池对话框 -- >
< el-dialog v-model = "batchApplyDialogVisible" title="批量应用号池" width="700px" >
< el -alert
: title = "`已选择 ${selectedAppIds.length} 个应用,将统一应用以下号池配置`"
type = "info"
:closable = "false"
style = "margin-bottom: 15px"
/ >
< el-form label -width = " 100px " >
< el-form-item label = "选中的应用" >
< div style = "max-height: 100px; overflow-y: auto" >
< el-tag
v-for = "appId in selectedAppIds"
:key = "appId"
style = "margin: 4px"
size = "default"
>
{ { appList . find ( a => a . id === appId ) ? . name || appId } }
< / el-tag >
< / div >
< / el-form-item >
< el-form-item label = "选择号池" required >
< el-select
v-model = "batchApplyShortcutId"
placeholder = "请选择号池"
style = "width: 100%"
>
< el-option
v-for = "shortcut in shortcutList"
:key = "shortcut.id"
:label = "shortcut.name"
:value = "shortcut.id"
>
< div style = "display: flex; justify-content: space-between; align-items: center" >
< span > { { shortcut . name } } < / span >
< span style = "font-size: 12px; color: #999; margin-left: 10px" > { { shortcut . endpoint } } < / span >
< / div >
< / el-option >
< / el-select >
< / el-form-item >
< el-form-item v-if = "batchApplyShortcutId" label="号池详情" >
< div
v-if = "shortcutList.find(s => s.id === batchApplyShortcutId)"
style = "background: #f5f7fa; padding: 15px; border-radius: 4px"
>
< div > < strong > 名称 : < / strong > { { shortcutList . find ( s => s . id === batchApplyShortcutId ) ? . name } } < / div >
< div style = "margin-top: 8px" > < strong > 终结点 : < / strong > { { shortcutList . find ( s => s . id === batchApplyShortcutId ) ? . endpoint } } < / div >
< div style = "margin-top: 8px" > < strong > API Key : < / strong > { { shortcutList . find ( s => s . id === batchApplyShortcutId ) ? . apiKey ? . substring ( 0 , 20 ) } } * * * < / div >
< / div >
< / el-form-item >
< / el-form >
< template # footer >
< el-button @click ="batchApplyDialogVisible = false" >
取消
< / el -button >
< el-button type = "primary" @click ="handleBatchApply" >
确定应用
< / el -button >
< / template >
< / el-dialog >
<!-- 号池列表对话框 -- >
< el-dialog v-model = "poolListDialogVisible" title="号池列表" width="900px" >
< el -table :data = "shortcutList" border stripe max -height = " 500px " >
< el-table-column prop = "name" label = "号池名称" min -width = " 180 " / >
< el-table-column prop = "endpoint" label = "终结点" min -width = " 250 " show -overflow -tooltip / >
< el-table-column prop = "extraUrl" label = "额外URL" min -width = " 150 " show -overflow -tooltip >
< template # default = "{ row }" >
{ { row . extraUrl || '-' } }
< / template >
< / el-table-column >
< el-table-column prop = "apiKey" label = "API Key" min -width = " 200 " show -overflow -tooltip >
< template # default = "{ row }" >
{ { row . apiKey ? . substring ( 0 , 30 ) } } { { row . apiKey ? . length > 30 ? '...' : '' } }
< / template >
< / el-table-column >
< el-table-column prop = "orderNum" label = "排序" width = "80" / >
< el-table-column prop = "creationTime" label = "创建时间" width = "160" / >
< / el-table >
< template # footer >
< el-button type = "primary" @click ="poolListDialogVisible = false" >
关闭
< / el -button >
< / template >
< / el-dialog >
@@ -470,8 +728,12 @@ onMounted(() => {
< / el-form-item >
< / el-form >
< template # footer >
< el-button @click ="modelDialogVisible = false" > 取消 < / el -button >
< el-button type = "primary" @click ="saveModel" > 保存 < / el -button >
< el-button @click ="modelDialogVisible = false" >
取消
< / el -button >
< el-button type = "primary" @click ="saveModel" >
保存
< / el -button >
< / template >
< / el-dialog >
< / div >
@@ -540,6 +802,9 @@ onMounted(() => {
border - radius : 6 px ;
cursor : pointer ;
transition : all 0.3 s ;
display : flex ;
align - items : center ;
gap : 10 px ;
& : hover {
border - color : # 409 eff ;
@@ -552,6 +817,7 @@ onMounted(() => {
}
. app - item - content {
flex : 1 ;
. app - name {
font - size : 14 px ;
font - weight : 500 ;