|
|
@@ -1,385 +1,422 @@
|
|
|
+/*
|
|
|
+Copyright (C) 2025 QuantumNous
|
|
|
+
|
|
|
+This program is free software: you can redistribute it and/or modify
|
|
|
+it under the terms of the GNU Affero General Public License as
|
|
|
+published by the Free Software Foundation, either version 3 of the
|
|
|
+License, or (at your option) any later version.
|
|
|
+
|
|
|
+This program is distributed in the hope that it will be useful,
|
|
|
+but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+GNU Affero General Public License for more details.
|
|
|
+
|
|
|
+You should have received a copy of the GNU Affero General Public License
|
|
|
+along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
+
|
|
|
+For commercial licensing, please contact [email protected]
|
|
|
+*/
|
|
|
import React, { useEffect, useState, useRef } from 'react';
|
|
|
import {
|
|
|
- Banner,
|
|
|
- Button,
|
|
|
- Form,
|
|
|
- Row,
|
|
|
- Col,
|
|
|
- Typography,
|
|
|
- Spin,
|
|
|
- Table,
|
|
|
- Modal,
|
|
|
- Input,
|
|
|
- InputNumber,
|
|
|
- Select,
|
|
|
+ Banner,
|
|
|
+ Button,
|
|
|
+ Form,
|
|
|
+ Row,
|
|
|
+ Col,
|
|
|
+ Typography,
|
|
|
+ Spin,
|
|
|
+ Table,
|
|
|
+ Modal,
|
|
|
+ Input,
|
|
|
+ InputNumber,
|
|
|
+ Select,
|
|
|
} from '@douyinfe/semi-ui';
|
|
|
const { Text } = Typography;
|
|
|
-import {
|
|
|
- API,
|
|
|
- showError,
|
|
|
- showSuccess,
|
|
|
-} from '../../../helpers';
|
|
|
+import { API, showError, showSuccess } from '../../../helpers';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
import { Plus, Trash2 } from 'lucide-react';
|
|
|
|
|
|
export default function SettingsPaymentGatewayCreem(props) {
|
|
|
- const { t } = useTranslation();
|
|
|
- const [loading, setLoading] = useState(false);
|
|
|
- const [inputs, setInputs] = useState({
|
|
|
- CreemApiKey: '',
|
|
|
- CreemWebhookSecret: '',
|
|
|
- CreemProducts: '[]',
|
|
|
- CreemTestMode: false,
|
|
|
- });
|
|
|
- const [originInputs, setOriginInputs] = useState({});
|
|
|
- const [products, setProducts] = useState([]);
|
|
|
- const [showProductModal, setShowProductModal] = useState(false);
|
|
|
- const [editingProduct, setEditingProduct] = useState(null);
|
|
|
- const [productForm, setProductForm] = useState({
|
|
|
- name: '',
|
|
|
- productId: '',
|
|
|
- price: 0,
|
|
|
- quota: 0,
|
|
|
- currency: 'USD',
|
|
|
- });
|
|
|
- const formApiRef = useRef(null);
|
|
|
+ const { t } = useTranslation();
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [inputs, setInputs] = useState({
|
|
|
+ CreemApiKey: '',
|
|
|
+ CreemWebhookSecret: '',
|
|
|
+ CreemProducts: '[]',
|
|
|
+ CreemTestMode: false,
|
|
|
+ });
|
|
|
+ const [originInputs, setOriginInputs] = useState({});
|
|
|
+ const [products, setProducts] = useState([]);
|
|
|
+ const [showProductModal, setShowProductModal] = useState(false);
|
|
|
+ const [editingProduct, setEditingProduct] = useState(null);
|
|
|
+ const [productForm, setProductForm] = useState({
|
|
|
+ name: '',
|
|
|
+ productId: '',
|
|
|
+ price: 0,
|
|
|
+ quota: 0,
|
|
|
+ currency: 'USD',
|
|
|
+ });
|
|
|
+ const formApiRef = useRef(null);
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- if (props.options && formApiRef.current) {
|
|
|
- const currentInputs = {
|
|
|
- CreemApiKey: props.options.CreemApiKey || '',
|
|
|
- CreemWebhookSecret: props.options.CreemWebhookSecret || '',
|
|
|
- CreemProducts: props.options.CreemProducts || '[]',
|
|
|
- CreemTestMode: props.options.CreemTestMode === 'true',
|
|
|
- };
|
|
|
- setInputs(currentInputs);
|
|
|
- setOriginInputs({ ...currentInputs });
|
|
|
- formApiRef.current.setValues(currentInputs);
|
|
|
+ useEffect(() => {
|
|
|
+ if (props.options && formApiRef.current) {
|
|
|
+ const currentInputs = {
|
|
|
+ CreemApiKey: props.options.CreemApiKey || '',
|
|
|
+ CreemWebhookSecret: props.options.CreemWebhookSecret || '',
|
|
|
+ CreemProducts: props.options.CreemProducts || '[]',
|
|
|
+ CreemTestMode: props.options.CreemTestMode === 'true',
|
|
|
+ };
|
|
|
+ setInputs(currentInputs);
|
|
|
+ setOriginInputs({ ...currentInputs });
|
|
|
+ formApiRef.current.setValues(currentInputs);
|
|
|
|
|
|
- // Parse products
|
|
|
- try {
|
|
|
- const parsedProducts = JSON.parse(currentInputs.CreemProducts);
|
|
|
- setProducts(parsedProducts);
|
|
|
- } catch (e) {
|
|
|
- setProducts([]);
|
|
|
- }
|
|
|
- }
|
|
|
- }, [props.options]);
|
|
|
+ // Parse products
|
|
|
+ try {
|
|
|
+ const parsedProducts = JSON.parse(currentInputs.CreemProducts);
|
|
|
+ setProducts(parsedProducts);
|
|
|
+ } catch (e) {
|
|
|
+ setProducts([]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [props.options]);
|
|
|
|
|
|
- const handleFormChange = (values) => {
|
|
|
- setInputs(values);
|
|
|
- };
|
|
|
+ const handleFormChange = (values) => {
|
|
|
+ setInputs(values);
|
|
|
+ };
|
|
|
|
|
|
- const submitCreemSetting = async () => {
|
|
|
- setLoading(true);
|
|
|
- try {
|
|
|
- const options = [];
|
|
|
+ const submitCreemSetting = async () => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const options = [];
|
|
|
|
|
|
- if (inputs.CreemApiKey && inputs.CreemApiKey !== '') {
|
|
|
- options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey });
|
|
|
- }
|
|
|
+ if (inputs.CreemApiKey && inputs.CreemApiKey !== '') {
|
|
|
+ options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey });
|
|
|
+ }
|
|
|
|
|
|
- if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') {
|
|
|
- options.push({ key: 'CreemWebhookSecret', value: inputs.CreemWebhookSecret });
|
|
|
- }
|
|
|
+ if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') {
|
|
|
+ options.push({
|
|
|
+ key: 'CreemWebhookSecret',
|
|
|
+ value: inputs.CreemWebhookSecret,
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // Save test mode setting
|
|
|
- options.push({ key: 'CreemTestMode', value: inputs.CreemTestMode ? 'true' : 'false' });
|
|
|
+ // Save test mode setting
|
|
|
+ options.push({
|
|
|
+ key: 'CreemTestMode',
|
|
|
+ value: inputs.CreemTestMode ? 'true' : 'false',
|
|
|
+ });
|
|
|
|
|
|
- // Save products as JSON string
|
|
|
- options.push({ key: 'CreemProducts', value: JSON.stringify(products) });
|
|
|
+ // Save products as JSON string
|
|
|
+ options.push({ key: 'CreemProducts', value: JSON.stringify(products) });
|
|
|
|
|
|
- // 发送请求
|
|
|
- const requestQueue = options.map(opt =>
|
|
|
- API.put('/api/option/', {
|
|
|
- key: opt.key,
|
|
|
- value: opt.value,
|
|
|
- })
|
|
|
- );
|
|
|
+ // 发送请求
|
|
|
+ const requestQueue = options.map((opt) =>
|
|
|
+ API.put('/api/option/', {
|
|
|
+ key: opt.key,
|
|
|
+ value: opt.value,
|
|
|
+ }),
|
|
|
+ );
|
|
|
|
|
|
- const results = await Promise.all(requestQueue);
|
|
|
+ const results = await Promise.all(requestQueue);
|
|
|
|
|
|
- // 检查所有请求是否成功
|
|
|
- const errorResults = results.filter(res => !res.data.success);
|
|
|
- if (errorResults.length > 0) {
|
|
|
- errorResults.forEach(res => {
|
|
|
- showError(res.data.message);
|
|
|
- });
|
|
|
- } else {
|
|
|
- showSuccess(t('更新成功'));
|
|
|
- // 更新本地存储的原始值
|
|
|
- setOriginInputs({ ...inputs });
|
|
|
- props.refresh?.();
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- showError(t('更新失败'));
|
|
|
- }
|
|
|
- setLoading(false);
|
|
|
- };
|
|
|
+ // 检查所有请求是否成功
|
|
|
+ const errorResults = results.filter((res) => !res.data.success);
|
|
|
+ if (errorResults.length > 0) {
|
|
|
+ errorResults.forEach((res) => {
|
|
|
+ showError(res.data.message);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ showSuccess(t('更新成功'));
|
|
|
+ // 更新本地存储的原始值
|
|
|
+ setOriginInputs({ ...inputs });
|
|
|
+ props.refresh?.();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ showError(t('更新失败'));
|
|
|
+ }
|
|
|
+ setLoading(false);
|
|
|
+ };
|
|
|
|
|
|
- const openProductModal = (product = null) => {
|
|
|
- if (product) {
|
|
|
- setEditingProduct(product);
|
|
|
- setProductForm({ ...product });
|
|
|
- } else {
|
|
|
- setEditingProduct(null);
|
|
|
- setProductForm({
|
|
|
- name: '',
|
|
|
- productId: '',
|
|
|
- price: 0,
|
|
|
- quota: 0,
|
|
|
- currency: 'USD',
|
|
|
- });
|
|
|
- }
|
|
|
- setShowProductModal(true);
|
|
|
- };
|
|
|
+ const openProductModal = (product = null) => {
|
|
|
+ if (product) {
|
|
|
+ setEditingProduct(product);
|
|
|
+ setProductForm({ ...product });
|
|
|
+ } else {
|
|
|
+ setEditingProduct(null);
|
|
|
+ setProductForm({
|
|
|
+ name: '',
|
|
|
+ productId: '',
|
|
|
+ price: 0,
|
|
|
+ quota: 0,
|
|
|
+ currency: 'USD',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setShowProductModal(true);
|
|
|
+ };
|
|
|
|
|
|
- const closeProductModal = () => {
|
|
|
- setShowProductModal(false);
|
|
|
- setEditingProduct(null);
|
|
|
- setProductForm({
|
|
|
- name: '',
|
|
|
- productId: '',
|
|
|
- price: 0,
|
|
|
- quota: 0,
|
|
|
- currency: 'USD',
|
|
|
- });
|
|
|
- };
|
|
|
+ const closeProductModal = () => {
|
|
|
+ setShowProductModal(false);
|
|
|
+ setEditingProduct(null);
|
|
|
+ setProductForm({
|
|
|
+ name: '',
|
|
|
+ productId: '',
|
|
|
+ price: 0,
|
|
|
+ quota: 0,
|
|
|
+ currency: 'USD',
|
|
|
+ });
|
|
|
+ };
|
|
|
|
|
|
- const saveProduct = () => {
|
|
|
- if (!productForm.name || !productForm.productId || productForm.price <= 0 || productForm.quota <= 0 || !productForm.currency) {
|
|
|
- showError(t('请填写完整的产品信息'));
|
|
|
- return;
|
|
|
- }
|
|
|
+ const saveProduct = () => {
|
|
|
+ if (
|
|
|
+ !productForm.name ||
|
|
|
+ !productForm.productId ||
|
|
|
+ productForm.price <= 0 ||
|
|
|
+ productForm.quota <= 0 ||
|
|
|
+ !productForm.currency
|
|
|
+ ) {
|
|
|
+ showError(t('请填写完整的产品信息'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- let newProducts = [...products];
|
|
|
- if (editingProduct) {
|
|
|
- // 编辑现有产品
|
|
|
- const index = newProducts.findIndex(p => p.productId === editingProduct.productId);
|
|
|
- if (index !== -1) {
|
|
|
- newProducts[index] = { ...productForm };
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 添加新产品
|
|
|
- if (newProducts.find(p => p.productId === productForm.productId)) {
|
|
|
- showError(t('产品ID已存在'));
|
|
|
- return;
|
|
|
- }
|
|
|
- newProducts.push({ ...productForm });
|
|
|
- }
|
|
|
+ let newProducts = [...products];
|
|
|
+ if (editingProduct) {
|
|
|
+ // 编辑现有产品
|
|
|
+ const index = newProducts.findIndex(
|
|
|
+ (p) => p.productId === editingProduct.productId,
|
|
|
+ );
|
|
|
+ if (index !== -1) {
|
|
|
+ newProducts[index] = { ...productForm };
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 添加新产品
|
|
|
+ if (newProducts.find((p) => p.productId === productForm.productId)) {
|
|
|
+ showError(t('产品ID已存在'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ newProducts.push({ ...productForm });
|
|
|
+ }
|
|
|
|
|
|
- setProducts(newProducts);
|
|
|
- closeProductModal();
|
|
|
- };
|
|
|
+ setProducts(newProducts);
|
|
|
+ closeProductModal();
|
|
|
+ };
|
|
|
|
|
|
- const deleteProduct = (productId) => {
|
|
|
- const newProducts = products.filter(p => p.productId !== productId);
|
|
|
- setProducts(newProducts);
|
|
|
- };
|
|
|
+ const deleteProduct = (productId) => {
|
|
|
+ const newProducts = products.filter((p) => p.productId !== productId);
|
|
|
+ setProducts(newProducts);
|
|
|
+ };
|
|
|
|
|
|
- const columns = [
|
|
|
- {
|
|
|
- title: t('产品名称'),
|
|
|
- dataIndex: 'name',
|
|
|
- key: 'name',
|
|
|
- },
|
|
|
- {
|
|
|
- title: t('产品ID'),
|
|
|
- dataIndex: 'productId',
|
|
|
- key: 'productId',
|
|
|
- },
|
|
|
- {
|
|
|
- title: t('展示价格'),
|
|
|
- dataIndex: 'price',
|
|
|
- key: 'price',
|
|
|
- render: (price, record) => `${record.currency === 'EUR' ? '€' : '$'}${price}`,
|
|
|
- },
|
|
|
- {
|
|
|
- title: t('充值额度'),
|
|
|
- dataIndex: 'quota',
|
|
|
- key: 'quota',
|
|
|
- },
|
|
|
- {
|
|
|
- title: t('操作'),
|
|
|
- key: 'action',
|
|
|
- render: (_, record) => (
|
|
|
- <div className='flex gap-2'>
|
|
|
- <Button
|
|
|
- type='tertiary'
|
|
|
- size='small'
|
|
|
- onClick={() => openProductModal(record)}
|
|
|
- >
|
|
|
- {t('编辑')}
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- type='danger'
|
|
|
- theme='borderless'
|
|
|
- size='small'
|
|
|
- icon={<Trash2 size={14} />}
|
|
|
- onClick={() => deleteProduct(record.productId)}
|
|
|
- />
|
|
|
- </div>
|
|
|
- ),
|
|
|
- },
|
|
|
- ];
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: t('产品名称'),
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: t('产品ID'),
|
|
|
+ dataIndex: 'productId',
|
|
|
+ key: 'productId',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: t('展示价格'),
|
|
|
+ dataIndex: 'price',
|
|
|
+ key: 'price',
|
|
|
+ render: (price, record) =>
|
|
|
+ `${record.currency === 'EUR' ? '€' : '$'}${price}`,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: t('充值额度'),
|
|
|
+ dataIndex: 'quota',
|
|
|
+ key: 'quota',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: t('操作'),
|
|
|
+ key: 'action',
|
|
|
+ render: (_, record) => (
|
|
|
+ <div className='flex gap-2'>
|
|
|
+ <Button
|
|
|
+ type='tertiary'
|
|
|
+ size='small'
|
|
|
+ onClick={() => openProductModal(record)}
|
|
|
+ >
|
|
|
+ {t('编辑')}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type='danger'
|
|
|
+ theme='borderless'
|
|
|
+ size='small'
|
|
|
+ icon={<Trash2 size={14} />}
|
|
|
+ onClick={() => deleteProduct(record.productId)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
|
|
|
- return (
|
|
|
- <Spin spinning={loading}>
|
|
|
- <Form
|
|
|
- initValues={inputs}
|
|
|
- onValueChange={handleFormChange}
|
|
|
- getFormApi={(api) => (formApiRef.current = api)}
|
|
|
- >
|
|
|
- <Form.Section text={t('Creem 设置')}>
|
|
|
- <Text>
|
|
|
- {t('Creem 介绍')}
|
|
|
- <a
|
|
|
- href='https://creem.io'
|
|
|
- target='_blank'
|
|
|
- rel='noreferrer'
|
|
|
- >Creem Official Site</a>
|
|
|
- <br />
|
|
|
- </Text>
|
|
|
- <Banner
|
|
|
- type='info'
|
|
|
- description={t('Creem Setting Tips')}
|
|
|
- />
|
|
|
+ return (
|
|
|
+ <Spin spinning={loading}>
|
|
|
+ <Form
|
|
|
+ initValues={inputs}
|
|
|
+ onValueChange={handleFormChange}
|
|
|
+ getFormApi={(api) => (formApiRef.current = api)}
|
|
|
+ >
|
|
|
+ <Form.Section text={t('Creem 设置')}>
|
|
|
+ <Text>
|
|
|
+ {t('Creem 介绍')}
|
|
|
+ <a href='https://creem.io' target='_blank' rel='noreferrer'>
|
|
|
+ Creem Official Site
|
|
|
+ </a>
|
|
|
+ <br />
|
|
|
+ </Text>
|
|
|
+ <Banner type='info' description={t('Creem Setting Tips')} />
|
|
|
|
|
|
- <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
|
|
|
- <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
- <Form.Input
|
|
|
- field='CreemApiKey'
|
|
|
- label={t('API 密钥')}
|
|
|
- placeholder={t('Creem API 密钥,敏感信息不显示')}
|
|
|
- type='password'
|
|
|
- />
|
|
|
- </Col>
|
|
|
- <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
- <Form.Input
|
|
|
- field='CreemWebhookSecret'
|
|
|
- label={t('Webhook 密钥')}
|
|
|
- placeholder={t('用于验证回调 new-api 的 webhook 请求的密钥,敏感信息不显示')}
|
|
|
- type='password'
|
|
|
- />
|
|
|
- </Col>
|
|
|
- <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
- <Form.Switch
|
|
|
- field='CreemTestMode'
|
|
|
- label={t('测试模式')}
|
|
|
- extraText={t('启用后将使用 Creem Test Mode')}
|
|
|
- />
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
+ <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}>
|
|
|
+ <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
+ <Form.Input
|
|
|
+ field='CreemApiKey'
|
|
|
+ label={t('API 密钥')}
|
|
|
+ placeholder={t('Creem API 密钥,敏感信息不显示')}
|
|
|
+ type='password'
|
|
|
+ />
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
+ <Form.Input
|
|
|
+ field='CreemWebhookSecret'
|
|
|
+ label={t('Webhook 密钥')}
|
|
|
+ placeholder={t(
|
|
|
+ '用于验证回调 new-api 的 webhook 请求的密钥,敏感信息不显示',
|
|
|
+ )}
|
|
|
+ type='password'
|
|
|
+ />
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
|
|
+ <Form.Switch
|
|
|
+ field='CreemTestMode'
|
|
|
+ label={t('测试模式')}
|
|
|
+ extraText={t('启用后将使用 Creem Test Mode')}
|
|
|
+ />
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
|
|
|
- <div style={{ marginTop: 24 }}>
|
|
|
- <div className='flex justify-between items-center mb-4'>
|
|
|
- <Text strong>{t('产品配置')}</Text>
|
|
|
- <Button
|
|
|
- type='primary'
|
|
|
- icon={<Plus size={16} />}
|
|
|
- onClick={() => openProductModal()}
|
|
|
- >
|
|
|
- {t('添加产品')}
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
+ <div style={{ marginTop: 24 }}>
|
|
|
+ <div className='flex justify-between items-center mb-4'>
|
|
|
+ <Text strong>{t('产品配置')}</Text>
|
|
|
+ <Button
|
|
|
+ type='primary'
|
|
|
+ icon={<Plus size={16} />}
|
|
|
+ onClick={() => openProductModal()}
|
|
|
+ >
|
|
|
+ {t('添加产品')}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
|
|
|
- <Table
|
|
|
- columns={columns}
|
|
|
- dataSource={products}
|
|
|
- pagination={false}
|
|
|
- empty={
|
|
|
- <div className='text-center py-8'>
|
|
|
- <Text type='tertiary'>{t('暂无产品配置')}</Text>
|
|
|
- </div>
|
|
|
- }
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <Table
|
|
|
+ columns={columns}
|
|
|
+ dataSource={products}
|
|
|
+ pagination={false}
|
|
|
+ empty={
|
|
|
+ <div className='text-center py-8'>
|
|
|
+ <Text type='tertiary'>{t('暂无产品配置')}</Text>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </div>
|
|
|
|
|
|
- <Button onClick={submitCreemSetting} style={{ marginTop: 16 }}>
|
|
|
- {t('更新 Creem 设置')}
|
|
|
- </Button>
|
|
|
- </Form.Section>
|
|
|
- </Form>
|
|
|
+ <Button onClick={submitCreemSetting} style={{ marginTop: 16 }}>
|
|
|
+ {t('更新 Creem 设置')}
|
|
|
+ </Button>
|
|
|
+ </Form.Section>
|
|
|
+ </Form>
|
|
|
|
|
|
- {/* 产品配置模态框 */}
|
|
|
- <Modal
|
|
|
- title={editingProduct ? t('编辑产品') : t('添加产品')}
|
|
|
- visible={showProductModal}
|
|
|
- onOk={saveProduct}
|
|
|
- onCancel={closeProductModal}
|
|
|
- maskClosable={false}
|
|
|
- size='small'
|
|
|
- centered
|
|
|
+ {/* 产品配置模态框 */}
|
|
|
+ <Modal
|
|
|
+ title={editingProduct ? t('编辑产品') : t('添加产品')}
|
|
|
+ visible={showProductModal}
|
|
|
+ onOk={saveProduct}
|
|
|
+ onCancel={closeProductModal}
|
|
|
+ maskClosable={false}
|
|
|
+ size='small'
|
|
|
+ centered
|
|
|
+ >
|
|
|
+ <div className='space-y-4'>
|
|
|
+ <div>
|
|
|
+ <Text strong className='block mb-2'>
|
|
|
+ {t('产品名称')}
|
|
|
+ </Text>
|
|
|
+ <Input
|
|
|
+ value={productForm.name}
|
|
|
+ onChange={(value) =>
|
|
|
+ setProductForm({ ...productForm, name: value })
|
|
|
+ }
|
|
|
+ placeholder={t('例如:基础套餐')}
|
|
|
+ size='large'
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text strong className='block mb-2'>
|
|
|
+ {t('产品ID')}
|
|
|
+ </Text>
|
|
|
+ <Input
|
|
|
+ value={productForm.productId}
|
|
|
+ onChange={(value) =>
|
|
|
+ setProductForm({ ...productForm, productId: value })
|
|
|
+ }
|
|
|
+ placeholder={t('例如:prod_6I8rBerHpPxyoiU9WK4kot')}
|
|
|
+ size='large'
|
|
|
+ disabled={!!editingProduct}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text strong className='block mb-2'>
|
|
|
+ {t('货币')}
|
|
|
+ </Text>
|
|
|
+ <Select
|
|
|
+ value={productForm.currency}
|
|
|
+ onChange={(value) =>
|
|
|
+ setProductForm({ ...productForm, currency: value })
|
|
|
+ }
|
|
|
+ size='large'
|
|
|
+ className='w-full'
|
|
|
>
|
|
|
- <div className='space-y-4'>
|
|
|
- <div>
|
|
|
- <Text strong className='block mb-2'>
|
|
|
- {t('产品名称')}
|
|
|
- </Text>
|
|
|
- <Input
|
|
|
- value={productForm.name}
|
|
|
- onChange={(value) => setProductForm({ ...productForm, name: value })}
|
|
|
- placeholder={t('例如:基础套餐')}
|
|
|
- size='large'
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <Text strong className='block mb-2'>
|
|
|
- {t('产品ID')}
|
|
|
- </Text>
|
|
|
- <Input
|
|
|
- value={productForm.productId}
|
|
|
- onChange={(value) => setProductForm({ ...productForm, productId: value })}
|
|
|
- placeholder={t('例如:prod_6I8rBerHpPxyoiU9WK4kot')}
|
|
|
- size='large'
|
|
|
- disabled={!!editingProduct}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <Text strong className='block mb-2'>
|
|
|
- {t('货币')}
|
|
|
- </Text>
|
|
|
- <Select
|
|
|
- value={productForm.currency}
|
|
|
- onChange={(value) => setProductForm({ ...productForm, currency: value })}
|
|
|
- size='large'
|
|
|
- className='w-full'
|
|
|
- >
|
|
|
- <Select.Option value='USD'>{t('USD (美元)')}</Select.Option>
|
|
|
- <Select.Option value='EUR'>{t('EUR (欧元)')}</Select.Option>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <Text strong className='block mb-2'>
|
|
|
- {t('价格')} ({productForm.currency === 'EUR' ? t('欧元') : t('美元')})
|
|
|
- </Text>
|
|
|
- <InputNumber
|
|
|
- value={productForm.price}
|
|
|
- onChange={(value) => setProductForm({ ...productForm, price: value })}
|
|
|
- placeholder={t('例如:4.99')}
|
|
|
- min={0.01}
|
|
|
- precision={2}
|
|
|
- size='large'
|
|
|
- className='w-full'
|
|
|
- defaultValue={4.49}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <Text strong className='block mb-2'>
|
|
|
- {t('充值额度')}
|
|
|
- </Text>
|
|
|
- <InputNumber
|
|
|
- value={productForm.quota}
|
|
|
- onChange={(value) => setProductForm({ ...productForm, quota: value })}
|
|
|
- placeholder={t('例如:100000')}
|
|
|
- min={1}
|
|
|
- precision={0}
|
|
|
- size='large'
|
|
|
- className='w-full'
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </Modal>
|
|
|
- </Spin>
|
|
|
- );
|
|
|
-}
|
|
|
+ <Select.Option value='USD'>{t('USD (美元)')}</Select.Option>
|
|
|
+ <Select.Option value='EUR'>{t('EUR (欧元)')}</Select.Option>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text strong className='block mb-2'>
|
|
|
+ {t('价格')} (
|
|
|
+ {productForm.currency === 'EUR' ? t('欧元') : t('美元')})
|
|
|
+ </Text>
|
|
|
+ <InputNumber
|
|
|
+ value={productForm.price}
|
|
|
+ onChange={(value) =>
|
|
|
+ setProductForm({ ...productForm, price: value })
|
|
|
+ }
|
|
|
+ placeholder={t('例如:4.99')}
|
|
|
+ min={0.01}
|
|
|
+ precision={2}
|
|
|
+ size='large'
|
|
|
+ className='w-full'
|
|
|
+ defaultValue={4.49}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <Text strong className='block mb-2'>
|
|
|
+ {t('充值额度')}
|
|
|
+ </Text>
|
|
|
+ <InputNumber
|
|
|
+ value={productForm.quota}
|
|
|
+ onChange={(value) =>
|
|
|
+ setProductForm({ ...productForm, quota: value })
|
|
|
+ }
|
|
|
+ placeholder={t('例如:100000')}
|
|
|
+ min={1}
|
|
|
+ precision={0}
|
|
|
+ size='large'
|
|
|
+ className='w-full'
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Modal>
|
|
|
+ </Spin>
|
|
|
+ );
|
|
|
+}
|