bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
1,627 lines (1,356 loc) • 76.5 kB
JavaScript
const vscode = acquireVsCodeApi();
// 事件监听器管理
const eventListeners = [];
/**
* 添加事件监听器并记录,以便后续清理
*/
function addManagedEventListener(element, event, handler, options) {
if (element) {
element.addEventListener(event, handler, options);
eventListeners.push({ element, event, handler, options });
}
}
/**
* 清理所有注册的事件监听器
*/
function cleanupEventListeners() {
eventListeners.forEach(({ element, event, handler, options }) => {
try {
if (element && element.removeEventListener) {
element.removeEventListener(event, handler, options);
}
} catch (error) {
console.error('❌ [ByteFun Sidebar] 清理事件监听器失败:', error);
}
});
eventListeners.length = 0; // 清空数组
}
/**
* 页面卸载时清理资源
*/
function handlePageUnload() {
cleanupEventListeners();
}
// 监听页面卸载事件
addManagedEventListener(window, 'beforeunload', handlePageUnload);
addManagedEventListener(window, 'unload', handlePageUnload);
// 获取之前保存的状态
let state = vscode.getState() || { selectedPageId: null, activeMenuId: null, activeTab: 'workspace' };
// 页面加载时恢复选中状态
addManagedEventListener(document, 'DOMContentLoaded', function () {
if (state.selectedPageId) {
selectPage(state.selectedPageId);
}
if (state.activeMenuId) {
setActiveMenu(state.activeMenuId);
}
if (state.activeTab) {
switchTab(state.activeTab);
}
// 添加事件监听器
const projectsBtn = document.getElementById('projects-btn');
const libraryBtn = document.getElementById('library-btn');
const refreshBtn = document.getElementById('refresh-btn');
const syncAllBtn = document.getElementById('sync-all-btn');
if (projectsBtn) {
addManagedEventListener(projectsBtn, 'click', handleProjectClick);
}
if (libraryBtn) {
addManagedEventListener(libraryBtn, 'click', handleLibraryClick);
}
if (refreshBtn) {
addManagedEventListener(refreshBtn, 'click', handleRefreshClick);
}
if (syncAllBtn) {
addManagedEventListener(syncAllBtn, 'click', handleSyncAllClick);
}
// 添加 tab 切换事件监听器
const tabItems = document.querySelectorAll('.tab-item');
tabItems.forEach(tab => {
addManagedEventListener(tab, 'click', function () {
const tabId = this.getAttribute('data-tab');
switchTab(tabId);
});
});
// 添加发送按钮和输入框事件监听器(排除产品角色,产品角色现在使用item界面)
const chatRoles = ['design', 'frontEndDev', 'backEndDev', 'operations'];
chatRoles.forEach(role => {
const sendButton = document.getElementById(`${role}-send`);
const input = document.getElementById(`${role}-input`);
if (sendButton && input) {
// 发送按钮点击事件
addManagedEventListener(sendButton, 'click', function () {
sendChatMessage(role);
});
// 输入框回车事件
addManagedEventListener(input, 'keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendChatMessage(role);
}
});
// 输入框自动调整高度
addManagedEventListener(input, 'input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
}
});
// 替换刷新按钮的emoji图标为PNG图标
const refreshProductButtons = document.querySelectorAll('.refresh-product-button');
refreshProductButtons.forEach((button) => {
// 清空原有内容
button.innerHTML = '';
// 创建图标img元素
const refreshIcon = document.createElement('img');
refreshIcon.src = window.ICON_URIS.refreshIcon;
refreshIcon.className = 'refresh-icon';
refreshIcon.alt = '刷新';
refreshIcon.style.width = '17px';
refreshIcon.style.height = '14px';
refreshIcon.style.opacity = '0.8';
refreshIcon.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
button.appendChild(refreshIcon);
// 添加hover效果
button.addEventListener('mouseenter', function () {
refreshIcon.style.opacity = '1';
refreshIcon.style.transform = 'rotate(180deg)';
});
button.addEventListener('mouseleave', function () {
refreshIcon.style.opacity = '0.8';
refreshIcon.style.transform = 'rotate(0deg)';
});
});
// 为产品角色和设计角色添加刷新按钮事件监听器
refreshProductButtons.forEach((button, index) => {
addManagedEventListener(button, 'click', function () {
const panel = button.closest('.tab-panel');
if (panel) {
const role = panel.getAttribute('data-role');
if (role === 'product') {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'product'
});
} else if (role === 'design') {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'design'
});
} else if (role === 'frontEndDev') {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'frontEndDev'
});
} else if (role === 'backEndDev') {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'backEndDev'
});
}
}
});
});
// 添加团队聊天发送按钮和输入框事件监听器
const teamSendButton = document.getElementById('team-send');
const teamInput = document.getElementById('team-input');
if (teamSendButton && teamInput) {
// 发送按钮点击事件
addManagedEventListener(teamSendButton, 'click', function () {
sendTeamMessage();
});
// 输入框回车事件
addManagedEventListener(teamInput, 'keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendTeamMessage();
}
});
// 输入框自动调整高度
addManagedEventListener(teamInput, 'input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
}
// 添加清空聊天按钮事件监听器
const clearButtons = document.querySelectorAll('.chat-clear-button');
clearButtons.forEach((button, index) => {
const roleType = button.getAttribute('data-role');
addManagedEventListener(button, 'click', function (e) {
const roleType = this.getAttribute('data-role');
if (roleType) {
clearChatHistory(roleType);
} else {
console.error('🗑️ [事件触发] 🔍 未获取到角色类型');
}
});
});
// 添加预览设计稿按钮事件监听器
const previewDesignBtn = document.querySelector('.preview-design-btn');
if (previewDesignBtn) {
addManagedEventListener(previewDesignBtn, 'click', function (event) {
event.preventDefault();
handlePreviewDesignClick();
});
}
// 添加事件委托来处理聊天链接点击事件
addManagedEventListener(document, 'click', function (e) {
if (e.target.classList.contains('chat-link')) {
e.preventDefault();
const roleType = e.target.getAttribute('data-role');
// 优先使用 data-link-url,如果没有则使用 data-link-text
const linkText = e.target.getAttribute('data-link-url') || e.target.getAttribute('data-link-text');
if (roleType && linkText) {
clickChatLink(roleType, linkText);
} else {
console.warn('🔗 [事件委托] 缺少必要的属性:', { roleType, linkText });
}
}
});
// 通知扩展webview已准备就绪
vscode.postMessage({
command: 'webviewReady'
});
});
// 监听来自扩展的转发消息
addManagedEventListener(window, 'message', event => {
const message = event.data;
if (message && message.type) {
switch (message.type) {
case 'initProjectPageList':
// 在这里处理项目打开的逻辑
// 比如高亮显示对应的项目、更新UI状态等
handleInitProjectPageList(message.data);
break;
case 'chatSessionsLoaded':
handleChatSessionsLoaded(message.data);
break;
case 'teamChatSessionsLoaded':
handleTeamChatSessionsLoaded(message.data);
break;
case 'projectStatusUpdate':
handleProjectStatusUpdate(message.data.isProjectExists);
break;
case 'updateDesignPreviewVisibility':
updateDesignPreviewVisibility(message.data.visible);
break;
case 'productItems':
handleProductItemsLoaded(message.data);
break;
case 'designItems':
handleDesignItemsLoaded(message.data);
break;
case 'frontendDevItems':
handleFrontendDevItemsLoaded(message.data);
break;
case 'backendDevItems':
handleBackendDevItemsLoaded(message.data);
break;
case 'syncButtonComplete':
// 现在消息包含syncPageEnName信息
const syncPageEnName = message.data?.syncPageEnName;
if (syncPageEnName) {
switchSyncButtonToNormal(syncPageEnName);
}
break;
case 'syncLogicComplete':
const logicPageEnName = message.data?.pageNameEn;
if (logicPageEnName) {
switchSyncLogicButtonToNormal(logicPageEnName);
// 显示成功提示
vscode.postMessage({
command: 'showMessage',
message: '逻辑同步完成!',
type: 'info'
});
}
break;
default:
}
} else {
console.warn('📨 [消息监听] [前端] ⚠️ 收到无效消息:', message);
}
});
// 处理openProject转发消息的函数
function handleInitProjectPageList(projectData) {
if (projectData) {
const projectName = projectData.projectName || projectData.name;
if (projectName) {
// 更新页面列表标题
updatePageListHeader(projectName);
} else {
console.error('❌ [ByteFun Sidebar] 项目数据中没有项目名称');
}
// 解析并生成页面列表
if (projectData.projectPageList) {
try {
const pageList = typeof projectData.projectPageList === 'string'
? JSON.parse(projectData.projectPageList)
: projectData.projectPageList;
if (Array.isArray(pageList) && pageList.length > 0) {
generatePageList(pageList);
// 发送消息给sidebarProvider,要求创建页面TS文件
vscode.postMessage({
command: 'tryToCreatePageTSFile',
projectData: projectData
});
} else {
showEmptyPageList();
}
} catch (error) {
console.error('❌ [ByteFun Sidebar] 解析页面列表失败:', error);
showEmptyPageList();
}
} else {
showEmptyPageList();
}
} else {
console.error('❌ [ByteFun Sidebar] 项目数据为空');
}
}
// 更新页面列表标题
function updatePageListHeader(projectName) {
const header = document.querySelector('.page-list-header-title');
if (header) {
const newTitle = projectName + ' - 页面列表';
header.textContent = newTitle;
} else {
console.error('❌ [ByteFun Sidebar] 找不到页面列表标题元素');
}
}
// 生成页面列表
function generatePageList(pageList) {
const pageListContainer = document.querySelector('.page-list');
if (!pageListContainer) {
console.error('❌ [ByteFun Sidebar] 找不到页面列表容器');
return;
}
// 清空现有内容
pageListContainer.innerHTML = '';
// 递归生成树形页面项
pageList.forEach((page, index) => {
renderPageTree(page, pageListContainer, 0);
});
}
// 递归渲染页面树
function renderPageTree(page, container, level) {
const pageItem = createPageItem(page, level);
container.appendChild(pageItem);
// 如果是文件夹且有子项,创建子项容器
if (page.resourceId === 0 && page.children && page.children.length > 0) {
const childrenContainer = document.createElement('div');
childrenContainer.className = 'page-children-container';
childrenContainer.setAttribute('data-parent-id', page.nodeId);
childrenContainer.style.display = 'none'; // 默认折叠
// 递归渲染子项
page.children.forEach(child => {
renderPageTree(child, childrenContainer, level + 1);
});
container.appendChild(childrenContainer);
}
}
// 创建页面项元素
function createPageItem(page, level = 0) {
const pageItem = document.createElement('div');
// 判断是文件夹还是文件
const isFolder = page.resourceId === 0;
const hasChildren = isFolder && page.children && page.children.length > 0;
pageItem.className = isFolder ? 'page-item folder-item' : 'page-item file-item';
pageItem.setAttribute('data-page-id', page.nodeId || page.id);
pageItem.setAttribute('data-level', level);
// 设置缩进
pageItem.style.paddingLeft = `${level * 20 + 8}px`;
// 格式化时间
const updateTime = formatTime(page.nodeUpdateTime || page.resourceUpdateTime);
// 获取页面类型显示名称
const pageTypeName = getPageTypeName(page.type);
// 构建HTML内容
let expandIcon = '';
if (isFolder) {
expandIcon = hasChildren ?
`<span class="expand-icon" data-expanded="false">▶</span>` :
`<span class="expand-icon empty"> </span>`;
}
// 获取合适的图标
let iconContent = '';
if (isFolder) {
iconContent = `<span class="folder-icon">📁</span>`;
} else {
const iconSrc = page.thumbnail || getDefaultPageIcon(page.type);
iconContent = `<img src="${iconSrc}" alt="${page.name}">`;
}
// 构建文件夹名称(包含子项计数)
let displayName = page.name;
if (isFolder && page.children && page.children.length > 0) {
displayName = `${page.name} (${page.children.length})`;
}
pageItem.innerHTML = `
<div class="page-item-header">
${expandIcon}
<div class="page-icon">
${iconContent}
</div>
<div class="page-content">
<div class="page-title-row">
<div class="page-title">${displayName}</div>
<div class="page-time">${updateTime}</div>
</div>
${!isFolder ? `
<div class="page-meta">
<div class="page-author">
<svg class="page-author-icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
</svg>
${page.authorName || page.resourceAuthorName || 'Unknown'}
</div>
<div class="page-type">${pageTypeName}</div>
</div>
` : ''}
</div>
${!isFolder ? `<button class="page-sync-button" title="同步代码" data-page-id="${page.nodeId || page.id}">同步代码</button>` : ''}
</div>
`;
// 为文件夹添加展开/折叠事件
if (isFolder && hasChildren) {
const expandIcon = pageItem.querySelector('.expand-icon');
const header = pageItem.querySelector('.page-item-header');
addManagedEventListener(header, 'click', (e) => {
e.stopPropagation();
toggleFolderExpansion(page.nodeId, expandIcon);
});
// 添加样式,使文件夹可点击
header.style.cursor = 'pointer';
} else if (!isFolder) {
// 为文件添加点击事件
const header = pageItem.querySelector('.page-item-header');
addManagedEventListener(header, 'click', () => openProjectPage(page));
}
// 为文件的图片添加错误处理
if (!isFolder) {
const img = pageItem.querySelector('img');
if (img) {
addManagedEventListener(img, 'error', function () {
this.src = getDefaultPageIcon(page.type);
});
}
// 为同步按钮添加事件监听器
const syncButton = pageItem.querySelector('.page-sync-button');
if (syncButton) {
addManagedEventListener(syncButton, 'click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发页面点击事件
handlePageSyncClick(page);
});
}
}
return pageItem;
}
// 切换文件夹展开/折叠状态
function toggleFolderExpansion(folderId, expandIcon) {
const childrenContainer = document.querySelector(`[data-parent-id="${folderId}"]`);
if (childrenContainer) {
const isExpanded = expandIcon.getAttribute('data-expanded') === 'true';
if (isExpanded) {
// 折叠
childrenContainer.style.display = 'none';
expandIcon.textContent = '▶';
expandIcon.setAttribute('data-expanded', 'false');
} else {
// 展开
childrenContainer.style.display = 'block';
expandIcon.textContent = '▼';
expandIcon.setAttribute('data-expanded', 'true');
}
}
}
// 获取页面类型名称
function getPageTypeName(type) {
const typeMap = {
1: '文件夹',
2: '页面',
3: '组件',
4: '模板',
5: '子模块',
6: '公共页面'
};
return typeMap[type] || '页面';
}
// 获取默认页面图标
function getDefaultPageIcon(type) {
const iconMap = {
1: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0LjUgMTMuNWgtMTNBLjUuNSAwIDAxMSAxM1YzYS41LjUgMCAwMS41LS41SDZsMS0xaDdhLjUuNSAwIDAxLjUuNXY5YS41LjUgMCAwMS0uNS41eiIvPgo8L3N2Zz4K', // 文件夹
2: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIgMmEyIDIgMCAwMC0yIDJ2OGEyIDIgMCAwMDIgMmgxMmEyIDIgMCAwMDItMlY0YTIgMiAwIDAwLTItMkgyem0wIDJoMTJ2OEgyVjR6Ii8+Cjwvc3ZnPgo=', // 页面
3: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTggMGE4IDggMCAxMDAgMTZBOCA4IDAgMDA4IDB6TTcgM2EuNS41IDAgMDEuNS0uNWguNWEuNS41IDAgMDEuNS41djJhLjUuNSAwIDAxLS41LjVoLS41YS41LjUgMCAwMS0uNS0uNVYzem0wIDVhLjUuNSAwIDAxLjUtLjVoLjVhLjUuNSAwIDAxLjUuNXY0YS41LjUgMCAwMS0uNS41aC0uNWEuNS41IDAgMDEtLjUtLjV6bTAgMmEuNS41IDAgMDEuNS0uNWgzYS41LjUgMCAwMTAgMWgtNWEuNS41IDAgMDEtLjUtLjV6Ii8+Cjwvc3ZnPgo=', // 组件
4: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMgMmEyIDIgMCAwMC0yIDJ2OGEyIDIgMCAwMDIgMmgxMGEyIDIgMCAwMDItMlY0YTIgMiAwIDAwLTItMkgzem0wIDJoMTB2OEgzVjR6bTIgMmEuNS41IDAgMDEuNS0uNWg1YS41LjUgMCAwMTAgMWgtNWEuNS41IDAgMDEtLjUtLjV6bTAgMmEuNS41IDAgMDEuNS0uNWgzYS41LjUgMCAwMTAgMWgtM2EuNS41IDAgMDEtLjUtLjV6Ii8+Cjwvc3ZnPgo=', // 模板
5: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTggMGE4IDggMCAxMDAgMTZBOCA4IDAgMDA4IDB6bTMuNSA2TDggOS41IDQuNSA2aDd6Ii8+Cjwvc3ZnPgo=', // 子模块
6: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzAwNzBmMyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTggMGE4IDggMCAxMDAgMTZBOCA4IDAgMDA4IDB6bTAgMTRhNiA2IDAgMTEwLTEyIDYgNiAwIDAxMCAxMnoiLz4KPC9zdmc+Cg==' // 公共页面
};
return iconMap[type] || iconMap[2]; // 默认使用页面图标
}
// 格式化时间
function formatTime(timestamp) {
if (!timestamp) return '未知时间';
try {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
// 计算时间差
const minutes = Math.floor(diff / (1000 * 60));
const hours = Math.floor(diff / (1000 * 60 * 60));
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (minutes < 1) {
return '刚刚';
} else if (minutes < 60) {
return minutes + '分钟前';
} else if (hours < 24) {
return hours + '小时前';
} else if (days < 7) {
return days + '天前';
} else {
// 超过一周显示具体日期
return date.toLocaleDateString('zh-CN', {
month: 'short',
day: 'numeric'
});
}
} catch (error) {
console.error('格式化时间失败:', error);
return '时间错误';
}
}
// 显示空页面列表
function showEmptyPageList() {
const pageListContainer = document.querySelector('.page-list');
if (pageListContainer) {
pageListContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📁</div>
<div>从我的项目打开vscode工程</div>
</div>
`;
} else {
console.error('❌ [ByteFun Sidebar] 找不到页面列表容器');
}
}
function selectPage(pageId) {
// 移除所有页面的选中状态
const allPages = document.querySelectorAll('.page-item');
allPages.forEach(page => {
page.classList.remove('active');
});
// 为指定页面添加选中状态
const targetPage = document.querySelector('[data-page-id="' + pageId + '"]');
if (targetPage) {
targetPage.classList.add('active');
state.selectedPageId = pageId;
vscode.setState(state);
}
}
function setActiveMenu(menuId) {
// 移除所有菜单项的激活状态
const allMenuItems = document.querySelectorAll('.menu-item');
allMenuItems.forEach(item => {
item.classList.remove('active');
});
// 为指定菜单项添加激活状态
if (menuId === 'project') {
allMenuItems[0].classList.add('active');
} else if (menuId === 'library') {
allMenuItems[1].classList.add('active');
}
state.activeMenuId = menuId;
vscode.setState(state);
}
function handleProjectClick() {
setActiveMenu('project');
vscode.postMessage({
command: 'openProjects'
});
}
function handleLibraryClick() {
setActiveMenu('library');
vscode.postMessage({
command: 'openLibrary'
});
}
function handleRefreshClick() {
vscode.postMessage({
command: 'refresh'
});
}
function handleSyncAllClick() {
vscode.postMessage({
command: 'syncAllPages'
});
}
function handlePageSyncClick(pageData) {
vscode.postMessage({
command: 'syncPage',
pageData: pageData
});
}
function openProjectPage(page) {
selectPage(page.nodeId || page.id);
vscode.postMessage({
command: 'openProjectPage',
pageData: page
});
}
// Tab 切换功能
function switchTab(tabId) {
// 获取所有tab项和面板
const tabItems = document.querySelectorAll('.tab-item');
const tabPanels = document.querySelectorAll('.tab-panel');
// 移除所有active状态
tabItems.forEach(item => {
item.classList.remove('active');
});
tabPanels.forEach(panel => {
panel.classList.remove('active');
});
// 激活选中的tab
const selectedTabItem = document.querySelector(`[data-tab="${tabId}"]`);
const selectedTabPanel = document.querySelector(`[data-panel="${tabId}"]`);
if (selectedTabItem) {
selectedTabItem.classList.add('active');
}
if (selectedTabPanel) {
selectedTabPanel.classList.add('active');
}
// 保存状态
state.activeTab = tabId;
vscode.setState(state);
// 如果是聊天角色tab,加载聊天记录
if (tabId === 'product') {
// 产品角色使用特殊的item加载方式
loadProductItems();
} else if (tabId === 'design') {
// 设计角色使用特殊的item加载方式
loadDesignItems();
} else if (tabId === 'frontEndDev') {
// 前端开发角色使用特殊的item加载方式
loadFrontendDevItems();
} else if (tabId === 'backEndDev') {
// 后端开发角色使用特殊的item加载方式
loadBackendDevItems();
} else if (['operations'].includes(tabId)) {
// 其他角色使用聊天方式
loadChatMessages(tabId);
} else if (tabId === 'team') {
loadTeamChatMessages();
}
}
// 加载聊天消息
function loadChatMessages(roleType) {
vscode.postMessage({
command: 'loadChatSessions',
roleType: roleType
});
}
// 加载产品项目
function loadProductItems() {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'product'
});
}
// 加载设计项目
function loadDesignItems() {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'design'
});
}
// 加载前端开发项目(用于前端开发tab)
function loadFrontendDevItems() {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'frontEndDev'
});
}
// 加载后端开发项目(用于后端开发tab)
function loadBackendDevItems() {
vscode.postMessage({
command: 'loadChatSessions',
roleType: 'backEndDev'
});
}
// 发送聊天消息
function sendChatMessage(roleType) {
const input = document.getElementById(`${roleType}-input`);
const sendButton = document.getElementById(`${roleType}-send`);
if (!input || !sendButton) {
return;
}
const content = input.value.trim();
if (!content) {
return;
}
// 禁用输入框和按钮
input.disabled = true;
sendButton.disabled = true;
sendButton.textContent = '发送中...';
// 获取当前会话(如果没有则会自动创建)
vscode.postMessage({
command: 'sendMessage',
roleType: roleType,
content: content
});
// 清空输入框
input.value = '';
input.style.height = 'auto';
}
// 渲染聊天消息
function renderChatMessages(roleType, sessions) {
const messagesContainer = document.getElementById(`${roleType}-messages`);
if (!messagesContainer) {
return;
}
// 清空现有内容
messagesContainer.innerHTML = '';
if (!sessions || sessions.length === 0) {
// 创建默认对话并显示自我介绍
createDefaultChatSession(roleType);
return;
}
// 找到最新的会话(第一个)
const latestSession = sessions[0];
if (!latestSession || !latestSession.messages || latestSession.messages.length === 0) {
// 如果会话为空,添加自我介绍
addIntroductionMessage(roleType);
return;
}
// 渲染会话中的所有消息
latestSession.messages.forEach((message, index) => {
const messageElement = createChatMessageElement(message);
messagesContainer.appendChild(messageElement);
});
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 重新启用输入框
resetChatInput(roleType);
}
// 创建聊天消息元素
function createChatMessageElement(message) {
const messageElement = document.createElement('div');
messageElement.className = `chat-message ${message.role}`;
// 创建头像元素
const avatar = document.createElement('div');
avatar.className = `chat-avatar ${message.role}`;
if (message.role === 'user') {
avatar.className += ' user';
avatar.textContent = '我';
} else {
// 为AI助手添加角色特定的头像和样式
avatar.className += ` assistant ${message.roleType}`;
const roleAvatars = {
product: '📊',
design: '🎨',
frontEndDev: '💻',
backEndDev: '🧪',
operations: '📈'
};
avatar.textContent = roleAvatars[message.roleType] || '🤖';
}
// 创建消息包装器
const messageWrapper = document.createElement('div');
messageWrapper.className = 'chat-message-wrapper';
// 创建消息内容
const content = document.createElement('div');
content.className = 'chat-message-content';
// 解析并渲染带链接的内容
const processedContent = parseChatLinks(message.content, message.roleType, message);
content.innerHTML = processedContent;
// 创建时间戳(如果有的话)
if (message.timestamp) {
const timeElement = document.createElement('div');
timeElement.className = 'chat-message-time';
timeElement.textContent = formatChatTime(message.timestamp);
messageWrapper.appendChild(content);
messageWrapper.appendChild(timeElement);
} else {
messageWrapper.appendChild(content);
}
// 组装消息元素
messageElement.appendChild(avatar);
messageElement.appendChild(messageWrapper);
return messageElement;
}
// 创建默认聊天会话
function createDefaultChatSession(roleType) {
vscode.postMessage({
command: 'createChatSession',
roleType: roleType
});
}
// 添加自我介绍消息
function addIntroductionMessage(roleType) {
const messagesContainer = document.getElementById(`${roleType}-messages`);
if (!messagesContainer) {
return;
}
const introductions = {
product: '👋 你好!我是产品经理,我可以帮助你进行需求分析、产品规划、用户体验设计等工作。',
design: '👋 你好!我是UI设计师,我可以帮助你进行UI/UX设计、视觉设计、交互设计等工作。',
frontEndDev: '👋 你好!我是前端开发工程师,我可以帮助你解决前端的技术问题、代码实现、架构设计等问题。',
backEndDev: '👋 你好!我是后端开发工程师,我可以帮助你解决后端的技术问题、代码实现、架构设计等问题。',
test: '👋 你好!我是测试工程师,我可以帮助你进行测试用例设计、质量保证、Bug分析等工作。',
operations: '👋 你好!我是运营专员,我可以帮助你进行用户运营、数据分析、活动策划等工作。'
};
const introMessage = {
role: 'assistant',
content: introductions[roleType] || '👋 你好!我可以帮助你解决各种问题。',
roleType: roleType,
timestamp: Date.now()
};
const messageElement = createChatMessageElement(introMessage);
messagesContainer.appendChild(messageElement);
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 重置聊天输入框状态
function resetChatInput(roleType) {
const input = document.getElementById(`${roleType}-input`);
const sendButton = document.getElementById(`${roleType}-send`);
if (input) {
input.disabled = false;
}
if (sendButton) {
sendButton.disabled = false;
sendButton.textContent = '发送';
}
}
// 格式化聊天时间
function formatChatTime(timestamp) {
if (!timestamp) return '';
try {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
// 计算时间差
const minutes = Math.floor(diff / (1000 * 60));
const hours = Math.floor(diff / (1000 * 60 * 60));
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (minutes < 1) {
return '刚刚';
} else if (minutes < 60) {
return minutes + 'm';
} else if (hours < 24) {
return hours + 'h';
} else if (days < 7) {
return days + 'd';
} else {
// 超过一周显示具体日期
return date.toLocaleDateString('zh-CN', {
month: 'short',
day: 'numeric'
});
}
} catch (error) {
console.error('格式化聊天时间失败:', error);
return '';
}
}
// 处理聊天会话加载完成
function handleChatSessionsLoaded(data) {
// 特殊处理产品角色和设计角色 - 已经改为使用items消息类型
if (data && data.roleType === 'product') {
return;
}
if (data && data.roleType === 'design') {
return;
}
// 特殊处理前端开发和后端开发角色 - 已经改为使用items消息类型
if (data && data.roleType === 'frontEndDev') {
return;
}
if (data && data.roleType === 'backEndDev') {
return;
}
if (data && data.roleType && Array.isArray(data.sessions)) {
renderChatMessages(data.roleType, data.sessions);
} else {
console.error('🔄 [流程-2] [前端] ❌ 聊天消息数据格式错误:', data);
}
}
// 处理产品项目加载完成
function handleProductItemsLoaded(items) {
if (Array.isArray(items)) {
renderProductItems(items);
} else {
console.error('📊 [产品角色] ❌ 产品项目数据格式错误:', items);
renderProductItems([]);
}
}
// 渲染产品项目列表
function renderProductItems(items) {
const itemsContainer = document.getElementById('product-items');
if (!itemsContainer) {
console.error('📊 [产品角色] ❌ 未找到产品项目容器');
return;
}
// 清空现有内容
itemsContainer.innerHTML = '';
if (!items || items.length === 0) {
// 显示空状态
const emptyState = document.createElement('div');
emptyState.className = 'empty-product-state';
emptyState.innerHTML = `
<div class="empty-product-state-icon">📋</div>
<div>暂无产品文档</div>
<div style="font-size: 12px; margin-top: 8px; opacity: 0.7;">
请在AI输入框输入发送:设计产品需求。
</div>
`;
itemsContainer.appendChild(emptyState);
return;
}
// 渲染每个项目
items.forEach(item => {
const itemElement = createProductItemElement(item);
itemsContainer.appendChild(itemElement);
});
}
// 创建产品项目元素
function createProductItemElement(item) {
const itemElement = document.createElement('div');
itemElement.className = 'product-item';
// 为业务逻辑item添加特殊类名以应用不同的高度
if (item.type === 'business_logic') {
itemElement.classList.add('business-logic');
}
// 创建操作按钮容器
const actions = document.createElement('div');
actions.className = 'product-item-actions';
// // 只在业务逻辑设计item中显示编辑按钮
// if (item.type === 'business_logic') {
// // 创建编辑按钮
// const editButton = document.createElement('button');
// editButton.className = 'edit-button icon-button';
// editButton.title = '编辑文档'; // 添加hover提示
// // 创建图标img元素
// const editIcon = document.createElement('img');
// editIcon.src = window.ICON_URIS.editIcon;
// editIcon.className = 'button-icon';
// editIcon.alt = '编辑';
// editButton.appendChild(editIcon);
// editButton.addEventListener('click', function (e) {
// e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
// handleProductItemEdit(item);
// });
// actions.appendChild(editButton);
// }
// 为整个item添加点击事件和cursor样式
itemElement.style.cursor = 'pointer';
itemElement.addEventListener('click', function () {
handleProductItemClick(item);
});
// 区分两种不同的布局
if (item.type === 'business_logic') {
// 业务逻辑设计item:一行布局,无左侧图标
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
// 只创建标题,状态图标已在后端合并到title中
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
contentArea.appendChild(title);
itemElement.appendChild(contentArea);
} else {
// 产品需求文档item:保持原有布局(图标+内容)
const icon = document.createElement('div');
icon.className = 'product-item-icon';
// 使用PNG图标替换emoji
const iconImg = document.createElement('img');
iconImg.src = window.ICON_URIS.productIcon;
iconImg.className = 'item-icon-img';
iconImg.alt = '产品需求文档';
icon.appendChild(iconImg);
const content = document.createElement('div');
content.className = 'product-item-content';
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
const subtitle = document.createElement('div');
subtitle.className = 'product-item-subtitle';
subtitle.textContent = '产品需求文档';
content.appendChild(title);
content.appendChild(subtitle);
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
contentArea.appendChild(icon);
contentArea.appendChild(content);
itemElement.appendChild(contentArea);
}
itemElement.appendChild(actions);
return itemElement;
}
// 处理产品项目点击事件(预览)
function handleProductItemClick(item) {
if (item.type === 'document' && item.path) {
// 打开产品需求设计规范预览
vscode.postMessage({
command: 'openPrdDesignSpec'
});
} else if (item.type === 'business_logic' && item.moduleData) {
// 处理业务逻辑设计点击预览
vscode.postMessage({
command: 'openBusinessLogicModule',
moduleData: item.moduleData
});
}
}
// 处理产品项目编辑事件
function handleProductItemEdit(item) {
if (item.type === 'document') {
// 编辑产品需求文档
vscode.postMessage({
command: 'editProductDocument',
item: item
});
} else if (item.type === 'business_logic' && item.moduleData) {
// 编辑业务逻辑设计文档
vscode.postMessage({
command: 'editBusinessLogicModule',
moduleData: item.moduleData
});
}
}
// 处理设计项目加载完成
function handleDesignItemsLoaded(items) {
if (Array.isArray(items)) {
renderDesignItems(items);
} else {
console.error('🎨 [设计角色] ❌ 设计项目数据格式错误:', items);
renderDesignItems([]);
}
}
// 处理前端开发项目数据加载完成
function handleFrontendDevItemsLoaded(data) {
const items = data?.items || [];
if (Array.isArray(items)) {
renderFrontendDevItems(items);
} else {
console.error('💻 [前端开发角色] ❌ 前端开发项目数据格式错误:', data);
renderFrontendDevItems([]);
}
}
// 处理后端开发项目数据加载完成
function handleBackendDevItemsLoaded(data) {
const items = data?.items || [];
if (Array.isArray(items)) {
renderBackendDevItems(items);
} else {
console.error('🔧 [后端开发角色] ❌ 后端开发项目数据格式错误:', data);
renderBackendDevItems([]);
}
}
// 渲染设计项目列表
function renderDesignItems(items) {
const itemsContainer = document.getElementById('design-messages');
if (!itemsContainer) {
console.error('🎨 [设计角色] ❌ 未找到设计项目容器');
return;
}
// 清空现有内容
itemsContainer.innerHTML = '';
if (!items || items.length === 0) {
// 显示空状态
const emptyState = document.createElement('div');
emptyState.className = 'empty-product-state';
emptyState.innerHTML = `
<div class="empty-product-state-icon">🎨</div>
<div>暂无UI设计稿</div>
<div style="font-size: 12px; margin-top: 8px; opacity: 0.7;">
AI输入发送:设计UI规范。完成后审核UI设计规范,并开始AI设计UI界面。
</div>
`;
itemsContainer.appendChild(emptyState);
return;
}
// 渲染每个项目
items.forEach(item => {
const itemElement = createDesignItemElement(item);
itemsContainer.appendChild(itemElement);
});
}
// 创建设计项目元素
function createDesignItemElement(item) {
const itemElement = document.createElement('div');
itemElement.className = 'product-item';
// 为页面UI设计item添加特殊类名
if (item.type === 'page_ui_design') {
itemElement.classList.add('business-logic');
// 设置页面ID和页面英文名用于后续查找
if (item.pageData && item.pageData.id) {
itemElement.setAttribute('data-page-id', item.pageData.id);
}
if (item.pageData && item.pageData.pageNameEN) {
itemElement.setAttribute('data-page-en-name', item.pageData.pageNameEN);
}
}
// 为整个item添加点击事件和cursor样式
itemElement.style.cursor = 'pointer';
itemElement.addEventListener('click', function () {
handleDesignItemClick(item);
});
// 创建操作按钮容器
const actions = document.createElement('div');
actions.className = 'product-item-actions';
// 只有页面UI设计item才显示按钮组
if (item.type === 'page_ui_design') {
// 创建同步按钮
const syncButton = document.createElement('button');
syncButton.className = 'edit-button icon-button';
syncButton.title = '同步UI部分'; // 添加hover提示
// 创建图标img元素
const synIcon = document.createElement('img');
synIcon.src = window.ICON_URIS.synIcon;
synIcon.className = 'button-icon';
synIcon.alt = '同步UI部分';
// 根据status状态添加颜色类(status在pageData中)
if (item.pageData && item.pageData.status === 'completed') {
synIcon.classList.add('syn-icon-completed');
}
syncButton.appendChild(synIcon);
syncButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
handleUIDesignSync(item);
});
actions.appendChild(syncButton);
// 创建设计思路按钮
const thinkingButton = document.createElement('button');
thinkingButton.className = 'edit-button icon-button';
thinkingButton.title = '设计思考'; // 添加hover提示
// 创建图标img元素
const thinkIcon = document.createElement('img');
thinkIcon.src = window.ICON_URIS.thinkIcon;
thinkIcon.className = 'button-icon';
thinkIcon.alt = '思路';
// 根据status状态添加颜色类(status在pageData中)
if (item.pageData && item.pageData.status === 'completed') {
thinkIcon.classList.add('think-icon-completed');
}
thinkingButton.appendChild(thinkIcon);
thinkingButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
handleUIDesignThinking(item);
});
// 创建编辑按钮
const editButton = document.createElement('button');
editButton.className = 'edit-button icon-button';
editButton.title = '编辑UI'; // 添加hover提示
// 创建图标img元素
const editIcon = document.createElement('img');
editIcon.src = window.ICON_URIS.editIcon;
editIcon.className = 'button-icon';
editIcon.alt = '编辑';
editButton.appendChild(editIcon);
editButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
handleDesignItemEdit(item);
});
actions.appendChild(thinkingButton);
actions.appendChild(editButton);
}
// 区分两种不同的布局
if (item.type === 'page_ui_design') {
// 页面UI设计item:一行布局,无左侧图标
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
// 只创建标题,状态图标已在后端合并到title中
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
contentArea.appendChild(title);
itemElement.appendChild(contentArea);
} else {
// 预览设计稿item:保持原有布局(图标+内容)
const icon = document.createElement('div');
icon.className = 'product-item-icon';
// 使用PNG图标替换emoji
const iconImg = document.createElement('img');
iconImg.src = window.ICON_URIS.uiDesignIcon;
iconImg.className = 'item-icon-img';
iconImg.alt = '设计稿预览';
icon.appendChild(iconImg);
const content = document.createElement('div');
content.className = 'product-item-content';
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
const subtitle = document.createElement('div');
subtitle.className = 'product-item-subtitle clickable';
subtitle.textContent = item.subtitle || '查看设计规范';
// 为副标题添加点击事件
subtitle.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡
handleDesignSpecClick();
});
content.appendChild(title);
content.appendChild(subtitle);
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
contentArea.appendChild(icon);
contentArea.appendChild(content);
itemElement.appendChild(contentArea);
}
// 只有有按钮的item才添加actions容器
if (actions.children.length > 0) {
itemElement.appendChild(actions);
}
return itemElement;
}
// 渲染前端开发项目列表
function renderFrontendDevItems(items) {
const itemsContainer = document.getElementById('frontEndDev-messages');
if (!itemsContainer) {
console.error('💻 [前端开发角色] ❌ 未找到前端开发项目容器');
return;
}
// 清空现有内容
itemsContainer.innerHTML = '';
if (items.length === 0) {
itemsContainer.innerHTML = '<div class="empty-state">暂无前端开发项目</div>';
return;
}
// 渲染每个项目
items.forEach(item => {
const itemElement = createFrontendDevItemElement(item);
itemsContainer.appendChild(itemElement);
});
}
// 渲染后端开发项目列表
function renderBackendDevItems(items) {
const itemsContainer = document.getElementById('backEndDev-messages');
if (!itemsContainer) {
console.error('🔧 [后端开发角色] ❌ 未找到后端开发项目容器');
return;
}
// 清空现有内容
itemsContainer.innerHTML = '';
if (items.length === 0) {
itemsContainer.innerHTML = '<div class="empty-state">暂无后端开发项目</div>';
return;
}
// 渲染每个项目
items.forEach(item => {
const itemElement = createBackendDevItemElement(item);
itemsContainer.appendChild(itemElement);
});
}
// 创建前端开发项目元素
function createFrontendDevItemElement(item) {
const itemElement = document.createElement('div');
itemElement.className = 'product-item business-logic';
// 设置页面ID和页面英文名用于后续查找(参考UI设计实现)
if (item.pageData && item.pageData.id) {
itemElement.setAttribute('data-page-id', item.pageData.id);
}
if (item.pageData && item.pageData.pageNameEN) {
itemElement.setAttribute('data-page-en-name', item.pageData.pageNameEN);
}
// 为整个item添加点击事件和cursor样式
itemElement.style.cursor = 'pointer';
itemElement.addEventListener('click', function () {
handleFrontendDevItemClick(item);
});
// 创建操作按钮容器
const actions = document.createElement('div');
actions.className = 'product-item-actions';
// 创建同步逻辑部分按钮
const syncLogicButton = document.createElement('button');
syncLogicButton.className = 'edit-button icon-button';
syncLogicButton.title = '同步逻辑部分'; // 添加hover提示
// 创建同步图标img元素
const syncIcon = document.createElement('img');
syncIcon.src = window.ICON_URIS.synIcon;
syncIcon.className = 'button-icon';
syncIcon.alt = '同步逻辑部分';
// 根据codeStatus状态添加颜色类(与思考设计完成状态颜色一样)
if (item.pageData && item.pageData.codeStatus === 'completed') {
syncIcon.classList.add('syn-icon-completed');
}
syncLogicButton.appendChild(syncIcon);
syncLogicButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
handleSyncLogicClick(item);
});
actions.appendChild(syncLogicButton);
// 创建代码设计按钮
const codeDesignButton = document.createElement('button');
codeDesignButton.className = 'edit-button icon-button';
codeDesignButton.title = '代码设计'; // 添加hover提示
// 创建图标img元素
const thinkIcon = document.createElement('img');
thinkIcon.src = window.ICON_URIS.thinkIcon;
thinkIcon.className = 'button-icon';
thinkIcon.alt = '代码设计';
// 根据thinkStatus状态添加颜色类(thinkStatus在pageData中)
if (item.pageData && item.pageData.thinkStatus === 'completed') {
thinkIcon.classList.add('think-icon-completed');
}
codeDesignButton.appendChild(thinkIcon);
codeDesignButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发item点击事件
handleCodeDesignClick(item);
});
actions.appendChild(codeDesignButton);
// 前端开发item:一行布局,无左侧图标
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
// 只创建标题,状态图标已在后端合并到title中
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
contentArea.appendChild(title);
itemElement.appendChild(contentArea);
itemElement.appendChild(actions);
return itemElement;
}
// 创建后端开发项目元素
function createBackendDevItemElement(item) {
const itemElement = document.createElement('div');
itemElement.className = 'product-item';
// 为整个item添加点击事件和cursor样式
itemElement.style.cursor = 'pointer';
itemElement.addEventListener('click', function () {
handleBackendDevItemClick(item);
});
// 区分两种不同的布局
if (item.type === 'backend_api_design') {
// 后端接口设计item:保持原有布局(图标+内容),类似于设计角色的"预览设计稿"
const icon = document.createElement('div');
icon.className = 'product-item-icon';
// 使用PNG图标替换emoji
const iconImg = document.createElement('img');
iconImg.src = window.ICON_URIS.productIcon; // 可以使用productIcon或者其他合适的图标
iconImg.className = 'item-icon-img';
iconImg.alt = '后端接口设计';
icon.appendChild(iconImg);
const content = document.createElement('div');
content.className = 'product-item-content';
const title = document.createElement('div');
title.className = 'product-item-title';
title.textContent = item.title;
const subtitle = document.createElement('div');
subtitle.className = 'product-item-subtitle';
subtitle.textContent = item.subtitle || '管理后端API接口';
content.appendChild(title);
content.appendChild(subtitle);
const contentArea = document.createElement('div');
contentArea.style.display = 'flex';
contentArea.style.alignItems = 'center';
contentArea.style.flex = '1';
contentArea.appendChild(icon);
contentArea.appendChild(content);
itemElement.appendChild(cont