-
+
📋 每日总结
@@ -294,30 +305,352 @@ function SummaryApp() {
{list.map((item, index) => (
-
-
-
- {item.msgTime || formatDate(item.createdAt)}
-
-
- {item.staffName || item.staffId || '未知老师'}
-
-
-
- {item.summary || item.messageContext || '无内容'}
-
-
+
))}
)
}
+function SummaryCard({ item }: { item: SummaryItem }) {
+ const parsed = parseSummary(item.summary)
+ const hasStructure = parsed.sections.length > 0 || parsed.tags.length > 0
+
+ return (
+
+ {/* 头部 */}
+
+
+
+
+ {item.msgTime || formatDate(item.createdAt)}
+
+
+
+
+
+ {item.staffName || item.staffId || '未知老师'}
+
+
+
+
+ {hasStructure ? (
+ <>
+ {parsed.sections.map((section, idx) => (
+
+ ))}
+ {parsed.tags.length > 0 &&
}
+ >
+ ) : (
+
+ {item.summary || item.messageContext || '无内容'}
+
+ )}
+
+ )
+}
+
+function SectionBlock({ section }: { section: SummarySection }) {
+ const config = getSectionConfig(section.title)
+ const showTitle = displaySectionTitle(section.title)
+
+ return (
+
+
+ {config.icon}
+ {showTitle}
+
+
+ {section.items.map((text, idx) => (
+
+
+ {section.title.includes('核心诉求') ? (
+
+ ) : section.title.includes('处理结果') ? (
+
+ ) : section.title.includes('待办') ? (
+
+ ) : (
+
+ )}
+
+
+ {text}
+
+
+ ))}
+
+
+ )
+}
+
+function TagList({ tags }: { tags: string[] }) {
+ return (
+
+
+ 🏷️
+ 关键标签
+
+
+ {tags.map((tag, idx) => (
+
+ #{tag}
+
+ ))}
+
+
+ )
+}
+
+function getSectionConfig(title: string) {
+ if (title.includes('核心诉求')) {
+ return { icon: '📝', titleColor: '#1677ff' }
+ }
+ if (title.includes('处理结果')) {
+ return { icon: '✅', titleColor: '#52c41a' }
+ }
+ if (title.includes('待办') || title.includes('后续')) {
+ return { icon: '⏰', titleColor: '#fa8c16' }
+ }
+ return { icon: '•', titleColor: '#666' }
+}
+
+function displaySectionTitle(title: string) {
+ if (title.includes('核心诉求')) return '学员咨询'
+ if (title.includes('处理结果')) return '老师解答'
+ if (title.includes('待办')) return '跟进建议'
+ return title
+}
+
+function NumberBadge({ n }: { n: number }) {
+ return (
+
+ {n}
+
+ )
+}
+
+function GreenCheck() {
+ return (
+
+ ✓
+
+ )
+}
+
+function OrangeClock() {
+ return (
+
+ 🕐
+
+ )
+}
+
+function Dot({ color }: { color: string }) {
+ return (
+
+ )
+}
+
+function CalendarIcon() {
+ return (
+
+ )
+}
+
+function UserIcon() {
+ return (
+
+ )
+}
+
+function parseSummary(text?: string): ParsedSummary {
+ if (!text) return { sections: [], tags: [], raw: '' }
+
+ const result: ParsedSummary = { sections: [], tags: [], raw: text }
+ let content = text
+
+ // 提取关键标签:支持 "**关键标签**:#a#b" 或 "关键标签:#a#b" 或单独行
+ const tagPatterns = [
+ /(?:\*\*)?关键标签(?:\*\*)?[::]\s*(.+?)(?=\n\s*\*\*|\n\s*$|$)/s,
+ /(?:\*\*)?标签(?:\*\*)?[::]\s*(.+?)(?=\n\s*\*\*|\n\s*$|$)/s
+ ]
+ for (const pattern of tagPatterns) {
+ const tagMatch = content.match(pattern)
+ if (tagMatch) {
+ result.tags = parseTags(tagMatch[1])
+ content = content.replace(tagMatch[0], '')
+ break
+ }
+ }
+
+ // 按 **标题** 分隔区块,标题可能有空格
+ const sectionRegex = /\*\*\s*(核心诉求|处理结果|待办事项|待处理|后续跟进|关键标签)\s*\*\*/g
+ const parts: { title: string; text: string }[] = []
+ let lastIndex = 0
+ let lastTitle = ''
+ let match
+
+ while ((match = sectionRegex.exec(content)) !== null) {
+ if (lastTitle) {
+ parts.push({ title: lastTitle, text: content.slice(lastIndex, match.index) })
+ }
+ lastTitle = match[1]
+ lastIndex = match.index + match[0].length
+ }
+ if (lastTitle) {
+ parts.push({ title: lastTitle, text: content.slice(lastIndex) })
+ }
+
+ for (const part of parts) {
+ if (part.title === '关键标签') {
+ const tags = parseTags(part.text)
+ if (tags.length) result.tags = tags
+ continue
+ }
+ const items = parseItems(part.text)
+ if (items.length > 0) {
+ result.sections.push({ title: part.title, items })
+ }
+ }
+
+ // 没有任何结构化区块时,尝试整段按行解析列表
+ if (result.sections.length === 0 && result.tags.length === 0) {
+ const items = parseItems(content)
+ if (items.length > 1) {
+ result.sections.push({ title: '总结', items })
+ }
+ }
+
+ return result
+}
+
+function parseItems(text: string): string[] {
+ const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 0)
+ const items: string[] = []
+
+ for (const line of lines) {
+ // 匹配 1. / 1. / - / * / • 开头的列表项
+ const match = line.match(/^(?:\d+[..]\s*|[\-*•]\s+)(.+)$/)
+ if (match) {
+ items.push(cleanText(match[1]))
+ } else if (items.length > 0 && !line.match(/^\*\*/)) {
+ // 续行(非标题)
+ items[items.length - 1] += ' ' + cleanText(line)
+ }
+ }
+
+ // 如果整段没有列表项,把段落本身作为一条内容(兼容纯文本区块)
+ if (items.length === 0 && text.trim().length > 0) {
+ items.push(cleanText(text.trim()))
+ }
+
+ return items
+}
+
+function parseTags(text: string): string[] {
+ const s = text.trim()
+ if (!s) return []
+ // 支持 #标签1#标签2 或 标签1#标签2 或 #标签1 #标签2
+ return s.split(/[#\s]+/)
+ .map(t => t.trim())
+ .filter(t => t.length > 0 && t !== '、' && t !== ',')
+}
+
+function cleanText(s: string): string {
+ return s.replace(/\*\*/g, '').trim()
+}
+
function formatDate(timestamp?: number) {
if (!timestamp) return '-'
const date = new Date(timestamp)
diff --git a/init.sql b/init.sql
index d75ef3f..1a75398 100644
--- a/init.sql
+++ b/init.sql
@@ -42,6 +42,42 @@ CREATE TABLE IF NOT EXISTS `archive_messages` (
INDEX `idx_corp_msgtype` (`corp_id`, `msgtype`, `msgtime`) COMMENT '企业+消息类型+时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企微会话存档消息表';
+-- ============================================================
+-- 腾讯云 COS 配置表 (cos_config)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `cos_config` (
+ `id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
+ `secret_id` VARCHAR(255) NOT NULL COMMENT '腾讯云 SecretId',
+ `secret_key` VARCHAR(255) NOT NULL COMMENT '腾讯云 SecretKey',
+ `bucket` VARCHAR(255) NOT NULL COMMENT '存储桶名称',
+ `region` VARCHAR(64) NOT NULL COMMENT '地域,如 ap-shanghai',
+ `path_prefix` VARCHAR(255) DEFAULT 'archive/media/' COMMENT 'COS 路径前缀',
+ `domain` VARCHAR(255) DEFAULT NULL COMMENT '自定义访问域名(可选)',
+ `enabled` TINYINT(1) DEFAULT 0 COMMENT '是否启用:0-停用 1-启用',
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ INDEX `idx_enabled` (`enabled`) COMMENT '启用状态索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='腾讯云 COS 配置表';
+
+-- ============================================================
+-- 会话存档媒体文件 COS 映射表 (archive_media_files)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `archive_media_files` (
+ `id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
+ `msg_id` VARCHAR(255) NOT NULL COMMENT '企微消息 msgid',
+ `archive_message_id` BIGINT(20) NOT NULL COMMENT 'archive_messages.id',
+ `sdkfileid` VARCHAR(512) DEFAULT NULL COMMENT '企微媒体文件 sdkfileid',
+ `cos_url` VARCHAR(1024) NOT NULL COMMENT 'COS 文件访问 URL',
+ `file_size` BIGINT(20) DEFAULT 0 COMMENT '文件大小(字节)',
+ `file_type` VARCHAR(32) DEFAULT NULL COMMENT '文件类型:image/voice/video/file',
+ `status` TINYINT(1) DEFAULT 1 COMMENT '状态:1-有效 0-无效',
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ UNIQUE KEY `uk_archive_msg_id` (`archive_message_id`) COMMENT '消息ID唯一',
+ UNIQUE KEY `uk_msg_id` (`msg_id`) COMMENT '企微消息ID唯一',
+ INDEX `idx_status` (`status`) COMMENT '状态索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话存档媒体文件 COS 映射表';
+
-- ============================================================
-- 会话表 (conversations)
-- ============================================================