Selaa lähdekoodia

feat:t-leader,designer-01

0235711 15 tuntia sitten
vanhempi
commit
a7d00b292e
32 muutettua tiedostoa jossa 5986 lisäystä ja 421 poistoa
  1. 96 0
      direct-fix-placeholder-issues.js
  2. 98 0
      fix-placeholder-images.js
  3. 19 0
      package-lock.json
  4. 2 1
      package.json
  5. 72 0
      replace-placeholder-links.js
  6. 3 1
      src/app/app.routes.ts
  7. 1 1
      src/app/pages/admin/admin-layout/admin-layout.ts
  8. 2 2
      src/app/pages/customer-service/consultation-order/consultation-order.ts
  9. 1 1
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.html
  10. 2 2
      src/app/pages/customer-service/project-detail/project-detail.html
  11. 67 0
      src/app/pages/designer/dashboard/dashboard.html
  12. 467 0
      src/app/pages/designer/dashboard/dashboard.scss
  13. 146 4
      src/app/pages/designer/dashboard/dashboard.ts
  14. 46 0
      src/app/pages/designer/dashboard/skill-radar/skill-radar.component.html
  15. 292 0
      src/app/pages/designer/dashboard/skill-radar/skill-radar.component.scss
  16. 178 0
      src/app/pages/designer/dashboard/skill-radar/skill-radar.component.ts
  17. 134 0
      src/app/pages/designer/material-share/material-share.html
  18. 492 0
      src/app/pages/designer/material-share/material-share.scss
  19. 228 0
      src/app/pages/designer/material-share/material-share.ts
  20. 62 5
      src/app/pages/designer/project-detail/project-detail.html
  21. 229 0
      src/app/pages/designer/project-detail/project-detail.scss
  22. 122 4
      src/app/pages/designer/project-detail/project-detail.ts
  23. 1 1
      src/app/pages/login/set-admin-role.html
  24. 504 0
      src/app/pages/team-leader/knowledge-base/knowledge-base.html
  25. 716 0
      src/app/pages/team-leader/knowledge-base/knowledge-base.scss
  26. 441 0
      src/app/pages/team-leader/knowledge-base/knowledge-base.ts
  27. 115 56
      src/app/pages/team-leader/quality-management/quality-management.html
  28. 1316 338
      src/app/pages/team-leader/quality-management/quality-management.scss
  29. 7 3
      src/app/pages/team-leader/quality-management/quality-management.ts
  30. 1 1
      src/app/services/auth.service.ts
  31. 125 0
      src/app/services/project.service.ts
  32. 1 1
      src/app/shared/components/designer-nav/designer-nav.html

+ 96 - 0
direct-fix-placeholder-issues.js

@@ -0,0 +1,96 @@
+const fs = require('fs');
+const path = require('path');
+
+// 定义需要修复的文件路径
+const filesToFix = [
+  {
+    path: path.join(__dirname, 'src', 'app', 'shared', 'components', 'designer-nav', 'designer-nav.html'),
+    type: 'html'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'admin', 'admin-layout', 'admin-layout.ts'),
+    type: 'ts'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'customer-service', 'customer-service-layout', 'customer-service-layout.html'),
+    type: 'html'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'customer-service', 'consultation-order', 'consultation-order.ts'),
+    type: 'ts'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'customer-service', 'project-detail', 'project-detail.html'),
+    type: 'html'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'login', 'set-admin-role.html'),
+    type: 'html'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'pages', 'team-leader', 'knowledge-base', 'knowledge-base.ts'),
+    type: 'ts'
+  },
+  {
+    path: path.join(__dirname, 'src', 'app', 'services', 'auth.service.ts'),
+    type: 'ts'
+  }
+];
+
+// 修复HTML文件
+function fixHtmlFile(filePath) {
+  try {
+    let content = fs.readFileSync(filePath, 'utf8');
+    
+    // 修复img标签内包含div的问题
+    const fixedContent = content.replace(/<img\s+src="<div\s+style="([^"]*)">([^<]*)<\/div>"\s+alt="([^"]*)"\s+class="([^"]*)"\s*\/?>/g,
+      '<div style="$1" class="$4" title="$3">$2</div>');
+    
+    if (content !== fixedContent) {
+      fs.writeFileSync(filePath, fixedContent, 'utf8');
+      console.log(`已修复HTML文件: ${filePath}`);
+      return 1;
+    }
+  } catch (error) {
+    console.error(`修复HTML文件失败: ${filePath}`, error);
+  }
+  return 0;
+}
+
+// 修复TypeScript文件
+function fixTsFile(filePath) {
+  try {
+    let content = fs.readFileSync(filePath, 'utf8');
+    
+    // 修复avatar属性的格式问题
+    const fixedContent = content.replace(/avatar:\s*'\{\s*width:\s*(\d+),\s*height:\s*(\d+),\s*bgColor:\s*'(#[0-9A-Fa-f]+)',\s*textColor:\s*'(#[0-9A-Fa-f]+)',\s*text:\s*'([^']*)'\s*\}'/g,
+      (match, width, height, bgColor, textColor, text) => {
+        // 生成SVG data URL
+        const svg = `data:image/svg+xml,%3Csvg width='${width}' height='${height}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='${encodeURIComponent(bgColor)}'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='${Math.min(parseInt(width), parseInt(height)) / 3}' font-weight='bold' text-anchor='middle' fill='${encodeURIComponent(textColor)}' dy='0.3em'%3E${encodeURIComponent(text)}%3C/text%3E%3C/svg%3E`;
+        return `avatar: '${svg}'`;
+      });
+    
+    if (content !== fixedContent) {
+      fs.writeFileSync(filePath, fixedContent, 'utf8');
+      console.log(`已修复TypeScript文件: ${filePath}`);
+      return 1;
+    }
+  } catch (error) {
+    console.error(`修复TypeScript文件失败: ${filePath}`, error);
+  }
+  return 0;
+}
+
+// 执行修复
+console.log('开始直接修复placeholder链接问题...');
+let totalFixed = 0;
+
+filesToFix.forEach(file => {
+  if (file.type === 'html') {
+    totalFixed += fixHtmlFile(file.path);
+  } else if (file.type === 'ts') {
+    totalFixed += fixTsFile(file.path);
+  }
+});
+
+console.log(`直接修复完成!总共修复了 ${totalFixed} 个文件。`);

+ 98 - 0
fix-placeholder-images.js

@@ -0,0 +1,98 @@
+const fs = require('fs');
+const path = require('path');
+
+// 定义要替换的目录
+const srcDir = path.join(__dirname, 'src');
+
+// 定义要搜索的文件扩展名
+const fileExtensions = ['.ts', '.html'];
+
+// 修复HTML文件中的placeholder链接
+function fixHtmlPlaceholders(fileContent) {
+  let modifiedContent = fileContent;
+  let replacementCount = 0;
+
+  // 正则表达式匹配img标签中的via.placeholder.com链接
+  const imgPlaceholderRegex = /<img\s+src="https:\/\/via\.placeholder\.com\/(\d+)x(\d+)\/([0-9A-Fa-f]+)\/([0-9A-Fa-f]+)\?text=([^"']*)"\s+alt="([^"]*)"\s+class="([^"]*)"\s*\/?>/g;
+
+  // 替换匹配的img标签为div元素
+  modifiedContent = modifiedContent.replace(imgPlaceholderRegex, (match, width, height, bgColor, textColor, text, alt, className) => {
+    replacementCount++;
+    
+    // 返回一个带有内联样式的div元素,保留原有的类名
+    return `<div style="width: ${width}px; height: ${height}px; background-color: #${bgColor}; color: #${textColor}; display: flex; align-items: center; justify-content: center; font-size: ${Math.min(parseInt(width), parseInt(height)) / 3}px; font-weight: bold;" class="${className}" title="${alt}">${text}</div>`;
+  });
+
+  return { modifiedContent, replacementCount };
+}
+
+// 修复TypeScript文件中的placeholder链接
+function fixTsPlaceholders(fileContent) {
+  let modifiedContent = fileContent;
+  let replacementCount = 0;
+
+  // 正则表达式匹配TypeScript中的via.placeholder.com链接
+  const tsPlaceholderRegex = /'https:\/\/via\.placeholder\.com\/(\d+)x(\d+)\/([0-9A-Fa-f]+)\/([0-9A-Fa-f]+)\?text=([^']*)'/g;
+
+  // 替换匹配的链接为一个简单的base64编码的data URL
+  modifiedContent = modifiedContent.replace(tsPlaceholderRegex, (match, width, height, bgColor, textColor, text) => {
+    replacementCount++;
+    
+    // 生成一个简单的SVG data URL,避免对外部服务的依赖
+    // 这是一个简化的SVG,包含背景色和文本
+    const svg = `data:image/svg+xml,%3Csvg width='${width}' height='${height}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23${bgColor}'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='${Math.min(parseInt(width), parseInt(height)) / 3}' font-weight='bold' text-anchor='middle' fill='%23${textColor}' dy='0.3em'%3E${encodeURIComponent(text)}%3C/text%3E%3C/svg%3E`;
+    
+    return `'${svg}'`;
+  });
+
+  return { modifiedContent, replacementCount };
+}
+
+// 遍历目录中的文件
+function traverseDirectory(dir) {
+  const files = fs.readdirSync(dir);
+  let totalReplacements = 0;
+
+  files.forEach(file => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+
+    if (stat.isDirectory()) {
+      const subDirReplacements = traverseDirectory(filePath);
+      totalReplacements += subDirReplacements;
+    } else if (file.endsWith('.html')) {
+      // 读取文件内容
+      const fileContent = fs.readFileSync(filePath, 'utf8');
+      
+      // 修复HTML中的placeholder链接
+      const { modifiedContent, replacementCount } = fixHtmlPlaceholders(fileContent);
+      
+      // 如果有替换,写入文件
+      if (replacementCount > 0) {
+        fs.writeFileSync(filePath, modifiedContent, 'utf8');
+        console.log(`已修复 ${filePath} 中的 ${replacementCount} 个HTML placeholder链接`);
+        totalReplacements += replacementCount;
+      }
+    } else if (file.endsWith('.ts')) {
+      // 读取文件内容
+      const fileContent = fs.readFileSync(filePath, 'utf8');
+      
+      // 修复TypeScript中的placeholder链接
+      const { modifiedContent, replacementCount } = fixTsPlaceholders(fileContent);
+      
+      // 如果有替换,写入文件
+      if (replacementCount > 0) {
+        fs.writeFileSync(filePath, modifiedContent, 'utf8');
+        console.log(`已修复 ${filePath} 中的 ${replacementCount} 个TypeScript placeholder链接`);
+        totalReplacements += replacementCount;
+      }
+    }
+  });
+
+  return totalReplacements;
+}
+
+// 执行修复操作
+console.log('开始修复placeholder链接...');
+const totalReplacements = traverseDirectory(srcDir);
+console.log(`修复完成!总共修复了 ${totalReplacements} 个placeholder链接。`);

+ 19 - 0
package-lock.json

@@ -16,6 +16,7 @@
         "@angular/material": "^20.2.2",
         "@angular/platform-browser": "^20.1.0",
         "@angular/router": "^20.1.0",
+        "chart.js": "^4.5.0",
         "echarts": "^6.0.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
@@ -2218,6 +2219,12 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "license": "MIT"
+    },
     "node_modules/@listr2/prompt-adapter-inquirer": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz",
@@ -5617,6 +5624,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/chart.js": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
+      "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@kurkle/color": "^0.3.0"
+      },
+      "engines": {
+        "pnpm": ">=8"
+      }
+    },
     "node_modules/chokidar": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",

+ 2 - 1
package.json

@@ -29,6 +29,7 @@
     "@angular/material": "^20.2.2",
     "@angular/platform-browser": "^20.1.0",
     "@angular/router": "^20.1.0",
+    "chart.js": "^4.5.0",
     "echarts": "^6.0.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
@@ -50,4 +51,4 @@
     "typescript": "~5.8.2",
     "typescript-eslint": "8.40.0"
   }
-}
+}

+ 72 - 0
replace-placeholder-links.js

@@ -0,0 +1,72 @@
+const fs = require('fs');
+const path = require('path');
+
+// 定义要替换的目录
+const srcDir = path.join(__dirname, 'src');
+
+// 定义要搜索的文件扩展名
+const fileExtensions = ['.ts', '.html'];
+
+// 替换 via.placeholder.com 链接的函数
+function replacePlaceholderLinks(fileContent, filePath) {
+  let modifiedContent = fileContent;
+  let replacementCount = 0;
+
+  // 正则表达式匹配 via.placeholder.com 链接
+  const placeholderRegex = /https:\/\/via\.placeholder\.com\/(\d+)x(\d+)\/([0-9A-Fa-f]+)\/([0-9A-Fa-f]+)\?text=([^"']*)/g;
+
+  // 替换匹配的链接
+  modifiedContent = modifiedContent.replace(placeholderRegex, (match, width, height, bgColor, textColor, text) => {
+    replacementCount++;
+    
+    // 根据文件类型和上下文决定返回什么
+    if (filePath.endsWith('.html')) {
+      // 对于HTML文件,返回一个带有内联样式的div作为占位图
+      return `<div style="width: ${width}px; height: ${height}px; background-color: #${bgColor}; color: #${textColor}; display: flex; align-items: center; justify-content: center; font-size: ${Math.min(width, height) / 3}px; font-weight: bold;">${text}</div>`;
+    } else if (filePath.endsWith('.ts')) {
+      // 对于TypeScript文件,返回一个本地占位图配置对象
+      return `{ width: ${width}, height: ${height}, bgColor: '#${bgColor}', textColor: '#${textColor}', text: '${text}' }`;
+    }
+    
+    // 如果不是HTML或TS文件,保持原样
+    return match;
+  });
+
+  return { modifiedContent, replacementCount };
+}
+
+// 遍历目录中的文件
+function traverseDirectory(dir) {
+  const files = fs.readdirSync(dir);
+  let totalReplacements = 0;
+
+  files.forEach(file => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+
+    if (stat.isDirectory()) {
+      const subDirReplacements = traverseDirectory(filePath);
+      totalReplacements += subDirReplacements;
+    } else if (fileExtensions.some(ext => file.endsWith(ext))) {
+      // 读取文件内容
+      const fileContent = fs.readFileSync(filePath, 'utf8');
+      
+      // 替换 via.placeholder.com 链接
+      const { modifiedContent, replacementCount } = replacePlaceholderLinks(fileContent, filePath);
+      
+      // 如果有替换,写入文件
+      if (replacementCount > 0) {
+        fs.writeFileSync(filePath, modifiedContent, 'utf8');
+        console.log(`已替换 ${filePath} 中的 ${replacementCount} 个placeholder链接`);
+        totalReplacements += replacementCount;
+      }
+    }
+  });
+
+  return totalReplacements;
+}
+
+// 执行替换操作
+console.log('开始替换 via.placeholder.com 链接...');
+const totalReplacements = traverseDirectory(srcDir);
+console.log(`替换完成!总共替换了 ${totalReplacements} 个placeholder链接。`);

+ 3 - 1
src/app/app.routes.ts

@@ -19,6 +19,7 @@ import { Dashboard as TeamLeaderDashboard } from './pages/team-leader/dashboard/
 import { TeamManagementComponent } from './pages/team-leader/team-management/team-management';
 import { ProjectReviewComponent } from './pages/team-leader/project-review/project-review';
 import { QualityManagementComponent } from './pages/team-leader/quality-management/quality-management';
+import { KnowledgeBaseComponent } from './pages/team-leader/knowledge-base/knowledge-base';
 
 // 财务页面
 import { Dashboard as FinanceDashboard } from './pages/finance/dashboard/dashboard';
@@ -75,7 +76,8 @@ export const routes: Routes = [
       { path: 'dashboard', component: TeamLeaderDashboard, title: '组长工作台' },
       { path: 'team-management', component: TeamManagementComponent, title: '团队管理' },
       { path: 'project-review', component: ProjectReviewComponent, title: '项目审核' },
-      { path: 'quality-management', component: QualityManagementComponent, title: '质量管理' }
+      { path: 'quality-management', component: QualityManagementComponent, title: '质量管理' },
+      { path: 'knowledge-base', component: KnowledgeBaseComponent, title: '知识库与能力复制' }
     ]
   },
 

+ 1 - 1
src/app/pages/admin/admin-layout/admin-layout.ts

@@ -11,7 +11,7 @@ import { RouterModule, RouterOutlet } from '@angular/router';
 }) 
 export class AdminLayout {
   sidebarOpen = true;
-  currentUser = { name: '超级管理员', avatar: 'https://via.placeholder.com/40x40/CCFFCC/555555?text=ADMIN' };
+  currentUser = { name: '超级管理员', avatar: "data:image/svg+xml,%3Csvg width='40' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23CCFFCC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EADMIN%3C/text%3E%3C/svg%3E" };
   currentDate = new Date();
 
   toggleSidebar() {

+ 2 - 2
src/app/pages/customer-service/consultation-order/consultation-order.ts

@@ -117,7 +117,7 @@ export class ConsultationOrder {
           phone: '138****5678',
           customerType: '老客户',
           source: '官网咨询',
-          avatar: 'https://via.placeholder.com/64x40/F0F0F0/555555?text=IMG'
+          avatar: "data:image/svg+xml,%3Csvg width='64' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23E6E6E6'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E"
         },
         {
           id: '2',
@@ -125,7 +125,7 @@ export class ConsultationOrder {
           phone: '139****1234',
           customerType: 'VIP客户',
           source: '推荐介绍',
-          avatar: 'https://via.placeholder.com/65x40/DCDCDC/555555?text=IMG'
+          avatar: "data:image/svg+xml,%3Csvg width='65' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23DCDCDC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E"
         }
       ]);
     }

+ 1 - 1
src/app/pages/customer-service/customer-service-layout/customer-service-layout.html

@@ -36,7 +36,7 @@
       <span class="notification-badge">3</span>
     </button>
     <div class="user-profile">
-      <img src="https://via.placeholder.com/40x40/FFCCCC/555555?text=CS" alt="用户头像" class="user-avatar">
+      <div style="width: 40px; height: 40px; background-color: #FFCCCC; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 13.333333333333334px; font-weight: bold;" class="user-avatar" title="用户头像">CS</div>
       <span class="user-name">客服小李</span>
     </div>
   </div>

+ 2 - 2
src/app/pages/customer-service/project-detail/project-detail.html

@@ -260,7 +260,7 @@
               <h4 class="card-title">项目团队</h4>
               <div class="team-info">
                 <div class="team-member">
-                  <img src="https://via.placeholder.com/64x48/F0F0F0/555555?text=IMG" alt="客服小李" class="member-avatar">
+                  <div style="width: 64px; height: 48px; background-color: #DCDCDC; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: bold;" class="member-avatar" title="客服小李">IMG</div>
                   <div class="member-details">
                     <div class="member-name">客服小李</div>
                     <div class="member-role">客户经理</div>
