徐福静0235668 4 dienas atpakaļ
vecāks
revīzija
866dad2e75

+ 168 - 0
src/app/pages/admin/dashboard/components/write-content/write-content.component.html

@@ -0,0 +1,168 @@
+<div class="write-content">
+  <!-- 步骤 1: 选择内容类型 -->
+  <div class="step-content" *ngIf="currentStep() === 1">
+    <div class="content-types-grid">
+      <div 
+        class="content-type-card" 
+        *ngFor="let type of contentTypes"
+        (click)="selectContentType(type)"
+        [style.border-color]="type.color"
+      >
+        <div class="card-icon" [style.background-color]="type.color">
+          <svg viewBox="0 0 24 24">
+            <path [attr.d]="type.icon"/>
+          </svg>
+        </div>
+        <div class="card-content">
+          <h3 class="card-title">{{ type.title }}</h3>
+          <p class="card-description">{{ type.description }}</p>
+        </div>
+        <div class="card-arrow">
+          <svg viewBox="0 0 24 24">
+            <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
+          </svg>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 步骤 2: 填写表单 -->
+  <div class="step-content" *ngIf="currentStep() === 2">
+    <div class="form-header">
+      <button class="back-button" (click)="goBack()">
+        <svg viewBox="0 0 24 24">
+          <path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/>
+        </svg>
+        返回
+      </button>
+      <div class="selected-type">
+        <div class="type-icon" [style.background-color]="selectedContentType()?.color">
+          <svg viewBox="0 0 24 24">
+            <path [attr.d]="selectedContentType()?.icon"/>
+          </svg>
+        </div>
+        <span>{{ selectedContentType()?.title }}</span>
+      </div>
+    </div>
+
+    <form class="content-form" (ngSubmit)="submitForm()">
+      <!-- 标题 -->
+      <div class="form-group">
+        <label for="title" class="form-label">标题 *</label>
+        <input 
+            type="text" 
+            id="title" 
+            class="form-input"
+            placeholder="请输入标题"
+            [value]="formData().title"
+            (input)="updateFormField('title', $any($event.target).value || '')"
+            required
+          >
+      </div>
+
+      <!-- 描述 -->
+      <div class="form-group">
+        <label for="description" class="form-label">描述 *</label>
+        <textarea 
+          id="description" 
+          class="form-textarea"
+          placeholder="请输入描述"
+          rows="3"
+          [value]="formData().description"
+          (input)="updateFormField('description', $any($event.target).value || '')"
+          required
+        ></textarea>
+      </div>
+
+      <!-- 详细内容 -->
+      <div class="form-group">
+        <label for="content" class="form-label">详细内容</label>
+        <textarea 
+          id="content" 
+          class="form-textarea content-textarea"
+          placeholder="请输入详细内容"
+          rows="6"
+          [value]="formData().content"
+          (input)="updateFormField('content', $any($event.target).value || '')"
+        ></textarea>
+      </div>
+
+      <!-- 标签 -->
+      <div class="form-group">
+        <label class="form-label">标签</label>
+        <div class="tags-container">
+          <div class="tags-list">
+            <span 
+              class="tag" 
+              *ngFor="let tag of formData().tags"
+              (click)="removeTag(tag)"
+            >
+              {{ tag }}
+              <svg class="tag-remove" viewBox="0 0 24 24">
+                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
+              </svg>
+            </span>
+          </div>
+          <div class="tag-input-container">
+            <input 
+              type="text" 
+              class="tag-input"
+              placeholder="添加标签"
+              [value]="newTag()"
+              (input)="newTag.set($any($event.target).value || '')"
+              (keypress)="onTagKeyPress($event)"
+            >
+            <button type="button" class="add-tag-button" (click)="addTag()" [disabled]="!newTag().trim()">
+              <svg viewBox="0 0 24 24">
+                <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+              </svg>
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- 优先级和截止日期 -->
+      <div class="form-row">
+        <div class="form-group">
+          <label for="priority" class="form-label">优先级</label>
+          <select 
+            id="priority" 
+            class="form-select"
+            [value]="formData().priority"
+            (change)="updateFormField('priority', $any($event.target).value || '')"
+          >
+            <option value="low">低</option>
+            <option value="medium">中</option>
+            <option value="high">高</option>
+          </select>
+        </div>
+
+        <div class="form-group">
+          <label for="dueDate" class="form-label">截止日期</label>
+          <input 
+            type="date" 
+            id="dueDate" 
+            class="form-input"
+            [value]="formData().dueDate"
+            (input)="updateFormField('dueDate', $any($event.target).value || '')"
+          >
+        </div>
+      </div>
+
+      <!-- 提交按钮 -->
+      <div class="form-actions">
+        <button type="button" class="cancel-button" (click)="onClose()">
+          取消
+        </button>
+        <button 
+          type="submit" 
+          class="submit-button"
+          [disabled]="!isFormValid()"
+          [style.background-color]="selectedContentType()?.color"
+        >
+          创建{{ selectedContentType()?.title }}
+        </button>
+      </div>
+    </form>
+  </div>
+</div>

+ 448 - 0
src/app/pages/admin/dashboard/components/write-content/write-content.component.scss

