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
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
180 additions
and
20 deletions
+180
-20
web/src/pages/Channel/EditChannel.js
+180
-20
No files found.
web/src/pages/Channel/EditChannel.js
View file @
9660b22c
...
@@ -26,6 +26,7 @@ import {
...
@@ -26,6 +26,7 @@ import {
Form
,
Form
,
Row
,
Row
,
Col
,
Col
,
Highlight
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
getChannelModels
,
copy
,
getChannelIcon
,
getModelCategories
}
from
'../../helpers'
;
import
{
getChannelModels
,
copy
,
getChannelIcon
,
getModelCategories
}
from
'../../helpers'
;
import
{
import
{
...
@@ -122,6 +123,8 @@ const EditChannel = (props) => {
...
@@ -122,6 +123,8 @@ const EditChannel = (props) => {
const
[
vertexFileList
,
setVertexFileList
]
=
useState
([]);
const
[
vertexFileList
,
setVertexFileList
]
=
useState
([]);
const
vertexErroredNames
=
useRef
(
new
Set
());
// 避免重复报错
const
vertexErroredNames
=
useRef
(
new
Set
());
// 避免重复报错
const
[
isMultiKeyChannel
,
setIsMultiKeyChannel
]
=
useState
(
false
);
const
[
isMultiKeyChannel
,
setIsMultiKeyChannel
]
=
useState
(
false
);
const
[
channelSearchValue
,
setChannelSearchValue
]
=
useState
(
''
);
const
[
useManualInput
,
setUseManualInput
]
=
useState
(
false
);
// 是否使用手动输入模式
const
getInitValues
=
()
=>
({
...
originInputs
});
const
getInitValues
=
()
=>
({
...
originInputs
});
const
handleInputChange
=
(
name
,
value
)
=>
{
const
handleInputChange
=
(
name
,
value
)
=>
{
if
(
formApiRef
.
current
)
{
if
(
formApiRef
.
current
)
{
...
@@ -190,6 +193,9 @@ const EditChannel = (props) => {
...
@@ -190,6 +193,9 @@ const EditChannel = (props) => {
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
}
}
setBasicModels
(
localModels
);
setBasicModels
(
localModels
);
// 重置手动输入模式状态
setUseManualInput
(
false
);
}
}
//setAutoBan
//setAutoBan
};
};
...
@@ -418,6 +424,8 @@ const EditChannel = (props) => {
...
@@ -418,6 +424,8 @@ const EditChannel = (props) => {
}
else
{
}
else
{
formApiRef
.
current
?.
setValues
(
getInitValues
());
formApiRef
.
current
?.
setValues
(
getInitValues
());
}
}
// 重置手动输入模式状态
setUseManualInput
(
false
);
}
else
{
}
else
{
formApiRef
.
current
?.
reset
();
formApiRef
.
current
?.
reset
();
}
}
...
@@ -468,6 +476,24 @@ const EditChannel = (props) => {
...
@@ -468,6 +476,24 @@ const EditChannel = (props) => {
let
localInputs
=
{
...
formValues
};
let
localInputs
=
{
...
formValues
};
if
(
localInputs
.
type
===
41
)
{
if
(
localInputs
.
type
===
41
)
{
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
{
// 文件上传模式
let
keys
=
vertexKeys
;
let
keys
=
vertexKeys
;
// 若当前未选择文件,尝试从已上传文件列表解析(异步读取)
// 若当前未选择文件,尝试从已上传文件列表解析(异步读取)
...
@@ -506,6 +532,7 @@ const EditChannel = (props) => {
...
@@ -506,6 +532,7 @@ const EditChannel = (props) => {
}
}
}
}
}
}
}
// 如果是编辑模式且 key 为空字符串,避免提交空值覆盖旧密钥
// 如果是编辑模式且 key 为空字符串,避免提交空值覆盖旧密钥
if
(
isEdit
&&
(
!
localInputs
.
key
||
localInputs
.
key
.
trim
()
===
''
))
{
if
(
isEdit
&&
(
!
localInputs
.
key
||
localInputs
.
key
.
trim
()
===
''
))
{
...
@@ -646,23 +673,33 @@ const EditChannel = (props) => {
...
@@ -646,23 +673,33 @@ const EditChannel = (props) => {
if
(
!
checked
)
{
if
(
!
checked
)
{
setMultiToSingle
(
false
);
setMultiToSingle
(
false
);
setMultiKeyMode
(
'random'
);
setMultiKeyMode
(
'random'
);
}
else
{
// 批量模式下禁用手动输入,并清空手动输入的内容
setUseManualInput
(
false
);
if
(
inputs
.
type
===
41
)
{
// 清空手动输入的密钥内容
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'key'
,
''
);
}
handleInputChange
(
'key'
,
''
);
}
}
}
}}
}}
>
{
t
(
'批量创建'
)}
<
/Checkbox
>
>
{
t
(
'批量创建'
)}
<
/Checkbox
>
{
batch
&&
(
{
/*{batch && (*/
}
<
Checkbox
disabled
=
{
isEdit
}
checked
=
{
multiToSingle
}
onChange
=
{()
=>
{
{
/* <Checkbox disabled={isEdit} checked={multiToSingle} onChange={() => {*/
}
setMultiToSingle
(
prev
=>
!
prev
);
{
/* setMultiToSingle(prev => !prev);*/
}
setInputs
(
prev
=>
{
{
/* setInputs(prev => {*/
}
const
newInputs
=
{
...
prev
};
{
/* const newInputs = { ...prev };*/
}
if
(
!
multiToSingle
)
{
{
/* if (!multiToSingle) {*/
}
newInputs
.
multi_key_mode
=
multiKeyMode
;
{
/* newInputs.multi_key_mode = multiKeyMode;*/
}
}
else
{
{
/* } else {*/
}
delete
newInputs
.
multi_key_mode
;
{
/* delete newInputs.multi_key_mode;*/
}
}
{
/* }*/
}
return
newInputs
;
{
/* return newInputs;*/
}
});
{
/* });*/
}
}}
>
{
t
(
'密钥聚合模式'
)}
<
/Checkbox
>
{
/* }}>{t('密钥聚合模式')}</Checkbox>*/
}
)
}
{
/*)}*/
}
<
/Space
>
<
/Space
>
)
:
null
;
)
:
null
;
...
@@ -670,16 +707,68 @@ const EditChannel = (props) => {
...
@@ -670,16 +707,68 @@ const EditChannel = (props) => {
()
=>
()
=>
CHANNEL_OPTIONS
.
map
((
opt
)
=>
({
CHANNEL_OPTIONS
.
map
((
opt
)
=>
({
...
opt
,
...
opt
,
label
:
(
// 保持 label 为纯文本以支持搜索
<
span
className
=
"flex items-center gap-2"
>
label
:
opt
.
label
,
{
getChannelIcon
(
opt
.
value
)}
{
opt
.
label
}
<
/span
>
),
})),
})),
[],
[],
);
);
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
(
return
(
<>
<>
<
SideSheet
<
SideSheet
...
@@ -749,6 +838,8 @@ const EditChannel = (props) => {
...
@@ -749,6 +838,8 @@ const EditChannel = (props) => {
style
=
{{
width
:
'100%'
}}
style
=
{{
width
:
'100%'
}}
filter
filter
searchPosition
=
'dropdown'
searchPosition
=
'dropdown'
onSearch
=
{(
value
)
=>
setChannelSearchValue
(
value
)}
renderOptionItem
=
{
renderChannelOption
}
onChange
=
{(
value
)
=>
handleInputChange
(
'type'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'type'
,
value
)}
/
>
/
>
...
@@ -797,6 +888,73 @@ const EditChannel = (props) => {
...
@@ -797,6 +888,73 @@ const EditChannel = (props) => {
)
:
(
)
:
(
<>
<>
{
inputs
.
type
===
41
?
(
{
inputs
.
type
===
41
?
(
<>
{
!
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
<
Form
.
Upload
field
=
'vertex_files'
field
=
'vertex_files'
label
=
{
t
(
'密钥文件 (.json)'
)}
label
=
{
t
(
'密钥文件 (.json)'
)}
...
@@ -813,6 +971,8 @@ const EditChannel = (props) => {
...
@@ -813,6 +971,8 @@ const EditChannel = (props) => {
rules
=
{
isEdit
?
[]
:
[{
required
:
true
,
message
:
t
(
'请上传密钥文件'
)
}]}
rules
=
{
isEdit
?
[]
:
[{
required
:
true
,
message
:
t
(
'请上传密钥文件'
)
}]}
extraText
=
{
batchExtra
}
extraText
=
{
batchExtra
}
/
>
/
>
)}
<
/
>
)
:
(
)
:
(
<
Form
.
Input
<
Form
.
Input
field
=
'key'
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