@@ -272,7 +272,7 @@
                   </button>
                 </div>
                 <div class="team-member">
-                  <img src="https://via.placeholder.com/91x48/DCDCDC/555555?text=IMG" alt="张设计师" class="member-avatar">
+                  <div style="width: 91px; height: 48px; background-color: #DCDCDC; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: bold;" class="member-avatar" title="张设计师">IMG</div>
                   <div class="member-details">
                     <div class="member-name">张设计师</div>
                     <div class="member-role">主设计师</div>

+ 67 - 0
src/app/pages/designer/dashboard/dashboard.html

@@ -120,6 +120,73 @@
       </div>
     </section>
 
+    <!-- 设计师代班信息表 -->
+    <section class="shift-info">
+      <div class="section-header">
+        <h2>👥 代班信息</h2>
+        <button class="add-shift-btn" (click)="openShiftModal()">
+          添加代班任务
+        </button>
+      </div>
+      <div class="shift-list">
+        <div class="shift-item" *ngFor="let shift of shiftTasks">
+          <div class="shift-header">
+            <div class="shift-project">{{ shift.projectName }}</div>
+            <div class="shift-priority" [class.priority-high]="shift.priority === '高'" [class.priority-medium]="shift.priority === '中'" [class.priority-low]="shift.priority === '低'">
+              {{ shift.priority }}级
+            </div>
+          </div>
+          <div class="shift-details">
+            <div class="shift-task">{{ shift.taskDescription }}</div>
+            <div class="shift-time">代班时间: {{ shift.shiftDate }}</div>
+          </div>
+          <div class="shift-actions">
+            <button class="view-detail-btn" (click)="viewShiftDetail(shift.id)">查看详情</button>
+            <button class="mark-complete-btn" (click)="markShiftComplete(shift.id)">标记完成</button>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 个人项目饱和度 -->
+    <section class="workload-section">
+      <div class="section-header">
+        <h2>📊 项目饱和度</h2>
+      </div>
+      <div class="workload-container">
+        <div class="workload-bar">
+          <div 
+            class="workload-fill" 
+            [style.width]="workloadPercentage + '%'"
+            [class.workload-normal]="workloadPercentage < 75"
+            [class.workload-warning]="workloadPercentage >= 75 && workloadPercentage < 90"
+            [class.workload-critical]="workloadPercentage >= 90"
+          ></div>
+        </div>
+        <div class="workload-info">
+          <span class="workload-percentage">{{ workloadPercentage }}%</span>
+          <span class="workload-status">{{ getWorkloadStatus() }}</span>
+        </div>
+        <div class="project-timeline">
+          <h4>项目排期表</h4>
+          <div class="timeline-items">
+            <div class="timeline-item" *ngFor="let project of projectTimeline">
+              <div class="timeline-project">{{ project.name }}</div>
+              <div class="timeline-deadline">截止: {{ project.deadline }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 能力维度雷达图 -->
+    <section class="skill-radar-section">
+      <div class="section-header">
+        <h2>能力维度雷达图</h2>
+      </div>
+      <app-skill-radar></app-skill-radar>
+    </section>
+
     <!-- 快速入口区域 -->
     <section class="quick-access-section">
       <div class="section-header">

+ 467 - 0
src/app/pages/designer/dashboard/dashboard.scss

@@ -451,6 +451,179 @@
   border: 1px dashed $ios-border;
 }
 
