Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
9660b22c
authored
Jul 17, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 添加手动输入和文件上传模式切换功能,支持密钥搜索和高亮显示
parent
c5b9d713
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
227 additions
and
67 deletions
+227
-67
web/src/pages/Channel/EditChannel.js
+227
-67
No files found.
web/src/pages/Channel/EditChannel.js
View file @
9660b22c
...
...
@@ -26,6 +26,7 @@ import {
Form
,
Row
,
Col
,
Highlight
,
}
from
'@douyinfe/semi-ui'
;
import
{
getChannelModels
,
copy
,
getChannelIcon
,
getModelCategories
}
from
'../../helpers'
;
import
{
...
...
@@ -122,6 +123,8 @@ const EditChannel = (props) => {
const
[
vertexFileList
,
setVertexFileList
]
=
useState
([]);
const
vertexErroredNames
=
useRef
(
new
Set
());
// 避免重复报错
const
[
isMultiKeyChannel
,
setIsMultiKeyChannel
]
=
useState
(
false
);
const
[
channelSearchValue
,
setChannelSearchValue
]
=
useState
(
''
);
const
[
useManualInput
,
setUseManualInput
]
=
useState
(
false
);
// 是否使用手动输入模式
const
getInitValues
=
()
=>
({
...
originInputs
});
const
handleInputChange
=
(
name
,
value
)
=>
{
if
(
formApiRef
.
current
)
{
...
...
@@ -190,6 +193,9 @@ const EditChannel = (props) => {
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
}
setBasicModels
(
localModels
);
// 重置手动输入模式状态
setUseManualInput
(
false
);
}
//setAutoBan
};
...
...
@@ -418,6 +424,8 @@ const EditChannel = (props) => {
}
else
{
formApiRef
.
current
?.
setValues
(
getInitValues
());
}
// 重置手动输入模式状态
setUseManualInput
(
false
);
}
else
{
formApiRef
.
current
?.
reset
();
}
...
...
@@ -468,41 +476,60 @@ const EditChannel = (props) => {
let
localInputs
=
{
...
formValues
};
if
(
localInputs
.
type
===
41
)
{
let
keys
=
vertexKeys
;
// 若当前未选择文件,尝试从已上传文件列表解析(异步读取)
if
(
keys
.
length
===
0
&&
vertexFileList
.
length
>
0
)
{
try
{
const
parsed
=
await
Promise
.
all
(
vertexFileList
.
map
(
async
(
item
)
=>
{
const
fileObj
=
item
.
fileInstance
;
if
(
!
fileObj
)
return
null
;
const
txt
=
await
fileObj
.
text
();
return
JSON
.
parse
(
txt
);
})
);
keys
=
parsed
.
filter
(
Boolean
);
}
catch
(
err
)
{
showError
(
t
(
'解析密钥文件失败: {{msg}}'
,
{
msg
:
err
.
message
}));
return
;
}
}
// 创建模式必须上传密钥;编辑模式可选
if
(
keys
.
length
===
0
)
{
if
(
!
isEdit
)
{
showInfo
(
t
(
'请上传密钥文件!'
));
if
(
useManualInput
)
{
// 手动输入模式
if
(
localInputs
.
key
&&
localInputs
.
key
.
trim
()
!==
''
)
{
try
{
// 验证 JSON 格式
const
parsedKey
=
JSON
.
parse
(
localInputs
.
key
);
// 确保是有效的密钥格式
localInputs
.
key
=
JSON
.
stringify
(
parsedKey
);
}
catch
(
err
)
{
showError
(
t
(
'密钥格式无效,请输入有效的 JSON 格式密钥'
));
return
;
}
}
else
if
(
!
isEdit
)
{
showInfo
(
t
(
'请输入密钥!'
));
return
;
}
else
{
// 编辑模式且未上传新密钥,不修改 key
delete
localInputs
.
key
;
}
}
else
{
// 有新密钥,则覆盖
if
(
batch
)
{
localInputs
.
key
=
JSON
.
stringify
(
keys
);
// 文件上传模式
let
keys
=
vertexKeys
;
// 若当前未选择文件,尝试从已上传文件列表解析(异步读取)
if
(
keys
.
length
===
0
&&
vertexFileList
.
length
>
0
)
{
try
{
const
parsed
=
await
Promise
.
all
(
vertexFileList
.
map
(
async
(
item
)
=>
{
const
fileObj
=
item
.
fileInstance
;
if
(
!
fileObj
)
return
null
;
const
txt
=
await
fileObj
.
text
();
return
JSON
.
parse
(
txt
);
})
);
keys
=
parsed
.
filter
(
Boolean
);
}
catch
(
err
)
{
showError
(
t
(
'解析密钥文件失败: {{msg}}'
,
{
msg
:
err
.
message
}));
return
;
}
}
// 创建模式必须上传密钥;编辑模式可选
if
(
keys
.
length
===
0
)
{
if
(
!
isEdit
)
{
showInfo
(
t
(
'请上传密钥文件!'
));
return
;
}
else
{
// 编辑模式且未上传新密钥,不修改 key
delete
localInputs
.
key
;
}
}
else
{
localInputs
.
key
=
JSON
.
stringify
(
keys
[
0
]);
// 有新密钥,则覆盖
if
(
batch
)
{
localInputs
.
key
=
JSON
.
stringify
(
keys
);
}
else
{
localInputs
.
key
=
JSON
.
stringify
(
keys
[
0
]);
}
}
}
}
...
...
@@ -646,23 +673,33 @@ const EditChannel = (props) => {
if
(
!
checked
)
{
setMultiToSingle
(
false
);
setMultiKeyMode
(
'random'
);
}
else
{
// 批量模式下禁用手动输入,并清空手动输入的内容
setUseManualInput
(
false
);
if
(
inputs
.
type
===
41
)
{
// 清空手动输入的密钥内容
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'key'
,
''
);
}
handleInputChange
(
'key'
,
''
);
}
}
}}
>
{
t
(
'批量创建'
)}
<
/Checkbox
>
{
batch
&&
(
<
Checkbox
disabled
=
{
isEdit
}
checked
=
{
multiToSingle
}
onChange
=
{()
=>
{
setMultiToSingle
(
prev
=>
!
prev
);
setInputs
(
prev
=>
{
const
newInputs
=
{
...
prev
};
if
(
!
multiToSingle
)
{
newInputs
.
multi_key_mode
=
multiKeyMode
;
}
else
{
delete
newInputs
.
multi_key_mode
;
}
return
newInputs
;
});
}}
>
{
t
(
'密钥聚合模式'
)}
<
/Checkbox
>
)
}
{
/*{batch && (*/
}
{
/* <Checkbox disabled={isEdit} checked={multiToSingle} onChange={() => {*/
}
{
/* setMultiToSingle(prev => !prev);*/
}
{
/* setInputs(prev => {*/
}
{
/* const newInputs = { ...prev };*/
}
{
/* if (!multiToSingle) {*/
}
{
/* newInputs.multi_key_mode = multiKeyMode;*/
}
{
/* } else {*/
}
{
/* delete newInputs.multi_key_mode;*/
}
{
/* }*/
}
{
/* return newInputs;*/
}
{
/* });*/
}
{
/* }}>{t('密钥聚合模式')}</Checkbox>*/
}
{
/*)}*/
}
<
/Space
>
)
:
null
;
...
...
@@ -670,16 +707,68 @@ const EditChannel = (props) => {
()
=>
CHANNEL_OPTIONS
.
map
((
opt
)
=>
({
...
opt
,
label
:
(
<
span
className
=
"flex items-center gap-2"
>
{
getChannelIcon
(
opt
.
value
)}
{
opt
.
label
}
<
/span
>
),
// 保持 label 为纯文本以支持搜索
label
:
opt
.
label
,
})),
[],
);
const
renderChannelOption
=
(
renderProps
)
=>
{
const
{
disabled
,
selected
,
label
,
value
,
focused
,
className
,
style
,
onMouseEnter
,
onClick
,
...
rest
}
=
renderProps
;
const
searchWords
=
channelSearchValue
?
[
channelSearchValue
]
:
[];
// 构建样式类名
const
optionClassName
=
[
'flex items-center gap-3 px-3 py-2 transition-all duration-200 rounded-lg mx-2 my-1'
,
focused
&&
'bg-blue-50 shadow-sm'
,
selected
&&
'bg-blue-100 text-blue-700 shadow-lg ring-2 ring-blue-200 ring-opacity-50'
,
disabled
&&
'opacity-50 cursor-not-allowed'
,
!
disabled
&&
'hover:bg-gray-50 hover:shadow-md cursor-pointer'
,
className
].
filter
(
Boolean
).
join
(
' '
);
return
(
<
div
style
=
{
style
}
className
=
{
optionClassName
}
onClick
=
{()
=>
!
disabled
&&
onClick
()}
onMouseEnter
=
{
e
=>
onMouseEnter
()}
>
<
div
className
=
"flex items-center gap-3 w-full"
>
<
div
className
=
"flex-shrink-0 w-5 h-5 flex items-center justify-center"
>
{
getChannelIcon
(
value
)}
<
/div
>
<
div
className
=
"flex-1 min-w-0"
>
<
Highlight
sourceString
=
{
label
}
searchWords
=
{
searchWords
}
className
=
"text-sm font-medium truncate"
/>
<
/div
>
{
selected
&&
(
<
div
className
=
"flex-shrink-0 text-blue-600"
>
<
svg
width
=
"16"
height
=
"16"
viewBox
=
"0 0 16 16"
fill
=
"currentColor"
>
<
path
d
=
"M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
/>
<
/svg
>
<
/div
>
)}
<
/div
>
<
/div
>
);
};
return
(
<>
<
SideSheet
...
...
@@ -749,6 +838,8 @@ const EditChannel = (props) => {
style
=
{{
width
:
'100%'
}}
filter
searchPosition
=
'dropdown'
onSearch
=
{(
value
)
=>
setChannelSearchValue
(
value
)}
renderOptionItem
=
{
renderChannelOption
}
onChange
=
{(
value
)
=>
handleInputChange
(
'type'
,
value
)}
/
>
...
...
@@ -797,22 +888,91 @@ const EditChannel = (props) => {
)
:
(
<>
{
inputs
.
type
===
41
?
(
<
Form
.
Upload
field
=
'vertex_files'
label
=
{
t
(
'密钥文件 (.json)'
)}
accept
=
'.json'
draggable
dragIcon
=
{
<
IconBolt
/>
}
dragMainText
=
{
t
(
'点击上传文件或拖拽文件到这里'
)}
dragSubText
=
{
t
(
'仅支持 JSON 文件'
)}
style
=
{{
marginTop
:
10
}}
uploadTrigger
=
'custom'
beforeUpload
=
{()
=>
false
}
onChange
=
{
handleVertexUploadChange
}
fileList
=
{
vertexFileList
}
rules
=
{
isEdit
?
[]
:
[{
required
:
true
,
message
:
t
(
'请上传密钥文件'
)
}]}
extraText
=
{
batchExtra
}
/
>
<>
{
!
batch
&&
(
<
div
className
=
"flex items-center justify-between mb-3"
>
<
Text
className
=
"text-sm font-medium"
>
{
t
(
'密钥输入方式'
)}
<
/Text
>
<
Space
>
<
Button
size
=
"small"
type
=
{
!
useManualInput
?
'primary'
:
'tertiary'
}
onClick
=
{()
=>
{
setUseManualInput
(
false
);
// 切换到文件上传模式时清空手动输入的密钥
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'key'
,
''
);
}
handleInputChange
(
'key'
,
''
);
}}
>
{
t
(
'文件上传'
)}
<
/Button
>
<
Button
size
=
"small"
type
=
{
useManualInput
?
'primary'
:
'tertiary'
}
onClick
=
{()
=>
{
setUseManualInput
(
true
);
// 切换到手动输入模式时清空文件上传相关状态
setVertexKeys
([]);
setVertexFileList
([]);
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'vertex_files'
,
[]);
}
setInputs
((
prev
)
=>
({
...
prev
,
vertex_files
:
[]
}));
}}
>
{
t
(
'手动输入'
)}
<
/Button
>
<
/Space
>
<
/div
>
)}
{
batch
&&
(
<
Banner
type
=
'info'
description
=
{
t
(
'批量创建模式下仅支持文件上传,不支持手动输入'
)}
className
=
'!rounded-lg mb-3'
/>
)}
{
useManualInput
&&
!
batch
?
(
<
Form
.
TextArea
field
=
'key'
label
=
{
isEdit
?
t
(
'密钥(编辑模式下,保存的密钥不会显示)'
)
:
t
(
'密钥'
)}
placeholder
=
{
t
(
'请输入 JSON 格式的密钥内容,例如:\n{\n "type": "service_account",\n "project_id": "your-project-id",\n "private_key_id": "...",\n "private_key": "...",\n "client_email": "...",\n "client_id": "...",\n "auth_uri": "...",\n "token_uri": "...",\n "auth_provider_x509_cert_url": "...",\n "client_x509_cert_url": "..."\n}'
)}
rules
=
{
isEdit
?
[]
:
[{
required
:
true
,
message
:
t
(
'请输入密钥'
)
}]}
autoComplete
=
'new-password'
onChange
=
{(
value
)
=>
handleInputChange
(
'key'
,
value
)}
extraText
=
{
<
div
className
=
"flex items-center gap-2"
>
<
Text
type
=
"tertiary"
size
=
"small"
>
{
t
(
'请输入完整的 JSON 格式密钥内容'
)}
<
/Text
>
{
batchExtra
}
<
/div
>
}
autosize
showClear
/>
)
:
(
<
Form
.
Upload
field
=
'vertex_files'
label
=
{
t
(
'密钥文件 (.json)'
)}
accept
=
'.json'
draggable
dragIcon
=
{
<
IconBolt
/>
}
dragMainText
=
{
t
(
'点击上传文件或拖拽文件到这里'
)}
dragSubText
=
{
t
(
'仅支持 JSON 文件'
)}
style
=
{{
marginTop
:
10
}}
uploadTrigger
=
'custom'
beforeUpload
=
{()
=>
false
}
onChange
=
{
handleVertexUploadChange
}
fileList
=
{
vertexFileList
}
rules
=
{
isEdit
?
[]
:
[{
required
:
true
,
message
:
t
(
'请上传密钥文件'
)
}]}
extraText
=
{
batchExtra
}
/
>
)}
<
/
>
)
:
(
<
Form
.
Input
field
=
'key'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment