SettingsSidebarModulesAdmin.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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 [email protected]
  14. */
  15. import React, { useState, useEffect, useContext } from 'react';
  16. import { useTranslation } from 'react-i18next';
  17. import {
  18. Card,
  19. Form,
  20. Button,
  21. Switch,
  22. Row,
  23. Col,
  24. Typography,
  25. } from '@douyinfe/semi-ui';
  26. import { API, showSuccess, showError } from '../../../helpers';
  27. import { StatusContext } from '../../../context/Status';
  28. const { Text } = Typography;
  29. export default function SettingsSidebarModulesAdmin(props) {
  30. const { t } = useTranslation();
  31. const [loading, setLoading] = useState(false);
  32. const [statusState, statusDispatch] = useContext(StatusContext);
  33. // 左侧边栏模块管理状态(管理员全局控制)
  34. const [sidebarModulesAdmin, setSidebarModulesAdmin] = useState({
  35. chat: {
  36. enabled: true,
  37. playground: true,
  38. chat: true,
  39. },
  40. console: {
  41. enabled: true,
  42. detail: true,
  43. token: true,
  44. log: true,
  45. midjourney: true,
  46. task: true,
  47. },
  48. personal: {
  49. enabled: true,
  50. topup: true,
  51. personal: true,
  52. },
  53. admin: {
  54. enabled: true,
  55. channel: true,
  56. models: true,
  57. deployment: true,
  58. redemption: true,
  59. user: true,
  60. subscription: true,
  61. setting: true,
  62. },
  63. });
  64. // 处理区域级别开关变更
  65. function handleSectionChange(sectionKey) {
  66. return (checked) => {
  67. const newModules = {
  68. ...sidebarModulesAdmin,
  69. [sectionKey]: {
  70. ...sidebarModulesAdmin[sectionKey],
  71. enabled: checked,
  72. },
  73. };
  74. setSidebarModulesAdmin(newModules);
  75. };
  76. }
  77. // 处理功能级别开关变更
  78. function handleModuleChange(sectionKey, moduleKey) {
  79. return (checked) => {
  80. const newModules = {
  81. ...sidebarModulesAdmin,
  82. [sectionKey]: {
  83. ...sidebarModulesAdmin[sectionKey],
  84. [moduleKey]: checked,
  85. },
  86. };
  87. setSidebarModulesAdmin(newModules);
  88. };
  89. }
  90. // 重置为默认配置
  91. function resetSidebarModules() {
  92. const defaultModules = {
  93. chat: {
  94. enabled: true,
  95. playground: true,
  96. chat: true,
  97. },
  98. console: {
  99. enabled: true,
  100. detail: true,
  101. token: true,
  102. log: true,
  103. midjourney: true,
  104. task: true,
  105. },
  106. personal: {
  107. enabled: true,
  108. topup: true,
  109. personal: true,
  110. },
  111. admin: {
  112. enabled: true,
  113. channel: true,
  114. models: true,
  115. deployment: true,
  116. redemption: true,
  117. user: true,
  118. subscription: true,
  119. setting: true,
  120. },
  121. };
  122. setSidebarModulesAdmin(defaultModules);
  123. showSuccess(t('已重置为默认配置'));
  124. }
  125. // 保存配置
  126. async function onSubmit() {
  127. setLoading(true);
  128. try {
  129. const res = await API.put('/api/option/', {
  130. key: 'SidebarModulesAdmin',
  131. value: JSON.stringify(sidebarModulesAdmin),
  132. });
  133. const { success, message } = res.data;
  134. if (success) {
  135. showSuccess(t('保存成功'));
  136. // 立即更新StatusContext中的状态
  137. statusDispatch({
  138. type: 'set',
  139. payload: {
  140. ...statusState.status,
  141. SidebarModulesAdmin: JSON.stringify(sidebarModulesAdmin),
  142. },
  143. });
  144. // 刷新父组件状态
  145. if (props.refresh) {
  146. await props.refresh();
  147. }
  148. } else {
  149. showError(message);
  150. }
  151. } catch (error) {
  152. showError(t('保存失败,请重试'));
  153. } finally {
  154. setLoading(false);
  155. }
  156. }
  157. useEffect(() => {
  158. // 从 props.options 中获取配置
  159. if (props.options && props.options.SidebarModulesAdmin) {
  160. try {
  161. const modules = JSON.parse(props.options.SidebarModulesAdmin);
  162. setSidebarModulesAdmin(modules);
  163. } catch (error) {
  164. // 使用默认配置
  165. const defaultModules = {
  166. chat: { enabled: true, playground: true, chat: true },
  167. console: {
  168. enabled: true,
  169. detail: true,
  170. token: true,
  171. log: true,
  172. midjourney: true,
  173. task: true,
  174. },
  175. personal: { enabled: true, topup: true, personal: true },
  176. admin: {
  177. enabled: true,
  178. channel: true,
  179. models: true,
  180. deployment: true,
  181. redemption: true,
  182. user: true,
  183. subscription: true,
  184. setting: true,
  185. },
  186. };
  187. setSidebarModulesAdmin(defaultModules);
  188. }
  189. }
  190. }, [props.options]);
  191. // 区域配置数据
  192. const sectionConfigs = [
  193. {
  194. key: 'chat',
  195. title: t('聊天区域'),
  196. description: t('操练场和聊天功能'),
  197. modules: [
  198. {
  199. key: 'playground',
  200. title: t('操练场'),
  201. description: t('AI模型测试环境'),
  202. },
  203. { key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
  204. ],
  205. },
  206. {
  207. key: 'console',
  208. title: t('控制台区域'),
  209. description: t('数据管理和日志查看'),
  210. modules: [
  211. { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
  212. { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
  213. { key: 'log', title: t('使用日志'), description: t('API使用记录') },
  214. {
  215. key: 'midjourney',
  216. title: t('绘图日志'),
  217. description: t('绘图任务记录'),
  218. },
  219. { key: 'task', title: t('任务日志'), description: t('系统任务记录') },
  220. ],
  221. },
  222. {
  223. key: 'personal',
  224. title: t('个人中心区域'),
  225. description: t('用户个人功能'),
  226. modules: [
  227. { key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
  228. {
  229. key: 'personal',
  230. title: t('个人设置'),
  231. description: t('个人信息设置'),
  232. },
  233. ],
  234. },
  235. {
  236. key: 'admin',
  237. title: t('管理员区域'),
  238. description: t('系统管理功能'),
  239. modules: [
  240. { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
  241. { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
  242. {
  243. key: 'deployment',
  244. title: t('模型部署'),
  245. description: t('模型部署管理'),
  246. },
  247. {
  248. key: 'subscription',
  249. title: t('订阅管理'),
  250. description: t('订阅套餐管理'),
  251. },
  252. {
  253. key: 'redemption',
  254. title: t('兑换码管理'),
  255. description: t('兑换码生成管理'),
  256. },
  257. { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
  258. {
  259. key: 'setting',
  260. title: t('系统设置'),
  261. description: t('系统参数配置'),
  262. },
  263. ],
  264. },
  265. ];
  266. return (
  267. <Card>
  268. <Form.Section
  269. text={t('侧边栏管理(全局控制)')}
  270. extraText={t(
  271. '全局控制侧边栏区域和功能显示,管理员隐藏的功能用户无法启用',
  272. )}
  273. >
  274. {sectionConfigs.map((section) => (
  275. <div key={section.key} style={{ marginBottom: '32px' }}>
  276. {/* 区域标题和总开关 */}
  277. <div
  278. style={{
  279. display: 'flex',
  280. justifyContent: 'space-between',
  281. alignItems: 'center',
  282. marginBottom: '16px',
  283. padding: '12px 16px',
  284. backgroundColor: 'var(--semi-color-fill-0)',
  285. borderRadius: '8px',
  286. border: '1px solid var(--semi-color-border)',
  287. }}
  288. >
  289. <div>
  290. <div
  291. style={{
  292. fontWeight: '600',
  293. fontSize: '16px',
  294. color: 'var(--semi-color-text-0)',
  295. marginBottom: '4px',
  296. }}
  297. >
  298. {section.title}
  299. </div>
  300. <Text
  301. type='secondary'
  302. size='small'
  303. style={{
  304. fontSize: '12px',
  305. color: 'var(--semi-color-text-2)',
  306. lineHeight: '1.4',
  307. }}
  308. >
  309. {section.description}
  310. </Text>
  311. </div>
  312. <Switch
  313. checked={sidebarModulesAdmin[section.key]?.enabled}
  314. onChange={handleSectionChange(section.key)}
  315. size='default'
  316. />
  317. </div>
  318. {/* 功能模块网格 */}
  319. <Row gutter={[16, 16]}>
  320. {section.modules.map((module) => (
  321. <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
  322. <Card
  323. bodyStyle={{ padding: '16px' }}
  324. hoverable
  325. style={{
  326. opacity: sidebarModulesAdmin[section.key]?.enabled
  327. ? 1
  328. : 0.5,
  329. transition: 'opacity 0.2s',
  330. }}
  331. >
  332. <div
  333. style={{
  334. display: 'flex',
  335. justifyContent: 'space-between',
  336. alignItems: 'center',
  337. height: '100%',
  338. }}
  339. >
  340. <div style={{ flex: 1, textAlign: 'left' }}>
  341. <div
  342. style={{
  343. fontWeight: '600',
  344. fontSize: '14px',
  345. color: 'var(--semi-color-text-0)',
  346. marginBottom: '4px',
  347. }}
  348. >
  349. {module.title}
  350. </div>
  351. <Text
  352. type='secondary'
  353. size='small'
  354. style={{
  355. fontSize: '12px',
  356. color: 'var(--semi-color-text-2)',
  357. lineHeight: '1.4',
  358. display: 'block',
  359. }}
  360. >
  361. {module.description}
  362. </Text>
  363. </div>
  364. <div style={{ marginLeft: '16px' }}>
  365. <Switch
  366. checked={
  367. sidebarModulesAdmin[section.key]?.[module.key]
  368. }
  369. onChange={handleModuleChange(section.key, module.key)}
  370. size='default'
  371. disabled={!sidebarModulesAdmin[section.key]?.enabled}
  372. />
  373. </div>
  374. </div>
  375. </Card>
  376. </Col>
  377. ))}
  378. </Row>
  379. </div>
  380. ))}
  381. <div
  382. style={{
  383. display: 'flex',
  384. gap: '12px',
  385. justifyContent: 'flex-start',
  386. alignItems: 'center',
  387. paddingTop: '8px',
  388. borderTop: '1px solid var(--semi-color-border)',
  389. }}
  390. >
  391. <Button
  392. size='default'
  393. type='tertiary'
  394. onClick={resetSidebarModules}
  395. style={{
  396. borderRadius: '6px',
  397. fontWeight: '500',
  398. }}
  399. >
  400. {t('重置为默认')}
  401. </Button>
  402. <Button
  403. size='default'
  404. type='primary'
  405. onClick={onSubmit}
  406. loading={loading}
  407. style={{
  408. borderRadius: '6px',
  409. fontWeight: '500',
  410. minWidth: '100px',
  411. }}
  412. >
  413. {t('保存设置')}
  414. </Button>
  415. </div>
  416. </Form.Section>
  417. </Card>
  418. );
  419. }