+/* 代班信息样式 */
+.shift-info {
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: $ios-spacing-xl;
+  box-shadow: $ios-shadow-card;
+  border: 1px solid $ios-border;
+  height: fit-content;
+}
+
+.shift-info .section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $ios-spacing-xl;
+}
+
+.shift-info .section-header h2 {
+  font-size: $ios-font-size-lg;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  display: flex;
+  align-items: center;
+  margin: 0;
+  font-family: $ios-font-family;
+}
+
+.add-shift-btn {
+  padding: $ios-spacing-sm $ios-spacing-lg;
+  border: 1px solid $ios-primary;
+  border-radius: $ios-radius-md;
+  background-color: $ios-card-background;
+  color: $ios-primary;
+  font-size: $ios-font-size-sm;
+  cursor: pointer;
+  transition: $ios-feedback-tap;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.add-shift-btn:hover {
+  background-color: $ios-primary;
+  color: white;
+  transform: translateY(-1px);
+  box-shadow: $ios-shadow-sm;
+}
+
+.add-shift-btn:active {
+  transform: translateY(0);
+}
+
+.shift-list {
+  display: flex;
+  flex-direction: column;
+  gap: $ios-spacing-lg;
+}
+
+.shift-item {
+  background-color: $ios-background-secondary;
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-lg;
+  border: 1px solid $ios-border;
+  transition: $ios-feedback-hover;
+}
+
+.shift-item:hover {
+  background-color: color-mix(in srgb, $ios-background-secondary 70%, white);
+  box-shadow: $ios-shadow-sm;
+}
+
+.shift-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $ios-spacing-md;
+}
+
+.shift-project {
+  font-size: $ios-font-size-base;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.shift-priority {
+  padding: $ios-spacing-xs $ios-spacing-sm;
+  border-radius: $ios-radius-full;
+  font-size: $ios-font-size-xs;
+  font-weight: $ios-font-weight-medium;
+  text-transform: uppercase;
+  font-family: $ios-font-family;
+}
+
+.shift-priority.priority-high {
+  background-color: color-mix(in srgb, $ios-danger 15%, transparent);
+  color: $ios-danger;
+  border: 1px solid $ios-danger;
+}
+
+.shift-priority.priority-medium {
+  background-color: color-mix(in srgb, $ios-warning 15%, transparent);
+  color: $ios-warning;
+  border: 1px solid $ios-warning;
+}
+
+.shift-priority.priority-low {
+  background-color: color-mix(in srgb, $ios-success 15%, transparent);
+  color: $ios-success;
+  border: 1px solid $ios-success;
+}
+
+.shift-details {
+  margin-bottom: $ios-spacing-lg;
+}
+
+.shift-task {
+  font-size: $ios-font-size-sm;
+  color: $ios-text-secondary;
+  margin-bottom: $ios-spacing-xs;
+  line-height: 1.4;
+}
+
+.shift-time {
+  font-size: $ios-font-size-xs;
+  color: $ios-text-tertiary;
+}
+
+.shift-actions {
+  display: flex;
+  gap: $ios-spacing-md;
+}
+
+.view-detail-btn,
+.mark-complete-btn {
+  flex: 1;
+  padding: $ios-spacing-sm;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-md;
+  font-size: $ios-font-size-xs;
+  cursor: pointer;
+  transition: $ios-feedback-tap;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.view-detail-btn {
+  background-color: $ios-card-background;
+  color: $ios-text-secondary;
+}
+
+.view-detail-btn:hover {
+  background-color: $ios-background-tertiary;
+  color: $ios-text-primary;
+  transform: translateY(-1px);
+}
+
+.mark-complete-btn {
+  background-color: $ios-primary;
+  color: white;
+  border-color: $ios-primary;
+}
+
+.mark-complete-btn:hover {
+  background-color: #003A8C;
+  transform: translateY(-1px);
+  box-shadow: $ios-shadow-sm;
+}
+
+.view-detail-btn:active,
+.mark-complete-btn:active {
+  transform: translateY(0);
+}
+
 .reminder-modal {
   position: fixed;
   top: 0;
@@ -546,4 +719,298 @@
   .quick-access-grid {
     grid-template-columns: 1fr;
   }
+}
+
+/* 设计师代班信息表样式 */
+.shift-info-section {
+  grid-column: 1 / -1;
+}
+
+.shift-table {
+  width: 100%;
+  background-color: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  overflow: hidden;
+  box-shadow: $ios-shadow-card;
+  border: 1px solid $ios-border;
+}
+
+.shift-table-header {
+  background-color: color-mix(in srgb, $ios-primary 10%, transparent);
+  padding: 16px 20px;
+  display: grid;
+  grid-template-columns: 2fr 1fr 1fr 1fr;
+  gap: 10px;
+  font-weight: $ios-font-weight-medium;
+  color: $ios-primary;
+  font-size: 15px;
+  border-bottom: 1px solid $ios-border;
+}
+
+.shift-table-row {
+  padding: 16px 20px;
+  display: grid;
+  grid-template-columns: 2fr 1fr 1fr 1fr;
+  gap: 10px;
+  border-bottom: 1px solid $ios-border;
+  align-items: center;
+  transition: background-color 0.2s ease;
+  
+  &:hover {
+    background-color: color-mix(in srgb, $ios-background-secondary 50%, $ios-card-background);
+  }
+  
+  &:last-child {
+    border-bottom: none;
+  }
+  
+  .project-name {
+    font-size: 16px;
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-medium;
+  }
+  
+  .priority-tag {
+    padding: 6px 12px;
+    border-radius: $ios-radius-full;
+    font-size: 13px;
+    text-align: center;
+    font-weight: $ios-font-weight-medium;
+    border: 1px solid transparent;
+  }
+  
+  .priority-high {
+    background-color: rgba(255, 59, 48, 0.1);
+    color: $ios-danger;
+    border-color: $ios-danger;
+  }
+  
+  .priority-medium {
+    background-color: rgba(255, 149, 0, 0.1);
+    color: $ios-warning;
+    border-color: $ios-warning;
+  }
+  
+  .priority-low {
+    background-color: rgba(0, 122, 255, 0.1);
+    color: $ios-primary;
+    border-color: $ios-primary;
+  }
+  
+  .shift-time {
+    font-size: 15px;
+    color: $ios-text-secondary;
+  }
+  
+  .shift-actions {
+    display: flex;
+    gap: 8px;
+    justify-content: flex-end;
+  }
+  
+  .btn-shift-action {
+    padding: 8px 14px;
+    border: none;
+    border-radius: $ios-radius-md;
+    font-size: 13px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    font-weight: $ios-font-weight-medium;
+    font-family: $ios-font-family;
+  }
+  
+  .btn-detail {
+    background-color: $ios-primary;
+    color: white;
+    
+    &:hover {
+      background-color: #003A8C;
+      transform: translateY(-1px);
+      box-shadow: $ios-shadow-md;
+    }
+  }
+  
+  .btn-complete {
+    background-color: $ios-success;
+    color: white;
+    
+    &:hover {
+      background-color: color.adjust($ios-success, $lightness: -5%);
+      transform: translateY(-1px);
+      box-shadow: $ios-shadow-md;
+    }
+  }
+}
+
+/* 个人项目饱和度样式 */
+.workload-section {
+  grid-column: 1 / -1;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 24px;
+}
+
+.workload-info {
+  background-color: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: 20px;
+  box-shadow: $ios-shadow-card;
+  border: 1px solid $ios-border;
+}
+
+.workload-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+  
+  h3 {
+    font-size: 18px;
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-medium;
+    margin: 0;
+    font-family: $ios-font-family;
+  }
+  
+  .workload-status {
+    padding: 6px 12px;
+    border-radius: $ios-radius-full;
+    font-size: 13px;
+    font-weight: $ios-font-weight-medium;
+    border: 1px solid transparent;
+  }
+  
+  .status-idle {
+    background-color: rgba(52, 199, 89, 0.1);
+    color: $ios-success;
+    border-color: $ios-success;
+  }
+  
+  .status-normal {
+    background-color: rgba(0, 122, 255, 0.1);
+    color: $ios-primary;
+    border-color: $ios-primary;
+  }
+  
+  .status-busy {
+    background-color: rgba(255, 149, 0, 0.1);
+    color: $ios-warning;
+    border-color: $ios-warning;
+  }
+  
+  .status-overloaded {
+    background-color: rgba(255, 59, 48, 0.1);
+    color: $ios-danger;
+    border-color: $ios-danger;
+  }
+}
+
+.workload-progress {
+  margin-bottom: 8px;
+  
+  .progress-bar {
+    height: 24px;
+    background-color: $ios-background-secondary;
+    border-radius: $ios-radius-full;
+    overflow: hidden;
+    border: 1px solid $ios-border;
+    position: relative;
+    
+    .progress-fill {
+      height: 100%;
+      background: linear-gradient(90deg, $ios-success, $ios-primary, $ios-warning, $ios-danger);
+      transition: width 0.6s ease;
+      border-radius: $ios-radius-full;
+    }
+    
+    .progress-percentage {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      color: white;
+      font-weight: $ios-font-weight-bold;
+      font-size: 15px;
+      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+    }
+  }
+}
+
+.timeline-info {
+  background-color: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: 20px;
+  box-shadow: $ios-shadow-card;
+  border: 1px solid $ios-border;
+}
+
+.timeline-header {
+  margin-bottom: 16px;
+  
+  h3 {
+    font-size: 18px;
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-medium;
+    margin: 0;
+    font-family: $ios-font-family;
+  }
+}
+
+.timeline-list {
+  display: grid;
+  gap: 12px;
+}
+
+.timeline-item {
+  padding: 12px 16px;
+  background-color: $ios-background-secondary;
+  border-radius: $ios-radius-md;
+  border-left: 4px solid $ios-primary;
+  
+  .timeline-project {
+    font-size: 15px;
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-medium;
+    margin-bottom: 4px;
+  }
+  
+  .timeline-deadline {
+    font-size: 13px;
+    color: $ios-text-secondary;
+  }
+  
+  &.deadline-soon {
+    border-left-color: $ios-warning;
+    background-color: color-mix(in srgb, $ios-warning 5%, transparent);
+  }
+  
+  &.deadline-overdue {
+    border-left-color: $ios-danger;
+    background-color: color-mix(in srgb, $ios-danger 5%, transparent);
+  }
+}
+
+/* 能力维度雷达图样式 */
+.skill-radar-section {
+  grid-column: 1 / -1;
+  margin-top: 24px;
+}
+
+/* 响应式布局调整 */
+@media (max-width: 1024px) {
+  .workload-section {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 768px) {
+  .shift-table-header,
+  .shift-table-row {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+  
+  .shift-actions {
+    justify-content: flex-start;
+  }
 }

+ 146 - 4
src/app/pages/designer/dashboard/dashboard.ts

@@ -1,12 +1,30 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
-import { Task, RenderProgress, CustomerFeedback } from '../../../models/project.model';
+import { Task } from '../../../models/project.model';
+import { SkillRadarComponent } from './skill-radar/skill-radar.component';
+
+interface ShiftTask {
+  id: string;
+  projectId: string;
+  projectName: string;
+  taskDescription: string;
+  priority: '高' | '中' | '低';
+  shiftDate: string;
+  status: '待处理' | '处理中' | '已完成';
+}
+
+interface ProjectTimelineItem {
+  id: string;
+  name: string;
+  deadline: string;
+  status: string;
+}
 
 @Component({
   selector: 'app-dashboard',
-  imports: [CommonModule, RouterModule],
+  imports: [CommonModule, RouterModule, SkillRadarComponent],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 })
@@ -14,15 +32,25 @@ export class Dashboard implements OnInit {
   tasks: Task[] = [];
   overdueTasks: Task[] = [];
   urgentTasks: Task[] = [];
-  pendingFeedbacks: {task: Task, feedback: CustomerFeedback}[] = [];
+  pendingFeedbacks: {task: Task, feedback: any}[] = [];
   reminderMessage: string = '';
   feedbackProjectId: string = '';
   countdowns: Map<string, string> = new Map();
+  
+  // 代班信息相关属性
+  shiftTasks: ShiftTask[] = [];
+  
+  // 个人项目饱和度相关属性
+  workloadPercentage: number = 0;
+  projectTimeline: ProjectTimelineItem[] = [];
 
   constructor(private projectService: ProjectService) {}
 
   ngOnInit(): void {
     this.loadTasks();
+    this.loadShiftTasks();
+    this.calculateWorkloadPercentage();
+    this.loadProjectTimeline();
   }
 
   loadTasks(): void {
@@ -208,4 +236,118 @@ export class Dashboard implements OnInit {
   clearReminder(): void {
     this.reminderMessage = '';
   }
+
+  // 代班任务相关方法
+  loadShiftTasks(): void {
+    // 在实际应用中,这里应该从服务中获取代班任务
+    // 这里使用模拟数据
+    this.shiftTasks = [
+      {
+        id: 'shift1',
+        projectId: 'project1',
+        projectName: '现代风格客厅设计',
+        taskDescription: '小图修改反馈和渲染进度跟踪',
+        priority: '高',
+        shiftDate: '2025-09-15',
+        status: '待处理'
+      },
+      {
+        id: 'shift2',
+        projectId: 'project2',
+        projectName: '北欧风卧室装修',
+        taskDescription: '查看客户反馈并提供初步修改建议',
+        priority: '中',
+        shiftDate: '2025-09-16',
+        status: '待处理'
+      },
+      {
+        id: 'shift3',
+        projectId: 'project3',
+        projectName: '新中式书房改造',
+        taskDescription: '完成剩余渲染任务',
+        priority: '低',
+        shiftDate: '2025-09-17',
+        status: '处理中'
+      }
+    ];
+  }
+
+  // 打开添加代班任务的模态框
+  openShiftModal(): void {
+    // 在实际应用中,这里应该打开一个模态框让用户添加代班任务
+    // 这里使用alert模拟
+    alert('将打开添加代班任务的表单');
+    // 实际实现可能是:this.modalService.openShiftModal();
+  }
+
+  // 查看代班任务详情
+  viewShiftDetail(shiftId: string): void {
+    const shift = this.shiftTasks.find(s => s.id === shiftId);
+    if (shift) {
+      // 实际应用中,这里应该打开详情页面或模态框
+      console.log('查看代班任务详情:', shift);
+      alert(`代班任务详情:\n项目:${shift.projectName}\n任务:${shift.taskDescription}\n优先级:${shift.priority}\n代班日期:${shift.shiftDate}`);
+    }
+  }
+
+  // 标记代班任务完成
+  markShiftComplete(shiftId: string): void {
+    const shiftIndex = this.shiftTasks.findIndex(s => s.id === shiftId);
+    if (shiftIndex !== -1) {
+      // 在实际应用中,这里应该调用API更新状态
+      this.shiftTasks[shiftIndex].status = '已完成';
+      alert('代班任务已标记为完成');
+    }
+  }
+
+  // 计算项目饱和度
+  calculateWorkloadPercentage(): void {
+    // 在实际应用中,这里应该从服务中获取真实的项目饱和度数据
+    // 这里使用模拟数据,根据当前任务数量计算饱和度
+    const totalCapacity = 5; // 假设设计师最大同时处理5个项目
+    const currentProjects = this.tasks.length;
+    
+    // 计算饱和度百分比
+    this.workloadPercentage = Math.round((currentProjects / totalCapacity) * 100);
+    
+    // 确保百分比在0-100之间
+    this.workloadPercentage = Math.min(Math.max(this.workloadPercentage, 0), 100);
+  }
+
+  // 加载项目排期表
+  loadProjectTimeline(): void {
+    // 在实际应用中,这里应该从服务中获取项目排期数据
+    // 这里使用模拟数据
+    this.projectTimeline = [
+      {
+        id: 'timeline1',
+        name: '现代风格客厅设计',
+        deadline: '2025-09-20',
+        status: '进行中'
+      },
+      {
+        id: 'timeline2',
+        name: '北欧风卧室装修',
+        deadline: '2025-09-25',
+        status: '进行中'
+      },
+      {
+        id: 'timeline3',
+        name: '新中式书房改造',
+        deadline: '2025-09-30',
+        status: '进行中'
+      }
+    ].sort((a, b) => new Date(a.deadline).getTime() - new Date(b.deadline).getTime());
+  }
+
+  // 获取项目饱和度状态文本
+  getWorkloadStatus(): string {
+    if (this.workloadPercentage < 75) {
+      return '工作负载正常';
+    } else if (this.workloadPercentage < 90) {
+      return '工作负载较高';
+    } else {
+      return '工作负载已满';
+    }
+  }
 }

+ 46 - 0
src/app/pages/designer/dashboard/skill-radar/skill-radar.component.html

@@ -0,0 +1,46 @@
+<div class="skill-radar-container">
+  <div class="skill-radar-header">
+    <h3>个人能力维度雷达图</h3>
+    <div class="view-toggle">
+      <button class="btn-toggle" [class.active]="viewMode === 'self'" (click)="switchViewMode('self')">个人视图</button>
+      <button class="btn-toggle" [class.active]="viewMode === 'comparison'" (click)="switchViewMode('comparison')">团队对比</button>
+    </div>
+  </div>
+  
+  <div class="skill-radar-content">
+    <div class="radar-chart-wrapper">
+      <canvas #radarChart></canvas>
+    </div>
+    
+    <div class="skill-legend">
+      <div class="legend-item" *ngIf="viewMode === 'comparison'">
+        <div class="legend-color" style="background-color: rgba(255, 99, 132, 0.6)"></div>
+        <span>个人能力</span>
+      </div>
+      <div class="legend-item" *ngIf="viewMode === 'comparison'">
+        <div class="legend-color" style="background-color: rgba(54, 162, 235, 0.6)"></div>
+        <span>团队平均</span>
+      </div>
+    </div>
+  </div>
+  
+  <div class="skill-analysis">
+    <h4>能力分析</h4>
+    <div class="analysis-items">
+      <div class="analysis-item" *ngFor="let item of skillAnalysis">
+        <div class="analysis-name">{{ item.name }}</div>
+        <div class="analysis-value">{{ item.score }}/100</div>
+        <div class="analysis-status">{{ item.status }}</div>
+      </div>
+    </div>
+  </div>
+  
+  <div class="improvement-suggestions">
+    <h4>提升建议</h4>
+    <div class="suggestion-list">
+      <div class="suggestion-item" *ngFor="let suggestion of improvementSuggestions">
+        <div class="suggestion-content">{{ suggestion }}</div>
+      </div>
+    </div>
+  </div>
+</div>

+ 292 - 0
src/app/pages/designer/dashboard/skill-radar/skill-radar.component.scss

@@ -0,0 +1,292 @@
+@use '../../ios-theme.scss' as *;
+
+.skill-radar-container {
+  background-color: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: $ios-spacing-xl;
+  box-shadow: $ios-shadow-card;
+  margin-bottom: $ios-spacing-xl;
+  border: 1px solid $ios-border;
+}
+.skill-radar-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $ios-spacing-xl;
+}
+
+.skill-radar-header h3 {
+  margin: 0;
+  font-size: $ios-font-size-lg;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.view-toggle {
+  display: flex;
+  background-color: $ios-background-secondary;
+  border-radius: $ios-radius-full;
+  padding: $ios-spacing-xs;
+}
+
+.btn-toggle {
+  padding: $ios-spacing-sm $ios-spacing-xl;
+  border: none;
+  border-radius: $ios-radius-full;
+  background-color: transparent;
+  color: $ios-text-secondary;
+  font-size: $ios-font-size-sm;
+  font-weight: $ios-font-weight-medium;
+  cursor: pointer;
+  transition: $ios-feedback-tap;
+  font-family: $ios-font-family;
+  outline: none;
+}
+
+.btn-toggle.active {
+  background-color: $ios-card-background;
+  color: $ios-primary;
+  box-shadow: $ios-shadow-sm;
+}
+
+.btn-toggle:hover:not(.active) {
+  opacity: 0.8;
+}
+
+.btn-toggle:active {
+  transform: scale(0.97);
+}
+
+.skill-legend {
+  display: flex;
+  gap: $ios-spacing-md;
+  justify-content: center;
+  margin-bottom: $ios-spacing-xl;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  font-size: $ios-font-size-sm;
+  color: $ios-text-secondary;
+  font-family: $ios-font-family;
+}
+
+.legend-color {
+  display: inline-block;
+  width: 12px;
+  height: 12px;
+  margin-right: $ios-spacing-xs;
+  border-radius: $ios-radius-full;
+  box-shadow: $ios-shadow-sm;
+}
+
+.radar-chart-wrapper {
+  width: 100%;
+  height: 300px;
+  margin-bottom: $ios-spacing-xl;
+  position: relative;
+  background-color: $ios-background-secondary;
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-lg;
+  border: 1px solid $ios-border;
+}
+
+.radar-chart-wrapper canvas {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+/* 技能分析区域 */
+.skill-analysis {
+  margin-bottom: $ios-spacing-xl;
+  background-color: $ios-background-secondary;
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-lg;
+  border: 1px solid $ios-border;
+}
+
+.skill-analysis h4 {
+  font-size: $ios-font-size-base;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  margin-bottom: $ios-spacing-md;
+  font-family: $ios-font-family;
+}
+
+.analysis-items {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  gap: $ios-spacing-md;
+}
+
+.analysis-item {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 0;
+  gap: $ios-spacing-xs;
+}
+
+.skill-name {
+  width: auto;
+  font-size: $ios-font-size-sm;
+  color: $ios-text-secondary;
+  font-weight: $ios-font-weight-medium;
+  flex-shrink: 0;
+  font-family: $ios-font-family;
+}
+
+.skill-score {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  gap: $ios-spacing-sm;
+}
+
+.score-bar {
+  flex: 1;
+  height: 8px;
+  background-color: $ios-background-tertiary;
+  border-radius: $ios-radius-full;
+  position: relative;
+  overflow: hidden;
+}
+
+.current-score {
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  background-color: $ios-primary;
+  border-radius: $ios-radius-full;
+  transition: width 0.5s ease-out;
+}
+
+.score-info {
+  display: flex;
+  justify-content: space-between;
+  font-size: $ios-font-size-xs;
+  color: $ios-text-tertiary;
+  margin-top: $ios-spacing-xs;
+}
+
+.target-score {
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  background-color: $ios-success;
+  opacity: 0.3;
+  border-radius: $ios-radius-full;
+}
+
+.score-text {
+  font-size: $ios-font-size-sm;
+  font-weight: $ios-font-weight-medium;
+  color: $ios-text-primary;
+  width: 40px;
+  text-align: right;
+  font-family: $ios-font-family;
+}
+
+.target-text {
+  font-size: $ios-font-size-xs;
+  color: $ios-text-tertiary;
+  font-family: $ios-font-family;
+}
+
+.skill-rating {
+  width: 60px;
+  text-align: right;
+  font-size: $ios-font-size-sm;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.rating-high {
+  color: $ios-success;
+}
+
+.rating-medium {
+  color: $ios-warning;
+}
+
+.rating-low {
+  color: $ios-danger;
+}
+
+/* 提升建议区域 */
+.improvement-suggestions {
+  margin-top: $ios-spacing-xl;
+  background-color: color-mix(in srgb, $ios-primary 5%, transparent);
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-lg;
+  border: 1px solid color-mix(in srgb, $ios-primary 20%, transparent);
+}
+
+.improvement-suggestions h4 {
+  margin: 0 0 $ios-spacing-sm 0;
+  font-size: $ios-font-size-base;
+  color: $ios-primary;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.suggestion-list {
+  margin: 0;
+  padding-left: $ios-spacing-lg;
+}
+
+.suggestion-list li {
+  font-size: $ios-font-size-sm;
+  color: $ios-text-secondary;
+  line-height: 1.6;
+  margin-bottom: $ios-spacing-xs;
+  font-family: $ios-font-family;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .skill-radar-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: $ios-spacing-md;
+  }
+
+  .view-toggle {
+    width: 100%;
+    justify-content: center;
+  }
+
+  .analysis-items {
+    grid-template-columns: 1fr;
+  }
+
+  .radar-chart-wrapper {
+    height: 250px;
+  }
+
+  .improvement-suggestions {
+    padding: $ios-spacing-md;
+  }
+
+  .radar-legend {
+    width: 100%;
+    justify-content: space-between;
+  }
+
+  .analysis-item {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 5px;
+  }
+
+  .skill-name {
+    width: auto;
+  }
+
+  .skill-score {
+      width: 100%;
+    }
+}

+ 178 - 0
src/app/pages/designer/dashboard/skill-radar/skill-radar.component.ts

@@ -0,0 +1,178 @@
+import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import Chart from 'chart.js/auto';
+
+interface SkillData {
+  label: string;
+  current: number;
+  target: number;
+  teamAvg: number;
+}
+
+interface SkillAnalysisItem {
+  name: string;
+  score: number;
+  status: string;
+}
+
+@Component({
+  selector: 'app-skill-radar',
+  imports: [CommonModule],
+  templateUrl: './skill-radar.component.html',
+  styleUrl: './skill-radar.component.scss'
+})
+export class SkillRadarComponent implements OnInit, AfterViewInit, OnDestroy {
+  @ViewChild('radarChart') radarChartRef!: ElementRef<HTMLCanvasElement>;
+  private radarChart: Chart | null = null;
+  
+  viewMode: 'self' | 'comparison' = 'self';
+  
+  // 能力维度数据
+  skillDimensions: SkillData[] = [
+    { label: '建模能力', current: 85, target: 95, teamAvg: 78 },
+    { label: '渲染技巧', current: 76, target: 90, teamAvg: 72 },
+    { label: '软装搭配', current: 90, target: 95, teamAvg: 85 },
+    { label: '沟通能力', current: 70, target: 85, teamAvg: 75 },
+    { label: '项目管理', current: 65, target: 80, teamAvg: 68 },
+    { label: '创新能力', current: 82, target: 90, teamAvg: 76 }
+  ];
+  
+  // 能力分析数据
+  skillAnalysis: SkillAnalysisItem[] = [
+    { name: '建模能力', score: 85, status: '优秀' },
+    { name: '渲染技巧', score: 76, status: '良好' },
+    { name: '软装搭配', score: 90, status: '优秀' },
+    { name: '沟通能力', score: 70, status: '需要提升' },
+    { name: '项目管理', score: 65, status: '需要提升' },
+    { name: '创新能力', score: 82, status: '良好' }
+  ];
+  
+  // 提升建议
+  improvementSuggestions = [
+    '在项目管理方面,建议使用敏捷管理方法,提高任务优先级判断能力',
+    '沟通能力可以通过主动与客户进行阶段性汇报来提升',
+    '渲染技巧可以学习新的灯光设置和材质调整方法,提升作品真实感',
+    '继续保持软装搭配和建模能力的优势,探索更多创新风格'
+  ];
+
+  constructor() { }
+
+  ngOnInit(): void {
+    // 初始化组件数据
+  }
+
+  ngAfterViewInit(): void {
+    // 初始化雷达图
+    this.initRadarChart();
+  }
+
+  // 切换视图模式
+  switchViewMode(mode: 'self' | 'comparison'): void {
+    this.viewMode = mode;
+    this.updateRadarChart();
+  }
+
+  // 初始化雷达图
+  private initRadarChart(): void {
+    const ctx = this.radarChartRef.nativeElement.getContext('2d');
+    if (!ctx) return;
+
+    const labels = this.skillDimensions.map(dim => dim.label);
+    const data = this.prepareChartData();
+
+    this.radarChart = new Chart(ctx, {
+      type: 'radar',
+      data: {
+        labels: labels,
+        datasets: data
+      },
+      options: this.getChartOptions()
+    });
+
+    // 窗口调整大小时重绘图表
+    window.addEventListener('resize', () => {
+      if (this.radarChart) {
+        this.radarChart.resize();
+      }
+    });
+  }
+
+  // 准备图表数据
+  private prepareChartData() {
+    const datasets = [
+      {
+        label: '个人能力',
+        data: this.skillDimensions.map(dim => dim.current),
+        backgroundColor: 'rgba(255, 99, 132, 0.2)',
+        borderColor: 'rgba(255, 99, 132, 1)',
+        pointBackgroundColor: 'rgba(255, 99, 132, 1)',
+        pointBorderColor: '#fff',
+        pointHoverBackgroundColor: '#fff',
+        pointHoverBorderColor: 'rgba(255, 99, 132, 1)'
+      }
+    ];
+
+    if (this.viewMode === 'comparison') {
+      datasets.push({
+        label: '团队平均',
+        data: this.skillDimensions.map(dim => dim.teamAvg),
+        backgroundColor: 'rgba(54, 162, 235, 0.2)',
+        borderColor: 'rgba(54, 162, 235, 1)',
+        pointBackgroundColor: 'rgba(54, 162, 235, 1)',
+        pointBorderColor: '#fff',
+        pointHoverBackgroundColor: '#fff',
+        pointHoverBorderColor: 'rgba(54, 162, 235, 1)'
+      });
+    }
+
+    return datasets;
+  }
+
+  // 获取图表配置
+  private getChartOptions() {
+    return {
+      scales: {
+        r: {
+          angleLines: {
+            display: true,
+            color: 'rgba(200, 200, 200, 0.3)'
+          },
+          suggestedMin: 0,
+          suggestedMax: 100,
+          ticks: {
+            stepSize: 20,
+            backdropColor: 'transparent'
+          }
+        }
+      },
+      plugins: {
+        legend: {
+          display: false
+        },
+        tooltip: {
+          callbacks: {
+            label: function(context: any) {
+              return `${context.dataset.label}: ${context.parsed.r}/100`;
+            }
+          }
+        }
+      },
+      maintainAspectRatio: false
+    };
+  }
+
+  // 更新雷达图
+  private updateRadarChart(): void {
+    if (this.radarChart) {
+      this.radarChart.data.datasets = this.prepareChartData();
+      this.radarChart.update();
+    }
+  }
+
+  // 清理图表资源
+  ngOnDestroy(): void {
+    if (this.radarChart) {
+      this.radarChart.destroy();
+    }
+  }
+}

+ 134 - 0
src/app/pages/designer/material-share/material-share.html

@@ -0,0 +1,134 @@
+<div class="material-share-container">
+  <header class="page-header">
+    <h1>内部素材共享盘</h1>
+    <div class="header-actions">
+      <input type="file" (change)="uploadMaterial($event)" class="upload-input" id="file-upload">
+      <label for="file-upload" class="upload-btn">上传素材</label>
+      <button (click)="toggleAdvancedSearch()" class="search-options-btn">
+        {{ showAdvancedSearch ? '收起筛选' : '高级筛选' }}
+      </button>
+    </div>
+  </header>
+
+  <!-- 搜索和筛选区域 -->
+  <div class="search-filter-section">
+    <div class="search-bar">
+      <input 
+        type="text" 
+        placeholder="搜索素材名称、标签或上传者" 
+        [(ngModel)]="searchQuery"
+        (input)="searchMaterials()"
+        class="search-input"
+      >
+      <button (click)="searchMaterials()" class="search-btn">搜索</button>
+    </div>
+
+    <!-- 高级筛选选项 -->
+    <div *ngIf="showAdvancedSearch" class="advanced-search">
+      <div class="filter-row">
+        <select [(ngModel)]="filterType" (change)="searchMaterials()" class="filter-select">
+          <option value="">所有类型</option>
+          <option value="模型">3D模型</option>
+          <option value="贴图">贴图素材</option>
+          <option value="材质">材质库</option>
+          <option value="参考图">参考图片</option>
+        </select>
+        <select [(ngModel)]="filterStyle" (change)="searchMaterials()" class="filter-select">
+          <option value="">所有风格</option>
+          <option value="现代">现代风格</option>
+          <option value="北欧">北欧风格</option>
+          <option value="新中式">新中式</option>
+          <option value="工业">工业风格</option>
+          <option value="美式">美式风格</option>
+        </select>
+        <select [(ngModel)]="filterPrice" (change)="searchMaterials()" class="filter-select">
+          <option value="">所有价格</option>
+          <option value="free">免费</option>
+          <option value="paid">付费</option>
+        </select>
+        <button (click)="resetFilters()" class="reset-btn">重置筛选</button>
+      </div>
+    </div>
+
+    <!-- 以图搜图功能 -->
+    <div class="image-search-section">
+      <label class="image-search-label">以图搜图:</label>
+      <input type="file" (change)="searchByImage($event)" class="image-upload-input" id="image-search-upload">
+      <label for="image-search-upload" class="image-upload-btn">选择图片</label>
+      <div *ngIf="selectedImageUrl" class="selected-image-preview">
+        <img [src]="selectedImageUrl" alt="搜索图片预览">
+        <button (click)="clearSelectedImage()" class="clear-image-btn">×</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 素材展示区域 -->
+  <div class="materials-grid">
+    <div *ngIf="isLoading" class="loading-state">
+      <div class="loading-spinner"></div>
+      <span>加载中...</span>
+    </div>
+    <div *ngIf="!isLoading && materials.length === 0" class="empty-state">
+      <div class="empty-icon">📁</div>
+      <p>暂无匹配的素材</p>
+      <button (click)="resetFilters()" class="reset-btn">查看全部素材</button>
+    </div>
+    <div *ngFor="let material of materials" class="material-card" (click)="viewMaterialDetail(material.id)">
+      <div class="material-thumbnail">
+        <img [src]="material.thumbnailUrl" alt="{{ material.name }}">
+        <div *ngIf="material.isNew" class="new-badge">新</div>
+        <div *ngIf="material.price > 0" class="price-badge">¥{{ material.price }}</div>
+      </div>
+      <div class="material-info">
+        <h3 class="material-name">{{ material.name }}</h3>
+        <div class="material-meta">
+          <span class="material-type">{{ material.type }}</span>
+          <span class="upload-time">{{ formatDate(material.uploadTime) }}</span>
+        </div>
+        <div class="material-tags">
+          <span *ngFor="let tag of material.tags.slice(0, 3)" class="tag">{{ tag }}</span>
+          <span *ngIf="material.tags.length > 3" class="more-tags">+{{ material.tags.length - 3 }}</span>
+        </div>
+        <div class="material-stats">
+          <span class="download-count">{{ material.downloadCount }}次下载</span>
+          <span class="cost-saving" *ngIf="material.costSaved > 0">节约¥{{ material.costSaved }}</span>
+        </div>
+      </div>
+      <div class="material-actions">
+        <button (click)="$event.stopPropagation(); downloadMaterial(material.id)" class="download-btn">
+          下载
+        </button>
+        <button (click)="$event.stopPropagation(); addToFavorites(material.id)" class="favorite-btn" [class.favorited]="isFavorited(material.id)">
+          {{ isFavorited(material.id) ? '★' : '☆' }}
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 分页控件 -->
+  <div class="pagination" *ngIf="!isLoading && materials.length > 0">
+    <button (click)="goToPage(currentPage - 1)" [disabled]="currentPage === 1" class="page-btn">上一页</button>
+    <button *ngFor="let page of visiblePages" (click)="goToPage(page)" class="page-btn" [class.active]="page === currentPage">{{ page }}</button>
+    <button (click)="goToPage(currentPage + 1)" [disabled]="currentPage === totalPages" class="page-btn">下一页</button>
+  </div>
+
+  <!-- 统计信息 -->
+  <div class="material-stats-summary">
+    <div class="stat-item">
+      <span class="stat-label">总素材数量</span>
+      <span class="stat-value">{{ totalMaterials }}</span>
+    </div>
+    <div class="stat-item">
+      <span class="stat-label">本月新增</span>
+      <span class="stat-value">{{ monthlyAdded }}</span>
+    </div>
+    <div class="stat-item">
+      <span class="stat-label">累计节约成本</span>
+      <span class="stat-value highlight">¥{{ totalCostSaved }}</span>
+    </div>
+    <div class="stat-item">
+      <span class="stat-label">个人已下载</span>
+      <span class="stat-value">{{ personalDownloads }}</span>
+    </div>
+  </div>
+</div>

+ 492 - 0
src/app/pages/designer/material-share/material-share.scss

@@ -0,0 +1,492 @@
+.material-share-container {
+  padding: 20px;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 15px 20px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.page-header h1 {
+  margin: 0;
+  font-size: 24px;
+  color: #333;
+}
+
+.header-actions {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.upload-input {
+  display: none;
+}
+
+.upload-btn,
+.search-options-btn {
+  padding: 8px 16px;
+  background-color: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: background-color 0.3s;
+}
+
+.upload-btn:hover,
+.search-options-btn:hover {
+  background-color: #40a9ff;
+}
+
+.search-options-btn {
+  background-color: #52c41a;
+}
+
+.search-options-btn:hover {
+  background-color: #73d13d;
+}
+
+/* 搜索和筛选区域 */
+.search-filter-section {
+  background-color: white;
+  padding: 15px 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+  margin-bottom: 20px;
+}
+
+.search-bar {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 15px;
+}
+
+.search-input {
+  flex: 1;
+  padding: 8px 12px;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  font-size: 14px;
+}
+
+.search-input:focus {
+  outline: none;
+  border-color: #40a9ff;
+  box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+}
+
+.search-btn {
+  padding: 8px 16px;
+  background-color: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.search-btn:hover {
+  background-color: #40a9ff;
+}
+
+.advanced-search {
+  padding-top: 15px;
+  border-top: 1px solid #f0f0f0;
+}
+
+.filter-row {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.filter-select {
+  padding: 6px 12px;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  font-size: 14px;
+  background-color: white;
+}
+
+.reset-btn {
+  margin-left: auto;
+  padding: 6px 12px;
+  background-color: #f5f5f5;
+  color: #666;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.reset-btn:hover {
+  background-color: #e6f7ff;
+  border-color: #40a9ff;
+  color: #40a9ff;
+}
+
+/* 以图搜图区域 */
+.image-search-section {
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.image-search-label {
+  font-size: 14px;
+  color: #666;
+}
+
+.image-upload-input {
+  display: none;
+}
+
+.image-upload-btn {
+  padding: 6px 12px;
+  background-color: #fa8c16;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.image-upload-btn:hover {
+  background-color: #ffa940;
+}
+
+.selected-image-preview {
+  position: relative;
+  width: 80px;
+  height: 80px;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.selected-image-preview img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.clear-image-btn {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 20px;
+  height: 20px;
+  background-color: rgba(0,0,0,0.5);
+  color: white;
+  border: none;
+  border-radius: 0 0 0 4px;
+  cursor: pointer;
+  font-size: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 素材展示区域 */
+.materials-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  gap: 20px;
+  margin-bottom: 20px;
+}
+
+.material-card {
+  background-color: white;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+  cursor: pointer;
+  transition: transform 0.3s, box-shadow 0.3s;
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.material-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+}
+
+.material-thumbnail {
+  position: relative;
+  width: 100%;
+  height: 180px;
+  overflow: hidden;
+}
+
+.material-thumbnail img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: transform 0.3s;
+}
+
+.material-card:hover .material-thumbnail img {
+  transform: scale(1.05);
+}
+
+.new-badge {
+  position: absolute;
+  top: 8px;
+  left: 8px;
+  background-color: #ff4d4f;
+  color: white;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+.price-badge {
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  background-color: #fa8c16;
+  color: white;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: bold;
+}
+
+.material-info {
+  padding: 12px;
+  flex: 1;
+}
+
+.material-name {
+  margin: 0 0 8px 0;
+  font-size: 16px;
+  font-weight: 500;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.material-meta {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #999;
+  margin-bottom: 8px;
+}
+
+.material-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+  margin-bottom: 8px;
+}
+
+.tag {
+  display: inline-block;
+  padding: 2px 8px;
+  background-color: #f0f0f0;
+  border-radius: 12px;
+  font-size: 12px;
+  color: #666;
+}
+
+.more-tags {
+  font-size: 12px;
+  color: #999;
+}
+
+.material-stats {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #666;
+}
+
+.cost-saving {
+  color: #52c41a;
+  font-weight: 500;
+}
+
+.material-actions {
+  padding: 8px 12px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.download-btn {
+  padding: 6px 12px;
+  background-color: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 12px;
+}
+
+.download-btn:hover {
+  background-color: #40a9ff;
+}
+
+.favorite-btn {
+  width: 24px;
+  height: 24px;
+  background-color: transparent;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.favorite-btn.favorited {
+  color: #faad14;
+  border-color: #faad14;
+}
+
+/* 加载和空状态 */
+.loading-state,
+.empty-state {
+  grid-column: 1 / -1;
+  text-align: center;
+  padding: 60px 20px;
+  color: #666;
+}
+
+.loading-spinner {
+  display: inline-block;
+  width: 30px;
+  height: 30px;
+  border: 3px solid #f3f3f3;
+  border-top: 3px solid #1890ff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 10px;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.empty-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
+}
+
+/* 分页控件 */
+.pagination {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 5px;
+  margin-bottom: 20px;
+}
+
+.page-btn {
+  padding: 6px 12px;
+  background-color: white;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+}
+
+.page-btn:hover:not(:disabled) {
+  border-color: #40a9ff;
+  color: #40a9ff;
+}
+
+.page-btn.active {
+  background-color: #1890ff;
+  color: white;
+  border-color: #1890ff;
+}
+
+.page-btn:disabled {
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+
+/* 统计信息 */
+.material-stats-summary {
+  background-color: white;
+  padding: 15px 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 20px;
+}
+
+.stat-item {
+  text-align: center;
+}
+
+.stat-label {
+  display: block;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 5px;
+}
+
+.stat-value {
+  display: block;
+  font-size: 20px;
+  font-weight: 500;
+  color: #333;
+}
+
+.stat-value.highlight {
+  color: #52c41a;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .page-header {
+    flex-direction: column;
+    gap: 10px;
+    align-items: flex-start;
+  }
+  
+  .header-actions {
+    width: 100%;
+    justify-content: space-between;
+  }
+  
+  .search-bar {
+    flex-direction: column;
+  }
+  
+  .filter-row {
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .materials-grid {
+    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  }
+  
+  .material-stats-summary {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}

+ 228 - 0
src/app/pages/designer/material-share/material-share.ts

@@ -0,0 +1,228 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { ProjectService } from '../../../services/project.service';
+
+// 临时定义MaterialItem接口,因为它在project.model.ts中不存在
+interface MaterialItem {
+  id: string;
+  name: string;
+  type: string;
+  style: string;
+  price: number;
+  thumbnailUrl: string;
+  downloadCount: number;
+  tags: string[];
+  costSaved: number;
+  isNew: boolean;
+  uploadTime: Date;
+}
+
+@Component({
+  selector: 'app-material-share',
+  imports: [CommonModule, FormsModule],
+  templateUrl: './material-share.html',
+  styleUrl: './material-share.scss'
+})
+export class MaterialShare implements OnInit {
+  materials: MaterialItem[] = [];
+  searchQuery: string = '';
+  filterType: string = '';
+  filterStyle: string = '';
+  filterPrice: string = '';
+  showAdvancedSearch: boolean = false;
+  isLoading: boolean = false;
+  selectedImageUrl: string | null = null;
+  currentPage: number = 1;
+  itemsPerPage: number = 12;
+  totalMaterials: number = 0;
+  totalPages: number = 0;
+  visiblePages: number[] = [];
+  monthlyAdded: number = 0;
+  totalCostSaved: number = 0;
+  personalDownloads: number = 0;
+  favoriteMaterials: Set<string> = new Set();
+
+  constructor(private projectService: ProjectService) {}
+
+  ngOnInit(): void {
+    this.loadMaterials();
+    this.loadFavoriteMaterials();
+    this.loadMaterialStatistics();
+  }
+
+  loadMaterials(): void {
+    this.isLoading = true;
+    
+    // 模拟API加载素材列表
+    setTimeout(() => {
+      this.projectService.getMaterials(
+        this.searchQuery, 
+        this.filterType, 
+        this.filterStyle, 
+        this.filterPrice,
+        this.currentPage,
+        this.itemsPerPage
+      ).subscribe(data => {
+        this.materials = data.materials;
+        this.totalMaterials = data.total;
+        this.totalPages = Math.ceil(this.totalMaterials / this.itemsPerPage);
+        this.updateVisiblePages();
+        this.isLoading = false;
+      });
+    }, 800);
+  }
+
+  searchMaterials(): void {
+    this.currentPage = 1;
+    this.loadMaterials();
+  }
+
+  resetFilters(): void {
+    this.searchQuery = '';
+    this.filterType = '';
+    this.filterStyle = '';
+    this.filterPrice = '';
+    this.selectedImageUrl = null;
+    this.currentPage = 1;
+    this.loadMaterials();
+  }
+
+  toggleAdvancedSearch(): void {
+    this.showAdvancedSearch = !this.showAdvancedSearch;
+  }
+
+  searchByImage(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files[0]) {
+      const file = input.files[0];
+      const reader = new FileReader();
+      
+      reader.onload = (e: ProgressEvent<FileReader>) => {
+        if (e.target?.result) {
+          this.selectedImageUrl = e.target.result as string;
+          // 模拟以图搜图功能
+          this.simulateImageSearch();
+        }
+      };
+      
+      reader.readAsDataURL(file);
+    }
+  }
+
+  simulateImageSearch(): void {
+    this.isLoading = true;
+    // 模拟以图搜图的延迟
+    setTimeout(() => {
+      // 在实际应用中,这里应该调用真正的以图搜图API
+      // 这里只是简单地筛选与图片风格可能相关的素材
+      if (this.filterStyle === '') {
+        this.filterStyle = '现代'; // 假设上传的图片是现代风格
+      }
+      this.loadMaterials();
+    }, 1500);
+  }
+
+  clearSelectedImage(): void {
+    this.selectedImageUrl = null;
+    // 重新加载所有素材
+    this.loadMaterials();
+  }
+
+  uploadMaterial(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files[0]) {
+      const file = input.files[0];
+      // 模拟上传过程
+      this.isLoading = true;
+      setTimeout(() => {
+        // 实际应用中应调用上传API
+        console.log('上传素材:', file.name);
+        alert('素材上传成功!等待审核后即可在共享盘中看到');
+        this.isLoading = false;
+        // 重置输入,允许重新选择同一文件
+        input.value = '';
+      }, 1000);
+    }
+  }
+
+  downloadMaterial(materialId: string): void {
+    // 模拟下载过程
+    this.isLoading = true;
+    setTimeout(() => {
+      // 实际应用中应调用下载API
+      this.projectService.recordMaterialDownload(materialId).subscribe(() => {
+        console.log('下载素材:', materialId);
+        alert('素材下载成功!');
+        this.isLoading = false;
+        // 刷新统计数据
+        this.loadMaterialStatistics();
+      });
+    }, 800);
+  }
+
+  viewMaterialDetail(materialId: string): void {
+    // 在实际应用中,这里应该导航到素材详情页
+    console.log('查看素材详情:', materialId);
+    alert('素材ID: ' + materialId);
+  }
+
+  addToFavorites(materialId: string): void {
+    if (this.isFavorited(materialId)) {
+      this.favoriteMaterials.delete(materialId);
+    } else {
+      this.favoriteMaterials.add(materialId);
+    }
+    // 实际应用中应调用API保存收藏状态
+    this.projectService.updateFavoriteMaterial(materialId, this.isFavorited(materialId)).subscribe();
+  }
+
+  isFavorited(materialId: string): boolean {
+    return this.favoriteMaterials.has(materialId);
+  }
+
+  loadFavoriteMaterials(): void {
+    // 实际应用中应从服务获取收藏列表
+    this.projectService.getFavoriteMaterials().subscribe(favorites => {
+      favorites.forEach(id => this.favoriteMaterials.add(id));
+    });
+  }
+
+  loadMaterialStatistics(): void {
+    // 模拟加载统计数据
+    this.projectService.getMaterialStatistics().subscribe(stats => {
+      this.monthlyAdded = stats.monthlyAdded;
+      this.totalCostSaved = stats.totalCostSaved;
+      this.personalDownloads = stats.personalDownloads;
+    });
+  }
+
+  // 分页相关方法
+  goToPage(page: number): void {
+    if (page >= 1 && page <= this.totalPages) {
+      this.currentPage = page;
+      this.loadMaterials();
+    }
+  }
+
+  updateVisiblePages(): void {
+    // 计算当前可见的页码
+    const maxVisiblePages = 5;
+    let startPage = Math.max(1, this.currentPage - Math.floor(maxVisiblePages / 2));
+    let endPage = Math.min(this.totalPages, startPage + maxVisiblePages - 1);
+    
+    if (endPage - startPage + 1 < maxVisiblePages) {
+      startPage = Math.max(1, endPage - maxVisiblePages + 1);
+    }
+    
+    this.visiblePages = [];
+    for (let i = startPage; i <= endPage; i++) {
+      this.visiblePages.push(i);
+    }
+  }
+
+  // 辅助方法
+  formatDate(date: Date): string {
+    return new Date(date).toLocaleDateString('zh-CN');
+  }
+}

+ 62 - 5
src/app/pages/designer/project-detail/project-detail.html

@@ -193,13 +193,70 @@
       </div>
       <div *ngIf="renderProgress && !isLoadingRenderProgress && !errorLoadingRenderProgress" class="progress-content">
         <!-- 渲染超时预警 -->
-        <div *ngIf="renderProgress.estimatedTimeRemaining <= 3" class="timeout-warning">
-          <div class="warning-icon">⚠️</div>
-          <div class="warning-text">
-            <span class="warning-title">渲染即将超时</span>
-            <span class="warning-time">预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</span>
+      <div *ngIf="renderProgress.estimatedTimeRemaining <= 3" class="timeout-warning">
+        <div class="warning-icon">⚠️</div>
+        <div class="warning-text">
+          <span class="warning-title">渲染即将超时</span>
+          <span class="warning-time">预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</span>
+        </div>
+      </div>
+
+      <!-- 渲染异常反馈模块 -->
+      <div class="render-exception-section">
+        <h3>渲染异常反馈</h3>
+        <div class="exception-feedback-form">
+          <div class="form-group">
+            <label>异常类型:</label>
+            <select [(ngModel)]="exceptionType" class="exception-select">
+              <option value="failed">渲染失败</option>
+              <option value="stuck">渲染卡顿</option>
+              <option value="quality">渲染质量问题</option>
+              <option value="other">其他问题</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>详细描述:</label>
+            <textarea 
+              [(ngModel)]="exceptionDescription" 
+              placeholder="请描述渲染过程中遇到的具体问题..."
+              class="exception-textarea"
+            ></textarea>
+          </div>
+          <div class="form-group">
+            <label>上传截图 (可选):</label>
+            <input type="file" (change)="uploadExceptionScreenshot($event)" class="screenshot-upload" id="screenshot-upload">
+            <label for="screenshot-upload" class="upload-btn">选择文件</label>
+            <div *ngIf="exceptionScreenshotUrl" class="screenshot-preview">
+              <img [src]="exceptionScreenshotUrl" alt="异常截图">
+              <button (click)="clearExceptionScreenshot()" class="clear-screenshot-btn">×</button>
+            </div>
+          </div>
+          <button 
+            (click)="submitExceptionFeedback()" 
+            [disabled]="!exceptionDescription.trim()"
+            class="submit-feedback-btn"
+          >
+            提交反馈并联系技术支持
+          </button>
+        </div>
+
+        <!-- 历史反馈记录 -->
+        <div class="exception-history" *ngIf="exceptionHistories.length > 0">
+          <h4>历史反馈记录</h4>
+          <div class="history-list">
+            <div *ngFor="let history of exceptionHistories" class="history-item">
+              <div class="history-header">
+                <span class="history-type">{{ getExceptionTypeText(history.type) }}</span>
+                <span class="history-time">{{ formatDate(history.submitTime) }}</span>
+              </div>
+              <div class="history-description">{{ history.description }}</div>
+              <div class="history-status" [class.status-pending]="history.status === '待处理'" [class.status-processing]="history.status === '处理中'" [class.status-resolved]="history.status === '已解决'">
+                {{ history.status }} - {{ history.response || '暂无回复' }}
+              </div>
+            </div>
           </div>
         </div>
+      </div>
         
         <div class="progress-bar-container">
           <div class="progress-bar">

+ 229 - 0
src/app/pages/designer/project-detail/project-detail.scss

@@ -99,6 +99,235 @@ h4{font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;color:$ios-te
 .timeout-warning{background-color:#fff3cd;color:$ios-warning;padding:$ios-spacing-md;border-radius:$ios-radius-md;margin-bottom:$ios-spacing-lg;display:flex;align-items:center;gap:$ios-spacing-sm;border-left:4px solid $ios-warning}
 .warning-title{font-weight:$ios-font-weight-medium;display:block}
 .warning-time{font-size:$ios-font-size-sm;display:block}
+
+/* 渲染异常反馈模块样式 */
+.render-exception-section {
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 24px;
+}
+
+.render-exception-section h3 {
+  margin-top: 0;
+  margin-bottom: 16px;
+  color: #333;
+  font-size: 18px;
+  font-weight: 600;
+}
+
+.exception-feedback-form {
+  background-color: white;
+  padding: 16px;
+  border-radius: 8px;
+  margin-bottom: 20px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.form-group {
+  margin-bottom: 16px;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 6px;
+  color: #555;
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.exception-select {
+  width: 100%;
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  font-size: 14px;
+  background-color: white;
+  cursor: pointer;
+}
+
+.exception-textarea {
+  width: 100%;
+  min-height: 120px;
+  padding: 10px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  font-size: 14px;
+  resize: vertical;
+  font-family: inherit;
+}
+
+.screenshot-upload {
+  display: none;
+}
+
+.upload-btn {
+  display: inline-block;
+  padding: 8px 16px;
+  background-color: #007bff;
+  color: white;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 500;
+  transition: background-color 0.2s;
+}
+
+.upload-btn:hover {
+  background-color: #0056b3;
+}
+
+.screenshot-preview {
+  margin-top: 12px;
+  position: relative;
+  max-width: 200px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.screenshot-preview img {
+  width: 100%;
+  display: block;
+}
+
+.clear-screenshot-btn {
+  position: absolute;
+  top: 5px;
+  right: 5px;
+  background-color: rgba(0, 0, 0, 0.5);
+  color: white;
+  border: none;
+  border-radius: 50%;
+  width: 24px;
+  height: 24px;
+  font-size: 16px;
+  line-height: 1;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.clear-screenshot-btn:hover {
+  background-color: rgba(0, 0, 0, 0.7);
+}
+
+.submit-feedback-btn {
+  padding: 10px 20px;
+  background-color: #28a745;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  font-size: 14px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.submit-feedback-btn:hover:not(:disabled) {
+  background-color: #218838;
+}
+
+.submit-feedback-btn:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+/* 历史反馈记录样式 */
+.exception-history {
+  background-color: white;
+  padding: 16px;
+  border-radius: 8px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.exception-history h4 {
+  margin-top: 0;
+  margin-bottom: 12px;
+  color: #555;
+  font-size: 16px;
+  font-weight: 600;
+}
+
+.history-list {
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.history-item {
+  padding: 12px;
+  border: 1px solid #eee;
+  border-radius: 4px;
+  margin-bottom: 12px;
+  transition: border-color 0.2s;
+}
+
+.history-item:hover {
+  border-color: #ddd;
+}
+
+.history-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.history-type {
+  font-weight: 600;
+  color: #333;
+  font-size: 14px;
+}
+
+.history-time {
+  font-size: 12px;
+  color: #999;
+}
+
+.history-description {
+  font-size: 14px;
+  color: #555;
+  margin-bottom: 8px;
+  line-height: 1.4;
+}
+
+.history-status {
+  font-size: 12px;
+  padding: 4px 8px;
+  border-radius: 3px;
+  display: inline-block;
+}
+
+.history-status.status-pending {
+  background-color: #fff3cd;
+  color: #856404;
+}
+
+.history-status.status-processing {
+  background-color: #d1ecf1;
+  color: #0c5460;
+}
+
+.history-status.status-resolved {
+  background-color: #d4edda;
+  color: #155724;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .render-exception-section {
+    padding: 16px;
+  }
+  
+  .form-group {
+    margin-bottom: 12px;
+  }
+  
+  .screenshot-preview {
+    max-width: 150px;
+  }
+}
 .progress-bar-container{position:relative;margin-bottom:$ios-spacing-lg}
 .progress-bar{width:100%;height:12px;background-color:$ios-background-tertiary;border-radius:$ios-radius-full;overflow:hidden}
 .progress-fill{height:100%;background-color:$ios-primary;border-radius:$ios-radius-full}

+ 122 - 4
src/app/pages/designer/project-detail/project-detail.ts

@@ -1,6 +1,7 @@
 import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ActivatedRoute, Router } from '@angular/router';
+import { FormsModule } from '@angular/forms';
 import { ProjectService } from '../../../services/project.service';
 import {
   Project,
@@ -12,9 +13,18 @@ import {
   ProjectStage
 } from '../../../models/project.model';
 
+interface ExceptionHistory {
+  id: string;
+  type: 'failed' | 'stuck' | 'quality' | 'other';
+  description: string;
+  submitTime: Date;
+  status: '待处理' | '处理中' | '已解决';
+  response?: string;
+}
+
 @Component({
   selector: 'app-project-detail',
-  imports: [CommonModule],
+  imports: [CommonModule, FormsModule],
   templateUrl: './project-detail.html',
   styleUrl: './project-detail.scss'
 })
@@ -34,6 +44,13 @@ export class ProjectDetail implements OnInit, OnDestroy {
   private countdownInterval: any;
   projects: {id: string, name: string, status: string}[] = [];
   showDropdown: boolean = false;
+  
+  // 渲染异常反馈相关属性
+  exceptionType: 'failed' | 'stuck' | 'quality' | 'other' = 'failed';
+  exceptionDescription: string = '';
+  exceptionScreenshotUrl: string | null = null;
+  exceptionHistories: ExceptionHistory[] = [];
+  isSubmittingFeedback: boolean = false;
 
   constructor(
     private route: ActivatedRoute,
@@ -55,15 +72,21 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   ngOnInit(): void {
-    this.projectId = this.route.snapshot.paramMap.get('id') || '';
-    this.loadProjectData();
+    this.route.paramMap.subscribe(params => {
+      this.projectId = params.get('id') || '';
+      this.loadProjectData();
+      this.loadExceptionHistories();
+    });
     
     // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
     document.addEventListener('click', this.closeDropdownOnClickOutside);
   }
 
-  // 在组件销毁时移除事件监听器
+  // 在组件销毁时移除事件监听器和清理资源
   ngOnDestroy(): void {
+    if (this.countdownInterval) {
+      clearInterval(this.countdownInterval);
+    }
     document.removeEventListener('click', this.closeDropdownOnClickOutside);
   }
 
@@ -96,6 +119,13 @@ export class ProjectDetail implements OnInit, OnDestroy {
       { id: '4', name: '工业风餐厅设计', status: '待处理' }
     ];
   }
+  
+  // 加载历史反馈记录
+  loadExceptionHistories(): void {
+    this.projectService.getExceptionHistories(this.projectId).subscribe(histories => {
+      this.exceptionHistories = histories;
+    });
+  }
 
   loadProjectDetails(): void {
     this.projectService.getProjectById(this.projectId).subscribe(project => {
@@ -341,4 +371,92 @@ export class ProjectDetail implements OnInit, OnDestroy {
     const remainingSeconds = seconds % 60;
     return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
   }
+
+  // 上传异常截图
+  uploadExceptionScreenshot(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files[0]) {
+      const file = input.files[0];
+      // 在实际应用中,这里应该上传文件到服务器
+      // 这里我们使用FileReader来生成一个预览URL
+      const reader = new FileReader();
+      reader.onload = (e) => {
+        this.exceptionScreenshotUrl = e.target?.result as string;
+      };
+      reader.readAsDataURL(file);
+    }
+  }
+
+  // 清除异常截图
+  clearExceptionScreenshot(): void {
+    this.exceptionScreenshotUrl = null;
+    const input = document.getElementById('screenshot-upload') as HTMLInputElement;
+    if (input) {
+      input.value = '';
+    }
+  }
+
+  // 提交异常反馈
+  submitExceptionFeedback(): void {
+    if (!this.exceptionDescription.trim() || this.isSubmittingFeedback) {
+      return;
+    }
+
+    this.isSubmittingFeedback = true;
+    
+    // 模拟提交反馈到服务器
+    setTimeout(() => {
+      const newException: ExceptionHistory = {
+        id: `exception-${Date.now()}`,
+        type: this.exceptionType,
+        description: this.exceptionDescription,
+        submitTime: new Date(),
+        status: '待处理'
+      };
+
+      // 添加到历史记录中
+      this.exceptionHistories.unshift(newException);
+      
+      // 通知客服和技术支持
+      this.notifyTechnicalSupport(newException);
+      
+      // 清空表单
+      this.exceptionDescription = '';
+      this.clearExceptionScreenshot();
+      
+      // 显示成功消息
+      alert('异常反馈已提交,技术支持将尽快处理');
+      
+      this.isSubmittingFeedback = false;
+    }, 1000);
+  }
+
+  // 通知技术支持
+  notifyTechnicalSupport(exception: ExceptionHistory): void {
+    // 实际应用中应调用消息服务通知技术支持和客服
+    console.log(`通知技术支持和客服:渲染异常 - 项目ID: ${this.projectId}`);
+    console.log(`异常类型: ${this.getExceptionTypeText(exception.type)}, 描述: ${exception.description}`);
+  }
+
+  // 获取异常类型文本
+  getExceptionTypeText(type: string): string {
+    const typeMap: Record<string, string> = {
+      'failed': '渲染失败',
+      'stuck': '渲染卡顿',
+      'quality': '渲染质量问题',
+      'other': '其他问题'
+    };
+    return typeMap[type] || type;
+  }
+
+  // 格式化日期
+  formatDate(date: Date | string): string {
+    const d = typeof date === 'string' ? new Date(date) : date;
+    const year = d.getFullYear();
+    const month = String(d.getMonth() + 1).padStart(2, '0');
+    const day = String(d.getDate()).padStart(2, '0');
+    const hours = String(d.getHours()).padStart(2, '0');
+    const minutes = String(d.getMinutes()).padStart(2, '0');
+    return `${year}-${month}-${day} ${hours}:${minutes}`;
+  }
 }

+ 1 - 1
src/app/pages/login/set-admin-role.html

@@ -80,7 +80,7 @@
             const adminUser = {
                 id: '1',
                 name: '超级管理员',
-                avatar: 'https://via.placeholder.com/40x40/CCFFCC/555555?text=ADMIN',
+                avatar: '<div style=\'width: 40px; height: 40px; background-color: #CCFFCC; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 13.333333333333334px; font-weight: bold;\'>ADMIN</div>',
                 roles: ['admin', 'user'],
                 permissions: ['view-all', 'edit-all', 'delete-all'],
                 lastLogin: new Date().toISOString()

+ 504 - 0
src/app/pages/team-leader/knowledge-base/knowledge-base.html

@@ -0,0 +1,504 @@
+<header class="page-header">
+  <h1>知识库与能力复制</h1>
+</header>
+
+<main class="knowledge-base-main">
+  <!-- 全流程SOP管理 -->
+  <section class="sop-management-section">
+    <div class="section-header">
+      <h2>全流程SOP管理</h2>
+      <button (click)="addNewSOP()" class="btn-add">添加SOP</button>
+    </div>
+    
+    <div class="sop-tabs">
+      <div class="tab-buttons">
+        <button (click)="activeSOPTab = 'overview'" [class.active]="activeSOPTab === 'overview'">概览</button>
+        <button (click)="activeSOPTab = 'modeling'" [class.active]="activeSOPTab === 'modeling'">建模阶段</button>
+        <button (click)="activeSOPTab = 'rendering'" [class.active]="activeSOPTab === 'rendering'">渲染阶段</button>
+        <button (click)="activeSOPTab = 'postProduction'" [class.active]="activeSOPTab === 'postProduction'">后期阶段</button>
+        <button (click)="activeSOPTab = 'delivery'" [class.active]="activeSOPTab === 'delivery'">交付阶段</button>
+      </div>
+      
+      <div class="tab-content">
+        <!-- SOP概览 -->
+        <div *ngIf="activeSOPTab === 'overview'" class="sop-overview">
+          <div class="sop-summary">
+            <h3>标准作业流程总览</h3>
+            <p>全流程SOP覆盖从方案到交付的完整环节,明确每个阶段的交付物要求、时间节点标准和质量验收指标。</p>
+            <button (click)="exportFullSOP()" class="btn-export">导出完整SOP PDF</button>
+          </div>
+          
+          <div class="sop-stages">
+            <div class="stage-item" (click)="activeSOPTab = 'modeling'">
+              <div class="stage-icon">1</div>
+              <h4>建模阶段</h4>
+              <p>3天内完成,误差≤2%</p>
+            </div>
+            <div class="stage-item" (click)="activeSOPTab = 'rendering'">
+              <div class="stage-icon">2</div>
+              <h4>渲染阶段</h4>
+              <p>2-5天,真实度≥90%</p>
+            </div>
+            <div class="stage-item" (click)="activeSOPTab = 'postProduction'">
+              <div class="stage-icon">3</div>
+              <h4>后期阶段</h4>
+              <p>1-2天,细节处理</p>
+            </div>
+            <div class="stage-item" (click)="activeSOPTab = 'delivery'">
+              <div class="stage-icon">4</div>
+              <h4>交付阶段</h4>
+              <p>文档整理,客户确认</p>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 建模阶段SOP详情 -->
+        <div *ngIf="activeSOPTab === 'modeling'" class="sop-detail">
+          <div class="sop-header">
+            <h3>建模阶段SOP</h3>
+            <div class="sop-actions">
+              <button (click)="editSOP('modeling')" class="btn-edit">编辑</button>
+              <button (click)="exportSOP('modeling')" class="btn-export">导出PDF</button>
+            </div>
+          </div>
+          
+          <div class="sop-content">
+            <div class="sop-section">
+              <h4>交付物要求</h4>
+              <ul class="requirement-list">
+                <li>提交完整的3D模型文件(.max/.blend/.skp等格式)</li>
+                <li>提供户型匹配度检查报告,误差≤2%</li>
+                <li>材质贴图完整且正确命名</li>
+                <li>灯光方案初步设置</li>
+                <li>模型优化报告(面数、材质数量等)</li>
+              </ul>
+            </div>
+            
+            <div class="sop-section">
+              <h4>时间节点标准</h4>
+              <div class="timeline">
+                <div class="timeline-item">
+                  <span class="time-marker">Day 1</span>
+                  <span class="task">完成基础建模和户型匹配</span>
+                </div>
+                <div class="timeline-item">
+                  <span class="time-marker">Day 2</span>
+                  <span class="task">完成细节建模和材质贴图</span>
+                </div>
+                <div class="timeline-item">
+                  <span class="time-marker">Day 3</span>
+                  <span class="task">完成灯光设置和模型优化</span>
+                </div>
+              </div>
+            </div>
+            
+            <div class="sop-section">
+              <h4>质量验收指标</h4>
+              <table class="quality-table">
+                <thead>
+                  <tr>
+                    <th>指标项</th>
+                    <th>普通客户</th>
+                    <th>优质客户</th>
+                    <th>VIP客户</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr>
+                    <td>模型精度</td>
+                    <td>≥95%</td>
+                    <td>≥98%</td>
+                    <td>≥99%</td>
+                  </tr>
+                  <tr>
+                    <td>户型匹配度</td>
+                    <td>≥98%</td>
+                    <td>≥99%</td>
+                    <td>100%</td>
+                  </tr>
+                  <tr>
+                    <td>材质还原度</td>
+                    <td>≥90%</td>
+                    <td>≥95%</td>
+                    <td>≥98%</td>
+                  </tr>
+                  <tr>
+                    <td>面数控制</td>
+                    <td>≤100万</td>
+                    <td>≤150万</td>
+                    <td>≤200万</td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+            
+            <!-- 关联视频教程 -->
+            <div class="sop-section">
+              <h4>关联视频教程</h4>
+              <div class="video-tutorials">
+                <div *ngFor="let tutorial of getTutorialsByStage('modeling')" class="tutorial-item">
+                  <div class="tutorial-thumbnail">
+                    <img [src]="tutorial.thumbnail" alt="教程缩略图">
+                    <span class="duration">{{ tutorial.duration }}</span>
+                  </div>
+                  <div class="tutorial-info">
+                    <h5>{{ tutorial.title }}</h5>
+                    <p>{{ tutorial.description }}</p>
+                    <button (click)="playTutorial(tutorial.id)" class="btn-play">播放</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 渲染阶段SOP详情 -->
+        <div *ngIf="activeSOPTab === 'rendering'" class="sop-detail">
+          <div class="sop-header">
+            <h3>渲染阶段SOP</h3>
+            <div class="sop-actions">
+              <button (click)="editSOP('rendering')" class="btn-edit">编辑</button>
+              <button (click)="exportSOP('rendering')" class="btn-export">导出PDF</button>
+            </div>
+          </div>
+          
+          <div class="sop-content">
+            <div class="sop-section">
+              <h4>交付物要求</h4>
+              <ul class="requirement-list">
+                <li>提交高清渲染图(PSD+JPG格式)</li>
+                <li>提供多角度渲染预览(至少3个角度)</li>
+                <li>保留渲染参数文件</li>
+                <li>渲染设置说明文档</li>
+              </ul>
+            </div>
+            
+            <div class="sop-section">
+              <h4>时间节点标准</h4>
+              <div class="timeline">
+                <div class="timeline-item">
+                  <span class="time-marker">Day 1-2</span>
+                  <span class="task">完成测试渲染和参数调整</span>
+                </div>
+                <div class="timeline-item">
+                  <span class="time-marker">Day 3-4</span>
+                  <span class="task">完成大图渲染</span>
+                </div>
+                <div class="timeline-item">
+                  <span class="time-marker">Day 5</span>
+                  <span class="task">完成渲染图检查和整理</span>
+                </div>
+              </div>
+            </div>
+            
+            <div class="sop-section">
+              <h4>质量验收指标</h4>
+              <table class="quality-table">
+                <thead>
+                  <tr>
+                    <th>指标项</th>
+                    <th>普通客户</th>
+                    <th>优质客户</th>
+                    <th>VIP客户</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr>
+                    <td>像素要求</td>
+                    <td>≥800px</td>
+                    <td>≥1200px</td>
+                    <td>≥1600px</td>
+                  </tr>
+                  <tr>
+                    <td>渲染真实度</td>
+                    <td>≥90%</td>
+                    <td>≥95%</td>
+                    <td>≥98%</td>
+                  </tr>
+                  <tr>
+                    <td>光影效果</td>
+                    <td>自然</td>
+                    <td>层次丰富</td>
+                    <td>专业级</td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+            
+            <!-- 关联视频教程 -->
+            <div class="sop-section">
+              <h4>关联视频教程</h4>
+              <div class="video-tutorials">
+                <div *ngFor="let tutorial of getTutorialsByStage('rendering')" class="tutorial-item">
+                  <div class="tutorial-thumbnail">
+                    <img [src]="tutorial.thumbnail" alt="教程缩略图">
+                    <span class="duration">{{ tutorial.duration }}</span>
+                  </div>
+                  <div class="tutorial-info">
+                    <h5>{{ tutorial.title }}</h5>
+                    <p>{{ tutorial.description }}</p>
+                    <button (click)="playTutorial(tutorial.id)" class="btn-play">播放</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 后期阶段SOP详情 -->
+        <div *ngIf="activeSOPTab === 'postProduction'" class="sop-detail">
+          <div class="sop-header">
+            <h3>后期阶段SOP</h3>
+            <div class="sop-actions">
+              <button (click)="editSOP('postProduction')" class="btn-edit">编辑</button>
+              <button (click)="exportSOP('postProduction')" class="btn-export">导出PDF</button>
+            </div>
+          </div>
+          
+          <div class="sop-content">
+            <!-- 此处省略后期阶段SOP详情内容 -->
+            <div class="sop-section">
+              <h4>交付物要求</h4>
+              <ul class="requirement-list">
+                <li>提交最终效果图(JPG+PSD格式)</li>
+                <li>提供不同尺寸版本(用于不同场景)</li>
+                <li>保留分层文件</li>
+              </ul>
+            </div>
+            
+            <!-- 关联视频教程 -->
+            <div class="sop-section">
+              <h4>关联视频教程</h4>
+              <div class="video-tutorials">
+                <div *ngFor="let tutorial of getTutorialsByStage('postProduction')" class="tutorial-item">
+                  <div class="tutorial-thumbnail">
+                    <img [src]="tutorial.thumbnail" alt="教程缩略图">
+                    <span class="duration">{{ tutorial.duration }}</span>
+                  </div>
+                  <div class="tutorial-info">
+                    <h5>{{ tutorial.title }}</h5>
+                    <p>{{ tutorial.description }}</p>
+                    <button (click)="playTutorial(tutorial.id)" class="btn-play">播放</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 交付阶段SOP详情 -->
+        <div *ngIf="activeSOPTab === 'delivery'" class="sop-detail">
+          <div class="sop-header">
+            <h3>交付阶段SOP</h3>
+            <div class="sop-actions">
+              <button (click)="editSOP('delivery')" class="btn-edit">编辑</button>
+              <button (click)="exportSOP('delivery')" class="btn-export">导出PDF</button>
+            </div>
+          </div>
+          
+          <div class="sop-content">
+            <!-- 此处省略交付阶段SOP详情内容 -->
+            <div class="sop-section">
+              <h4>交付物要求</h4>
+              <ul class="requirement-list">
+                <li>提交完整的效果图文件包</li>
+                <li>提供项目总结报告</li>
+                <li>客户确认单</li>
+              </ul>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+  
+  <!-- 视频教程库 -->
+  <section class="video-tutorial-section">
+    <div class="section-header">
+      <h2>视频教程库</h2>
+      <div class="search-filter">
+        <input type="text" placeholder="搜索教程名称..." [(ngModel)]="searchQuery">
+        <select [(ngModel)]="filterStage">
+          <option value="all">全部阶段</option>
+          <option value="modeling">建模阶段</option>
+          <option value="rendering">渲染阶段</option>
+          <option value="postProduction">后期阶段</option>
+        </select>
+      </div>
+    </div>
+    
+    <div class="tutorial-grid">
+      <div *ngFor="let tutorial of filteredTutorials" class="tutorial-card">
+        <div class="tutorial-thumbnail">
+          <img [src]="tutorial.thumbnail" alt="教程缩略图">
+          <span class="duration">{{ tutorial.duration }}</span>
+        </div>
+        <div class="tutorial-info">
+          <h4>{{ tutorial.title }}</h4>
+          <p class="tutorial-description">{{ tutorial.description }}</p>
+          <div class="tutorial-meta">
+            <span class="stage-tag">{{ getStageLabel(tutorial.stage) }}</span>
+            <span class="view-count">{{ tutorial.views }}次观看</span>
+            <span class="upload-date">{{ tutorial.uploadDate | date:'yyyy-MM-dd' }}</span>
+          </div>
+          <button (click)="playTutorial(tutorial.id)" class="btn-play">播放</button>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 分页 -->
+    <div class="pagination">
+      <button (click)="previousPage()" [disabled]="currentPage === 1" class="btn-prev">上一页</button>
+      <span class="page-info">第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
+      <button (click)="nextPage()" [disabled]="currentPage === totalPages" class="btn-next">下一页</button>
+    </div>
+  </section>
+  
+  <!-- 实践考核 -->
+  <section class="assessment-section">
+    <div class="section-header">
+      <h2>实践考核</h2>
+      <button (click)="createAssessment()" class="btn-create">创建考核任务</button>
+    </div>
+    
+    <div class="assessment-tabs">
+      <div class="tab-buttons">
+        <button (click)="activeAssessmentTab = 'pending'" [class.active]="activeAssessmentTab === 'pending'">待分配考核</button>
+        <button (click)="activeAssessmentTab = 'ongoing'" [class.active]="activeAssessmentTab === 'ongoing'">进行中考核</button>
+        <button (click)="activeAssessmentTab = 'completed'" [class.active]="activeAssessmentTab === 'completed'">已完成考核</button>
+      </div>
+      
+      <div class="tab-content">
+        <!-- 待分配考核 -->
+        <div *ngIf="activeAssessmentTab === 'pending'" class="assessment-list">
+          <div *ngFor="let assessment of pendingAssessments" class="assessment-item">
+            <div class="assessment-header">
+              <h3>{{ assessment.title }}</h3>
+              <span class="assessment-stage">{{ getStageLabel(assessment.stage) }}</span>
+            </div>
+            <div class="assessment-info">
+              <p>{{ assessment.description }}</p>
+              <div class="assessment-meta">
+                <span class="difficulty">{{ getDifficultyLabel(assessment.difficulty) }}</span>
+                <span class="duration">预计耗时: {{ assessment.expectedDuration }}小时</span>
+              </div>
+            </div>
+            <div class="assessment-actions">
+              <button (click)="assignAssessment(assessment.id)" class="btn-assign">分配考核</button>
+              <button (click)="previewAssessment(assessment.id)" class="btn-preview">预览</button>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 进行中考核 -->
+        <div *ngIf="activeAssessmentTab === 'ongoing'" class="assessment-list">
+          <div *ngFor="let assessment of ongoingAssessments" class="assessment-item">
+            <div class="assessment-header">
+              <h3>{{ assessment.title }}</h3>
+              <span class="assessment-stage">{{ getStageLabel(assessment.stage) }}</span>
+            </div>
+            <div class="assessment-info">
+              <p>{{ assessment.description }}</p>
+              <div class="assessment-meta">
+                <span class="assignee">被考核人: {{ assessment.assigneeName }}</span>
+                <span class="deadline">截止日期: {{ assessment.deadline | date:'yyyy-MM-dd' }}</span>
+                <span class="status">状态: {{ getStatusLabel(assessment.status) }}</span>
+              </div>
+            </div>
+            <div class="assessment-actions">
+              <button (click)="viewSubmission(assessment.id)" class="btn-view">查看提交</button>
+              <button (click)="extendDeadline(assessment.id)" class="btn-extend">延长截止日期</button>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 已完成考核 -->
+        <div *ngIf="activeAssessmentTab === 'completed'" class="assessment-list">
+          <div *ngFor="let assessment of completedAssessments" class="assessment-item">
+            <div class="assessment-header">
+              <h3>{{ assessment.title }}</h3>
+              <span class="assessment-stage">{{ getStageLabel(assessment.stage) }}</span>
+            </div>
+            <div class="assessment-info">
+              <p>{{ assessment.description }}</p>
+              <div class="assessment-meta">
+                <span class="assignee">被考核人: {{ assessment.assigneeName }}</span>
+                <span class="completion-date">完成日期: {{ assessment.completionDate | date:'yyyy-MM-dd' }}</span>
+                <span class="score">得分: {{ assessment.score }}分</span>
+              </div>
+              <div class="assessment-result" [class.pass]="assessment.result === 'pass'" [class.fail]="assessment.result === 'fail'">
+                {{ assessment.result === 'pass' ? '通过' : '未通过' }}
+              </div>
+            </div>
+            <div class="assessment-actions">
+              <button (click)="viewAssessmentReport(assessment.id)" class="btn-view">查看报告</button>
+              <button (click)="syncToCapability(assessment.id)" class="btn-sync">同步至能力看板</button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+  
+  <!-- 培训效果跟踪 -->
+  <section class="training-effect-section">
+    <div class="section-header">
+      <h2>培训效果跟踪</h2>
+      <button (click)="generateEffectReport()" class="btn-generate">生成效果报告</button>
+    </div>
+    
+    <div class="effect-metrics">
+      <div class="metric-card">
+        <h3>平均学习时长</h3>
+        <p class="metric-value">{{ averageLearningHours }}小时</p>
+        <p class="metric-change" [class.positive]="learningHoursChange > 0" [class.negative]="learningHoursChange < 0">
+          {{ learningHoursChange > 0 ? '+' : '' }}{{ learningHoursChange }}% 较上期
+        </p>
+      </div>
+      
+      <div class="metric-card">
+        <h3>考核通过率</h3>
+        <p class="metric-value">{{ passRate }}%</p>
+        <p class="metric-change" [class.positive]="passRateChange > 0" [class.negative]="passRateChange < 0">
+          {{ passRateChange > 0 ? '+' : '' }}{{ passRateChange }}% 较上期
+        </p>
+      </div>
+      
+      <div class="metric-card">
+        <h3>修改率下降</h3>
+        <p class="metric-value">{{ revisionRateReduction }}%</p>
+        <p class="metric-description">培训后项目修改率下降</p>
+      </div>
+      
+      <div class="metric-card">
+        <h3>客户满意度提升</h3>
+        <p class="metric-value">{{ satisfactionImprovement }}%</p>
+        <p class="metric-description">培训后客户满意度提升</p>
+      </div>
+    </div>
+    
+    <div class="effect-charts">
+      <div class="chart-container">
+        <h3>培训参与度趋势</h3>
+        <div class="chart-placeholder">
+          <!-- 这里应该是培训参与度趋势图 -->
+          <svg width="500" height="300" viewBox="0 0 500 300">
+            <rect x="50" y="50" width="400" height="200" fill="#f5f5f5" stroke="#ddd"/>
+            <text x="250" y="150" text-anchor="middle" fill="#999">培训参与度趋势图</text>
+          </svg>
+        </div>
+      </div>
+      
+      <div class="chart-container">
+        <h3>考核成绩分布</h3>
+        <div class="chart-placeholder">
+          <!-- 这里应该是考核成绩分布图 -->
+          <svg width="500" height="300" viewBox="0 0 500 300">
+            <rect x="50" y="50" width="400" height="200" fill="#f5f5f5" stroke="#ddd"/>
+            <text x="250" y="150" text-anchor="middle" fill="#999">考核成绩分布图</text>
+          </svg>
+        </div>
+      </div>
+    </div>
+  </section>
+</main>

+ 716 - 0
src/app/pages/team-leader/knowledge-base/knowledge-base.scss

@@ -0,0 +1,716 @@
+@use '../ios-theme.scss' as *;
+
+.knowledge-base-main {
+  padding: $ios-spacing-xl;
+  background-color: $ios-background;
+  min-height: calc(100vh - 60px);
+}
+
+/* 通用样式 */
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid $ios-border;
+  
+  h2 {
+    font-size: 20px;
+    font-weight: 600;
+    color: $ios-text-primary;
+  }
+  
+  .search-filter {
+    display: flex;
+    gap: 10px;
+    
+    input[type="text"], select {
+      padding: 8px 12px;
+      border: 1px solid $ios-border;
+      border-radius: $ios-radius-lg;
+      font-size: 14px;
+      background-color: $ios-background;
+      color: $ios-text-primary;
+      
+      &:focus {
+        outline: none;
+        border-color: $ios-primary;
+        box-shadow: 0 0 0 2px rgba(0, 71, 171, 0.1);
+      }
+    }
+  }
+}
+
+/* 按钮样式 */
+.btn-add, .btn-export, .btn-edit, .btn-play, .btn-assign, .btn-preview, .btn-view, .btn-extend, .btn-sync, .btn-create, .btn-generate {
+  padding: 8px 16px;
+  border: none;
+  border-radius: $ios-radius-lg;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s;
+  
+  &:hover {
+    opacity: 0.9;
+    transform: translateY(-1px);
+  }
+  
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+    transform: none;
+  }
+}
+
+.btn-add, .btn-create {
+  background-color: $ios-primary;
+  color: $ios-background;
+}
+
+.btn-export, .btn-sync {
+  background-color: $ios-info;
+  color: $ios-background;
+}
+
+.btn-edit {
+  background-color: $ios-warning;
+  color: $ios-background;
+}
+
+.btn-play {
+  background-color: $ios-success;
+  color: $ios-background;
+}
+
+.btn-assign, .btn-preview, .btn-view, .btn-extend, .btn-generate {
+  background-color: $ios-background-secondary;
+  color: $ios-text-primary;
+  
+  &:hover {
+    background-color: $ios-background-tertiary;
+  }
+}
+
+/* SOP管理样式 */
+.sop-tabs {
+  background-color: $ios-background;
+  border-radius: $ios-radius-lg;
+  overflow: hidden;
+  box-shadow: $ios-shadow-sm;
+  
+  .tab-buttons {
+    display: flex;
+    background-color: $ios-background-secondary;
+    
+    button {
+      flex: 1;
+      padding: $ios-spacing-lg $ios-spacing-xl;
+      border: none;
+      background-color: transparent;
+      font-size: $ios-font-size-base;
+      font-weight: $ios-font-weight-medium;
+      color: $ios-text-secondary;
+      cursor: pointer;
+      transition: all 0.2s;
+      
+      &.active {
+        background-color: $ios-background;
+        color: $ios-primary;
+        box-shadow: inset 0 -2px 0 $ios-primary;
+      }
+      
+      &:hover:not(.active) {
+        background-color: rgba(255, 255, 255, 0.5);
+      }
+    }
+  }
+  
+  .tab-content {
+    padding: $ios-spacing-xl;
+  }
+}
+
+.sop-overview {
+  .sop-summary {
+    margin-bottom: $ios-spacing-xl;
+    
+    p {
+      color: $ios-text-secondary;
+      margin: 10px 0 20px 0;
+    }
+  }
+  
+  .sop-stages {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 15px;
+    
+    .stage-item {
+      padding: $ios-spacing-xl;
+      background-color: $ios-background-secondary;
+      border-radius: $ios-radius-lg;
+      text-align: center;
+      cursor: pointer;
+      transition: all 0.2s;
+      
+      &:hover {
+        background-color: $ios-primary-light;
+        transform: translateY(-2px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      .stage-icon {
+        display: inline-flex;
+        justify-content: center;
+        align-items: center;
+        width: 40px;
+        height: 40px;
+        background-color: $ios-primary;
+        color: $ios-background;
+        border-radius: 50%;
+        font-weight: $ios-font-weight-semibold;
+        margin-bottom: 10px;
+      }
+      
+      h4 {
+        margin: 5px 0;
+        color: $ios-text-primary;
+      }
+      
+      p {
+        color: $ios-text-secondary;
+        font-size: 13px;
+        margin: 0;
+      }
+    }
+  }
+}
+
+.sop-detail {
+  .sop-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: $ios-spacing-xl;
+    
+    h3 {
+      font-size: 18px;
+      color: $ios-text-primary;
+    }
+    
+    .sop-actions {
+      display: flex;
+      gap: 10px;
+    }
+  }
+  
+  .sop-content {
+    .sop-section {
+      margin-bottom: 25px;
+      
+      h4 {
+        font-size: 16px;
+        margin-bottom: 15px;
+        color: $ios-text-primary;
+        padding-bottom: 8px;
+        border-bottom: 1px solid $ios-border;
+      }
+      
+      .requirement-list {
+        list-style: none;
+        padding: 0;
+        margin: 0;
+        
+        li {
+          padding: 8px 0;
+          padding-left: 24px;
+          position: relative;
+          color: $ios-text-primary;
+          
+          &::before {
+            content: "•";
+            position: absolute;
+            left: 0;
+            color: $ios-primary;
+            font-weight: bold;
+          }
+        }
+      }
+      
+      .timeline {
+        position: relative;
+        padding-left: 24px;
+        
+        &::before {
+          content: '';
+          position: absolute;
+          left: 8px;
+          top: 0;
+          bottom: 0;
+          width: 2px;
+          background-color: $ios-border;
+        }
+        
+        .timeline-item {
+          position: relative;
+          padding-bottom: 15px;
+          
+          &::before {
+            content: '';
+            position: absolute;
+            left: -24px;
+            top: 4px;
+            width: 12px;
+            height: 12px;
+            border-radius: 50%;
+            background-color: $ios-primary;
+          }
+          
+          .time-marker {
+            font-weight: $ios-font-weight-semibold;
+            color: $ios-primary;
+            margin-right: 10px;
+          }
+          
+          .task {
+            color: $ios-text-primary;
+          }
+        }
+      }
+      
+      .quality-table {
+        width: 100%;
+        border-collapse: collapse;
+        
+        th, td {
+          padding: 10px;
+          text-align: left;
+          border-bottom: 1px solid $ios-border;
+        }
+        
+        th {
+          background-color: $ios-background-secondary;
+          color: $ios-text-primary;
+          font-weight: $ios-font-weight-semibold;
+        }
+        
+        td {
+          color: $ios-text-primary;
+        }
+        
+        tr:hover td {
+          background-color: $ios-primary-light;
+        }
+      }
+      
+      .video-tutorials {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+        gap: 15px;
+        
+        .tutorial-item {
+          display: flex;
+          gap: 15px;
+          padding: 15px;
+          background-color: $ios-background-secondary;
+          border-radius: $ios-radius-lg;
+          
+          .tutorial-thumbnail {
+            position: relative;
+            width: 120px;
+            height: 80px;
+            flex-shrink: 0;
+            
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+              border-radius: $ios-radius-lg;
+            }
+            
+            .duration {
+              position: absolute;
+              bottom: 5px;
+              right: 5px;
+              background-color: rgba(0, 0, 0, 0.7);
+              color: $ios-background;
+              padding: 2px 6px;
+              border-radius: 4px;
+              font-size: 12px;
+            }
+          }
+          
+          .tutorial-info {
+            flex: 1;
+            
+            h5 {
+              margin: 0 0 8px 0;
+              color: $ios-text-primary;
+              font-size: 14px;
+            }
+            
+            p {
+              margin: 0 0 10px 0;
+              color: $ios-text-secondary;
+              font-size: 12px;
+              line-height: 1.4;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 视频教程样式 */
+.tutorial-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+  margin-bottom: 20px;
+}
+
+.tutorial-card {
+  background-color: $ios-background;
+  border-radius: $ios-radius-lg;
+  overflow: hidden;
+  box-shadow: $ios-shadow-sm;
+  transition: all 0.2s;
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: $ios-shadow-lg;
+  }
+  
+  .tutorial-thumbnail {
+    position: relative;
+    width: 100%;
+    height: 180px;
+    
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+    
+    .duration {
+      position: absolute;
+      bottom: 10px;
+      right: 10px;
+      background-color: rgba(0, 0, 0, 0.7);
+      color: $ios-background;
+      padding: 4px 8px;
+      border-radius: 4px;
+      font-size: 12px;
+    }
+  }
+  
+  .tutorial-info {
+    padding: 15px;
+    
+    h4 {
+      margin: 0 0 10px 0;
+      color: $ios-text-primary;
+      font-size: 16px;
+    }
+    
+    .tutorial-description {
+      margin: 0 0 12px 0;
+      color: $ios-text-secondary;
+      font-size: 13px;
+      line-height: 1.4;
+    }
+    
+    .tutorial-meta {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 10px;
+      margin-bottom: 15px;
+      
+      .stage-tag {
+        background-color: $ios-primary-light;
+        color: $ios-primary;
+        padding: 2px 8px;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 500;
+      }
+      
+      .view-count, .upload-date {
+        color: $ios-text-secondary;
+        font-size: 12px;
+      }
+    }
+  }
+}
+
+.pagination {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 20px;
+  margin-top: 20px;
+  
+  .page-info {
+    color: $ios-text-secondary;
+    font-size: 14px;
+  }
+}
+
+/* 实践考核样式 */
+.assessment-tabs {
+  background-color: $ios-background;
+  border-radius: $ios-radius-lg;
+  overflow: hidden;
+  box-shadow: $ios-shadow-sm;
+  
+  .tab-buttons {
+    display: flex;
+    background-color: $ios-background-secondary;
+    
+    button {
+      flex: 1;
+      padding: 12px 20px;
+      border: none;
+      background-color: transparent;
+      font-size: 14px;
+      font-weight: 500;
+      color: $ios-text-secondary;
+      cursor: pointer;
+      transition: all 0.2s;
+      
+      &.active {
+        background-color: $ios-background;
+        color: $ios-primary;
+        box-shadow: inset 0 -2px 0 $ios-primary;
+      }
+      
+      &:hover:not(.active) {
+        background-color: rgba(255, 255, 255, 0.5);
+      }
+    }
+  }
+  
+  .tab-content {
+    padding: 20px;
+  }
+}
+
+.assessment-list {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  
+  .assessment-item {
+    padding: 20px;
+    background-color: $ios-background-secondary;
+    border-radius: $ios-radius-lg;
+    
+    .assessment-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 15px;
+      
+      h3 {
+        margin: 0;
+        color: $ios-text-primary;
+        font-size: 16px;
+      }
+      
+      .assessment-stage {
+        background-color: $ios-primary-light;
+        color: $ios-primary;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+      }
+    }
+    
+    .assessment-info {
+      margin-bottom: 15px;
+      
+      p {
+        margin: 0 0 10px 0;
+        color: $ios-text-secondary;
+        font-size: 13px;
+        line-height: 1.4;
+      }
+      
+      .assessment-meta {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 15px;
+        
+        .difficulty, .duration, .assignee, .deadline, .status, .completion-date, .score {
+          color: $ios-text-primary;
+          font-size: 13px;
+        }
+        
+        .difficulty.easy {
+          color: $ios-success;
+        }
+        
+        .difficulty.medium {
+          color: $ios-warning;
+        }
+        
+        .difficulty.hard {
+          color: $ios-danger;
+        }
+      }
+      
+      .assessment-result {
+        display: inline-block;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+        margin-top: 10px;
+        
+        &.pass {
+          background-color: $ios-success;
+          color: $ios-background;
+        }
+        
+        &.fail {
+          background-color: $ios-danger;
+          color: $ios-background;
+        }
+      }
+    }
+    
+    .assessment-actions {
+      display: flex;
+      gap: 10px;
+    }
+  }
+}
+
+/* 培训效果跟踪样式 */
+.effect-metrics {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 20px;
+  margin-bottom: 30px;
+  
+  .metric-card {
+    padding: 20px;
+    background-color: $ios-background;
+    border-radius: $ios-radius-lg;
+    box-shadow: $ios-shadow-sm;
+    text-align: center;
+    
+    h3 {
+        margin: 0 0 10px 0;
+        color: $ios-text-secondary;
+        font-size: 14px;
+        font-weight: 500;
+      }
+    
+    .metric-value {
+        margin: 0 0 5px 0;
+        color: $ios-text-primary;
+        font-size: 28px;
+        font-weight: 700;
+      }
+    
+    .metric-change {
+      margin: 0;
+      font-size: 13px;
+      font-weight: 500;
+      
+      &.positive {
+          color: $ios-success;
+        }
+        
+        &.negative {
+          color: $ios-danger;
+        }
+    }
+    
+    .metric-description {
+        margin: 0;
+        color: $ios-text-secondary;
+        font-size: 12px;
+      }
+  }
+}
+
+.effect-charts {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
+  gap: 20px;
+  
+  .chart-container {
+    background-color: $ios-background;
+    border-radius: $ios-radius-lg;
+    padding: 20px;
+    box-shadow: $ios-shadow-sm;
+    
+    h3 {
+        margin: 0 0 20px 0;
+        color: $ios-text-primary;
+        font-size: 16px;
+        text-align: center;
+      }
+    
+    .chart-placeholder {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .knowledge-base-main {
+    padding: 10px;
+  }
+  
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+    
+    .search-filter {
+      width: 100%;
+      flex-direction: column;
+      
+      input[type="text"], select {
+        width: 100%;
+      }
+    }
+  }
+  
+  .sop-tabs .tab-buttons, .assessment-tabs .tab-buttons {
+    flex-direction: column;
+  }
+  
+  .tutorial-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .effect-charts {
+    grid-template-columns: 1fr;
+    
+    .chart-container {
+      padding: 15px;
+      
+      .chart-placeholder svg {
+        width: 100%;
+        max-width: 300px;
+        height: auto;
+      }
+    }
+  }
+  
+  .pagination {
+    flex-direction: column;
+    gap: 10px;
+  }
+}

+ 441 - 0
src/app/pages/team-leader/knowledge-base/knowledge-base.ts

@@ -0,0 +1,441 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+
+// 定义SOP接口
+interface SOP {
+  id: string;
+  title: string;
+  stage: 'modeling' | 'rendering' | 'postProduction' | 'delivery';
+  requirements: string[];
+  timeline: { day: string; task: string }[];
+  qualityMetrics: {
+    metric: string;
+    standard: string;
+    premium: string;
+    vip: string;
+  }[];
+}
+
+// 定义视频教程接口
+interface VideoTutorial {
+  id: string;
+  title: string;
+  description: string;
+  thumbnail: string;
+  duration: string;
+  stage: 'modeling' | 'rendering' | 'postProduction' | 'all';
+  views: number;
+  uploadDate: Date;
+  videoUrl: string;
+}
+
+// 定义考核任务接口
+interface Assessment {
+  id: string;
+  title: string;
+  description: string;
+  stage: 'modeling' | 'rendering' | 'postProduction';
+  difficulty: 'easy' | 'medium' | 'hard';
+  expectedDuration: number;
+  assigneeId?: string;
+  assigneeName?: string;
+  deadline?: Date;
+  status?: 'not_started' | 'in_progress' | 'submitted' | 'graded';
+  submissionUrl?: string;
+  score?: number;
+  result?: 'pass' | 'fail';
+  completionDate?: Date;
+  comments?: string;
+}
+
+@Component({
+  selector: 'app-knowledge-base',
+  templateUrl: './knowledge-base.html',
+  styleUrl: './knowledge-base.scss',
+  standalone: true,
+  imports: [CommonModule, FormsModule]
+})
+
+export class KnowledgeBaseComponent implements OnInit {
+  // SOP相关
+  sops: SOP[] = [];
+  activeSOPTab = 'overview';
+  
+  // 视频教程相关
+  videoTutorials: VideoTutorial[] = [];
+  searchQuery = '';
+  filterStage = 'all';
+  filteredTutorials: VideoTutorial[] = [];
+  currentPage = 1;
+  itemsPerPage = 6;
+  totalPages = 1;
+  
+  // 考核相关
+  pendingAssessments: Assessment[] = [];
+  ongoingAssessments: Assessment[] = [];
+  completedAssessments: Assessment[] = [];
+  activeAssessmentTab = 'pending';
+  
+  // 培训效果相关
+  averageLearningHours = 8.5;
+  learningHoursChange = 12;
+  passRate = 85;
+  passRateChange = 5;
+  revisionRateReduction = 20;
+  satisfactionImprovement = 15;
+
+  constructor(private router: Router) {}
+
+  ngOnInit(): void {
+    this.initializeMockData();
+    this.filterTutorials();
+  }
+
+  // 初始化模拟数据
+  initializeMockData(): void {
+    // 初始化SOP数据
+    this.sops = [
+      {
+        id: 'sop-001',
+        title: '建模阶段SOP',
+        stage: 'modeling',
+        requirements: [
+          '提交完整的3D模型文件(.max/.blend/.skp等格式)',
+          '提供户型匹配度检查报告,误差≤2%',
+          '材质贴图完整且正确命名',
+          '灯光方案初步设置',
+          '模型优化报告(面数、材质数量等)'
+        ],
+        timeline: [
+          { day: 'Day 1', task: '完成基础建模和户型匹配' },
+          { day: 'Day 2', task: '完成细节建模和材质贴图' },
+          { day: 'Day 3', task: '完成灯光设置和模型优化' }
+        ],
+        qualityMetrics: [
+          { metric: '模型精度', standard: '≥95%', premium: '≥98%', vip: '≥99%' },
+          { metric: '户型匹配度', standard: '≥98%', premium: '≥99%', vip: '100%' },
+          { metric: '材质还原度', standard: '≥90%', premium: '≥95%', vip: '≥98%' },
+          { metric: '面数控制', standard: '≤100万', premium: '≤150万', vip: '≤200万' }
+        ]
+      },
+      {
+        id: 'sop-002',
+        title: '渲染阶段SOP',
+        stage: 'rendering',
+        requirements: [
+          '提交高清渲染图(PSD+JPG格式)',
+          '提供多角度渲染预览(至少3个角度)',
+          '保留渲染参数文件',
+          '渲染设置说明文档'
+        ],
+        timeline: [
+          { day: 'Day 1-2', task: '完成测试渲染和参数调整' },
+          { day: 'Day 3-4', task: '完成大图渲染' },
+          { day: 'Day 5', task: '完成渲染图检查和整理' }
+        ],
+        qualityMetrics: [
+          { metric: '像素要求', standard: '≥800px', premium: '≥1200px', vip: '≥1600px' },
+          { metric: '渲染真实度', standard: '≥90%', premium: '≥95%', vip: '≥98%' },
+          { metric: '光影效果', standard: '自然', premium: '层次丰富', vip: '专业级' }
+        ]
+      }
+    ];
+    
+    // 初始化视频教程数据
+    this.videoTutorials = [
+      {
+        id: 'tut-001',
+        title: '宋式雕花建模技巧详解',
+        description: '详细讲解宋式风格雕花的建模方法和技巧',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#E6E6E6\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '18:25',
+        stage: 'modeling',
+        views: 156,
+        uploadDate: new Date(2023, 8, 15),
+        videoUrl: '#'
+      },
+      {
+        id: 'tut-002',
+        title: '渲染真实度优化方法',
+        description: '提升渲染图真实度的实用技巧和参数设置',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#DCDCDC\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '22:40',
+        stage: 'rendering',
+        views: 213,
+        uploadDate: new Date(2023, 8, 10),
+        videoUrl: '#'
+      },
+      {
+        id: 'tut-003',
+        title: '现代风格客厅建模教程',
+        description: '从零开始学习现代风格客厅的建模流程',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#F0F0F0\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '35:15',
+        stage: 'modeling',
+        views: 189,
+        uploadDate: new Date(2023, 8, 5),
+        videoUrl: '#'
+      },
+      {
+        id: 'tut-004',
+        title: '后期光影调整技巧',
+        description: '如何通过后期处理提升图片的光影效果',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#E6E6E6\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '15:30',
+        stage: 'postProduction',
+        views: 142,
+        uploadDate: new Date(2023, 7, 28),
+        videoUrl: '#'
+      },
+      {
+        id: 'tut-005',
+        title: '材质贴图高级设置',
+        description: '深入了解材质贴图的高级参数设置和调整方法',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#DCDCDC\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '28:10',
+        stage: 'modeling',
+        views: 167,
+        uploadDate: new Date(2023, 7, 20),
+        videoUrl: '#'
+      },
+      {
+        id: 'tut-006',
+        title: '灯光设置实战指南',
+        description: '室内设计中灯光设置的实战技巧和案例分析',
+        thumbnail: '{ width: 300, height: 200, bgColor: \'#F0F0F0\', textColor: \'#555555\', text: \'IMG\' }',
+        duration: '31:45',
+        stage: 'rendering',
+        views: 198,
+        uploadDate: new Date(2023, 7, 15),
+        videoUrl: '#'
+      }
+    ];
+    
+    // 初始化考核任务数据
+    this.pendingAssessments = [
+      {
+        id: 'ass-001',
+        title: '完成100㎡现代风格客厅建模',
+        description: '根据提供的户型图,完成100㎡现代风格客厅的3D建模,要求符合SOP标准',
+        stage: 'modeling',
+        difficulty: 'medium',
+        expectedDuration: 8
+      },
+      {
+        id: 'ass-002',
+        title: '中式风格卧室渲染练习',
+        description: '使用提供的3D模型,完成中式风格卧室的渲染,重点关注光影效果和材质表现',
+        stage: 'rendering',
+        difficulty: 'medium',
+        expectedDuration: 6
+      }
+    ];
+    
+    this.ongoingAssessments = [
+      {
+        id: 'ass-003',
+        title: '北欧风格厨房后期处理',
+        description: '对提供的渲染图进行后期处理,提升图片质量和视觉效果',
+        stage: 'postProduction',
+        difficulty: 'easy',
+        expectedDuration: 4,
+        assigneeId: 'd3',
+        assigneeName: '王五',
+        deadline: new Date(2023, 9, 25),
+        status: 'in_progress'
+      },
+      {
+        id: 'ass-004',
+        title: '别墅客厅综合练习',
+        description: '完成别墅客厅的建模、渲染和后期处理全流程',
+        stage: 'modeling',
+        difficulty: 'hard',
+        expectedDuration: 12,
+        assigneeId: 'd4',
+        assigneeName: '赵六',
+        deadline: new Date(2023, 9, 30),
+        status: 'submitted',
+        submissionUrl: '#'
+      }
+    ];
+    
+    this.completedAssessments = [
+      {
+        id: 'ass-005',
+        title: '小户型卧室建模考核',
+        description: '完成小户型卧室的3D建模,重点考察户型匹配度和模型精度',
+        stage: 'modeling',
+        difficulty: 'easy',
+        expectedDuration: 4,
+        assigneeId: 'd1',
+        assigneeName: '张三',
+        deadline: new Date(2023, 9, 10),
+        status: 'graded',
+        submissionUrl: '#',
+        score: 95,
+        result: 'pass',
+        completionDate: new Date(2023, 9, 8),
+        comments: '模型精度高,户型匹配度好,细节处理到位'
+      },
+      {
+        id: 'ass-006',
+        title: '餐厅渲染技巧考核',
+        description: '完成餐厅场景的渲染,考察渲染参数设置和光影效果表现',
+        stage: 'rendering',
+        difficulty: 'medium',
+        expectedDuration: 6,
+        assigneeId: 'd2',
+        assigneeName: '李四',
+        deadline: new Date(2023, 9, 15),
+        status: 'graded',
+        submissionUrl: '#',
+        score: 88,
+        result: 'pass',
+        completionDate: new Date(2023, 9, 14),
+        comments: '渲染效果良好,光影层次丰富,但材质细节可进一步提升'
+      }
+    ];
+  }
+
+  // SOP相关方法
+  addNewSOP(): void {
+    // 实现添加新SOP的逻辑
+    console.log('添加新SOP');
+  }
+  
+  editSOP(stage: string): void {
+    // 实现编辑SOP的逻辑
+    console.log('编辑SOP:', stage);
+  }
+  
+  exportSOP(stage: string): void {
+    // 实现导出SOP的逻辑
+    console.log('导出SOP:', stage);
+  }
+  
+  exportFullSOP(): void {
+    // 实现导出完整SOP的逻辑
+    console.log('导出完整SOP');
+  }
+  
+  getTutorialsByStage(stage: string): VideoTutorial[] {
+    return this.videoTutorials.filter(tutorial => tutorial.stage === stage);
+  }
+
+  // 视频教程相关方法
+  filterTutorials(): void {
+    let filtered = this.videoTutorials;
+    
+    // 按搜索关键词过滤
+    if (this.searchQuery) {
+      const query = this.searchQuery.toLowerCase();
+      filtered = filtered.filter(tutorial => 
+        tutorial.title.toLowerCase().includes(query) || 
+        tutorial.description.toLowerCase().includes(query)
+      );
+    }
+    
+    // 按阶段过滤
+    if (this.filterStage !== 'all') {
+      filtered = filtered.filter(tutorial => tutorial.stage === this.filterStage);
+    }
+    
+    // 分页
+    this.totalPages = Math.ceil(filtered.length / this.itemsPerPage);
+    const startIndex = (this.currentPage - 1) * this.itemsPerPage;
+    this.filteredTutorials = filtered.slice(startIndex, startIndex + this.itemsPerPage);
+  }
+  
+  nextPage(): void {
+    if (this.currentPage < this.totalPages) {
+      this.currentPage++;
+      this.filterTutorials();
+    }
+  }
+  
+  previousPage(): void {
+    if (this.currentPage > 1) {
+      this.currentPage--;
+      this.filterTutorials();
+    }
+  }
+  
+  playTutorial(tutorialId: string): void {
+    // 实现播放视频教程的逻辑
+    console.log('播放教程:', tutorialId);
+  }
+  
+  getStageLabel(stage: string): string {
+    const labels: { [key: string]: string } = {
+      modeling: '建模阶段',
+      rendering: '渲染阶段',
+      postProduction: '后期阶段',
+      all: '全阶段'
+    };
+    return labels[stage] || stage;
+  }
+
+  // 考核相关方法
+  createAssessment(): void {
+    // 实现创建考核任务的逻辑
+    console.log('创建考核任务');
+  }
+  
+  assignAssessment(assessmentId: string): void {
+    // 实现分配考核任务的逻辑
+    console.log('分配考核任务:', assessmentId);
+  }
+  
+  previewAssessment(assessmentId: string): void {
+    // 实现预览考核任务的逻辑
+    console.log('预览考核任务:', assessmentId);
+  }
+  
+  viewSubmission(assessmentId: string): void {
+    // 实现查看考核提交的逻辑
+    console.log('查看考核提交:', assessmentId);
+  }
+  
+  extendDeadline(assessmentId: string): void {
+    // 实现延长考核截止日期的逻辑
+    console.log('延长考核截止日期:', assessmentId);
+  }
+  
+  viewAssessmentReport(assessmentId: string): void {
+    // 实现查看考核报告的逻辑
+    console.log('查看考核报告:', assessmentId);
+  }
+  
+  syncToCapability(assessmentId: string): void {
+    // 实现同步至能力看板的逻辑
+    console.log('同步至能力看板:', assessmentId);
+  }
+  
+  getDifficultyLabel(difficulty: string): string {
+    const labels: { [key: string]: string } = {
+      easy: '简单',
+      medium: '中等',
+      hard: '困难'
+    };
+    return labels[difficulty] || difficulty;
+  }
+  
+  getStatusLabel(status?: string): string {
+    if (!status) return '未知';
+    const labels: { [key: string]: string } = {
+      not_started: '未开始',
+      in_progress: '进行中',
+      submitted: '已提交',
+      graded: '已评分'
+    };
+    return labels[status] || status;
+  }
+
+  // 培训效果相关方法
+  generateEffectReport(): void {
+    // 实现生成培训效果报告的逻辑
+    console.log('生成培训效果报告');
+  }
+}

+ 115 - 56
src/app/pages/team-leader/quality-management/quality-management.html

@@ -1,4 +1,4 @@
-<header class="quality-management-header">
+<header>
   <h1>质量管理</h1>
 </header>
 
@@ -19,7 +19,8 @@
       
       <div class="tab-content">
         <!-- 建模阶段标准 -->
-        <div *ngIf="activeTab === 'modeling'" class="phase-content">
+        @if (activeTab === 'modeling') {
+        <div class="phase-content">
           <div class="sop-section">
             <h3>交付物要求</h3>
             <ul class="requirement-list">
@@ -69,9 +70,12 @@
             </ul>
           </div>
         </div>
+        }
+        
         
         <!-- 渲染阶段标准 -->
-        <div *ngIf="activeTab === 'rendering'" class="phase-content">
+        @if (activeTab === 'rendering') {
+        <div class="phase-content">
           <div class="sop-section">
             <h3>交付物要求</h3>
             <ul class="requirement-list">
@@ -120,9 +124,11 @@
             </ul>
           </div>
         </div>
+        }
         
         <!-- 后期阶段标准 -->
-        <div *ngIf="activeTab === 'postProduction'" class="phase-content">
+        @if (activeTab === 'postProduction') {
+        <div class="phase-content">
           <div class="sop-section">
             <h3>交付物要求</h3>
             <ul class="requirement-list">
@@ -171,10 +177,11 @@
             </ul>
           </div>
         </div>
+        }
       </div>
     </div>
   </section>
-
+  
   <!-- 效果图质量整改跟踪 -->
   <section class="rectification-section">
     <div class="section-header">
@@ -192,54 +199,64 @@
     </div>
     
     <div class="rectification-list">
-      <div *ngFor="let task of filteredTasks" class="rectification-task" [class.priority-high]="task.priority === 'high'">
-        <div class="task-header">
-          <h3>{{ task.projectName }}</h3>
-          <span [class]="'status ' + task.status">{{ getStatusText(task.status) }}</span>
+      @for (task of filteredTasks; track task.id) {
+      <div class="rectification-item" [class.priority-high]="task.priority === 'high'">
+        <div class="rectification-header">
+          <h4>{{ task.projectName }}</h4>
+          <span [class]="'rectification-status status-' + task.status">{{ getStatusText(task.status) }}</span>
         </div>
         
-        <div class="task-details">
+        <div class="rectification-details">
           <div class="detail-item">
-            <span class="label">负责组员:</span>
-            <span class="value">{{ task.designerName }}</span>
+            <span class="detail-label">负责组员:</span>
+            <span class="detail-value">{{ task.designerName }}</span>
           </div>
           <div class="detail-item">
-            <span class="label">问题描述:</span>
-            <span class="value">{{ task.issueDescription }}</span>
+            <span class="detail-label">问题描述:</span>
+            <span class="detail-value">{{ task.issueDescription }}</span>
           </div>
           <div class="detail-item">
-            <span class="label">优先级:</span>
-            <span [class]="'priority ' + task.priority">{{ getPriorityText(task.priority) }}</span>
+            <span class="detail-label">优先级:</span>
+            <span class="detail-value priority-{{ task.priority }}">{{ getPriorityText(task.priority) }}</span>
           </div>
           <div class="detail-item">
-            <span class="label">建议完成时间:</span>
-            <span class="value">{{ task.suggestedDeadline | date:'yyyy-MM-dd' }}</span>
+            <span class="detail-label">建议完成时间:</span>
+            <span class="detail-value">{{ task.suggestedDeadline | date:'yyyy-MM-dd' }}</span>
           </div>
           
-          <div *ngIf="task.sopComplianceScore !== null" class="detail-item">
-            <span class="label">SOP符合度评分:</span>
-            <span class="value score">{{ task.sopComplianceScore }}/100</span>
+          @if (task.sopComplianceScore !== null) {
+          <div class="detail-item">
+            <span class="detail-label">SOP符合度评分:</span>
+            <span class="detail-value score">{{ task.sopComplianceScore }}/100</span>
           </div>
+          }
           
-          <div *ngIf="task.managerComment" class="detail-item comment">
-            <span class="label">组长评语:</span>
-            <span class="value">{{ task.managerComment }}</span>
+          @if (task.managerComment) {
+          <div class="detail-item">
+            <span class="detail-label">组长评语:</span>
+            <span class="detail-value">{{ task.managerComment }}</span>
           </div>
+          }
         </div>
         
-        <div class="task-actions">
-          <button (click)="viewTaskDetails(task.id)" class="btn-view">查看详情</button>
+        <div class="rectification-actions">
+          <button (click)="viewTaskDetails(task.id)" class="btn">查看详情</button>
           
-          <div *ngIf="task.status === 'review'">
-            <button (click)="approveTask(task.id)" class="btn-approve">通过</button>
-            <button (click)="rejectTask(task.id)" class="btn-reject">驳回</button>
+          @if (task.status === 'review') {
+          <div>
+            <button (click)="approveTask(task.id)" class="btn btn-primary">通过</button>
+            <button (click)="rejectTask(task.id)" class="btn">驳回</button>
           </div>
+          }
           
-          <div *ngIf="task.status === 'completed'">
-            <button (click)="syncToProjectReview(task.id)" class="btn-sync">同步至项目评审</button>
+          @if (task.status === 'completed') {
+          <div>
+            <button (click)="syncToProjectReview(task.id)" class="btn btn-primary">同步至项目评审</button>
           </div>
+          }
         </div>
       </div>
+      }
     </div>
   </section>
 
@@ -247,57 +264,99 @@
   <section class="training-section">
     <div class="section-header">
       <h2>能力提升工具</h2>
+      <p class="section-description">通过视频教程和实践作业提升团队技能,确保SOP标准执行</p>
     </div>
     
     <div class="training-content">
+      <!-- 视频教程区域 -->
       <div class="video-tutorials">
-        <h3>视频教程</h3>
-        <div class="video-list">
-          <div *ngFor="let video of videoTutorials" class="video-item">
-            <div class="video-thumbnail">
-              <img [src]="video.thumbnailUrl" alt="视频缩略图">
-              <div class="video-duration">{{ video.duration }}</div>
+        <div class="section-subheader">
+          <h3>视频教程</h3>
+          <button class="btn-view-all">查看全部</button>
+        </div>
+        <div class="video-grid">
+          @for (video of videoTutorials; track video.id) {
+          <div class="video-card" (mouseenter)="video.isHovered = true" (mouseleave)="video.isHovered = false">
+            <div class="video-thumbnail-container">
+              <div class="video-thumbnail">
+                <img [src]="video.thumbnailUrl" alt="视频缩略图" class="thumbnail-img">
+                <div class="video-duration">{{ video.duration }}</div>
+                <div class="play-overlay" [class.visible]="video.isHovered">
+                  <button (click)="playVideo(video.id)" class="play-button">
+                    <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+                      <circle cx="20" cy="20" r="20" fill="rgba(0,0,0,0.6)"/>
+                      <path d="M15 13L28 20L15 27V13Z" fill="white"/>
+                    </svg>
+                  </button>
+                </div>
+              </div>
             </div>
             <div class="video-info">
-              <h4>{{ video.title }}</h4>
+              <div class="category-badge">{{ video.category }}</div>
+              <h4 class="video-title">{{ video.title }}</h4>
               <p class="video-description">{{ video.description }}</p>
               <div class="video-meta">
-                <span class="category">{{ video.category }}</span>
                 <span class="views">{{ video.views }} 次观看</span>
+                <span class="date">2天前</span>
               </div>
-              <button (click)="playVideo(video.id)" class="btn-play">播放</button>
             </div>
           </div>
+          }
         </div>
       </div>
       
+      <!-- 实践作业区域 -->
       <div class="practice-assignments">
-        <h3>实践作业</h3>
+        <div class="section-subheader">
+          <h3>实践作业</h3>
+          <div class="assignment-stats">
+            <span class="stat">待评审: 2</span>
+            <span class="stat">进行中: 3</span>
+          </div>
+        </div>
         <div class="assignment-list">
-          <div *ngFor="let assignment of practiceAssignments" class="assignment-item">
+          @for (assignment of practiceAssignments; track assignment.id) {
+          <div class="assignment-card">
             <div class="assignment-header">
-              <h4>{{ assignment.title }}</h4>
-              <span [class]="'status ' + assignment.status">{{ getAssignmentStatusText(assignment.status) }}</span>
+              <div class="assignment-title-section">
+                <h4>{{ assignment.title }}</h4>
+                <div class="assignment-badges">
+                  <span class="badge related-sop">关联SOP: {{ assignment.relatedSOP }}</span>
+                </div>
+              </div>
+              <span [class]="'status-badge status-' + assignment.status">{{ getAssignmentStatusText(assignment.status) }}</span>
             </div>
             <div class="assignment-details">
               <p class="description">{{ assignment.description }}</p>
-              <div class="detail-item">
-                <span class="label">关联SOP:</span>
-                <span class="value">{{ assignment.relatedSOP }}</span>
-              </div>
-              <div class="detail-item">
-                <span class="label">截止日期:</span>
-                <span class="value">{{ assignment.deadline | date:'yyyy-MM-dd' }}</span>
-              </div>
-              <div *ngIf="assignment.score !== null" class="detail-item">
-                <span class="label">得分:</span>
-                <span class="value score">{{ assignment.score }}/100</span>
+              <div class="assignment-meta">
+                <div class="meta-item">
+                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M8 2V3M8 13V14M1 8H2M14 8H15M12.5 3.5L13.5 2.5M3.5 13.5L2.5 12.5M12.5 12.5L13.5 13.5M3.5 3.5L2.5 2.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                    <circle cx="8" cy="8" r="5" stroke="currentColor" stroke-width="2"/>
+                  </svg>
+                  <span>截止日期: {{ assignment.deadline | date:'yyyy-MM-dd' }}</span>
+                </div>
+                @if (assignment.score !== null) {
+                <div class="meta-item score-item">
+                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                    <path d="M8 1L10.5 5.5L16 6L12 9L13 14L8 12L3 14L4 9L0 6L5.5 5.5L8 1Z" stroke="currentColor" stroke-width="2" fill="none"/>
+                  </svg>
+                  <span>得分: {{ assignment.score }}/100</span>
+                </div>
+                }
               </div>
             </div>
             <div class="assignment-actions">
-              <button (click)="reviewAssignment(assignment.id)" class="btn-review">评审作业</button>
+              <button (click)="reviewAssignment(assignment.id)" class="btn-review btn-primary">
+                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M8 1L10.5 5.5L16 6L12 9L13 14L8 12L3 14L4 9L0 6L5.5 5.5L8 1Z" stroke="white" stroke-width="2" fill="none"/>
+                </svg>
+                评审作业
+              </button>
+              <button class="btn-secondary">下载资料</button>
             </div>
           </div>
+          }
         </div>
       </div>
     </div>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1316 - 338
src/app/pages/team-leader/quality-management/quality-management.scss


+ 7 - 3
src/app/pages/team-leader/quality-management/quality-management.ts

@@ -27,6 +27,7 @@ interface VideoTutorial {
   category: string;
   views: number;
   relatedSOP: string;
+  isHovered: boolean;
 }
 
 // 定义实践作业接口
@@ -136,7 +137,8 @@ export class QualityManagementComponent implements OnInit {
         duration: '15:30',
         category: '渲染技巧',
         views: 1256,
-        relatedSOP: '渲染阶段'
+        relatedSOP: '渲染阶段',
+        isHovered: false
       },
       {
         id: 'video-2',
@@ -146,7 +148,8 @@ export class QualityManagementComponent implements OnInit {
         duration: '22:15',
         category: '建模技巧',
         views: 892,
-        relatedSOP: '建模阶段'
+        relatedSOP: '建模阶段',
+        isHovered: false
       },
       {
         id: 'video-3',
@@ -156,7 +159,8 @@ export class QualityManagementComponent implements OnInit {
         duration: '18:45',
         category: '后期处理',
         views: 1053,
-        relatedSOP: '后期阶段'
+        relatedSOP: '后期阶段',
+        isHovered: false
       }
     ];
     

+ 1 - 1
src/app/services/auth.service.ts

@@ -65,7 +65,7 @@ export class AuthService {
         const mockUser: UserInfo = {
           id: '1',
           name: '超级管理员',
-          avatar: 'https://via.placeholder.com/40x40/CCFFCC/555555?text=ADMIN',
+          avatar: "data:image/svg+xml,%3Csvg width='40' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23CCFFCC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EADMIN%3C/text%3E%3C/svg%3E",
           roles: ['admin', 'user'],
           permissions: ['view-all', 'edit-all', 'delete-all'],
           lastLogin: new Date().toISOString()

+ 125 - 0
src/app/services/project.service.ts

@@ -14,6 +14,44 @@ import {
   ProjectStage
 } from '../models/project.model';
 
+// 材料统计数据接口
+interface MaterialStatistics {
+  monthlyAdded: number;
+  totalCostSaved: number;
+  personalDownloads: number;
+}
+
+// 材料项接口
+interface MaterialItem {
+  id: string;
+  name: string;
+  type: string;
+  style: string;
+  price: number;
+  thumbnailUrl: string;
+  downloadCount: number;
+  tags: string[];
+  costSaved: number;
+  isNew: boolean;
+  uploadTime: Date;
+}
+
+// 材料列表响应接口
+interface MaterialsResponse {
+  materials: MaterialItem[];
+  total: number;
+}
+
+// 异常历史记录接口
+interface ExceptionHistory {
+  id: string;
+  type: 'failed' | 'stuck' | 'quality' | 'other';
+  description: string;
+  submitTime: Date;
+  status: '待处理' | '处理中' | '已解决';
+  response?: string;
+}
+
 @Injectable({
   providedIn: 'root'
 })
@@ -321,4 +359,91 @@ export class ProjectService {
     }
     return of(project);
   }
+
+  // 获取材料统计数据
+  getMaterialStatistics(): Observable<MaterialStatistics> {
+    return of({
+      monthlyAdded: 15,
+      totalCostSaved: 2800,
+      personalDownloads: 42
+    });
+  }
+
+  // 获取异常历史记录
+  getExceptionHistories(projectId: string): Observable<ExceptionHistory[]> {
+    return of([
+      {
+        id: 'eh1',
+        type: 'quality',
+        description: '光线效果不符合预期',
+        submitTime: new Date('2025-09-05'),
+        status: '已解决',
+        response: '已调整灯光参数'
+      },
+      {
+        id: 'eh2',
+        type: 'stuck',
+        description: '沙发尺寸需要调整',
+        submitTime: new Date('2025-09-07'),
+        status: '待处理'
+      }
+    ]);
+  }
+
+  // 更新收藏材料状态
+  updateFavoriteMaterial(materialId: string, isFavorite: boolean): Observable<void> {
+    // 模拟API调用
+    console.log(`${isFavorite ? '添加' : '取消'}材料收藏:`, materialId);
+    return of(void 0);
+  }
+
+  // 获取收藏材料列表
+  getFavoriteMaterials(): Observable<string[]> {
+    // 模拟返回收藏的材料ID列表
+    return of(['mat1', 'mat3', 'mat5']);
+  }
+
+  // 获取材料列表
+  getMaterials(
+    searchQuery: string = '',
+    filterType: string = '',
+    filterStyle: string = '',
+    filterPrice: string = '',
+    page: number = 1,
+    pageSize: number = 10
+  ): Observable<MaterialsResponse> {
+    // 模拟材料数据
+    const mockMaterials: MaterialItem[] = [
+      { id: 'mat1', name: '现代风格沙发', type: '家具', style: '现代', price: 2800, thumbnailUrl: 'https://picsum.photos/200/150', downloadCount: 120, tags: ['现代', '舒适', '客厅', '布艺'], costSaved: 500, isNew: false, uploadTime: new Date('2025-08-15') },
+      { id: 'mat2', name: '宋式风格茶几', type: '家具', style: '宋式', price: 1500, thumbnailUrl: 'https://picsum.photos/200/151', downloadCount: 85, tags: ['宋式', '古典', '实木', '客厅'], costSaved: 300, isNew: true, uploadTime: new Date('2025-09-07') },
+      { id: 'mat3', name: '欧式吊灯', type: '灯具', style: '欧式', price: 2200, thumbnailUrl: 'https://picsum.photos/200/152', downloadCount: 150, tags: ['欧式', '奢华', '客厅', '水晶'], costSaved: 450, isNew: false, uploadTime: new Date('2025-08-20') },
+      { id: 'mat4', name: '中式地毯', type: '纺织品', style: '中式', price: 1800, thumbnailUrl: 'https://picsum.photos/200/153', downloadCount: 95, tags: ['中式', '传统', '卧室', '羊毛'], costSaved: 350, isNew: false, uploadTime: new Date('2025-08-25') },
+      { id: 'mat5', name: '北欧风格窗帘', type: '纺织品', style: '北欧', price: 1200, thumbnailUrl: 'https://picsum.photos/200/154', downloadCount: 130, tags: ['北欧', '简约', '卧室', '亚麻'], costSaved: 250, isNew: true, uploadTime: new Date('2025-09-05') }
+    ];
+    
+    // 简单的搜索和过滤逻辑
+    let filtered = [...mockMaterials];
+    if (searchQuery) {
+      filtered = filtered.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()));
+    }
+    if (filterType) {
+      filtered = filtered.filter(m => m.type === filterType);
+    }
+    if (filterStyle) {
+      filtered = filtered.filter(m => m.style === filterStyle);
+    }
+    
+    // 分页
+    const startIndex = (page - 1) * pageSize;
+    const paginated = filtered.slice(startIndex, startIndex + pageSize);
+    
+    return of({ materials: paginated, total: filtered.length });
+  }
+
+  // 记录材料下载
+  recordMaterialDownload(materialId: string): Observable<void> {
+    // 模拟下载记录
+    console.log('记录材料下载:', materialId);
+    return of(void 0);
+  }
 }

+ 1 - 1
src/app/shared/components/designer-nav/designer-nav.html

@@ -36,7 +36,7 @@
       <span class="notification-badge">3</span>
     </button>
     <div class="user-profile">
-      <img src="https://via.placeholder.com/40x40/CCCCFF/555555?text=DESIGN" alt="用户头像" class="user-avatar">
+      <div style="width: 40px; height: 40px; background-color: #CCCCFF; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 13.333333333333334px; font-weight: bold;" class="user-avatar" title="用户头像">DESIGN</div>
       <span class="user-name">{{userName}}</span>
     </div>
   </div>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä