feat: 完善专家支持中心、简历详情和日历页面功能
- 专家支持中心:填充真实Q&A数据,修改为"多多畅职机器人",按月份分组对话,禁用新对话按钮 - 简历详情:修复14个岗位的简历显示问题,支持markdown解析,添加编辑权限控制提示 - 日历页面:优化AI课和营销能力课显示,使用统一的event-description样式 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -131,6 +131,8 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
|
||||
navigate(`/live?${params.toString()}`);
|
||||
break;
|
||||
case 'public-course':
|
||||
case 'ai-course':
|
||||
case 'marketing-course':
|
||||
// 公开课(包括AI课、企业高管公开课、营销课) - 跳转到公共课直播间
|
||||
navigate(`/public-courses?${params.toString()}`);
|
||||
break;
|
||||
@@ -202,7 +204,7 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
|
||||
<div className="event-type-indicator" style={{ backgroundColor: typeColor.bg }}></div>
|
||||
<div className="event-card-title">
|
||||
<h4>{eventItem.title}</h4>
|
||||
<span className="event-type-tag" style={{
|
||||
<span className="event-type-tag" style={{
|
||||
backgroundColor: typeColor.light,
|
||||
color: typeColor.bg
|
||||
}}>
|
||||
@@ -225,7 +227,14 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
|
||||
<span>{status.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* AI课和营销能力课显示课程信息 */}
|
||||
{(eventItem.type === 'ai-course' || eventItem.type === 'marketing-course') && (
|
||||
<div className="event-description">
|
||||
{eventItem.title} - {eventItem.teacher}老师
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 企业高管公开课添加线下参与标签 */}
|
||||
{eventItem.type === 'public-course' && (
|
||||
<div className="event-info-row" style={{ marginTop: '8px' }}>
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
background-image: url("@/assets/images/Common/modal_bg.png");
|
||||
background-size: 100% 100%;
|
||||
|
||||
/* 保存按钮hover提示样式 */
|
||||
.save-button-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&:hover .save-button-tooltip {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
|
||||
@@ -68,7 +68,8 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
currentContent = customVersion?.content || '';
|
||||
} else if (data?.content) {
|
||||
const hasModified = !!data.content.modified;
|
||||
currentContent = (!hasModified || version === "1") ? data.content.original : data.content.modified;
|
||||
// 如果没有modified版本,始终使用original
|
||||
currentContent = (version === "1" || !hasModified) ? data.content.original : data.content.modified;
|
||||
}
|
||||
|
||||
const parsed = parseResumeMarkdown(currentContent);
|
||||
@@ -209,20 +210,21 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
result.personalInfo.name = positionMatch[1].trim();
|
||||
}
|
||||
|
||||
// 提取项目经历
|
||||
const projectSectionMatch = markdownContent.match(/# 一、项目经历\s+([\s\S]*?)(?=# 二、专业技能|$)/);
|
||||
// 提取项目经历 - 兼容"专业技能"和"掌握技能"两种标题
|
||||
const projectSectionMatch = markdownContent.match(/# 一、项目经历\s+([\s\S]*?)(?=# 二、(?:专业技能|掌握技能)|# 三、|$)/);
|
||||
if (projectSectionMatch) {
|
||||
const projectContent = projectSectionMatch[1];
|
||||
|
||||
|
||||
// 提取项目名称
|
||||
const projectNameMatch = projectContent.match(/### (一)项目名称:(.+)/);
|
||||
const roleMatch = projectContent.match(/### (二)实习岗位:(.+)/);
|
||||
const timeMatch = projectContent.match(/### (三)实习时间:(.+)/);
|
||||
const companyMatch = projectContent.match(/### (四)实习单位:(.+)/);
|
||||
|
||||
// 提取岗位职责内容 - 从"### (五)岗位职责:"开始到下一个section
|
||||
const responsibilityMatch = projectContent.match(/### (五)岗位职责:\s+([\s\S]*?)$/);
|
||||
|
||||
// 兼容不同的顺序 - 使用非捕获组(?:)
|
||||
const timeMatch = projectContent.match(/### (?:(三)|(四))实习时间:(.+)/);
|
||||
const companyMatch = projectContent.match(/### (?:(三)|(四))实习单位:(.+)/);
|
||||
|
||||
// 提取岗位职责内容 - 兼容有冒号和没有冒号的情况
|
||||
const responsibilityMatch = projectContent.match(/### (五)岗位职责[::]?\s+([\s\S]*?)$/);
|
||||
|
||||
if (projectNameMatch) {
|
||||
result.projects = [{
|
||||
name: projectNameMatch[1].trim(),
|
||||
@@ -234,11 +236,11 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 提取专业技能
|
||||
const skillsSectionMatch = markdownContent.match(/# 二、专业技能\s+([\s\S]*?)$/);
|
||||
// 提取专业技能 - 兼容"专业技能"和"掌握技能"两种标题
|
||||
const skillsSectionMatch = markdownContent.match(/# 二、(?:专业技能|掌握技能)\s+([\s\S]*?)(?=# 三、|$)/);
|
||||
if (skillsSectionMatch) {
|
||||
const skillsContent = skillsSectionMatch[1];
|
||||
|
||||
|
||||
// 提取核心能力
|
||||
const coreSkillsMatch = skillsContent.match(/### (一)核心能力\s+([\s\S]*?)(?=### (二)复合能力|$)/);
|
||||
if (coreSkillsMatch) {
|
||||
@@ -248,9 +250,9 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
.map(skill => skill.trim().replace(/;\s*$/, ''));
|
||||
result.skills.core = coreSkills;
|
||||
}
|
||||
|
||||
// 提取复合能力
|
||||
const additionalSkillsMatch = skillsContent.match(/### (二)复合能力\s+([\s\S]*?)$/);
|
||||
|
||||
// 提取复合能力
|
||||
const additionalSkillsMatch = skillsContent.match(/### (二)复合能力\s+([\s\S]*?)(?=# 三、|$)/);
|
||||
if (additionalSkillsMatch) {
|
||||
const additionalSkills = additionalSkillsMatch[1]
|
||||
.split(/\d+\.\s+/)
|
||||
@@ -260,6 +262,15 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 提取个人评价/个人总结 - 兼容两种标题
|
||||
const personalSummaryMatch = markdownContent.match(/# 三、(?:个人评价|个人总结)\s+([\s\S]*?)$/);
|
||||
if (personalSummaryMatch) {
|
||||
const summaryText = personalSummaryMatch[1].trim();
|
||||
if (summaryText) {
|
||||
result.personalSummary = [summaryText];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -286,7 +297,8 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
if (data.content.original) {
|
||||
// 有original字段,可能有或没有modified字段
|
||||
const hasModified = !!data.content.modified;
|
||||
const selectedContent = (!hasModified || version === "1") ? data.content.original : data.content.modified;
|
||||
// 如果没有modified版本,始终使用original
|
||||
const selectedContent = (version === "1" || !hasModified) ? data.content.original : data.content.modified;
|
||||
console.log('选择的内容长度:', selectedContent ? selectedContent.length : 0);
|
||||
|
||||
// 如果是markdown格式的字符串,使用解析器
|
||||
@@ -322,7 +334,8 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
console.log('处理content.original结构');
|
||||
// 处理有content.original的数据结构
|
||||
const hasModified = !!currentTemplate.content.modified;
|
||||
const selectedContent = (!hasModified || version === "1") ? currentTemplate.content.original : currentTemplate.content.modified;
|
||||
// 如果没有modified版本,始终使用original
|
||||
const selectedContent = (version === "1" || !hasModified) ? currentTemplate.content.original : currentTemplate.content.modified;
|
||||
|
||||
const parsedContent = parseResumeMarkdown(selectedContent);
|
||||
if (parsedContent) {
|
||||
@@ -483,17 +496,45 @@ export default ({ visible, onClose, data, initialVersion = "2" }) => {
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<IconSave />}
|
||||
onClick={handleSaveEdit}
|
||||
style={{
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
<div className="save-button-wrapper">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<IconSave />}
|
||||
disabled={true}
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#d9d9d9',
|
||||
borderColor: '#d9d9d9',
|
||||
color: '#ffffff',
|
||||
cursor: 'not-allowed',
|
||||
opacity: 0.5
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-35px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||
color: '#fff',
|
||||
padding: '6px 12px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
whiteSpace: 'nowrap',
|
||||
pointerEvents: 'none',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.3s',
|
||||
zIndex: 1000
|
||||
}}
|
||||
className="save-button-tooltip"
|
||||
>
|
||||
非学员与导师无修改权限
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<IconClose />}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
color: #1d2129;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.support-list-title-content {
|
||||
width: 100%;
|
||||
@@ -30,6 +32,7 @@
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e4ecf2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.support-list-title-icon {
|
||||
width: 24px;
|
||||
@@ -37,23 +40,81 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.support-list-title-new-btn-wrapper {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&:hover .support-list-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.support-list-tooltip {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 6px 12px;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
.support-list-title-new-btn {
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
border-radius: 4px;
|
||||
background-color: #0077ff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
> span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: #c9cdd4;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
background-color: #c9cdd4;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.disabled):hover {
|
||||
background-color: #0066dd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect, Fragment } from "react";
|
||||
import IconFont from "@/components/IconFont";
|
||||
import expertSupportData from "@/data/expertSupportData";
|
||||
import "./index.css";
|
||||
@@ -12,6 +12,17 @@ const STATUS = {
|
||||
const Index = ({ onSelectConversation }) => {
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
|
||||
// 页面加载时默认选中第一个对话
|
||||
useEffect(() => {
|
||||
if (expertSupportData?.conversations && expertSupportData.conversations.length > 0 && !selectedId) {
|
||||
const firstConversation = expertSupportData.conversations[0];
|
||||
setSelectedId(firstConversation.id);
|
||||
if (onSelectConversation) {
|
||||
onSelectConversation(firstConversation);
|
||||
}
|
||||
}
|
||||
}, [onSelectConversation]);
|
||||
|
||||
const handleClickNew = () => {
|
||||
console.log("点击了新对话");
|
||||
};
|
||||
@@ -24,9 +35,22 @@ const Index = ({ onSelectConversation }) => {
|
||||
};
|
||||
|
||||
// 根据日期分组对话
|
||||
const todayConversations = expertSupportData.conversations.filter(c => c.date === "今天");
|
||||
const weekConversations = expertSupportData.conversations.filter(c => c.date === "7天内");
|
||||
const monthConversations = expertSupportData.conversations.filter(c => c.date === "30天内");
|
||||
const groupedConversations = {};
|
||||
expertSupportData.conversations.forEach(conversation => {
|
||||
const dateGroup = conversation.date;
|
||||
if (!groupedConversations[dateGroup]) {
|
||||
groupedConversations[dateGroup] = [];
|
||||
}
|
||||
groupedConversations[dateGroup].push(conversation);
|
||||
});
|
||||
|
||||
// 定义日期组的显示顺序(最新的在前)
|
||||
const dateGroupOrder = [
|
||||
"2024年12月", "2024年11月", "2024年10月",
|
||||
"2024年9月", "2024年8月", "2024年7月",
|
||||
"2024年6月", "2024年5月", "2024年4月",
|
||||
"2024年3月", "更早"
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="support-list-wrapper">
|
||||
@@ -34,70 +58,40 @@ const Index = ({ onSelectConversation }) => {
|
||||
<div className="support-list-title-content">
|
||||
<IconFont className="support-list-title-icon" src="recuUY5vFY3hVP" />
|
||||
<span>咨询对话</span>
|
||||
<div className="support-list-title-new-btn" onClick={handleClickNew}>
|
||||
<span>+</span>
|
||||
<span>新对话</span>
|
||||
<div className="support-list-title-new-btn-wrapper">
|
||||
<div className="support-list-title-new-btn disabled">
|
||||
<span>+</span>
|
||||
<span>新对话</span>
|
||||
</div>
|
||||
<div className="support-list-tooltip">非学员本人暂无权限</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="support-list">
|
||||
{todayConversations.length > 0 && (
|
||||
<>
|
||||
<p className="support-list-date">今天</p>
|
||||
<ul className="support-list-content">
|
||||
{todayConversations.map(conversation => (
|
||||
<li
|
||||
key={conversation.id}
|
||||
className={`support-list-content-item ${selectedId === conversation.id ? 'selected' : ''}`}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<p>{conversation.title}</p>
|
||||
<div className={`support-list-content-item-status status-${conversation.status}`}>
|
||||
{STATUS[conversation.status].text}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{weekConversations.length > 0 && (
|
||||
<>
|
||||
<p className="support-list-date">7天内</p>
|
||||
<ul className="support-list-content">
|
||||
{weekConversations.map(conversation => (
|
||||
<li
|
||||
key={conversation.id}
|
||||
className={`support-list-content-item ${selectedId === conversation.id ? 'selected' : ''}`}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<p>{conversation.title}</p>
|
||||
<div className={`support-list-content-item-status status-${conversation.status}`}>
|
||||
{STATUS[conversation.status].text}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{monthConversations.length > 0 && (
|
||||
<>
|
||||
<p className="support-list-date">30天内</p>
|
||||
<ul className="support-list-content">
|
||||
{monthConversations.map(conversation => (
|
||||
<li
|
||||
key={conversation.id}
|
||||
className={`support-list-content-item ${selectedId === conversation.id ? 'selected' : ''}`}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<p>{conversation.title}</p>
|
||||
<div className={`support-list-content-item-status status-${conversation.status}`}>
|
||||
{STATUS[conversation.status].text}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
{dateGroupOrder.map(dateGroup => {
|
||||
const conversations = groupedConversations[dateGroup];
|
||||
if (!conversations || conversations.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Fragment key={dateGroup}>
|
||||
<p className="support-list-date">{dateGroup}</p>
|
||||
<ul className="support-list-content">
|
||||
{conversations.map(conversation => (
|
||||
<li
|
||||
key={conversation.id}
|
||||
className={`support-list-content-item ${selectedId === conversation.id ? 'selected' : ''}`}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<p>{conversation.title}</p>
|
||||
<div className={`support-list-content-item-status status-${conversation.status}`}>
|
||||
{STATUS[conversation.status].text}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user