@@ -0,0 +1,448 @@
+// 变量定义
+$primary-color: #165DFF;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-color: #1D2129;
+$text-color-secondary: #4E5969;
+$border-color: #E5E6EB;
+$bg-color: #F7F8FA;
+$card-bg: #FFFFFF;
+$shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-hover: 0 8px 24px rgba(0, 0, 0, 0.12);
+$border-radius: 12px;
+$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+.write-content {
+  .step-content {
+    animation: slideIn 0.3s ease;
+  }
+
+  // 内容类型选择网格
+  .content-types-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+    gap: 20px;
+    padding: 8px;
+  }
+
+  // 内容类型卡片
+  .content-type-card {
+    background: $card-bg;
+    border: 2px solid $border-color;
+    border-radius: $border-radius;
+    padding: 24px;
+    cursor: pointer;
+    transition: $transition;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    position: relative;
+    overflow: hidden;
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 4px;
+      background: currentColor;
+      transform: scaleX(0);
+      transition: $transition;
+    }
+
+    &:hover {
+      transform: translateY(-4px);
+      box-shadow: $shadow-hover;
+      border-color: currentColor;
+
+      &::before {
+        transform: scaleX(1);
+      }
+
+      .card-arrow {
+        transform: translateX(4px);
+      }
+    }
+
+    .card-icon {
+      width: 48px;
+      height: 48px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      flex-shrink: 0;
+
+      svg {
+        width: 24px;
+        height: 24px;
+        fill: white;
+      }
+    }
+
+    .card-content {
+      flex: 1;
+
+      .card-title {
+        font-size: 18px;
+        font-weight: 700;
+        color: $text-color;
+        margin: 0 0 8px 0;
+      }
+
+      .card-description {
+        font-size: 14px;
+        color: $text-color-secondary;
+        margin: 0;
+        line-height: 1.4;
+      }
+    }
+
+    .card-arrow {
+      width: 24px;
+      height: 24px;
+      color: $text-color-secondary;
+      transition: $transition;
+
+      svg {
+        width: 100%;
+        height: 100%;
+        fill: currentColor;
+      }
+    }
+  }
+
+  // 表单头部
+  .form-header {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    margin-bottom: 32px;
+    padding-bottom: 16px;
+    border-bottom: 2px solid $border-color;
+
+    .back-button {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      background: none;
+      border: 1px solid $border-color;
+      border-radius: 8px;
+      padding: 8px 16px;
+      color: $text-color-secondary;
+      cursor: pointer;
+      transition: $transition;
+      font-size: 14px;
+      font-weight: 500;
+
+      &:hover {
+        background: $bg-color;
+        border-color: $primary-color;
+        color: $primary-color;
+      }
+
+      svg {
+        width: 16px;
+        height: 16px;
+        fill: currentColor;
+      }
+    }
+
+    .selected-type {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-color;
+
+      .type-icon {
+        width: 32px;
+        height: 32px;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        svg {
+          width: 16px;
+          height: 16px;
+          fill: white;
+        }
+      }
+    }
+  }
+
+  // 表单样式
+  .content-form {
+    .form-group {
+      margin-bottom: 24px;
+
+      .form-label {
+        display: block;
+        font-size: 14px;
+        font-weight: 600;
+        color: $text-color;
+        margin-bottom: 8px;
+      }
+
+      .form-input,
+      .form-textarea,
+      .form-select {
+        width: 100%;
+        padding: 12px 16px;
+        border: 2px solid $border-color;
+        border-radius: 8px;
+        font-size: 14px;
+        color: $text-color;
+        background: $card-bg;
+        transition: $transition;
+        font-family: inherit;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+        }
+
+        &::placeholder {
+          color: $text-color-secondary;
+        }
+      }
+
+      .form-textarea {
+        resize: vertical;
+        min-height: 80px;
+
+        &.content-textarea {
+          min-height: 120px;
+        }
+      }
+    }
+
+    .form-row {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 20px;
+
+      @media (max-width: 600px) {
+        grid-template-columns: 1fr;
+      }
+    }
+  }
+
+  // 标签容器
+  .tags-container {
+    .tags-list {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      margin-bottom: 12px;
+
+      .tag {
+        display: inline-flex;
+        align-items: center;
+        gap: 6px;
+        background: rgba(22, 93, 255, 0.1);
+        color: $primary-color;
+        padding: 6px 12px;
+        border-radius: 20px;
+        font-size: 12px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: $transition;
+
+        &:hover {
+          background: rgba(22, 93, 255, 0.2);
+
+          .tag-remove {
+            opacity: 1;
+          }
+        }
+
+        .tag-remove {
+          width: 14px;
+          height: 14px;
+          fill: currentColor;
+          opacity: 0.6;
+          transition: $transition;
+        }
+      }
+    }
+
+    .tag-input-container {
+      display: flex;
+      gap: 8px;
+
+      .tag-input {
+        flex: 1;
+        padding: 8px 12px;
+        border: 1px solid $border-color;
+        border-radius: 6px;
+        font-size: 13px;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+        }
+      }
+
+      .add-tag-button {
+        width: 36px;
+        height: 36px;
+        background: $primary-color;
+        border: none;
+        border-radius: 6px;
+        color: white;
+        cursor: pointer;
+        transition: $transition;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        &:hover:not(:disabled) {
+          background: darken($primary-color, 10%);
+          transform: scale(1.05);
+        }
+
+        &:disabled {
+          background: $border-color;
+          cursor: not-allowed;
+        }
+
+        svg {
+          width: 16px;
+          height: 16px;
+          fill: currentColor;
+        }
+      }
+    }
+  }
+
+  // 表单操作按钮
+  .form-actions {
+    display: flex;
+    gap: 16px;
+    justify-content: flex-end;
+    margin-top: 32px;
+    padding-top: 24px;
+    border-top: 1px solid $border-color;
+
+    .cancel-button,
+    .submit-button {
+      padding: 12px 24px;
+      border-radius: 8px;
+      font-size: 14px;
+      font-weight: 600;
+      cursor: pointer;
+      transition: $transition;
+      border: none;
+    }
+
+    .cancel-button {
+      background: $bg-color;
+      color: $text-color-secondary;
+      border: 1px solid $border-color;
+
+      &:hover {
+        background: darken($bg-color, 5%);
+        border-color: $text-color-secondary;
+      }
+    }
+
+    .submit-button {
+      background: $primary-color;
+      color: white;
+      min-width: 120px;
+
+      &:hover:not(:disabled) {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
+      }
+
+      &:disabled {
+        background: $border-color;
+        cursor: not-allowed;
+        transform: none;
+        box-shadow: none;
+      }
+    }
+  }
+}
+
+// 动画
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateX(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .write-content {
+    .content-types-grid {
+      grid-template-columns: 1fr;
+    }
+
+    .content-type-card {
+      padding: 20px;
+
+      .card-icon {
+        width: 40px;
+        height: 40px;
+
+        svg {
+          width: 20px;
+          height: 20px;
+        }
+      }
+
+      .card-content {
+        .card-title {
+          font-size: 16px;
+        }
+
+        .card-description {
+          font-size: 13px;
+        }
+      }
+    }
+
+    .form-actions {
+      flex-direction: column;
+
+      .cancel-button,
+      .submit-button {
+        width: 100%;
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .write-content {
+    .content-type-card {
+      padding: 16px;
+      flex-direction: column;
+      text-align: center;
+      gap: 12px;
+
+      .card-arrow {
+        transform: rotate(90deg);
+      }
+    }
+
+    .form-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 12px;
+    }
+  }
+}

+ 150 - 0
src/app/pages/admin/dashboard/components/write-content/write-content.component.ts

@@ -0,0 +1,150 @@
+import { Component, EventEmitter, Output, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+interface ContentType {
+  id: string;
+  title: string;
+  description: string;
+  icon: string;
+  color: string;
+}
+
+interface FormData {
+  title: string;
+  description: string;
+  content: string;
+  tags: string[];
+  priority: 'low' | 'medium' | 'high';
+  dueDate: string;
+}
+
+@Component({
+  selector: 'app-write-content',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './write-content.component.html',
+  styleUrl: './write-content.component.scss'
+})
+export class WriteContentComponent {
+  @Output() close = new EventEmitter<void>();
+
+  currentStep = signal(1);
+  selectedContentType = signal<ContentType | null>(null);
+  
+  formData = signal<FormData>({
+    title: '',
+    description: '',
+    content: '',
+    tags: [],
+    priority: 'medium',
+    dueDate: ''
+  });
+
+  newTag = signal('');
+
+  contentTypes: ContentType[] = [
+    {
+      id: 'project',
+      title: '新建项目',
+      description: '创建一个新的设计项目',
+      icon: 'M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z',
+      color: '#165DFF'
+    },
+    {
+      id: 'task',
+      title: '创建任务',
+      description: '为项目添加新的任务',
+      icon: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
+      color: '#00B42A'
+    },
+    {
+      id: 'note',
+      title: '记录笔记',
+      description: '记录重要的想法和备忘',
+      icon: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
+      color: '#FF7D00'
+    },
+    {
+      id: 'announcement',
+      title: '发布公告',
+      description: '向团队发布重要通知',
+      icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z',
+      color: '#F53F3F'
+    }
+  ];
+
+  selectContentType(type: ContentType): void {
+    this.selectedContentType.set(type);
+    this.currentStep.set(2);
+  }
+
+  goBack(): void {
+    if (this.currentStep() === 2) {
+      this.currentStep.set(1);
+      this.selectedContentType.set(null);
+    }
+  }
+
+  addTag(): void {
+    const tag = this.newTag().trim();
+    if (tag && !this.formData().tags.includes(tag)) {
+      const currentData = this.formData();
+      this.formData.set({
+        ...currentData,
+        tags: [...currentData.tags, tag]
+      });
+      this.newTag.set('');
+    }
+  }
+
+  removeTag(tag: string): void {
+    const currentData = this.formData();
+    this.formData.set({
+      ...currentData,
+      tags: currentData.tags.filter(t => t !== tag)
+    });
+  }
+
+  onTagKeyPress(event: KeyboardEvent): void {
+    if (event.key === 'Enter') {
+      event.preventDefault();
+      this.addTag();
+    }
+  }
+
+  updateFormField(field: keyof FormData, value: any): void {
+    const currentData = this.formData();
+    this.formData.set({
+      ...currentData,
+      [field]: value
+    });
+  }
+
+  submitForm(): void {
+    const data = {
+      type: this.selectedContentType()?.id,
+      ...this.formData()
+    };
+    
+    console.log('提交数据:', data);
+    
+    // 这里可以调用服务来保存数据
+    // this.contentService.createContent(data).subscribe(...);
+    
+    // 显示成功消息
+    alert('内容创建成功!');
+    
+    // 关闭模态框
+    this.close.emit();
+  }
+
+  isFormValid(): boolean {
+    const data = this.formData();
+    return !!(data.title.trim() && data.description.trim());
+  }
+
+  onClose(): void {
+    this.close.emit();
+  }
+}

+ 25 - 0
src/app/pages/admin/dashboard/dashboard.html

@@ -208,4 +208,29 @@
       </div>
     </div>
   </div>
+</div>
+
+<!-- 编写按钮 -->
+<button class="write-button" (click)="openWriteModal()" title="创建新内容">
+  <svg viewBox="0 0 24 24">
+    <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
+  </svg>
+</button>
+
+<!-- 模态框 -->
+<div class="modal-overlay" *ngIf="showWriteModal" (click)="closeWriteModal()">
+  <div class="modal-content" (click)="$event.stopPropagation()">
+    <div class="modal-header">
+      <h2 class="modal-title">创建新内容</h2>
+      <p class="modal-subtitle">选择您要创建的内容类型</p>
+      <button class="close-button" (click)="closeWriteModal()">
+        <svg viewBox="0 0 24 24">
+          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
+        </svg>
+      </button>
+    </div>
+    <div class="modal-body">
+      <app-write-content (close)="closeWriteModal()"></app-write-content>
+    </div>
+  </div>
 </div>

+ 421 - 120
src/app/pages/admin/dashboard/dashboard.scss

@@ -12,87 +12,259 @@ $border-color: #E5E6EB;
 $background-primary: #FFFFFF;
 $background-secondary: #F2F3F5;
 $background-tertiary: #F7F8FA;
-$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
-$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
-$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
-$border-radius: 8px;
-$transition: all 0.3s ease;
+$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06);
+$shadow-md: 0 8px 24px rgba(0, 0, 0, 0.12);
+$shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.16);
+$border-radius: 12px;
+$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
 // 主容器
 .admin-dashboard {
-  padding: 20px 0;
+  padding: 32px 0;
+  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
+  min-height: 100vh;
+}
+
+// 编写按钮样式
+.write-button {
+  position: fixed;
+  bottom: 32px;
+  right: 32px;
+  width: 64px;
+  height: 64px;
+  background: linear-gradient(135deg, $primary-color, #7c3aed);
+  border: none;
+  border-radius: 50%;
+  box-shadow: 0 8px 24px rgba(22, 93, 255, 0.3);
+  cursor: pointer;
+  transition: $transition;
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  
+  &:hover {
+    transform: translateY(-4px) scale(1.05);
+    box-shadow: 0 12px 32px rgba(22, 93, 255, 0.4);
+  }
+  
+  &:active {
+    transform: translateY(-2px) scale(1.02);
+  }
+  
+  svg {
+    width: 28px;
+    height: 28px;
+    fill: white;
+  }
+}
+
+// 模态框样式
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(8px);
+  z-index: 2000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  animation: fadeIn 0.3s ease;
+}
+
+.modal-content {
+  background: $background-primary;
+  border-radius: 16px;
+  width: 100%;
+  max-width: 600px;
+  max-height: 90vh;
+  overflow-y: auto;
+  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
+  animation: slideUp 0.3s ease;
+  
+  .modal-header {
+    padding: 32px 32px 0;
+    border-bottom: 1px solid $border-color;
+    
+    .modal-title {
+      font-size: 24px;
+      font-weight: 700;
+      color: $text-primary;
+      margin: 0 0 16px 0;
+      background: linear-gradient(135deg, $primary-color, #7c3aed);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      background-clip: text;
+    }
+    
+    .modal-subtitle {
+      font-size: 16px;
+      color: $text-secondary;
+      margin: 0 0 24px 0;
+    }
+    
+    .close-button {
+      position: absolute;
+      top: 24px;
+      right: 24px;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: rgba(0, 0, 0, 0.05);
+      border-radius: 50%;
+      cursor: pointer;
+      transition: $transition;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:hover {
+        background: rgba(0, 0, 0, 0.1);
+        transform: scale(1.1);
+      }
+      
+      svg {
+        width: 20px;
+        height: 20px;
+        fill: $text-secondary;
+      }
+    }
+  }
+  
+  .modal-body {
+    padding: 32px;
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(40px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
 }
 
 // 页面标题
 .page-header {
-  margin-bottom: 24px;
+  margin-bottom: 32px;
+  text-align: center;
 
   .page-title {
-    font-size: 28px;
-    font-weight: 600;
+    font-size: 32px;
+    font-weight: 700;
     color: $text-primary;
-    margin: 0 0 8px 0;
+    margin: 0 0 12px 0;
+    background: linear-gradient(135deg, $primary-color, #7c3aed);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
   }
 
   .page-description {
-    font-size: 16px;
+    font-size: 18px;
     color: $text-secondary;
     margin: 0;
+    font-weight: 400;
   }
 }
 
 // 统计卡片网格
 .stats-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
-  gap: 20px;
-  margin-bottom: 32px;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 24px;
+  margin-bottom: 40px;
 }
 
 // 统计卡片
 .stat-card {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
-  padding: 20px;
+  padding: 28px;
   box-shadow: $shadow-sm;
   display: flex;
   align-items: center;
-  gap: 16px;
+  gap: 20px;
   transition: $transition;
-  border: 1px solid $border-color;
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    background: linear-gradient(90deg, $primary-color, $success-color);
+    transform: scaleX(0);
+    transition: $transition;
+  }
 
   &:hover {
     box-shadow: $shadow-md;
-    transform: translateY(-2px);
+    transform: translateY(-4px);
+    
+    &::before {
+      transform: scaleX(1);
+    }
   }
 
   .stat-icon {
-    width: 48px;
-    height: 48px;
-    border-radius: 50%;
+    width: 56px;
+    height: 56px;
+    border-radius: 16px;
     display: flex;
     align-items: center;
     justify-content: center;
     color: white;
+    position: relative;
+
+    &::before {
+      content: '';
+      position: absolute;
+      inset: 0;
+      border-radius: inherit;
+      background: inherit;
+      opacity: 0.1;
+      transform: scale(1.3);
+    }
 
     &.primary {
-      background-color: $primary-color;
+      background: linear-gradient(135deg, $primary-color, #4c9aff);
     }
 
     &.secondary {
-      background-color: $secondary-color;
+      background: linear-gradient(135deg, $secondary-color, #6366f1);
     }
 
     &.success {
-      background-color: $success-color;
+      background: linear-gradient(135deg, $success-color, #52c41a);
     }
 
     &.warning {
-      background-color: $warning-color;
+      background: linear-gradient(135deg, $warning-color, #ffa940);
     }
 
     &.danger {
-      background-color: $danger-color;
+      background: linear-gradient(135deg, $danger-color, #ff7875);
     }
   }
 
@@ -100,37 +272,43 @@ $transition: all 0.3s ease;
     flex: 1;
 
     .stat-value {
-      font-size: 28px;
-      font-weight: 600;
+      font-size: 32px;
+      font-weight: 800;
       color: $text-primary;
-      margin-bottom: 4px;
+      margin-bottom: 6px;
+      line-height: 1;
+      letter-spacing: -0.02em;
     }
 
     .stat-label {
-      font-size: 14px;
+      font-size: 15px;
       color: $text-secondary;
+      font-weight: 500;
     }
   }
 
   .stat-trend {
-    font-size: 12px;
-    font-weight: 500;
-    padding: 4px 8px;
-    border-radius: 4px;
+    font-size: 13px;
+    font-weight: 600;
+    padding: 6px 10px;
+    border-radius: 8px;
     white-space: nowrap;
+    display: flex;
+    align-items: center;
+    gap: 4px;
 
     &.positive {
-      background-color: color-mix(in srgb, $success-color 5%, transparent);
+      background: rgba(0, 180, 42, 0.1);
       color: $success-color;
     }
 
     &.negative {
-      background-color: color-mix(in srgb, $danger-color 5%, transparent);
+      background: rgba(245, 63, 63, 0.1);
       color: $danger-color;
     }
 
     &.neutral {
-      background-color: $background-tertiary;
+      background: $background-tertiary;
       color: $text-secondary;
     }
   }
@@ -139,150 +317,202 @@ $transition: all 0.3s ease;
 // 图表网格
 .charts-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
-  gap: 20px;
-  margin-bottom: 32px;
+  grid-template-columns: 2fr 1fr;
+  gap: 32px;
+  margin-bottom: 40px;
 }
 
 // 图表卡片
 .chart-card {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
   box-shadow: $shadow-sm;
   overflow: hidden;
-  border: 1px solid $border-color;
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  transition: $transition;
+
+  &:hover {
+    box-shadow: $shadow-md;
+    transform: translateY(-2px);
+  }
 
   .chart-header {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    padding: 20px;
+    padding: 24px;
     border-bottom: 1px solid $border-color;
+    background: linear-gradient(135deg, rgba(22, 93, 255, 0.02), rgba(124, 58, 237, 0.02));
 
     h3 {
-      font-size: 16px;
-      font-weight: 600;
+      font-size: 18px;
+      font-weight: 700;
       color: $text-primary;
       margin: 0;
     }
 
     .chart-period {
       display: flex;
-      gap: 8px;
+      gap: 4px;
+      background: $background-secondary;
+      padding: 4px;
+      border-radius: 8px;
 
       .period-btn {
-        padding: 6px 12px;
-        border: 1px solid $border-color;
-        background-color: $background-primary;
-        border-radius: 4px;
-        font-size: 12px;
+        padding: 8px 16px;
+        border: none;
+        background: transparent;
+        border-radius: 6px;
+        font-size: 13px;
         color: $text-secondary;
         cursor: pointer;
         transition: $transition;
+        font-weight: 500;
 
         &:hover {
-          background-color: $background-tertiary;
+          background: $background-primary;
+          color: $text-primary;
         }
 
         &.active {
-          background-color: $primary-color;
+          background: $primary-color;
           color: white;
-          border-color: $primary-color;
+          box-shadow: 0 2px 4px rgba(22, 93, 255, 0.3);
         }
       }
     }
   }
 
   .chart-container {
-    height: 300px;
-    padding: 20px;
+    height: 320px;
+    padding: 24px;
   }
 }
 
 // 最近活动区域
 .recent-activities {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
+  padding: 32px;
   box-shadow: $shadow-sm;
-  border: 1px solid $border-color;
-
-  .section-header {
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  transition: $transition;
+  
+  &:hover {
+    box-shadow: $shadow-md;
+  }
+  
+  .activities-header {
     display: flex;
-    justify-content: space-between;
     align-items: center;
-    padding: 20px;
-    border-bottom: 1px solid $border-color;
-
+    justify-content: space-between;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 2px solid $border-color;
+    
     h3 {
-      font-size: 16px;
-      font-weight: 600;
+      font-size: 20px;
+      font-weight: 700;
       color: $text-primary;
       margin: 0;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      
+      &::before {
+        content: '';
+        width: 4px;
+        height: 20px;
+        background: linear-gradient(135deg, $primary-color, $success-color);
+        border-radius: 2px;
+      }
     }
-
-    .view-all-link {
+    
+    .view-all {
       font-size: 14px;
       color: $primary-color;
       text-decoration: none;
+      font-weight: 600;
+      padding: 8px 16px;
+      border-radius: 8px;
+      background: rgba(22, 93, 255, 0.1);
       transition: $transition;
-
+      
       &:hover {
-        text-decoration: underline;
+        background: rgba(22, 93, 255, 0.2);
+        transform: translateY(-1px);
       }
     }
   }
-
-  .activities-list {
-    padding: 0 20px 20px 20px;
-
+  
+  .activity-list {
     .activity-item {
       display: flex;
-      align-items: flex-start;
-      gap: 12px;
+      align-items: center;
       padding: 16px 0;
-      border-bottom: 1px solid $border-color;
-
+      border-bottom: 1px solid rgba(229, 230, 235, 0.5);
+      transition: $transition;
+      
       &:last-child {
         border-bottom: none;
       }
-
+      
+      &:hover {
+        background: rgba(22, 93, 255, 0.02);
+        border-radius: 8px;
+        margin: 0 -16px;
+        padding: 16px;
+      }
+      
       .activity-icon {
-        width: 32px;
-        height: 32px;
-        border-radius: 50%;
-        background-color: $background-tertiary;
+        width: 40px;
+        height: 40px;
+        border-radius: 12px;
+        background: linear-gradient(135deg, rgba(22, 93, 255, 0.1), rgba(124, 58, 237, 0.1));
         display: flex;
         align-items: center;
         justify-content: center;
-        color: $primary-color;
-        flex-shrink: 0;
+        margin-right: 16px;
+        
+        svg {
+          width: 20px;
+          height: 20px;
+          fill: $primary-color;
+        }
       }
-
+      
       .activity-content {
         flex: 1;
-
+        
         .activity-text {
-          font-size: 14px;
+          font-size: 15px;
           color: $text-primary;
-          line-height: 1.5;
-          margin-bottom: 4px;
+          margin-bottom: 6px;
+          line-height: 1.4;
+          
+          .user {
+            font-weight: 700;
+            color: $primary-color;
+          }
+          
+          .action {
+            color: $text-secondary;
+            font-weight: 500;
+          }
+          
+          .target {
+            color: $text-primary;
+            font-weight: 600;
+            background: rgba(22, 93, 255, 0.1);
+            padding: 2px 6px;
+            border-radius: 4px;
+          }
         }
-
+        
         .activity-time {
-          font-size: 12px;
-          color: $text-tertiary;
-        }
-
-        .activity-user {
-          font-weight: 600;
-          color: $text-primary;
-        }
-
-        .activity-project,
-        .activity-task,
-        .activity-customer {
-          font-weight: 600;
-          color: $primary-color;
+          font-size: 13px;
+          color: $text-secondary;
+          font-weight: 500;
         }
       }
     }
@@ -290,27 +520,67 @@ $transition: all 0.3s ease;
 }
 
 // 响应式设计
-@media (max-width: 1200px) {
-  .stats-grid {
-    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+@media (max-width: 1400px) {
+  .admin-dashboard {
+    .charts-grid {
+      grid-template-columns: 1fr;
+      gap: 24px;
+    }
+    
+    .stats-grid {
+      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+    }
   }
+}
 
-  .charts-grid {
-    grid-template-columns: 1fr;
+@media (max-width: 1024px) {
+  .admin-dashboard {
+    padding: 24px 0;
+    
+    .stats-grid {
+      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+      gap: 20px;
+    }
+    
+    .stat-card {
+      padding: 24px;
+    }
   }
 }
 
 @media (max-width: 768px) {
+  .admin-dashboard {
+    padding: 20px 0;
+  }
+
   .page-title {
-    font-size: 24px !important;
+    font-size: 28px !important;
+  }
+
+  .page-description {
+    font-size: 15px !important;
   }
 
   .stats-grid {
     grid-template-columns: 1fr;
+    gap: 16px;
+  }
+
+  .stat-card {
+    padding: 20px;
+    
+    .stat-icon {
+      width: 44px;
+      height: 44px;
+    }
+    
+    .stat-value {
+      font-size: 28px !important;
+    }
   }
 
   .chart-container {
-    height: 250px !important;
+    height: 280px !important;
   }
 
   .chart-header {
@@ -321,32 +591,63 @@ $transition: all 0.3s ease;
 
   .recent-activities {
     margin-top: 20px;
+    padding: 24px;
   }
 }
 
 @media (max-width: 480px) {
   .admin-dashboard {
-    padding: 12px 0;
+    padding: 16px 0;
   }
 
   .page-title {
-    font-size: 20px !important;
+    font-size: 24px !important;
   }
 
   .page-description {
     font-size: 14px !important;
   }
 
-  .chart-container {
-    height: 200px !important;
-    padding: 12px !important;
+  .stats-grid {
+    gap: 12px;
   }
 
   .stat-card {
     padding: 16px;
+    
+    .stat-icon {
+      width: 40px;
+      height: 40px;
+    }
+    
+    .stat-value {
+      font-size: 24px !important;
+    }
+    
+    .stat-label {
+      font-size: 14px;
+    }
   }
 
-  .stat-value {
-    font-size: 24px !important;
+  .chart-container {
+    height: 240px !important;
+    padding: 12px !important;
+  }
+
+  .recent-activities {
+    padding: 20px;
+    
+    .activity-item {
+      .activity-icon {
+        width: 36px;
+        height: 36px;
+      }
+      
+      .activity-content {
+        .activity-text {
+          font-size: 14px;
+        }
+      }
+    }
   }
 }

+ 16 - 1
src/app/pages/admin/dashboard/dashboard.ts

@@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router';
 import { Subscription } from 'rxjs';
 import { signal } from '@angular/core';
 import { AdminDashboardService } from './dashboard.service';
+import { WriteContentComponent } from './components/write-content/write-content.component';
 // 使用全局echarts变量,不导入模块
 
 // 确保@Component装饰器存在
@@ -11,7 +12,7 @@ import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
 @Component({
   selector: 'app-admin-dashboard',
   standalone: true,
-  imports: [CommonModule, RouterModule],
+  imports: [CommonModule, RouterModule, WriteContentComponent],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 }) 
@@ -26,6 +27,9 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
     totalRevenue: signal(1258000)
   };
 
+  // 模态框控制
+  showWriteModal = signal(false);
+
   private subscriptions: Subscription = new Subscription();
   private projectChart: any | null = null;
   private revenueChart: any | null = null;
@@ -134,4 +138,15 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
   formatCurrency(amount: number): string {
     return '¥' + amount.toLocaleString('zh-CN');
   }
+
+  // 模态框控制方法
+  openWriteModal(): void {
+    this.showWriteModal.set(true);
+    document.body.style.overflow = 'hidden';
+  }
+
+  closeWriteModal(): void {
+    this.showWriteModal.set(false);
+    document.body.style.overflow = 'auto';
+  }
 }

+ 226 - 0
src/app/pages/admin/designers/designers.html

@@ -0,0 +1,226 @@
+<div class="designers-page">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">设计师管理</h2>
+      <p class="page-description">管理设计师团队,查看工作状态和绩效数据</p>
+    </div>
+    <div class="header-actions">
+      <button class="btn btn-primary" (click)="addDesigner()">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="12" y1="5" x2="12" y2="19"></line>
+          <line x1="5" y1="12" x2="19" y2="12"></line>
+        </svg>
+        添加设计师
+      </button>
+      <button class="btn btn-secondary" (click)="exportDesigners()">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+          <polyline points="7 10 12 15 17 10"></polyline>
+          <line x1="12" y1="15" x2="12" y2="3"></line>
+        </svg>
+        导出数据
+      </button>
+    </div>
+  </div>
+
+  <!-- 统计卡片 -->
+  <div class="stats-overview">
+    <div class="stat-card">
+      <div class="stat-icon primary">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+          <circle cx="12" cy="7" r="4"></circle>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ totalDesigners() }}</div>
+        <div class="stat-label">设计师总数</div>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon success">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+          <polyline points="22 4 12 14.01 9 11.01"></polyline>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ activeDesigners() }}</div>
+        <div class="stat-label">在线设计师</div>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon warning">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <circle cx="12" cy="12" r="10"></circle>
+          <line x1="12" y1="8" x2="12" y2="12"></line>
+          <line x1="12" y1="16" x2="12.01" y2="16"></line>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ busyDesigners() }}</div>
+        <div class="stat-label">忙碌设计师</div>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon danger">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <circle cx="12" cy="12" r="10"></circle>
+          <line x1="15" y1="9" x2="9" y2="15"></line>
+          <line x1="9" y1="9" x2="15" y2="15"></line>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ inactiveDesigners() }}</div>
+        <div class="stat-label">离线设计师</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 筛选和搜索 -->
+  <div class="filters-section">
+    <div class="search-box">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+        <circle cx="11" cy="11" r="8"></circle>
+        <path d="M21 21l-4.35-4.35"></path>
+      </svg>
+      <input 
+        type="text" 
+        placeholder="搜索设计师姓名或邮箱..."
+        [(ngModel)]="searchTerm"
+        (input)="searchTerm.set($any($event.target).value)"
+      >
+    </div>
+
+    <div class="filter-controls">
+      <select [(ngModel)]="selectedDepartment" (change)="selectedDepartment.set($any($event.target).value)">
+        <option value="all">全部部门</option>
+        <option *ngFor="let dept of departments" [value]="dept">{{ dept }}</option>
+      </select>
+
+      <select [(ngModel)]="selectedLevel" (change)="selectedLevel.set($any($event.target).value)">
+        <option value="all">全部等级</option>
+        <option value="junior">初级</option>
+        <option value="intermediate">中级</option>
+        <option value="senior">高级</option>
+        <option value="expert">专家</option>
+      </select>
+
+      <select [(ngModel)]="selectedStatus" (change)="selectedStatus.set($any($event.target).value)">
+        <option value="all">全部状态</option>
+        <option value="active">在线</option>
+        <option value="busy">忙碌</option>
+        <option value="inactive">离线</option>
+      </select>
+
+      <button class="btn btn-outline" (click)="resetFilters()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <polyline points="1 4 1 10 7 10"></polyline>
+          <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
+        </svg>
+        重置
+      </button>
+    </div>
+  </div>
+
+  <!-- 设计师列表 -->
+  <div class="designers-grid">
+    <div class="designer-card" *ngFor="let designer of filteredDesigners">
+      <div class="designer-header">
+        <div class="designer-avatar">
+          <img [src]="designer.avatar" [alt]="designer.name">
+          <div class="status-indicator" [class]="getStatusClass(designer.status)"></div>
+        </div>
+        <div class="designer-info">
+          <h3 class="designer-name">{{ designer.name }}</h3>
+          <p class="designer-department">{{ designer.department }}</p>
+          <div class="designer-level">
+            <span class="level-badge" [class]="'level-' + designer.level">
+              {{ getLevelText(designer.level) }}
+            </span>
+            <span class="status-text" [class]="getStatusClass(designer.status)">
+              {{ getStatusText(designer.status) }}
+            </span>
+          </div>
+        </div>
+        <div class="designer-actions">
+          <button class="action-btn" (click)="viewDesigner(designer)" title="查看详情">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
+              <circle cx="12" cy="12" r="3"></circle>
+            </svg>
+          </button>
+          <button class="action-btn" (click)="editDesigner(designer)" title="编辑">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
+              <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
+            </svg>
+          </button>
+          <button class="action-btn danger" (click)="deleteDesigner(designer)" title="删除">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="3 6 5 6 21 6"></polyline>
+              <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
+            </svg>
+          </button>
+        </div>
+      </div>
+
+      <div class="designer-stats">
+        <div class="stat-item">
+          <span class="stat-label">项目数量</span>
+          <span class="stat-value">{{ designer.projectCount }}</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">完成率</span>
+          <span class="stat-value">{{ designer.completionRate }}%</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">满意度</span>
+          <span class="stat-value">{{ designer.satisfactionScore }}/5.0</span>
+        </div>
+      </div>
+
+      <div class="designer-skills">
+        <span class="skill-tag" *ngFor="let skill of designer.skills">{{ skill }}</span>
+      </div>
+
+      <div class="designer-contact">
+        <div class="contact-item">
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
+            <polyline points="22 6 12 13 2 6"></polyline>
+          </svg>
+          <span>{{ designer.email }}</span>
+        </div>
+        <div class="contact-item">
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
+          </svg>
+          <span>{{ designer.phone }}</span>
+        </div>
+      </div>
+
+      <div class="designer-footer">
+        <span class="join-date">入职时间:{{ designer.joinDate | date:'yyyy-MM-dd' }}</span>
+        <span class="last-active">最后活跃:{{ designer.lastActiveDate | date:'MM-dd HH:mm' }}</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 空状态 -->
+  <div class="empty-state" *ngIf="filteredDesigners.length === 0">
+    <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+      <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+      <circle cx="12" cy="7" r="4"></circle>
+    </svg>
+    <h3>暂无设计师数据</h3>
+    <p>没有找到符合条件的设计师,请调整筛选条件或添加新的设计师。</p>
+    <button class="btn btn-primary" (click)="addDesigner()">
+      添加设计师
+    </button>
+  </div>
+</div>

+ 644 - 0
src/app/pages/admin/designers/designers.scss

@@ -0,0 +1,644 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$primary-dark: #0d2f5e;
+$secondary-color: #4E5BA6;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-primary: #1D2129;
+$text-secondary: #4E5969;
+$text-tertiary: #86909C;
+$border-color: #E5E6EB;
+$background-primary: #FFFFFF;
+$background-secondary: #F2F3F5;
+$background-tertiary: #F7F8FA;
+$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06);
+$shadow-md: 0 8px 24px rgba(0, 0, 0, 0.12);
+$shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.16);
+$border-radius: 12px;
+$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+.designers-page {
+  padding: 32px;
+  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
+  min-height: 100vh;
+
+  // 页面标题
+  .page-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 32px;
+    padding-bottom: 24px;
+    border-bottom: 1px solid $border-color;
+
+    .header-left {
+      .page-title {
+        font-size: 32px;
+        font-weight: 700;
+        color: $text-primary;
+        margin: 0 0 8px 0;
+        background: linear-gradient(135deg, $primary-color, #7c3aed);
+        -webkit-background-clip: text;
+        -webkit-text-fill-color: transparent;
+        background-clip: text;
+      }
+
+      .page-description {
+        font-size: 16px;
+        color: $text-secondary;
+        margin: 0;
+      }
+    }
+
+    .header-actions {
+      display: flex;
+      gap: 12px;
+    }
+  }
+
+  // 按钮样式
+  .btn {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 20px;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: $transition;
+    text-decoration: none;
+
+    &.btn-primary {
+      background: linear-gradient(135deg, $primary-color, #7c3aed);
+      color: white;
+      box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 6px 16px rgba(22, 93, 255, 0.4);
+      }
+    }
+
+    &.btn-secondary {
+      background: $background-primary;
+      color: $text-primary;
+      border: 1px solid $border-color;
+
+      &:hover {
+        background: $background-secondary;
+        border-color: $primary-color;
+      }
+    }
+
+    &.btn-outline {
+      background: transparent;
+      color: $text-secondary;
+      border: 1px solid $border-color;
+      padding: 8px 16px;
+      font-size: 13px;
+
+      &:hover {
+        background: $background-secondary;
+        border-color: $primary-color;
+        color: $primary-color;
+      }
+    }
+  }
+
+  // 统计卡片
+  .stats-overview {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 20px;
+    margin-bottom: 32px;
+
+    .stat-card {
+      background: $background-primary;
+      border-radius: $border-radius;
+      padding: 24px;
+      box-shadow: $shadow-sm;
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      transition: $transition;
+      border: 1px solid rgba(255, 255, 255, 0.8);
+
+      &:hover {
+        box-shadow: $shadow-md;
+        transform: translateY(-2px);
+      }
+
+      .stat-icon {
+        width: 48px;
+        height: 48px;
+        border-radius: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-shrink: 0;
+
+        &.primary {
+          background: linear-gradient(135deg, rgba(22, 93, 255, 0.1), rgba(124, 58, 237, 0.1));
+          color: $primary-color;
+        }
+
+        &.success {
+          background: rgba(0, 180, 42, 0.1);
+          color: $success-color;
+        }
+
+        &.warning {
+          background: rgba(255, 125, 0, 0.1);
+          color: $warning-color;
+        }
+
+        &.danger {
+          background: rgba(245, 63, 63, 0.1);
+          color: $danger-color;
+        }
+      }
+
+      .stat-content {
+        .stat-value {
+          font-size: 28px;
+          font-weight: 700;
+          color: $text-primary;
+          margin: 0 0 4px 0;
+        }
+
+        .stat-label {
+          font-size: 14px;
+          color: $text-secondary;
+          margin: 0;
+        }
+      }
+    }
+  }
+
+  // 筛选区域
+  .filters-section {
+    background: $background-primary;
+    border-radius: $border-radius;
+    padding: 24px;
+    margin-bottom: 24px;
+    box-shadow: $shadow-sm;
+    display: flex;
+    gap: 20px;
+    align-items: center;
+    flex-wrap: wrap;
+
+    .search-box {
+      position: relative;
+      flex: 1;
+      min-width: 300px;
+
+      svg {
+        position: absolute;
+        left: 12px;
+        top: 50%;
+        transform: translateY(-50%);
+        color: $text-tertiary;
+      }
+
+      input {
+        width: 100%;
+        padding: 12px 12px 12px 40px;
+        border: 1px solid $border-color;
+        border-radius: 8px;
+        font-size: 14px;
+        transition: $transition;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+        }
+
+        &::placeholder {
+          color: $text-tertiary;
+        }
+      }
+    }
+
+    .filter-controls {
+      display: flex;
+      gap: 12px;
+      align-items: center;
+
+      select {
+        padding: 10px 12px;
+        border: 1px solid $border-color;
+        border-radius: 8px;
+        font-size: 14px;
+        background: $background-primary;
+        color: $text-primary;
+        cursor: pointer;
+        transition: $transition;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+        }
+      }
+    }
+  }
+
+  // 设计师网格
+  .designers-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
+    gap: 24px;
+    margin-bottom: 32px;
+
+    .designer-card {
+      background: $background-primary;
+      border-radius: $border-radius;
+      padding: 24px;
+      box-shadow: $shadow-sm;
+      transition: $transition;
+      border: 1px solid rgba(255, 255, 255, 0.8);
+      position: relative;
+      overflow: hidden;
+
+      &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        height: 4px;
+        background: linear-gradient(90deg, $primary-color, $success-color);
+        transform: scaleX(0);
+        transition: $transition;
+      }
+
+      &:hover {
+        box-shadow: $shadow-md;
+        transform: translateY(-4px);
+
+        &::before {
+          transform: scaleX(1);
+        }
+      }
+
+      .designer-header {
+        display: flex;
+        align-items: flex-start;
+        gap: 16px;
+        margin-bottom: 20px;
+
+        .designer-avatar {
+          position: relative;
+          flex-shrink: 0;
+
+          img {
+            width: 60px;
+            height: 60px;
+            border-radius: 50%;
+            object-fit: cover;
+            border: 3px solid $background-secondary;
+          }
+
+          .status-indicator {
+            position: absolute;
+            bottom: 2px;
+            right: 2px;
+            width: 16px;
+            height: 16px;
+            border-radius: 50%;
+            border: 2px solid $background-primary;
+
+            &.status-active {
+              background: $success-color;
+            }
+
+            &.status-busy {
+              background: $warning-color;
+            }
+
+            &.status-inactive {
+              background: $text-tertiary;
+            }
+          }
+        }
+
+        .designer-info {
+          flex: 1;
+
+          .designer-name {
+            font-size: 18px;
+            font-weight: 600;
+            color: $text-primary;
+            margin: 0 0 4px 0;
+          }
+
+          .designer-department {
+            font-size: 14px;
+            color: $text-secondary;
+            margin: 0 0 8px 0;
+          }
+
+          .designer-level {
+            display: flex;
+            gap: 8px;
+            align-items: center;
+
+            .level-badge {
+              padding: 4px 8px;
+              border-radius: 6px;
+              font-size: 12px;
+              font-weight: 500;
+
+              &.level-junior {
+                background: rgba(0, 180, 42, 0.1);
+                color: $success-color;
+              }
+
+              &.level-intermediate {
+                background: rgba(22, 93, 255, 0.1);
+                color: $primary-color;
+              }
+
+              &.level-senior {
+                background: rgba(255, 125, 0, 0.1);
+                color: $warning-color;
+              }
+
+              &.level-expert {
+                background: rgba(124, 58, 237, 0.1);
+                color: #7c3aed;
+              }
+            }
+
+            .status-text {
+              font-size: 12px;
+              font-weight: 500;
+
+              &.status-active {
+                color: $success-color;
+              }
+
+              &.status-busy {
+                color: $warning-color;
+              }
+
+              &.status-inactive {
+                color: $text-tertiary;
+              }
+            }
+          }
+        }
+
+        .designer-actions {
+          display: flex;
+          gap: 8px;
+
+          .action-btn {
+            width: 32px;
+            height: 32px;
+            border: none;
+            border-radius: 6px;
+            background: $background-secondary;
+            color: $text-secondary;
+            cursor: pointer;
+            transition: $transition;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+
+            &:hover {
+              background: $primary-color;
+              color: white;
+            }
+
+            &.danger:hover {
+              background: $danger-color;
+              color: white;
+            }
+          }
+        }
+      }
+
+      .designer-stats {
+        display: grid;
+        grid-template-columns: repeat(3, 1fr);
+        gap: 16px;
+        margin-bottom: 16px;
+        padding: 16px;
+        background: $background-tertiary;
+        border-radius: 8px;
+
+        .stat-item {
+          text-align: center;
+
+          .stat-label {
+            display: block;
+            font-size: 12px;
+            color: $text-tertiary;
+            margin-bottom: 4px;
+          }
+
+          .stat-value {
+            font-size: 16px;
+            font-weight: 600;
+            color: $text-primary;
+          }
+        }
+      }
+
+      .designer-skills {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 6px;
+        margin-bottom: 16px;
+
+        .skill-tag {
+          padding: 4px 8px;
+          background: rgba(22, 93, 255, 0.1);
+          color: $primary-color;
+          border-radius: 4px;
+          font-size: 12px;
+          font-weight: 500;
+        }
+      }
+
+      .designer-contact {
+        margin-bottom: 16px;
+
+        .contact-item {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          margin-bottom: 8px;
+          font-size: 13px;
+          color: $text-secondary;
+
+          svg {
+            color: $text-tertiary;
+          }
+
+          &:last-child {
+            margin-bottom: 0;
+          }
+        }
+      }
+
+      .designer-footer {
+        display: flex;
+        justify-content: space-between;
+        font-size: 12px;
+        color: $text-tertiary;
+        padding-top: 16px;
+        border-top: 1px solid $border-color;
+      }
+    }
+  }
+
+  // 空状态
+  .empty-state {
+    text-align: center;
+    padding: 80px 20px;
+    background: $background-primary;
+    border-radius: $border-radius;
+    box-shadow: $shadow-sm;
+
+    svg {
+      color: $text-tertiary;
+      margin-bottom: 24px;
+    }
+
+    h3 {
+      font-size: 20px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0 0 8px 0;
+    }
+
+    p {
+      font-size: 14px;
+      color: $text-secondary;
+      margin: 0 0 24px 0;
+      max-width: 400px;
+      margin-left: auto;
+      margin-right: auto;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .designers-page {
+    .designers-grid {
+      grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .designers-page {
+    padding: 20px;
+
+    .page-header {
+      flex-direction: column;
+      gap: 16px;
+      align-items: stretch;
+
+      .header-actions {
+        justify-content: flex-start;
+      }
+    }
+
+    .stats-overview {
+      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+      gap: 16px;
+
+      .stat-card {
+        padding: 16px;
+        flex-direction: column;
+        text-align: center;
+
+        .stat-icon {
+          width: 40px;
+          height: 40px;
+        }
+
+        .stat-content .stat-value {
+          font-size: 24px;
+        }
+      }
+    }
+
+    .filters-section {
+      flex-direction: column;
+      align-items: stretch;
+      gap: 16px;
+
+      .search-box {
+        min-width: auto;
+      }
+
+      .filter-controls {
+        flex-wrap: wrap;
+      }
+    }
+
+    .designers-grid {
+      grid-template-columns: 1fr;
+      gap: 16px;
+
+      .designer-card {
+        padding: 20px;
+
+        .designer-header {
+          .designer-avatar img {
+            width: 50px;
+            height: 50px;
+          }
+
+          .designer-actions {
+            flex-direction: column;
+          }
+        }
+
+        .designer-stats {
+          grid-template-columns: 1fr;
+          gap: 12px;
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .designers-page {
+    padding: 16px;
+
+    .page-header .page-title {
+      font-size: 24px;
+    }
+
+    .btn {
+      padding: 10px 16px;
+      font-size: 13px;
+    }
+
+    .designers-grid .designer-card {
+      padding: 16px;
+
+      .designer-header {
+        flex-direction: column;
+        align-items: center;
+        text-align: center;
+        gap: 12px;
+
+        .designer-actions {
+          flex-direction: row;
+        }
+      }
+    }
+  }
+}

+ 187 - 0
src/app/pages/admin/designers/designers.ts

@@ -0,0 +1,187 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+
+// 设计师接口定义
+interface Designer {
+  id: string;
+  name: string;
+  avatar: string;
+  email: string;
+  phone: string;
+  department: string;
+  level: 'junior' | 'intermediate' | 'senior' | 'expert';
+  skills: string[];
+  status: 'active' | 'inactive' | 'busy';
+  projectCount: number;
+  completionRate: number;
+  satisfactionScore: number;
+  joinDate: Date;
+  lastActiveDate: Date;
+}
+
+@Component({
+  selector: 'app-designers',
+  standalone: true,
+  imports: [CommonModule, RouterModule, FormsModule],
+  templateUrl: './designers.html',
+  styleUrl: './designers.scss'
+})
+export class Designers implements OnInit {
+  // 设计师数据
+  designers = signal<Designer[]>([
+    {
+      id: '1',
+      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="%23FFE6CC"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%23FF7D00" dy="0.3em"%3E张%3C/text%3E%3C/svg%3E',
+      email: 'zhang.xiaomei@company.com',
+      phone: '138****1234',
+      department: '室内设计部',
+      level: 'senior',
+      skills: ['室内设计', '3D建模', 'AutoCAD', 'SketchUp'],
+      status: 'active',
+      projectCount: 15,
+      completionRate: 95,
+      satisfactionScore: 4.8,
+      joinDate: new Date('2022-03-15'),
+      lastActiveDate: new Date()
+    },
+    {
+      id: '2',
+      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="%23E6F7FF"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%23165DFF" dy="0.3em"%3E李%3C/text%3E%3C/svg%3E',
+      email: 'li.sheji@company.com',
+      phone: '139****5678',
+      department: '建筑设计部',
+      level: 'expert',
+      skills: ['建筑设计', 'Revit', 'Rhino', '参数化设计'],
+      status: 'busy',
+      projectCount: 22,
+      completionRate: 98,
+      satisfactionScore: 4.9,
+      joinDate: new Date('2021-08-20'),
+      lastActiveDate: new Date()
+    },
+    {
+      id: '3',
+      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="%23F0F9FF"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%234E5BA6" dy="0.3em"%3E王%3C/text%3E%3C/svg%3E',
+      email: 'wang.chuangyi@company.com',
+      phone: '137****9012',
+      department: '景观设计部',
+      level: 'intermediate',
+      skills: ['景观设计', 'Lumion', 'Photoshop', '手绘'],
+      status: 'active',
+      projectCount: 8,
+      completionRate: 92,
+      satisfactionScore: 4.6,
+      joinDate: new Date('2023-01-10'),
+      lastActiveDate: new Date()
+    }
+  ]);
+
+  // 筛选和搜索
+  searchTerm = signal('');
+  selectedDepartment = signal('all');
+  selectedLevel = signal('all');
+  selectedStatus = signal('all');
+
+  // 统计数据
+  totalDesigners = signal(24);
+  activeDesigners = signal(18);
+  busyDesigners = signal(4);
+  inactiveDesigners = signal(2);
+
+  // 部门列表
+  departments = ['室内设计部', '建筑设计部', '景观设计部', '平面设计部'];
+
+  ngOnInit(): void {
+    this.loadDesigners();
+  }
+
+  loadDesigners(): void {
+    // 在实际应用中,这里会从服务加载数据
+    console.log('加载设计师数据');
+  }
+
+  // 筛选设计师
+  get filteredDesigners() {
+    return this.designers().filter(designer => {
+      const matchesSearch = designer.name.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
+                           designer.email.toLowerCase().includes(this.searchTerm().toLowerCase());
+      const matchesDepartment = this.selectedDepartment() === 'all' || designer.department === this.selectedDepartment();
+      const matchesLevel = this.selectedLevel() === 'all' || designer.level === this.selectedLevel();
+      const matchesStatus = this.selectedStatus() === 'all' || designer.status === this.selectedStatus();
+      
+      return matchesSearch && matchesDepartment && matchesLevel && matchesStatus;
+    });
+  }
+
+  // 获取等级显示文本
+  getLevelText(level: string): string {
+    const levelMap: { [key: string]: string } = {
+      'junior': '初级',
+      'intermediate': '中级',
+      'senior': '高级',
+      'expert': '专家'
+    };
+    return levelMap[level] || level;
+  }
+
+  // 获取状态显示文本
+  getStatusText(status: string): string {
+    const statusMap: { [key: string]: string } = {
+      'active': '在线',
+      'inactive': '离线',
+      'busy': '忙碌'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取状态样式类
+  getStatusClass(status: string): string {
+    return `status-${status}`;
+  }
+
+  // 查看设计师详情
+  viewDesigner(designer: Designer): void {
+    console.log('查看设计师详情:', designer);
+    // 在实际应用中,这里会跳转到设计师详情页面
+  }
+
+  // 编辑设计师
+  editDesigner(designer: Designer): void {
+    console.log('编辑设计师:', designer);
+    // 在实际应用中,这里会打开编辑对话框
+  }
+
+  // 删除设计师
+  deleteDesigner(designer: Designer): void {
+    if (confirm(`确定要删除设计师 ${designer.name} 吗?`)) {
+      const updatedDesigners = this.designers().filter(d => d.id !== designer.id);
+      this.designers.set(updatedDesigners);
+      console.log('删除设计师:', designer);
+    }
+  }
+
+  // 添加新设计师
+  addDesigner(): void {
+    console.log('添加新设计师');
+    // 在实际应用中,这里会打开添加设计师对话框
+  }
+
+  // 导出设计师数据
+  exportDesigners(): void {
+    console.log('导出设计师数据');
+    // 在实际应用中,这里会导出Excel或CSV文件
+  }
+
+  // 重置筛选条件
+  resetFilters(): void {
+    this.searchTerm.set('');
+    this.selectedDepartment.set('all');
+    this.selectedLevel.set('all');
+    this.selectedStatus.set('all');
+  }
+}