Przeglądaj źródła

Merge remote-tracking branch 'origin/alpha' into alpha

CaIon 8 miesięcy temu
rodzic
commit
41b0cf406c

+ 3 - 3
relay/channel/task/kling/adaptor.go

@@ -69,8 +69,8 @@ func (a *TaskAdaptor) Init(info *relaycommon.TaskRelayInfo) {
 	a.ChannelType = info.ChannelType
 	a.ChannelType = info.ChannelType
 	a.baseURL = info.BaseUrl
 	a.baseURL = info.BaseUrl
 
 
-	// apiKey format: "access_key,secret_key"
-	keyParts := strings.Split(info.ApiKey, ",")
+	// apiKey format: "access_key|secret_key"
+	keyParts := strings.Split(info.ApiKey, "|")
 	if len(keyParts) == 2 {
 	if len(keyParts) == 2 {
 		a.accessKey = strings.TrimSpace(keyParts[0])
 		a.accessKey = strings.TrimSpace(keyParts[0])
 		a.secretKey = strings.TrimSpace(keyParts[1])
 		a.secretKey = strings.TrimSpace(keyParts[1])
@@ -264,7 +264,7 @@ func (a *TaskAdaptor) createJWTToken() (string, error) {
 }
 }
 
 
 func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
 func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
-	parts := strings.Split(apiKey, ",")
+	parts := strings.Split(apiKey, "|")
 	if len(parts) != 2 {
 	if len(parts) != 2 {
 		return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
 		return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
 	}
 	}

+ 25 - 19
web/src/components/layout/NoticeModal.js

@@ -113,25 +113,31 @@ const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadK
     return (
     return (
       <div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
       <div className="max-h-[55vh] overflow-y-auto pr-2 card-content-scroll">
         <Timeline mode="alternate">
         <Timeline mode="alternate">
-          {processedAnnouncements.map((item, idx) => (
-            <Timeline.Item
-              key={idx}
-              type={item.type}
-              time={item.time}
-              className={item.isUnread ? '' : ''}
-            >
-              <div>
-                {item.isUnread ? (
-                  <span className="shine-text">
-                    {item.content}
-                  </span>
-                ) : (
-                  item.content
-                )}
-                {item.extra && <div className="text-xs text-gray-500">{item.extra}</div>}
-              </div>
-            </Timeline.Item>
-          ))}
+          {processedAnnouncements.map((item, idx) => {
+            const htmlContent = marked.parse(item.content || '');
+            const htmlExtra = item.extra ? marked.parse(item.extra) : '';
+            return (
+              <Timeline.Item
+                key={idx}
+                type={item.type}
+                time={item.time}
+                className={item.isUnread ? '' : ''}
+              >
+                <div>
+                  <div
+                    className={item.isUnread ? 'shine-text' : ''}
+                    dangerouslySetInnerHTML={{ __html: htmlContent }}
+                  />
+                  {item.extra && (
+                    <div
+                      className="text-xs text-gray-500"
+                      dangerouslySetInnerHTML={{ __html: htmlExtra }}
+                    />
+                  )}
+                </div>
+              </Timeline.Item>
+            );
+          })}
         </Timeline>
         </Timeline>
       </div>
       </div>
     );
     );

+ 6 - 1
web/src/i18n/locales/en.json

@@ -1615,6 +1615,7 @@
   "编辑公告": "Edit Notice",
   "编辑公告": "Edit Notice",
   "公告内容": "Notice Content",
   "公告内容": "Notice Content",
   "请输入公告内容": "Please enter the notice content",
   "请输入公告内容": "Please enter the notice content",
+  "请输入公告内容(支持 Markdown/HTML)": "Please enter the notice content (supports Markdown/HTML)",
   "发布日期": "Publish Date",
   "发布日期": "Publish Date",
   "请选择发布日期": "Please select the publish date",
   "请选择发布日期": "Please select the publish date",
   "发布时间": "Publish Time",
   "发布时间": "Publish Time",
@@ -1630,6 +1631,7 @@
   "请输入问题标题": "Please enter the question title",
   "请输入问题标题": "Please enter the question title",
   "回答内容": "Answer Content",
   "回答内容": "Answer Content",
   "请输入回答内容": "Please enter the answer content",
   "请输入回答内容": "Please enter the answer content",
+  "请输入回答内容(支持 Markdown/HTML)": "Please enter the answer content (supports Markdown/HTML)",
   "确定要删除此问答吗?": "Are you sure you want to delete this FAQ?",
   "确定要删除此问答吗?": "Are you sure you want to delete this FAQ?",
   "系统公告管理,可以发布系统通知和重要消息(最多100个,前端显示最新20条)": "System notice management, you can publish system notices and important messages (maximum 100, display latest 20 on the front end)",
   "系统公告管理,可以发布系统通知和重要消息(最多100个,前端显示最新20条)": "System notice management, you can publish system notices and important messages (maximum 100, display latest 20 on the front end)",
   "常见问答管理,为用户提供常见问题的答案(最多50个,前端显示最新20条)": "FAQ management, providing answers to common questions for users (maximum 50, display latest 20 on the front end)",
   "常见问答管理,为用户提供常见问题的答案(最多50个,前端显示最新20条)": "FAQ management, providing answers to common questions for users (maximum 50, display latest 20 on the front end)",
@@ -1710,5 +1712,8 @@
   "可信": "Reliable",
   "可信": "Reliable",
   "所有上游数据均可信": "All upstream data is reliable",
   "所有上游数据均可信": "All upstream data is reliable",
   "以下上游数据可能不可信:": "The following upstream data may not be reliable: ",
   "以下上游数据可能不可信:": "The following upstream data may not be reliable: ",
-  "按倍率类型筛选": "Filter by ratio type"
+  "按倍率类型筛选": "Filter by ratio type",
+  "内容": "Content",
+  "放大编辑": "Expand editor",
+  "编辑公告内容": "Edit announcement content"
 }
 }

