SettingsSidebarModulesAdmin.jsx 12 KB

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