Browse Source

feat:mine 001

0235889 1 day ago
parent
commit
740dbf3d18

+ 15 - 0
.hintrc

@@ -0,0 +1,15 @@
+{
+  "extends": [
+    "development"
+  ],
+  "hints": {
+    "compat-api/css": [
+      "default",
+      {
+        "ignore": [
+          "backdrop-filter"
+        ]
+      }
+    ]
+  }
+}

+ 1 - 1
manager-web/src/modules/manager/mobile/mobile.routes.ts

@@ -16,7 +16,7 @@ export const MOBILE_ROUTES: Routes = [
       },
       },
       {
       {
         path: 'mine',
         path: 'mine',
-        loadComponent: () => import('./page-mine/page-mine').then(m => m.PageMine)
+        loadComponent: () => import('./page-mine/page-mine').then(m => m.PageMineComponent)
       },
       },
       {
       {
         path: '',
         path: '',

+ 127 - 1
manager-web/src/modules/manager/mobile/page-mine/page-mine.html

@@ -1 +1,127 @@
-<p>page-mine works!</p>
+
+<!-- page-mine.component.html -->
+<div class="container">
+  <header>
+    <div class="logo">
+      <i class="fas fa-heart"></i>
+      <h1>婚庆行业客户全生命周期AI管理系统</h1>
+    </div>
+    <div class="nav-links">
+      <a href="#" class="active">日历</a>
+      <a href="#">每日工作</a>
+      <a href="#">订单总览</a>
+      <a href="#">访客视图</a>
+      <a href="#">房间安排</a>
+    </div>
+  </header>
+  
+  <div class="main-content">
+    <div class="calendar-section">
+      <div class="section-header">
+        <h2>场地档期日历</h2>
+        <div class="controls">
+          <div class="date-controls">
+            <button class="btn btn-outline" (click)="changeMonth(-1)"><i class="fas fa-chevron-left"></i></button>
+            <div class="date-display">{{ currentYear }}年{{ currentMonth + 1 }}月</div>
+            <button class="btn btn-outline" (click)="changeMonth(1)"><i class="fas fa-chevron-right"></i></button>
+          </div>
+          <button class="btn btn-primary"><i class="fas fa-search"></i> 查询日期</button>
+        </div>
+      </div>
+      
+      <div class="weekdays">
+        <div *ngFor="let day of weekdays">{{ day }}</div>
+      </div>
+      
+      <div class="calendar">
+        <div *ngFor="let day of calendarDays" 
+             class="calendar-day" 
+             [class.today]="day.isToday" 
+             [class.empty]="day.isEmpty">
+          <div *ngIf="!day.isEmpty" class="day-header">
+            <div class="day-num">{{ day.date }}
+              <span *ngIf="day.isToday" class="today-label">今日</span>
+            </div>
+            <div class="lunar-date">{{ day.lunarDate }} {{ day.weekday }}</div>
+          </div>
+          <div *ngIf="!day.isEmpty" class="day-events">
+            <div *ngFor="let event of day.events" 
+                 class="event-tag"
+                 [class.event-booked]="event.type === 'booked'"
+                 [class.event-available]="event.type === 'available'"
+                 [class.event-consult]="event.type === 'consult'">
+              {{ event.venue }}: {{ event.title }}
+            </div>
+          </div>
+        </div>
+      </div>
+      
+      <div class="ai-section">
+        <div class="ai-header">
+          <i class="fas fa-robot"></i>
+          <h3>AI智能档期助手</h3>
+        </div>
+        
+        <div class="ai-recommendation">
+          <div class="ai-title">
+            <i class="fas fa-lightbulb"></i>
+            <span>今日智能建议</span>
+          </div>
+          <div class="ai-content">
+            <p>根据历史数据分析,本周末(7月5日-6日)为婚礼高峰期,建议:</p>
+            <ul>
+              <li>优先安排资深策划师处理九州厅、月光礼堂的咨询客户</li>
+              <li>星空之镜、海洋之心场地有较高意向客户,可主动跟进</li>
+              <li>塞纳花园连续3个周末空闲,建议推出限时优惠活动</li>
+            </ul>
+            <p><strong>预测:</strong>7月婚礼预订量预计增长25%,建议增加临时服务人员</p>
+          </div>
+        </div>
+        
+        <div class="stats-grid">
+          <div class="stat-card">
+            <div class="stat-value">78%</div>
+            <div class="stat-label">本月场地利用率</div>
+          </div>
+          <div class="stat-card">
+            <div class="stat-value">42</div>
+            <div class="stat-label">待处理咨询</div>
+          </div>
+          <div class="stat-card">
+            <div class="stat-value">15</div>
+            <div class="stat-label">本周婚礼活动</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <div class="venue-section">
+      <div class="section-header">
+        <h2>场地状态</h2>
+        <button class="btn btn-outline"><i class="fas fa-filter"></i> 筛选</button>
+      </div>
+      
+      <div class="venue-list">
+        <div *ngFor="let venue of venues" class="venue-item">
+          <div class="venue-info">
+            <div class="venue-name">{{ venue.name }}</div>
+            <div class="venue-status" 
+                 [class.status-available]="venue.status === 'available'"
+                 [class.status-booked]="venue.status === 'booked'"
+                 [class.status-hold]="venue.status === 'hold'">
+              {{ getStatusText(venue.status, venue.date) }}
+            </div>
+          </div>
+          <div class="venue-actions">
+            <div class="action-btn"><i class="fas fa-eye"></i></div>
+            <div class="action-btn"><i class="fas fa-edit"></i></div>
+          </div>
+        </div>
+        
+        <a href="#" class="view-more">
+          <i class="fas fa-chevron-down"></i> 查看42个场地/日期未定的客户
+        </a>
+      </div>
+    </div>
+  </div>
+</div>

+ 487 - 0
manager-web/src/modules/manager/mobile/page-mine/page-mine.scss

@@ -0,0 +1,487 @@
+/* page-mine.component.scss */
+:host {
+  display: block;
+  background: linear-gradient(135deg, #fdf2f8 0%, #f0f9ff 100%);
+  color: #333;
+  min-height: 100vh;
+  padding: 20px;
+}
+
+.container {
+  max-width: 1600px;
+  margin: 0 auto;
+  display: flex;
+  flex-direction: column;
+  gap: 25px;
+}
+
+header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px 25px;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  box-shadow: 0 8px 20px rgba(186, 47, 142, 0.1);
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  
+  h1 {
+    font-size: 26px;
+    font-weight: 700;
+    background: linear-gradient(135deg, #d6336c, #a61e4d);
+    -webkit-background-clip: text;
+    background-clip: text;
+    color: transparent;
+  }
+  
+  i {
+    font-size: 28px;
+    color: #d6336c;
+  }
+}
+
+.nav-links {
+  display: flex;
+  gap: 25px;
+  
+  a {
+    text-decoration: none;
+    font-weight: 500;
+    color: #555;
+    padding: 10px 15px;
+    border-radius: 12px;
+    transition: all 0.3s ease;
+    position: relative;
+    
+    &:hover, &.active {
+      color: #d6336c;
+      background: rgba(214, 51, 108, 0.08);
+    }
+    
+    &.active:after {
+      content: "";
+      position: absolute;
+      bottom: 0;
+      left: 15px;
+      right: 15px;
+      height: 3px;
+      background: #d6336c;
+      border-radius: 3px;
+    }
+  }
+}
+
+.main-content {
+  display: flex;
+  gap: 25px;
+}
+
+.calendar-section {
+  flex: 1;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  padding: 25px;
+  box-shadow: 0 8px 20px rgba(186, 47, 142, 0.08);
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 25px;
+  
+  h2 {
+    font-size: 22px;
+    font-weight: 600;
+    color: #d6336c;
+  }
+}
+
+.controls {
+  display: flex;
+  gap: 15px;
+  align-items: center;
+}
+
+.date-controls {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.date-display {
+  font-size: 18px;
+  font-weight: 500;
+  color: #555;
+  min-width: 220px;
+}
+
+.btn {
+  padding: 8px 18px;
+  border-radius: 12px;
+  border: none;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  
+  &-primary {
+    background: linear-gradient(135deg, #d6336c, #a61e4d);
+    color: white;
+  }
+  
+  &-outline {
+    background: transparent;
+    border: 1px solid #d6336c;
+    color: #d6336c;
+  }
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 10px rgba(214, 51, 108, 0.2);
+  }
+}
+
+.calendar {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 12px;
+  margin-bottom: 25px;
+}
+
+.weekdays {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  text-align: center;
+  margin-bottom: 15px;
+  font-weight: 500;
+  color: #777;
+}
+
+.calendar-day {
+  height: 110px;
+  background: #fff;
+  border-radius: 12px;
+  padding: 12px;
+  position: relative;
+  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+  border: 1px solid #eee;
+  overflow: hidden;
+  
+  &.empty {
+    background: transparent;
+    box-shadow: none;
+    border: none;
+  }
+  
+  &:hover:not(.empty) {
+    transform: translateY(-3px);
+    box-shadow: 0 5px 15px rgba(214, 51, 108, 0.15);
+  }
+}
+
+.day-header {
+  display: flex;
+  justify-content: space-between;
+  font-size: 14px;
+  margin-bottom: 8px;
+}
+
+.day-num {
+  font-weight: 700;
+  font-size: 18px;
+}
+
+.lunar-date {
+  color: #999;
+  font-size: 12px;
+}
+
+.day-events {
+  font-size: 12px;
+}
+
+.event-tag {
+  display: inline-block;
+  padding: 3px 8px;
+  border-radius: 20px;
+  font-size: 11px;
+  margin-top: 5px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+  
+  &.event-booked {
+    background: rgba(214, 51, 108, 0.1);
+    color: #d6336c;
+    border-left: 3px solid #d6336c;
+  }
+  
+  &.event-available {
+    background: rgba(40, 167, 69, 0.1);
+    color: #28a745;
+    border-left: 3px solid #28a745;
+  }
+  
+  &.event-consult {
+    background: rgba(23, 162, 184, 0.1);
+    color: #17a2b8;
+    border-left: 3px solid #17a2b8;
+  }
+}
+
+.today {
+  background: rgba(255, 228, 230, 0.5);
+  border: 2px solid #d6336c;
+}
+
+.today-label {
+  background: #d6336c;
+  color: white;
+  padding: 2px 6px;
+  border-radius: 4px;
+  font-size: 11px;
+  margin-left: 5px;
+}
+
+.venue-section {
+  width: 400px;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  padding: 25px;
+  box-shadow: 0 8px 20px rgba(186, 47, 142, 0.08);
+}
+
+.venue-list {
+  margin-top: 20px;
+  max-height: 600px;
+  overflow-y: auto;
+  padding-right: 10px;
+  
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: rgba(214, 51, 108, 0.3);
+    border-radius: 4px;
+  }
+}
+
+.venue-item {
+  padding: 15px;
+  margin-bottom: 12px;
+  border-radius: 12px;
+  background: #fff;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s ease;
+  border-left: 4px solid #d6336c;
+  
+  &:hover {
+    transform: translateX(5px);
+    box-shadow: 0 5px 15px rgba(214, 51, 108, 0.15);
+  }
+}
+
+.venue-info {
+  flex: 1;
+}
+
+.venue-name {
+  font-weight: 600;
+  margin-bottom: 5px;
+  color: #333;
+}
+
+.venue-status {
+  font-size: 13px;
+  padding: 3px 10px;
+  border-radius: 20px;
+  display: inline-block;
+  
+  &.status-available {
+    background: rgba(40, 167, 69, 0.15);
+    color: #28a745;
+  }
+  
+  &.status-booked {
+    background: rgba(214, 51, 108, 0.15);
+    color: #d6336c;
+  }
+  
+  &.status-hold {
+    background: rgba(255, 193, 7, 0.15);
+    color: #fd7e14;
+  }
+}
+
+.venue-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.action-btn {
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f8f9fa;
+  color: #777;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  &:hover {
+    background: #d6336c;
+    color: white;
+  }
+}
+
+.ai-section {
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 16px;
+  padding: 25px;
+  margin-top: 10px;
+  box-shadow: 0 8px 20px rgba(186, 47, 142, 0.08);
+}
+
+.ai-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+  
+  h3 {
+    font-size: 20px;
+    color: #d6336c;
+  }
+  
+  i {
+    font-size: 24px;
+    color: #d6336c;
+  }
+}
+
+.ai-recommendation {
+  background: linear-gradient(135deg, #fdf2f8, #f0f9ff);
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+}
+
+.ai-title {
+  font-weight: 600;
+  margin-bottom: 10px;
+  color: #d6336c;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.ai-content {
+  font-size: 14px;
+  line-height: 1.6;
+  color: #555;
+  
+  ul {
+    padding-left: 20px;
+    margin: 10px 0;
+  }
+  
+  li {
+    margin-bottom: 8px;
+  }
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 15px;
+  margin-top: 25px;
+}
+
+.stat-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 18px;
+  text-align: center;
+  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
+}
+
+.stat-value {
+  font-size: 28px;
+  font-weight: 700;
+  color: #d6336c;
+  margin: 10px 0;
+}
+
+.stat-label {
+  font-size: 14px;
+  color: #777;
+}
+
+.view-more {
+  display: block;
+  text-align: center;
+  margin-top: 25px;
+  padding: 12px;
+  background: rgba(214, 51, 108, 0.08);
+  color: #d6336c;
+  border-radius: 12px;
+  text-decoration: none;
+  font-weight: 500;
+  transition: all 0.3s ease;
+  
+  &:hover {
+    background: rgba(214, 51, 108, 0.15);
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .main-content {
+    flex-direction: column;
+  }
+  
+  .venue-section {
+    width: 100%;
+  }
+}
+
+@media (max-width: 768px) {
+  header {
+    flex-direction: column;
+    gap: 15px;
+  }
+  
+  .nav-links {
+    flex-wrap: wrap;
+    justify-content: center;
+  }
+  
+  .stats-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .calendar {
+    grid-template-columns: repeat(7, 1fr);
+    overflow-x: auto;
+  }
+  
+  .calendar-day {
+    min-width: 120px;
+  }
+}

+ 5 - 5
manager-web/src/modules/manager/mobile/page-mine/page-mine.spec.ts

@@ -1,18 +1,18 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 
-import { PageMine } from './page-mine';
+import { PageMineComponent } from './page-mine';
 
 
 describe('PageMine', () => {
 describe('PageMine', () => {
-  let component: PageMine;
-  let fixture: ComponentFixture<PageMine>;
+  let component: PageMineComponent;
+  let fixture: ComponentFixture<PageMineComponent>;
 
 
   beforeEach(async () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
     await TestBed.configureTestingModule({
-      imports: [PageMine]
+      imports: [PageMineComponent]
     })
     })
     .compileComponents();
     .compileComponents();
 
 
-    fixture = TestBed.createComponent(PageMine);
+    fixture = TestBed.createComponent(PageMineComponent);
     component = fixture.componentInstance;
     component = fixture.componentInstance;
     fixture.detectChanges();
     fixture.detectChanges();
   });
   });

+ 168 - 6
manager-web/src/modules/manager/mobile/page-mine/page-mine.ts

@@ -1,12 +1,174 @@
-import { Component } from '@angular/core';
-import { RouterModule } from '@angular/router';
+// page-mine.component.ts
+import { Component, OnInit } from '@angular/core';
 
 
 @Component({
 @Component({
   selector: 'app-page-mine',
   selector: 'app-page-mine',
-  imports: [RouterModule],
   templateUrl: './page-mine.html',
   templateUrl: './page-mine.html',
-  styleUrl: './page-mine.scss'
+  styleUrls: ['./page-mine.scss']
 })
 })
-export class PageMine {
+export class PageMineComponent implements OnInit {
+  weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
+  currentYear: number;
+  currentMonth: number;
+  calendarDays: any[] = [];
+  
+  venues = [
+    { name: '九州厅', status: 'booked', date: '7月5日' },
+    { name: '奥斯卡厅', status: 'available' },
+    { name: '星空之镜', status: 'available' },
+    { name: '塞纳花园', status: 'available' },
+    { name: '海洋之心', status: 'hold' },
+    { name: '冰雪奇缘', status: 'booked', date: '7月12日' },
+    { name: '暮光之城', status: 'booked', date: '7月19日' },
+    { name: '月光礼堂', status: 'booked', date: '7月26日' }
+  ];
+  
+  eventVenues = ['九州厅', '奥斯卡厅', '星空之镜', '塞纳花园', '海洋之心', '冰雪奇缘', '暮光之城', '月光礼堂'];
+  eventNames = ['张先生婚礼', '李小姐婚礼', '王先生婚礼', '赵小姐婚礼', '陈先生婚礼', '刘小姐婚礼'];
+  
+  constructor() { 
+    const today = new Date();
+    this.currentYear = today.getFullYear();
+    this.currentMonth = today.getMonth();
+  }
 
 
-}
+  ngOnInit(): void {
+    this.generateCalendar();
+  }
+
+  generateCalendar(): void {
+    this.calendarDays = [];
+    
+    // 获取当月第一天和最后一天
+    const firstDay = new Date(this.currentYear, this.currentMonth, 1);
+    const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0);
+    
+    // 获取第一天是星期几(0=周日,1=周一...)
+    const firstDayIndex = firstDay.getDay();
+    
+    // 获取当月天数
+    const daysInMonth = lastDay.getDate();
+    
+    // 获取今天日期
+    const today = new Date();
+    const isCurrentMonth = today.getMonth() === this.currentMonth && today.getFullYear() === this.currentYear;
+    
+    // 生成空白日期(上个月)
+    for (let i = 0; i < firstDayIndex; i++) {
+      this.calendarDays.push({ isEmpty: true });
+    }
+    
+    // 生成当月日期
+    for (let day = 1; day <= daysInMonth; day++) {
+      const dateObj = new Date(this.currentYear, this.currentMonth, day);
+      const weekday = this.weekdays[dateObj.getDay()];
+      
+      // 简化版农历日期
+      const lunarDate = this.getLunarDate(day);
+      
+      // 判断是否是今天
+      const isToday = isCurrentMonth && day === today.getDate();
+      
+      // 生成随机事件
+      const events = this.generateRandomEvents(day);
+      
+      this.calendarDays.push({
+        date: day,
+        lunarDate: lunarDate,
+        weekday: weekday,
+        isToday: isToday,
+        events: events,
+        isEmpty: false
+      });
+    }
+  }
+  
+  getLunarDate(day: number): string {
+    // 简化版农历日期
+    const lunarMonths = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
+    const lunarDays = ['初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
+                      '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
+                      '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'];
+    
+    // 随机生成农历月份(模拟)
+    const monthIndex = Math.floor(Math.random() * lunarMonths.length);
+    const dayIndex = Math.min(day - 1, lunarDays.length - 1);
+    
+    return `${lunarMonths[monthIndex]}月${lunarDays[dayIndex]}`;
+  }
+  
+  generateRandomEvents(day: number): any[] {
+    const events = [];
+    const today = new Date();
+    
+    // 如果是周末,增加事件数量
+    const dateObj = new Date(this.currentYear, this.currentMonth, day);
+    const isWeekend = dateObj.getDay() === 0 || dateObj.getDay() === 6;
+    
+    // 如果是过去的日期,只显示已预订事件
+    const isPast = dateObj < today;
+    
+    // 随机决定事件数量 (0-3)
+    let eventCount = Math.floor(Math.random() * (isWeekend ? 4 : 3));
+    if (isPast) eventCount = Math.min(eventCount, 2);
+    
+    for (let i = 0; i < eventCount; i++) {
+      const venue = this.eventVenues[Math.floor(Math.random() * this.eventVenues.length)];
+      
+      // 事件类型:30%空闲,30%咨询,40%已预订
+      const eventType = Math.random() < 0.3 ? 'available' : 
+                      Math.random() < 0.3 ? 'consult' : 'booked';
+      
+      let title = '';
+      switch (eventType) {
+        case 'available':
+          title = '空闲';
+          break;
+        case 'consult':
+          title = '咨询';
+          break;
+        case 'booked':
+          title = this.eventNames[Math.floor(Math.random() * this.eventNames.length)];
+          break;
+      }
+      
+      events.push({
+        venue: venue,
+        type: eventType,
+        title: title
+      });
+    }
+    
+    return events;
+  }
+  
+  changeMonth(offset: number): void {
+    // 更改月份
+    this.currentMonth += offset;
+    
+    // 处理年份变化
+    if (this.currentMonth > 11) {
+      this.currentMonth = 0;
+      this.currentYear++;
+    } else if (this.currentMonth < 0) {
+      this.currentMonth = 11;
+      this.currentYear--;
+    }
+    
+    // 重新生成日历
+    this.generateCalendar();
+  }
+  
+  getStatusText(status: string, date?: string): string {
+    switch (status) {
+      case 'available':
+        return '空闲';
+      case 'booked':
+        return `已预订 (${date})`;
+      case 'hold':
+        return '意向沟通中';
+      default:
+        return '';
+    }
+  }
+}