+ 2 - 0
web/src/pages/Channel/EditChannel.js

@@ -64,6 +64,8 @@ function type2secretPrompt(type) {
       return '按照如下格式输入:AppId|SecretId|SecretKey';
       return '按照如下格式输入:AppId|SecretId|SecretKey';
     case 33:
     case 33:
       return '按照如下格式输入:Ak|Sk|Region';
       return '按照如下格式输入:Ak|Sk|Region';
+    case 50:
+      return '按照如下格式输入: AccessKey|SecretKey';
     default:
     default:
       return '请输入渠道对应的鉴权密钥';
       return '请输入渠道对应的鉴权密钥';
   }
   }

+ 25 - 5
web/src/pages/Detail/index.js

@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useRef, useState, useMemo, useCallback }
 import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
 import { initVChartSemiTheme } from '@visactor/vchart-semi-theme';
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
 import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle } from 'lucide-react';
 import { Wallet, Activity, Zap, Gauge, PieChart, Server, Bell, HelpCircle } from 'lucide-react';
+import { marked } from 'marked';
 
 
 import {
 import {
   Card,
   Card,
@@ -1267,10 +1268,27 @@ const Detail = (props) => {
                       onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)}
                       onScroll={() => handleCardScroll(announcementScrollRef, setShowAnnouncementScrollHint)}
                     >
                     >
                       {announcementData.length > 0 ? (
                       {announcementData.length > 0 ? (
-                        <Timeline
-                          mode="alternate"
-                          dataSource={announcementData}
-                        />
+                        <Timeline mode="alternate">
+                          {announcementData.map((item, idx) => (
+                            <Timeline.Item
+                              key={idx}
+                              type={item.type || 'default'}
+                              time={item.time}
+                            >
+                              <div>
+                                <div
+                                  dangerouslySetInnerHTML={{ __html: marked.parse(item.content || '') }}
+                                />
+                                {item.extra && (
+                                  <div
+                                    className="text-xs text-gray-500"
+                                    dangerouslySetInnerHTML={{ __html: marked.parse(item.extra) }}
+                                  />
+                                )}
+                              </div>
+                            </Timeline.Item>
+                          ))}
+                        </Timeline>
                       ) : (
                       ) : (
                         <div className="flex justify-center items-center py-8">
                         <div className="flex justify-center items-center py-8">
                           <Empty
                           <Empty
@@ -1321,7 +1339,9 @@ const Detail = (props) => {
                               header={item.question}
                               header={item.question}
                               itemKey={index.toString()}
                               itemKey={index.toString()}
                             >
                             >
-                              <p>{item.answer}</p>
+                              <div
+                                dangerouslySetInnerHTML={{ __html: marked.parse(item.answer || '') }}
+                              />
                             </Collapse.Panel>
                             </Collapse.Panel>
                           ))}
                           ))}
                         </Collapse>
                         </Collapse>

