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
78b9b21a
authored
Sep 29, 2025
by
Seefs
Committed by
GitHub
Sep 29, 2025
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1910 from seefs001/fix/volcengine_default_baseurl
alpha -> main
parents
a48f6a7d
6eabe26e
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
279 additions
and
29 deletions
+279
-29
controller/topup_stripe.go
+2
-1
dto/gemini.go
+1
-0
main.go
+18
-0
model/option.go
+3
-0
relay/channel/gemini/relay-gemini.go
+10
-0
setting/payment_stripe.go
+1
-0
web/index.html
+1
-0
web/src/components/settings/PaymentSetting.jsx
+1
-0
web/src/components/table/channels/modals/EditChannelModal.jsx
+105
-23
web/src/components/table/task-logs/modals/ContentModal.jsx
+110
-4
web/src/i18n/locales/en.json
+1
-0
web/src/i18n/locales/zh.json
+2
-1
web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx
+24
-0
No files found.
controller/topup_stripe.go
View file @
78b9b21a
...
@@ -225,7 +225,8 @@ func genStripeLink(referenceId string, customerId string, email string, amount i
...
@@ -225,7 +225,8 @@ func genStripeLink(referenceId string, customerId string, email string, amount i
Quantity
:
stripe
.
Int64
(
amount
),
Quantity
:
stripe
.
Int64
(
amount
),
},
},
},
},
Mode
:
stripe
.
String
(
string
(
stripe
.
CheckoutSessionModePayment
)),
Mode
:
stripe
.
String
(
string
(
stripe
.
CheckoutSessionModePayment
)),
AllowPromotionCodes
:
stripe
.
Bool
(
setting
.
StripePromotionCodesEnabled
),
}
}
if
""
==
customerId
{
if
""
==
customerId
{
...
...
dto/gemini.go
View file @
78b9b21a
...
@@ -251,6 +251,7 @@ type GeminiChatTool struct {
...
@@ -251,6 +251,7 @@ type GeminiChatTool struct {
GoogleSearchRetrieval
any
`json:"googleSearchRetrieval,omitempty"`
GoogleSearchRetrieval
any
`json:"googleSearchRetrieval,omitempty"`
CodeExecution
any
`json:"codeExecution,omitempty"`
CodeExecution
any
`json:"codeExecution,omitempty"`
FunctionDeclarations
any
`json:"functionDeclarations,omitempty"`
FunctionDeclarations
any
`json:"functionDeclarations,omitempty"`
URLContext
any
`json:"urlContext,omitempty"`
}
}
type
GeminiChatGenerationConfig
struct
{
type
GeminiChatGenerationConfig
struct
{
...
...
main.go
View file @
78b9b21a
package
main
package
main
import
(
import
(
"bytes"
"embed"
"embed"
"fmt"
"fmt"
"log"
"log"
...
@@ -16,6 +17,7 @@ import (
...
@@ -16,6 +17,7 @@ import (
"one-api/setting/ratio_setting"
"one-api/setting/ratio_setting"
"os"
"os"
"strconv"
"strconv"
"strings"
"time"
"time"
"github.com/bytedance/gopkg/util/gopool"
"github.com/bytedance/gopkg/util/gopool"
...
@@ -147,6 +149,22 @@ func main() {
...
@@ -147,6 +149,22 @@ func main() {
})
})
server
.
Use
(
sessions
.
Sessions
(
"session"
,
store
))
server
.
Use
(
sessions
.
Sessions
(
"session"
,
store
))
analyticsInjectBuilder
:=
&
strings
.
Builder
{}
if
os
.
Getenv
(
"UMAMI_WEBSITE_ID"
)
!=
""
{
umamiSiteID
:=
os
.
Getenv
(
"UMAMI_WEBSITE_ID"
)
umamiScriptURL
:=
os
.
Getenv
(
"UMAMI_SCRIPT_URL"
)
if
umamiScriptURL
==
""
{
umamiScriptURL
=
"https://analytics.umami.is/script.js"
}
analyticsInjectBuilder
.
WriteString
(
"<script defer src=
\"
"
)
analyticsInjectBuilder
.
WriteString
(
umamiScriptURL
)
analyticsInjectBuilder
.
WriteString
(
"
\"
data-website-id=
\"
"
)
analyticsInjectBuilder
.
WriteString
(
umamiSiteID
)
analyticsInjectBuilder
.
WriteString
(
"
\"
></script>"
)
}
analyticsInject
:=
analyticsInjectBuilder
.
String
()
indexPage
=
bytes
.
ReplaceAll
(
indexPage
,
[]
byte
(
"<analytics></analytics>
\n
"
),
[]
byte
(
analyticsInject
))
router
.
SetRouter
(
server
,
buildFS
,
indexPage
)
router
.
SetRouter
(
server
,
buildFS
,
indexPage
)
var
port
=
os
.
Getenv
(
"PORT"
)
var
port
=
os
.
Getenv
(
"PORT"
)
if
port
==
""
{
if
port
==
""
{
...
...
model/option.go
View file @
78b9b21a
...
@@ -82,6 +82,7 @@ func InitOptionMap() {
...
@@ -82,6 +82,7 @@ func InitOptionMap() {
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
common
.
OptionMap
[
"StripePriceId"
]
=
setting
.
StripePriceId
common
.
OptionMap
[
"StripePriceId"
]
=
setting
.
StripePriceId
common
.
OptionMap
[
"StripeUnitPrice"
]
=
strconv
.
FormatFloat
(
setting
.
StripeUnitPrice
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"StripeUnitPrice"
]
=
strconv
.
FormatFloat
(
setting
.
StripeUnitPrice
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"StripePromotionCodesEnabled"
]
=
strconv
.
FormatBool
(
setting
.
StripePromotionCodesEnabled
)
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
...
@@ -330,6 +331,8 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -330,6 +331,8 @@ func updateOptionMap(key string, value string) (err error) {
setting
.
StripeUnitPrice
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
setting
.
StripeUnitPrice
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"StripeMinTopUp"
:
case
"StripeMinTopUp"
:
setting
.
StripeMinTopUp
,
_
=
strconv
.
Atoi
(
value
)
setting
.
StripeMinTopUp
,
_
=
strconv
.
Atoi
(
value
)
case
"StripePromotionCodesEnabled"
:
setting
.
StripePromotionCodesEnabled
=
value
==
"true"
case
"TopupGroupRatio"
:
case
"TopupGroupRatio"
:
err
=
common
.
UpdateTopupGroupRatioByJSONString
(
value
)
err
=
common
.
UpdateTopupGroupRatioByJSONString
(
value
)
case
"GitHubClientId"
:
case
"GitHubClientId"
:
...
...
relay/channel/gemini/relay-gemini.go
View file @
78b9b21a
...
@@ -245,6 +245,7 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
...
@@ -245,6 +245,7 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
functions
:=
make
([]
dto
.
FunctionRequest
,
0
,
len
(
textRequest
.
Tools
))
functions
:=
make
([]
dto
.
FunctionRequest
,
0
,
len
(
textRequest
.
Tools
))
googleSearch
:=
false
googleSearch
:=
false
codeExecution
:=
false
codeExecution
:=
false
urlContext
:=
false
for
_
,
tool
:=
range
textRequest
.
Tools
{
for
_
,
tool
:=
range
textRequest
.
Tools
{
if
tool
.
Function
.
Name
==
"googleSearch"
{
if
tool
.
Function
.
Name
==
"googleSearch"
{
googleSearch
=
true
googleSearch
=
true
...
@@ -254,6 +255,10 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
...
@@ -254,6 +255,10 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
codeExecution
=
true
codeExecution
=
true
continue
continue
}
}
if
tool
.
Function
.
Name
==
"urlContext"
{
urlContext
=
true
continue
}
if
tool
.
Function
.
Parameters
!=
nil
{
if
tool
.
Function
.
Parameters
!=
nil
{
params
,
ok
:=
tool
.
Function
.
Parameters
.
(
map
[
string
]
interface
{})
params
,
ok
:=
tool
.
Function
.
Parameters
.
(
map
[
string
]
interface
{})
...
@@ -281,6 +286,11 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
...
@@ -281,6 +286,11 @@ func CovertGemini2OpenAI(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
GoogleSearch
:
make
(
map
[
string
]
string
),
GoogleSearch
:
make
(
map
[
string
]
string
),
})
})
}
}
if
urlContext
{
geminiTools
=
append
(
geminiTools
,
dto
.
GeminiChatTool
{
URLContext
:
make
(
map
[
string
]
string
),
})
}
if
len
(
functions
)
>
0
{
if
len
(
functions
)
>
0
{
geminiTools
=
append
(
geminiTools
,
dto
.
GeminiChatTool
{
geminiTools
=
append
(
geminiTools
,
dto
.
GeminiChatTool
{
FunctionDeclarations
:
functions
,
FunctionDeclarations
:
functions
,
...
...
setting/payment_stripe.go
View file @
78b9b21a
...
@@ -5,3 +5,4 @@ var StripeWebhookSecret = ""
...
@@ -5,3 +5,4 @@ var StripeWebhookSecret = ""
var
StripePriceId
=
""
var
StripePriceId
=
""
var
StripeUnitPrice
=
8.0
var
StripeUnitPrice
=
8.0
var
StripeMinTopUp
=
1
var
StripeMinTopUp
=
1
var
StripePromotionCodesEnabled
=
false
web/index.html
View file @
78b9b21a
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
content=
"OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用"
content=
"OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用"
/>
/>
<title>
New API
</title>
<title>
New API
</title>
<analytics></analytics>
</head>
</head>
<body>
<body>
...
...
web/src/components/settings/PaymentSetting.jsx
View file @
78b9b21a
...
@@ -45,6 +45,7 @@ const PaymentSetting = () => {
...
@@ -45,6 +45,7 @@ const PaymentSetting = () => {
StripePriceId
:
''
,
StripePriceId
:
''
,
StripeUnitPrice
:
8.0
,
StripeUnitPrice
:
8.0
,
StripeMinTopUp
:
1
,
StripeMinTopUp
:
1
,
StripePromotionCodesEnabled
:
false
,
});
});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
...
...
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
78b9b21a
...
@@ -455,6 +455,14 @@ const EditChannelModal = (props) => {
...
@@ -455,6 +455,14 @@ const EditChannelModal = (props) => {
data
.
is_enterprise_account
=
false
;
data
.
is_enterprise_account
=
false
;
}
}
if
(
data
.
type
===
45
&&
(
!
data
.
base_url
||
(
typeof
data
.
base_url
===
'string'
&&
data
.
base_url
.
trim
()
===
''
))
)
{
data
.
base_url
=
'https://ark.cn-beijing.volces.com'
;
}
setInputs
(
data
);
setInputs
(
data
);
if
(
formApiRef
.
current
)
{
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValues
(
data
);
formApiRef
.
current
.
setValues
(
data
);
...
@@ -837,7 +845,9 @@ const EditChannelModal = (props) => {
...
@@ -837,7 +845,9 @@ const EditChannelModal = (props) => {
delete
localInputs
.
key
;
delete
localInputs
.
key
;
}
}
}
else
{
}
else
{
localInputs
.
key
=
batch
?
JSON
.
stringify
(
keys
)
:
JSON
.
stringify
(
keys
[
0
]);
localInputs
.
key
=
batch
?
JSON
.
stringify
(
keys
)
:
JSON
.
stringify
(
keys
[
0
]);
}
}
}
}
}
}
...
@@ -954,6 +964,56 @@ const EditChannelModal = (props) => {
...
@@ -954,6 +964,56 @@ const EditChannelModal = (props) => {
}
}
};
};
// 密钥去重函数
const
deduplicateKeys
=
()
=>
{
const
currentKey
=
formApiRef
.
current
?.
getValue
(
'key'
)
||
inputs
.
key
||
''
;
if
(
!
currentKey
.
trim
())
{
showInfo
(
t
(
'请先输入密钥'
));
return
;
}
// 按行分割密钥
const
keyLines
=
currentKey
.
split
(
'\n'
);
const
beforeCount
=
keyLines
.
length
;
// 使用哈希表去重,保持原有顺序
const
keySet
=
new
Set
();
const
deduplicatedKeys
=
[];
keyLines
.
forEach
((
line
)
=>
{
const
trimmedLine
=
line
.
trim
();
if
(
trimmedLine
&&
!
keySet
.
has
(
trimmedLine
))
{
keySet
.
add
(
trimmedLine
);
deduplicatedKeys
.
push
(
trimmedLine
);
}
});
const
afterCount
=
deduplicatedKeys
.
length
;
const
deduplicatedKeyText
=
deduplicatedKeys
.
join
(
'\n'
);
// 更新表单和状态
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'key'
,
deduplicatedKeyText
);
}
handleInputChange
(
'key'
,
deduplicatedKeyText
);
// 显示去重结果
const
message
=
t
(
'去重完成:去重前 {{before}} 个密钥,去重后 {{after}} 个密钥'
,
{
before
:
beforeCount
,
after
:
afterCount
,
},
);
if
(
beforeCount
===
afterCount
)
{
showInfo
(
t
(
'未发现重复密钥'
));
}
else
{
showSuccess
(
message
);
}
};
const
addCustomModels
=
()
=>
{
const
addCustomModels
=
()
=>
{
if
(
customModel
.
trim
()
===
''
)
return
;
if
(
customModel
.
trim
()
===
''
)
return
;
const
modelArray
=
customModel
.
split
(
','
).
map
((
model
)
=>
model
.
trim
());
const
modelArray
=
customModel
.
split
(
','
).
map
((
model
)
=>
model
.
trim
());
...
@@ -1049,24 +1109,41 @@ const EditChannelModal = (props) => {
...
@@ -1049,24 +1109,41 @@ const EditChannelModal = (props) => {
</
Checkbox
>
</
Checkbox
>
)
}
)
}
{
batch
&&
(
{
batch
&&
(
<
Checkbox
<>
disabled=
{
isEdit
}
<
Checkbox
checked=
{
multiToSingle
}
disabled=
{
isEdit
}
onChange=
{
()
=>
{
checked=
{
multiToSingle
}
setMultiToSingle
((
prev
)
=>
!
prev
);
onChange=
{
()
=>
{
setInputs
((
prev
)
=>
{
setMultiToSingle
((
prev
)
=>
{
const
newInputs
=
{
...
prev
};
const
nextValue
=
!
prev
;
if
(
!
multiToSingle
)
{
setInputs
((
prevInputs
)
=>
{
newInputs
.
multi_key_mode
=
multiKeyMode
;
const
newInputs
=
{
...
prevInputs
};
}
else
{
if
(
nextValue
)
{
delete
newInputs
.
multi_key_mode
;
newInputs
.
multi_key_mode
=
multiKeyMode
;
}
}
else
{
return
newInputs
;
delete
newInputs
.
multi_key_mode
;
});
}
}
}
return
newInputs
;
>
});
{
t
(
'密钥聚合模式'
)
}
return
nextValue
;
</
Checkbox
>
});
}
}
>
{
t
(
'密钥聚合模式'
)
}
</
Checkbox
>
{
inputs
.
type
!==
41
&&
(
<
Button
size=
'small'
type=
'tertiary'
theme=
'outline'
onClick=
{
deduplicateKeys
}
style=
{
{
textDecoration
:
'underline'
}
}
>
{
t
(
'密钥去重'
)
}
</
Button
>
)
}
</>
)
}
)
}
</
Space
>
</
Space
>
)
:
null
;
)
:
null
;
...
@@ -1268,7 +1345,10 @@ const EditChannelModal = (props) => {
...
@@ -1268,7 +1345,10 @@ const EditChannelModal = (props) => {
value=
{
inputs
.
vertex_key_type
||
'json'
}
value=
{
inputs
.
vertex_key_type
||
'json'
}
onChange=
{
(
value
)
=>
{
onChange=
{
(
value
)
=>
{
// 更新设置中的 vertex_key_type
// 更新设置中的 vertex_key_type
handleChannelOtherSettingsChange
(
'vertex_key_type'
,
value
);
handleChannelOtherSettingsChange
(
'vertex_key_type'
,
value
,
);
// 切换为 api_key 时,关闭批量与手动/文件切换,并清理已选文件
// 切换为 api_key 时,关闭批量与手动/文件切换,并清理已选文件
if
(
value
===
'api_key'
)
{
if
(
value
===
'api_key'
)
{
setBatch
(
false
);
setBatch
(
false
);
...
@@ -1288,7 +1368,8 @@ const EditChannelModal = (props) => {
...
@@ -1288,7 +1368,8 @@ const EditChannelModal = (props) => {
/>
/>
)
}
)
}
{
batch
?
(
{
batch
?
(
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
<
Form
.
Upload
<
Form
.
Upload
field=
'vertex_files'
field=
'vertex_files'
label=
{
t
(
'密钥文件 (.json)'
)
}
label=
{
t
(
'密钥文件 (.json)'
)
}
...
@@ -1324,7 +1405,7 @@ const EditChannelModal = (props) => {
...
@@ -1324,7 +1405,7 @@ const EditChannelModal = (props) => {
autoComplete=
'new-password'
autoComplete=
'new-password'
onChange=
{
(
value
)
=>
handleInputChange
(
'key'
,
value
)
}
onChange=
{
(
value
)
=>
handleInputChange
(
'key'
,
value
)
}
extraText=
{
extraText=
{
<
div
className=
'flex items-center gap-2'
>
<
div
className=
'flex items-center gap-2
flex-wrap
'
>
{
isEdit
&&
{
isEdit
&&
isMultiKeyChannel
&&
isMultiKeyChannel
&&
keyMode
===
'append'
&&
(
keyMode
===
'append'
&&
(
...
@@ -1352,7 +1433,8 @@ const EditChannelModal = (props) => {
...
@@ -1352,7 +1433,8 @@ const EditChannelModal = (props) => {
)
)
)
:
(
)
:
(
<>
<>
{
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
{
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
<>
<>
{
!
batch
&&
(
{
!
batch
&&
(
<
div
className=
'flex items-center justify-between mb-3'
>
<
div
className=
'flex items-center justify-between mb-3'
>
...
...
web/src/components/table/task-logs/modals/ContentModal.jsx
View file @
78b9b21a
...
@@ -17,8 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,8 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
React
from
'react'
;
import
React
,
{
useState
,
useEffect
}
from
'react'
;
import
{
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
Modal
,
Button
,
Typography
,
Spin
}
from
'@douyinfe/semi-ui'
;
import
{
IconExternalOpen
,
IconCopy
}
from
'@douyinfe/semi-icons'
;
const
{
Text
}
=
Typography
;
const
ContentModal
=
({
const
ContentModal
=
({
isModalOpen
,
isModalOpen
,
...
@@ -26,17 +29,120 @@ const ContentModal = ({
...
@@ -26,17 +29,120 @@ const ContentModal = ({
modalContent
,
modalContent
,
isVideo
,
isVideo
,
})
=>
{
})
=>
{
const
[
videoError
,
setVideoError
]
=
useState
(
false
);
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
);
useEffect
(()
=>
{
if
(
isModalOpen
&&
isVideo
)
{
setVideoError
(
false
);
setIsLoading
(
true
);
}
},
[
isModalOpen
,
isVideo
]);
const
handleVideoError
=
()
=>
{
setVideoError
(
true
);
setIsLoading
(
false
);
};
const
handleVideoLoaded
=
()
=>
{
setIsLoading
(
false
);
};
const
handleCopyUrl
=
()
=>
{
navigator
.
clipboard
.
writeText
(
modalContent
);
};
const
handleOpenInNewTab
=
()
=>
{
window
.
open
(
modalContent
,
'_blank'
);
};
const
renderVideoContent
=
()
=>
{
if
(
videoError
)
{
return
(
<
div
style=
{
{
textAlign
:
'center'
,
padding
:
'40px'
}
}
>
<
Text
type=
"tertiary"
style=
{
{
display
:
'block'
,
marginBottom
:
'16px'
}
}
>
视频无法在当前浏览器中播放,这可能是由于:
</
Text
>
<
Text
type=
"tertiary"
style=
{
{
display
:
'block'
,
marginBottom
:
'8px'
,
fontSize
:
'12px'
}
}
>
• 视频服务商的跨域限制
</
Text
>
<
Text
type=
"tertiary"
style=
{
{
display
:
'block'
,
marginBottom
:
'8px'
,
fontSize
:
'12px'
}
}
>
• 需要特定的请求头或认证
</
Text
>
<
Text
type=
"tertiary"
style=
{
{
display
:
'block'
,
marginBottom
:
'16px'
,
fontSize
:
'12px'
}
}
>
• 防盗链保护机制
</
Text
>
<
div
style=
{
{
marginTop
:
'20px'
}
}
>
<
Button
icon=
{
<
IconExternalOpen
/>
}
onClick=
{
handleOpenInNewTab
}
style=
{
{
marginRight
:
'8px'
}
}
>
在新标签页中打开
</
Button
>
<
Button
icon=
{
<
IconCopy
/>
}
onClick=
{
handleCopyUrl
}
>
复制链接
</
Button
>
</
div
>
<
div
style=
{
{
marginTop
:
'16px'
,
padding
:
'8px'
,
backgroundColor
:
'#f8f9fa'
,
borderRadius
:
'4px'
}
}
>
<
Text
type=
"tertiary"
style=
{
{
fontSize
:
'10px'
,
wordBreak
:
'break-all'
}
}
>
{
modalContent
}
</
Text
>
</
div
>
</
div
>
);
}
return
(
<
div
style=
{
{
position
:
'relative'
}
}
>
{
isLoading
&&
(
<
div
style=
{
{
position
:
'absolute'
,
top
:
'50%'
,
left
:
'50%'
,
transform
:
'translate(-50%, -50%)'
,
zIndex
:
10
}
}
>
<
Spin
size=
"large"
/>
</
div
>
)
}
<
video
src=
{
modalContent
}
controls
style=
{
{
width
:
'100%'
}
}
autoPlay
crossOrigin=
"anonymous"
onError=
{
handleVideoError
}
onLoadedData=
{
handleVideoLoaded
}
onLoadStart=
{
()
=>
setIsLoading
(
true
)
}
/>
</
div
>
);
};
return
(
return
(
<
Modal
<
Modal
visible=
{
isModalOpen
}
visible=
{
isModalOpen
}
onOk=
{
()
=>
setIsModalOpen
(
false
)
}
onOk=
{
()
=>
setIsModalOpen
(
false
)
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
closable=
{
null
}
closable=
{
null
}
bodyStyle=
{
{
height
:
'400px'
,
overflow
:
'auto'
}
}
bodyStyle=
{
{
height
:
isVideo
?
'450px'
:
'400px'
,
overflow
:
'auto'
,
padding
:
isVideo
&&
videoError
?
'0'
:
'24px'
}
}
width=
{
800
}
width=
{
800
}
>
>
{
isVideo
?
(
{
isVideo
?
(
<
video
src=
{
modalContent
}
controls
style=
{
{
width
:
'100%'
}
}
autoPlay
/>
renderVideoContent
()
)
:
(
)
:
(
<
p
style=
{
{
whiteSpace
:
'pre-line'
}
}
>
{
modalContent
}
</
p
>
<
p
style=
{
{
whiteSpace
:
'pre-line'
}
}
>
{
modalContent
}
</
p
>
)
}
)
}
...
...
web/src/i18n/locales/en.json
View file @
78b9b21a
...
@@ -837,6 +837,7 @@
...
@@ -837,6 +837,7 @@
"确定要充值 $"
:
"Confirm to top up $"
,
"确定要充值 $"
:
"Confirm to top up $"
,
"微信/支付宝 实付金额:"
:
"WeChat/Alipay actual payment amount:"
,
"微信/支付宝 实付金额:"
:
"WeChat/Alipay actual payment amount:"
,
"Stripe 实付金额:"
:
"Stripe actual payment amount:"
,
"Stripe 实付金额:"
:
"Stripe actual payment amount:"
,
"允许在 Stripe 支付中输入促销码"
:
"Allow entering promotion codes during Stripe checkout"
,
"支付中..."
:
"Paying"
,
"支付中..."
:
"Paying"
,
"支付宝"
:
"Alipay"
,
"支付宝"
:
"Alipay"
,
"收益统计"
:
"Income statistics"
,
"收益统计"
:
"Income statistics"
,
...
...
web/src/i18n/locales/zh.json
View file @
78b9b21a
...
@@ -32,5 +32,6 @@
...
@@ -32,5 +32,6 @@
"端口配置详细说明"
:
"限制外部请求只能访问指定端口。支持单个端口(80, 443)或端口范围(8000-8999)。空列表允许所有端口。默认包含常用Web端口。"
,
"端口配置详细说明"
:
"限制外部请求只能访问指定端口。支持单个端口(80, 443)或端口范围(8000-8999)。空列表允许所有端口。默认包含常用Web端口。"
,
"输入端口后回车,如:80 或 8000-8999"
:
"输入端口后回车,如:80 或 8000-8999"
,
"输入端口后回车,如:80 或 8000-8999"
:
"输入端口后回车,如:80 或 8000-8999"
,
"更新SSRF防护设置"
:
"更新SSRF防护设置"
,
"更新SSRF防护设置"
:
"更新SSRF防护设置"
,
"域名IP过滤详细说明"
:
"⚠️此功能为实验性选项,域名可能解析到多个 IPv4/IPv6 地址,若开启,请确保 IP 过滤列表覆盖这些地址,否则可能导致访问失败。"
"域名IP过滤详细说明"
:
"⚠️此功能为实验性选项,域名可能解析到多个 IPv4/IPv6 地址,若开启,请确保 IP 过滤列表覆盖这些地址,否则可能导致访问失败。"
,
"允许在 Stripe 支付中输入促销码"
:
"允许在 Stripe 支付中输入促销码"
}
}
web/src/pages/Setting/Payment/SettingsPaymentGatewayStripe.jsx
View file @
78b9b21a
...
@@ -45,6 +45,7 @@ export default function SettingsPaymentGateway(props) {
...
@@ -45,6 +45,7 @@ export default function SettingsPaymentGateway(props) {
StripePriceId
:
''
,
StripePriceId
:
''
,
StripeUnitPrice
:
8.0
,
StripeUnitPrice
:
8.0
,
StripeMinTopUp
:
1
,
StripeMinTopUp
:
1
,
StripePromotionCodesEnabled
:
false
,
});
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
formApiRef
=
useRef
(
null
);
const
formApiRef
=
useRef
(
null
);
...
@@ -63,6 +64,10 @@ export default function SettingsPaymentGateway(props) {
...
@@ -63,6 +64,10 @@ export default function SettingsPaymentGateway(props) {
props
.
options
.
StripeMinTopUp
!==
undefined
props
.
options
.
StripeMinTopUp
!==
undefined
?
parseFloat
(
props
.
options
.
StripeMinTopUp
)
?
parseFloat
(
props
.
options
.
StripeMinTopUp
)
:
1
,
:
1
,
StripePromotionCodesEnabled
:
props
.
options
.
StripePromotionCodesEnabled
!==
undefined
?
props
.
options
.
StripePromotionCodesEnabled
:
false
,
};
};
setInputs
(
currentInputs
);
setInputs
(
currentInputs
);
setOriginInputs
({
...
currentInputs
});
setOriginInputs
({
...
currentInputs
});
...
@@ -114,6 +119,16 @@ export default function SettingsPaymentGateway(props) {
...
@@ -114,6 +119,16 @@ export default function SettingsPaymentGateway(props) {
value
:
inputs
.
StripeMinTopUp
.
toString
(),
value
:
inputs
.
StripeMinTopUp
.
toString
(),
});
});
}
}
if
(
originInputs
[
'StripePromotionCodesEnabled'
]
!==
inputs
.
StripePromotionCodesEnabled
&&
inputs
.
StripePromotionCodesEnabled
!==
undefined
)
{
options
.
push
({
key
:
'StripePromotionCodesEnabled'
,
value
:
inputs
.
StripePromotionCodesEnabled
?
'true'
:
'false'
,
});
}
// 发送请求
// 发送请求
const
requestQueue
=
options
.
map
((
opt
)
=>
const
requestQueue
=
options
.
map
((
opt
)
=>
...
@@ -225,6 +240,15 @@ export default function SettingsPaymentGateway(props) {
...
@@ -225,6 +240,15 @@ export default function SettingsPaymentGateway(props) {
placeholder=
{
t
(
'例如:2,就是最低充值2$'
)
}
placeholder=
{
t
(
'例如:2,就是最低充值2$'
)
}
/>
/>
</
Col
>
</
Col
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
8
}
lg=
{
8
}
xl=
{
8
}
>
<
Form
.
Switch
field=
'StripePromotionCodesEnabled'
size=
'default'
checkedText=
'|'
uncheckedText=
'〇'
label=
{
t
(
'允许在 Stripe 支付中输入促销码'
)
}
/>
</
Col
>
</
Row
>
</
Row
>
<
Button
onClick=
{
submitStripeSetting
}
>
{
t
(
'更新 Stripe 设置'
)
}
</
Button
>
<
Button
onClick=
{
submitStripeSetting
}
>
{
t
(
'更新 Stripe 设置'
)
}
</
Button
>
</
Form
.
Section
>
</
Form
.
Section
>
...
...
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