You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

197 lines
5.9 KiB

  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useState, useRef, useEffect } from 'react';
  16. import { Modal, Form, Col, Row } from '@douyinfe/semi-ui';
  17. import { API, showError, showSuccess } from '../../../../helpers';
  18. import { Typography } from '@douyinfe/semi-ui';
  19. import { IconLink } from '@douyinfe/semi-icons';
  20. import { useTranslation } from 'react-i18next';
  21. import { useIsMobile } from '../../../../hooks/common/useIsMobile';
  22. const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
  23. const { t } = useTranslation();
  24. const [loading, setLoading] = useState(false);
  25. const formApiRef = useRef(null);
  26. const isMobile = useIsMobile();
  27. const isEdit = editingVendor && editingVendor.id !== undefined;
  28. const getInitValues = () => ({
  29. name: '',
  30. description: '',
  31. icon: '',
  32. status: true,
  33. sort_order: 999999,
  34. });
  35. const handleCancel = () => {
  36. handleClose();
  37. formApiRef.current?.reset();
  38. };
  39. const loadVendor = async () => {
  40. if (!isEdit || !editingVendor.id) return;
  41. setLoading(true);
  42. try {
  43. const res = await API.get(`/api/vendors/${editingVendor.id}`);
  44. const { success, message, data } = res.data;
  45. if (success) {
  46. // 将数字状态转为布尔值
  47. data.status = data.status === 1;
  48. if (formApiRef.current) {
  49. formApiRef.current.setValues({ ...getInitValues(), ...data });
  50. }
  51. } else {
  52. showError(message);
  53. }
  54. } catch (error) {
  55. showError(t('加载供应商信息失败'));
  56. }
  57. setLoading(false);
  58. };
  59. useEffect(() => {
  60. if (visible) {
  61. if (isEdit) {
  62. loadVendor();
  63. } else {
  64. formApiRef.current?.setValues(getInitValues());
  65. }
  66. } else {
  67. formApiRef.current?.reset();
  68. }
  69. }, [visible, editingVendor?.id]);
  70. const submit = async (values) => {
  71. setLoading(true);
  72. try {
  73. // 转换 status 为数字
  74. const submitData = {
  75. ...values,
  76. status: values.status ? 1 : 0,
  77. };
  78. if (isEdit) {
  79. submitData.id = editingVendor.id;
  80. const res = await API.put('/api/vendors/', submitData);
  81. const { success, message } = res.data;
  82. if (success) {
  83. showSuccess(t('供应商更新成功!'));
  84. refresh();
  85. handleClose();
  86. } else {
  87. showError(t(message));
  88. }
  89. } else {
  90. const res = await API.post('/api/vendors/', submitData);
  91. const { success, message } = res.data;
  92. if (success) {
  93. showSuccess(t('供应商创建成功!'));
  94. refresh();
  95. handleClose();
  96. } else {
  97. showError(t(message));
  98. }
  99. }
  100. } catch (error) {
  101. showError(error.response?.data?.message || t('操作失败'));
  102. }
  103. setLoading(false);
  104. };
  105. return (
  106. <Modal
  107. title={isEdit ? t('编辑供应商') : t('新增供应商')}
  108. visible={visible}
  109. onOk={() => formApiRef.current?.submitForm()}
  110. onCancel={handleCancel}
  111. confirmLoading={loading}
  112. size={isMobile ? 'full-width' : 'small'}
  113. >
  114. <Form
  115. initValues={getInitValues()}
  116. getFormApi={(api) => (formApiRef.current = api)}
  117. onSubmit={submit}
  118. >
  119. <Row gutter={12}>
  120. <Col span={24}>
  121. <Form.Input
  122. field='name'
  123. label={t('供应商名称')}
  124. placeholder={t('请输入供应商名称,如:OpenAI')}
  125. rules={[{ required: true, message: t('请输入供应商名称') }]}
  126. showClear
  127. />
  128. </Col>
  129. <Col span={24}>
  130. <Form.InputNumber
  131. field='sort_order'
  132. label={t('排序')}
  133. precision={0}
  134. placeholder={t('未设置')}
  135. extraText={t('越小越靠前,留空则不参与排序')}
  136. />
  137. </Col>
  138. <Col span={24}>
  139. <Form.TextArea
  140. field='description'
  141. label={t('描述')}
  142. placeholder={t('请输入供应商描述')}
  143. rows={3}
  144. showClear
  145. />
  146. </Col>
  147. <Col span={24}>
  148. <Form.Input
  149. field='icon'
  150. label={t('供应商图标')}
  151. placeholder={t('请输入图标名称')}
  152. extraText={
  153. <span>
  154. {t(
  155. "图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={'platform'}、OpenRouter.Avatar.shape={'square'},查询所有可用图标请 ",
  156. )}
  157. <Typography.Text
  158. link={{
  159. href: 'https://icons.lobehub.com/components/lobe-hub',
  160. target: '_blank',
  161. }}
  162. icon={<IconLink />}
  163. underline
  164. >
  165. {t('请点击我')}
  166. </Typography.Text>
  167. </span>
  168. }
  169. showClear
  170. />
  171. </Col>
  172. <Col span={24}>
  173. <Form.Switch field='status' label={t('状态')} initValue={true} />
  174. </Col>
  175. </Row>
  176. </Form>
  177. </Modal>
  178. );
  179. };
  180. export default EditVendorModal;