+ 74 - 19
web/src/pages/Setting/Dashboard/SettingsAnnouncements.js

@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
 import {
 import {
   Button,
   Button,
   Space,
   Space,
@@ -9,7 +9,9 @@ import {
   Divider,
   Divider,
   Modal,
   Modal,
   Tag,
   Tag,
-  Switch
+  Switch,
+  TextArea,
+  Tooltip
 } from '@douyinfe/semi-ui';
 } from '@douyinfe/semi-ui';
 import {
 import {
   IllustrationNoResult,
   IllustrationNoResult,
@@ -20,7 +22,8 @@ import {
   Edit,
   Edit,
   Trash2,
   Trash2,
   Save,
   Save,
-  Bell
+  Bell,
+  Maximize2
 } from 'lucide-react';
 } from 'lucide-react';
 import { API, showError, showSuccess, getRelativeTime, formatDateTimeString } from '../../../helpers';
 import { API, showError, showSuccess, getRelativeTime, formatDateTimeString } from '../../../helpers';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
@@ -33,6 +36,7 @@ const SettingsAnnouncements = ({ options, refresh }) => {
   const [announcementsList, setAnnouncementsList] = useState([]);
   const [announcementsList, setAnnouncementsList] = useState([]);
   const [showAnnouncementModal, setShowAnnouncementModal] = useState(false);
   const [showAnnouncementModal, setShowAnnouncementModal] = useState(false);
   const [showDeleteModal, setShowDeleteModal] = useState(false);
   const [showDeleteModal, setShowDeleteModal] = useState(false);
+  const [showContentModal, setShowContentModal] = useState(false);
   const [deletingAnnouncement, setDeletingAnnouncement] = useState(null);
   const [deletingAnnouncement, setDeletingAnnouncement] = useState(null);
   const [editingAnnouncement, setEditingAnnouncement] = useState(null);
   const [editingAnnouncement, setEditingAnnouncement] = useState(null);
   const [modalLoading, setModalLoading] = useState(false);
   const [modalLoading, setModalLoading] = useState(false);
@@ -51,6 +55,8 @@ const SettingsAnnouncements = ({ options, refresh }) => {
   // 面板启用状态
   // 面板启用状态
   const [panelEnabled, setPanelEnabled] = useState(true);
   const [panelEnabled, setPanelEnabled] = useState(true);
 
 
+  const formApiRef = useRef(null);
+
   const typeOptions = [
   const typeOptions = [
     { value: 'default', label: t('默认') },
     { value: 'default', label: t('默认') },
     { value: 'ongoing', label: t('进行中') },
     { value: 'ongoing', label: t('进行中') },
@@ -76,13 +82,16 @@ const SettingsAnnouncements = ({ options, refresh }) => {
       dataIndex: 'content',
       dataIndex: 'content',
       key: 'content',
       key: 'content',
       render: (text) => (
       render: (text) => (
-        <div style={{
-          maxWidth: '300px',
-          wordBreak: 'break-word',
-          whiteSpace: 'pre-wrap'
-        }}>
-          {text}
-        </div>
+        <Tooltip content={text} position='topLeft' showArrow>
+          <div style={{
+            maxWidth: '300px',
+            overflow: 'hidden',
+            textOverflow: 'ellipsis',
+            whiteSpace: 'nowrap'
+          }}>
+            {text}
+          </div>
+        </Tooltip>
       )
       )
     },
     },
     {
     {
@@ -121,13 +130,17 @@ const SettingsAnnouncements = ({ options, refresh }) => {
       dataIndex: 'extra',
       dataIndex: 'extra',
       key: 'extra',
       key: 'extra',
       render: (text) => (
       render: (text) => (
-        <div style={{
-          maxWidth: '200px',
-          wordBreak: 'break-word',
-          color: 'var(--semi-color-text-2)'
-        }}>
-          {text || '-'}
-        </div>
+        <Tooltip content={text || '-'} showArrow>
+          <div style={{
+            maxWidth: '200px',
+            overflow: 'hidden',
+            textOverflow: 'ellipsis',
+            whiteSpace: 'nowrap',
+            color: 'var(--semi-color-text-2)'
+          }}>
+            {text || '-'}
+          </div>
+        </Tooltip>
       )
       )
     },
     },
     {
     {
@@ -472,16 +485,31 @@ const SettingsAnnouncements = ({ options, refresh }) => {
         className="rounded-xl"
         className="rounded-xl"
         confirmLoading={modalLoading}
         confirmLoading={modalLoading}
       >
       >
-        <Form layout='vertical' initValues={announcementForm} key={editingAnnouncement ? editingAnnouncement.id : 'new'}>
+        <Form
+          layout='vertical'
+          initValues={announcementForm}
+          key={editingAnnouncement ? editingAnnouncement.id : 'new'}
+          getFormApi={(api) => (formApiRef.current = api)}
+        >
           <Form.TextArea
           <Form.TextArea
             field='content'
             field='content'
             label={t('公告内容')}
             label={t('公告内容')}
-            placeholder={t('请输入公告内容')}
+            placeholder={t('请输入公告内容(支持 Markdown/HTML)')}
             maxCount={500}
             maxCount={500}
             rows={3}
             rows={3}
             rules={[{ required: true, message: t('请输入公告内容') }]}
             rules={[{ required: true, message: t('请输入公告内容') }]}
             onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
             onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
           />
           />
+          <Button
+            theme='light'
+            type='tertiary'
+            size='small'
+            icon={<Maximize2 size={14} />}
+            style={{ marginBottom: 16 }}
+            onClick={() => setShowContentModal(true)}
+          >
+            {t('放大编辑')}
+          </Button>
           <Form.DatePicker
           <Form.DatePicker
             field='publishDate'
             field='publishDate'
             label={t('发布日期')}
             label={t('发布日期')}
@@ -523,6 +551,33 @@ const SettingsAnnouncements = ({ options, refresh }) => {
       >
       >
         <Text>{t('确定要删除此公告吗?')}</Text>
         <Text>{t('确定要删除此公告吗?')}</Text>
       </Modal>
       </Modal>
+
+      {/* 公告内容放大编辑 Modal */}
+      <Modal
+        title={t('编辑公告内容')}
+        visible={showContentModal}
+        onOk={() => {
+          // 将内容同步到表单
+          if (formApiRef.current) {
+            formApiRef.current.setValue('content', announcementForm.content);
+          }
+          setShowContentModal(false);
+        }}
+        onCancel={() => setShowContentModal(false)}
+        okText={t('确定')}
+        cancelText={t('取消')}
+        className="rounded-xl"
+        width={800}
+      >
+        <TextArea
+          value={announcementForm.content}
+          placeholder={t('请输入公告内容(支持 Markdown/HTML)')}
+          maxCount={500}
+          rows={15}
+          style={{ width: '100%' }}
+          onChange={(value) => setAnnouncementForm({ ...announcementForm, content: value })}
+        />
+      </Modal>
     </>
     </>
   );
   );
 };
 };

+ 25 - 17
web/src/pages/Setting/Dashboard/SettingsFAQ.js

@@ -8,7 +8,8 @@ import {
   Empty,
   Empty,
   Divider,
   Divider,
   Modal,
   Modal,
-  Switch
+  Switch,
+  Tooltip
 } from '@douyinfe/semi-ui';
 } from '@douyinfe/semi-ui';
 import {
 import {
   IllustrationNoResult,
   IllustrationNoResult,
@@ -54,13 +55,17 @@ const SettingsFAQ = ({ options, refresh }) => {
       dataIndex: 'question',
       dataIndex: 'question',
       key: 'question',
       key: 'question',
       render: (text) => (
       render: (text) => (
-        <div style={{
-          maxWidth: '300px',
-          wordBreak: 'break-word',
-          fontWeight: 'bold'
-        }}>
-          {text}
-        </div>
+        <Tooltip content={text} showArrow>
+          <div style={{
+            maxWidth: '300px',
+            overflow: 'hidden',
+            textOverflow: 'ellipsis',
+            whiteSpace: 'nowrap',
+            fontWeight: 'bold'
+          }}>
+            {text}
+          </div>
+        </Tooltip>
       )
       )
     },
     },
     {
     {
@@ -68,14 +73,17 @@ const SettingsFAQ = ({ options, refresh }) => {
       dataIndex: 'answer',
       dataIndex: 'answer',
       key: 'answer',
       key: 'answer',
       render: (text) => (
       render: (text) => (
-        <div style={{
-          maxWidth: '400px',
-          wordBreak: 'break-word',
-          whiteSpace: 'pre-wrap',
-          color: 'var(--semi-color-text-1)'
-        }}>
-          {text}
-        </div>
+        <Tooltip content={text} showArrow>
+          <div style={{
+            maxWidth: '400px',
+            overflow: 'hidden',
+            textOverflow: 'ellipsis',
+            whiteSpace: 'nowrap',
+            color: 'var(--semi-color-text-1)'
+          }}>
+            {text}
+          </div>
+        </Tooltip>
       )
       )
     },
     },
     {
     {
@@ -416,7 +424,7 @@ const SettingsFAQ = ({ options, refresh }) => {
           <Form.TextArea
           <Form.TextArea
             field='answer'
             field='answer'
             label={t('回答内容')}
             label={t('回答内容')}
-            placeholder={t('请输入回答内容')}
+            placeholder={t('请输入回答内容(支持 Markdown/HTML)')}
             maxCount={1000}
             maxCount={1000}
             rows={6}
             rows={6}
             rules={[{ required: true, message: t('请输入回答内容') }]}
             rules={[{ required: true, message: t('请输入回答内容') }]}

+ 10 - 10
web/src/pages/Setting/index.js

@@ -45,6 +45,16 @@ const Setting = () => {
       content: <OperationSetting />,
       content: <OperationSetting />,
       itemKey: 'operation',
       itemKey: 'operation',
     });
     });
+    panes.push({
+      tab: (
+        <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
+          <LayoutDashboard size={18} />
+          {t('仪表盘设置')}
+        </span>
+      ),
+      content: <DashboardSetting />,
+      itemKey: 'dashboard',
+    });
     panes.push({
     panes.push({
       tab: (
       tab: (
         <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
         <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
@@ -115,16 +125,6 @@ const Setting = () => {
       content: <SystemSetting />,
       content: <SystemSetting />,
       itemKey: 'system',
       itemKey: 'system',
     });
     });
-    panes.push({
-      tab: (
-        <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
-          <LayoutDashboard size={18} />
-          {t('仪表盘设置')}
-        </span>
-      ),
-      content: <DashboardSetting />,
-      itemKey: 'dashboard',
-    });
     panes.push({
     panes.push({
       tab: (
       tab: (
         <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
         <span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>