Browse Source

feat:colorusage

匡智超 2 days ago
parent
commit
dee6b4209c

+ 0 - 45
cloth-design/src/app/modules/cloth/mobile/device-management/device-management.html

@@ -1,45 +0,0 @@
-<!-- device-management.component.html -->
-<div class="container">
-  <h2>设备管理</h2>
-
-  <div class="new-device">
-    <input type="text" [(ngModel)]="newDeviceName" placeholder="设备名称" />
-    <button (click)="addDevice()">添加设备</button>
-  </div>
-
-@if (devices.length > 0) {
-  <div class="devices-list">
-    @for (device of devices; track device.id) {
-      <div class="device" (click)="selectDevice(device)">
-        <span>{{ device.get('name') }}</span>
-        <button (click)="deleteDevice(device)">删除</button>
-      </div>
-    }
-  </div>
-} @else {
-  <!-- 添加设备列表为空时的内容 -->
-  <div class="no-devices-message">
-    <p>⚠️ 没有可用设备</p>
-  </div>
-}
-
-  <ng-template #noDevices>
-    <p>没有设备。</p>
-  </ng-template>
-
-@if (selectedDevice) {
-  <div class="selected-device">
-    <h3>选中的设备</h3>
-    <p>{{ selectedDevice.get('name') }}</p>
-  </div>
-} @else {
-  <!-- 添加未选择设备时的内容 -->
-  <div class="no-selected-device">
-    <p>⚠️ 未选择设备</p>
-  </div>
-}
-
-  <ng-template #noSelectedDevice>
-    <p>没有选中的设备。</p>
-  </ng-template>
-</div>

+ 0 - 29
cloth-design/src/app/modules/cloth/mobile/device-management/device-management.scss

@@ -1,29 +0,0 @@
-/* device-management.component.scss */
-.container {
-  max-width: 800px;
-  margin: 0 auto;
-  padding: 20px;
-}
-
-.new-device {
-  display: flex;
-  gap: 10px;
-  margin-bottom: 20px;
-}
-
-.devices-list {
-  margin-bottom: 20px;
-}
-
-.device {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 10px;
-  border: 1px solid #ccc;
-  margin-bottom: 10px;
-  cursor: pointer;
-}
-.device:hover {
-  background-color: #f0f0f0;
-}

+ 0 - 23
cloth-design/src/app/modules/cloth/mobile/device-management/device-management.spec.ts

@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { DeviceManagement } from './device-management';
-
-describe('DeviceManagement', () => {
-  let component: DeviceManagement;
-  let fixture: ComponentFixture<DeviceManagement>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [DeviceManagement]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(DeviceManagement);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});

+ 0 - 59
cloth-design/src/app/modules/cloth/mobile/device-management/device-management.ts

@@ -1,59 +0,0 @@
-// device-management.component.ts
-import { Component, OnInit } from '@angular/core';
-import { CloudObject } from './../../../../../lib/ncloud';
-import { CloudQuery } from './../../../../../lib/ncloud';
-import { FormsModule } from '@angular/forms';
-
-@Component({
-  selector: 'app-device-management',
-  standalone: true, // 如果是独立组件
-  imports: [FormsModule], // 添加这行
-  templateUrl: './device-management.html',
-  styleUrls: ['./device-management.scss']
-})
-@Component({
-  selector: 'app-device-management',
-  standalone: true,
-  imports: [
-    // 确保导入了必要的模块
-  ],
-  templateUrl: './device-management.html',
-  styleUrls: ['./device-management.scss']
-})
-export class DeviceManagementComponent implements OnInit {
-  devices: CloudObject[] = [];
-  newDeviceName: string = '';
-  selectedDevice: CloudObject | undefined;
-
-  private deviceQuery: CloudQuery;
-
-  constructor() {
-    this.deviceQuery = new CloudQuery("Device");
-  }
-
-  async ngOnInit() {
-    await this.loadDevices();
-  }
-
-  async loadDevices() {
-    const devices = await this.deviceQuery.find();
-    this.devices = devices;
-  }
-
-  async addDevice() {
-    const device = new CloudObject("Device");
-    device.set({ name: this.newDeviceName });
-    await device.save();
-    this.devices.push(device);
-    this.newDeviceName = '';
-  }
-
-  async deleteDevice(device: CloudObject) {
-    await device.destroy();
-    this.devices = this.devices.filter(d => d.id !== device.id);
-  }
-
-  selectDevice(device: CloudObject) {
-    this.selectedDevice = device;
-  }
-}

+ 8 - 0
cloth-design/src/app/modules/cloth/mobile/page-design/page-design.component.ts

@@ -1,6 +1,7 @@
 import { Component } from '@angular/core';
 import { FormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common';
+import { ColorUsage } from '../../../../../lib/ncloud';
 
 @Component({
   selector: 'app-page-design',
@@ -33,6 +34,8 @@ export class PageDesignComponent {
   ];
   aiInput: string = '';
 
+  colorUsage: ColorUsage = new ColorUsage();
+
   selectPart(part: string) {
     this.activePart = part;
   }
@@ -40,6 +43,7 @@ export class PageDesignComponent {
   selectColor(color: string) {
     this.selectedColor = color;
     this.partColors[this.activePart] = color;
+    this.colorUsage.increment(color);
   }
 
   getPartGradient(part: string): string {
@@ -100,4 +104,8 @@ export class PageDesignComponent {
       (B < 255 ? B < 1 ? 0 : B : 255)
     ).toString(16).slice(1);
   }
+  // 新增颜色使用统计
+  getColorUsage(): { [key: string]: number } {
+    return this.colorUsage.getAll();
+}
 }

+ 129 - 85
cloth-design/src/app/modules/cloth/mobile/page-trends/page-trends.component.ts

@@ -1,5 +1,7 @@
 import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
 import { Chart } from 'chart.js/auto';
+import { ColorUsage } from '../../../../../lib/ncloud';
+import { PageDesignComponent } from '../page-design/page-design.component';
 
 // 定义JSON数据结构
 interface ColorData {
@@ -31,92 +33,134 @@ export class PageTrendsComponent implements AfterViewInit {
   @ViewChild('colorChart') colorChartRef!: ElementRef;
   private colorChart: any;
 
-  // JSON格式的颜色数据
-  colorData: ColorData = {
-    labels: ['海洋蓝', '活力红', '森林绿', '阳光黄', '梦幻紫', '珊瑚橙'],
-    datasets: [{
-      label: '使用次数',
-      data: [1850, 1620, 1540, 1420, 1360, 1280],
-      backgroundColor: [
-        '#3498db',
-        '#e74c3c',
-        '#2ecc71',
-        '#f1c40f',
-        '#9b59b6',
-        '#ff7f50'
-      ],
-      borderColor: [
-        '#2980b9',
-        '#c0392b',
-        '#27ae60',
-        '#f39c12',
-        '#8e44ad',
-        '#ff6347'
-      ],
-      borderWidth: 1,
-      borderRadius: 10
-    }]
-  };
-
-  // JSON格式的热门搭配数据
-  popularCombinations: ColorCombination[] = [
-    {
-      colors: ['#3498db', '#2c3e50'],
-      icon: 'fas fa-water',
-      name: '深海蓝黑',
-      count: '5,678 次使用'
-    },
-    {
-      colors: ['#e74c3c', '#f1c40f'],
-      icon: 'fas fa-sun',
-      name: '日落橙黄',
-      count: '4,987 次使用'
-    }
-  ];
-
-  ngAfterViewInit() {
-    this.initChart();
-  }
+  //constructor(private pageDesignComponent: PageDesignComponent) {}
+  colorUsage: ColorUsage = new ColorUsage();
 
-  private initChart() {
-    const ctx = this.colorChartRef.nativeElement.getContext('2d');
-    this.colorChart = new Chart(ctx, {
-      type: 'bar',
-      data: this.colorData,
-      options: {
-        responsive: true,
-        maintainAspectRatio: false,
-        plugins: {
-          legend: {
-            display: false
-          },
-          title: {
-            display: true,
-            text: '颜色使用频率统计',
-            font: {
-              size: 16
-            }
-          }
-        },
-        scales: {
-          y: {
-            beginAtZero: true,
-            grid: {
-              color: 'rgba(0, 0, 0, 0.05)'
-            }
-          },
-          x: {
-            grid: {
-              display: false
-            }
-          }
-        }
-      }
-    });
-  }
 
-  // 生成渐变背景样式
-  getGradientStyle(colors: string[]): string {
-    return `background: linear-gradient(135deg, ${colors[0]}, ${colors[1]});`;
+
+  /*colorData: ColorData = {
+    labels: ['海洋蓝', '活力红', '森林绿', '阳光黄', '梦幻紫', '珊瑚橙'],
+    datasets: [{
+      label: '使用次数',
+      data: [1850, 1620, 1540, 1420, 1360, 1280],
+      backgroundColor: [
+        '#3498db',
+        '#e74c3c',
+        '#2ecc71',
+        '#f1c40f',
+        '#9b59b6',
+        '#ff7f50'
+      ],
+      borderColor: [
+        '#2980b9',
+        '#c0392b',
+        '#27ae60',
+        '#f39c12',
+        '#8e44ad',
+        '#ff6347'
+      ],
+      borderWidth: 1,
+      borderRadius: 10
+    }]
+  };*/
+
+// 增加一个方法来获取颜色使用统计结果并生成图表数据
+private getColorData(): any {
+    const colorUsage = this.colorUsage.getAll();
+    const sortedColors = Object.entries(colorUsage).sort((a, b) => b[1] - a[1]);
+    const topColors = sortedColors.slice(0, 6);
+
+    return {
+      labels: topColors.map(([color]) => color),
+      datasets: [{
+        label: '使用次数',
+        data: topColors.map(([, count]) => count),
+        backgroundColor: topColors.map(([color]) => color),
+        borderColor: topColors.map(([color]) => this.darkenColor(color, 20)),
+        borderWidth: 1,
+        borderRadius: 10
+      }]
+    };
   }
+
+  // JSON格式的热门搭配数据
+  popularCombinations: ColorCombination[] = [
+    {
+      colors: ['#3498db', '#2c3e50'],
+      icon: 'fas fa-water',
+      name: '深海蓝黑',
+      count: '5,678 次使用'
+    },
+    {
+      colors: ['#e74c3c', '#f1c40f'],
+      icon: 'fas fa-sun',
+      name: '日落橙黄',
+      count: '4,987 次使用'
+    }
+  ];
+
+  ngAfterViewInit() {
+    this.initChart();
+  }
+
+  private initChart() {
+    const ctx = this.colorChartRef.nativeElement.getContext('2d');
+    this.colorChart = new Chart(ctx, {
+      type: 'bar',
+      //data: this.colorData,
+      data: this.getColorData(),
+      options: {
+        responsive: true,
+        maintainAspectRatio: false,
+        plugins: {
+          legend: {
+            display: false
+          },
+          title: {
+            display: true,
+            text: '颜色使用频率统计',
+            font: {
+              size: 16
+            }
+          }
+        },
+        scales: {
+          y: {
+            beginAtZero: true,
+            grid: {
+              color: 'rgba(0, 0, 0, 0.05)'
+            }
+          },
+          x: {
+            grid: {
+              display: false
+            }
+          }
+        }
+      }
+    });
+  }
+
+
+  // 生成渐变背景样式
+  getGradientStyle(colors: string[]): string {
+    return `background: linear-gradient(135deg, ${colors[0]}, ${colors[1]});`;
+  }
+
+// 增加一个方法来生成颜色的边框颜色
+private darkenColor(color: string, percent: number): string {
+  const num = parseInt(color.replace('#', ''), 16);
+  const amt = Math.round(2.55 * percent);
+  const R = (num >> 16) - amt;
+  const G = (num >> 8 & 0x00FF) - amt;
+  const B = (num & 0x0000FF) - amt;
+  
+  return '#' + (
+    0x1000000 +
+    (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
+    (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
+    (B < 255 ? B < 1 ? 0 : B : 255)
+  ).toString(16).slice(1);
+}
+
 }

+ 300 - 44
cloth-design/src/lib/ncloud.ts

@@ -1,6 +1,5 @@
 // CloudObject.ts
 
-// cloud-object.ts
 let serverURL = `https://dev.fmode.cn/parse`;
 if (location.protocol == "http:") {
     serverURL = `http://dev.fmode.cn:1337/parse`;
@@ -23,7 +22,7 @@ export class CloudObject {
 
     set(json: Record<string, any>) {
         Object.keys(json).forEach(key => {
-            if (["objectId", "id", "createdAt", "updatedAt"].includes(key)) {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
                 return;
             }
             this.data[key] = json[key];
@@ -36,7 +35,7 @@ export class CloudObject {
 
     async save() {
         let method = "POST";
-        let url = `${serverURL}/classes/${this.className}`;
+        let url = serverURL + `/classes/${this.className}`;
 
         // 更新
         if (this.id) {
@@ -47,8 +46,8 @@ export class CloudObject {
         const body = JSON.stringify(this.data);
         const response = await fetch(url, {
             headers: {
-                "Content-Type": "application/json;charset=UTF-8",
-                "X-Parse-Application-Id": "dev"
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
             },
             body: body,
             method: method,
@@ -56,41 +55,37 @@ export class CloudObject {
             credentials: "omit"
         });
 
-        const result = await response.json();
-        if (result.error) {
-            console.error(result.error);
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
         }
-        if (result.objectId) {
-            this.id = result.objectId;
-            this.createdAt = result.createdAt;
-            this.updatedAt = result.updatedAt;
+        if (result?.objectId) {
+            this.id = result?.objectId;
         }
         return this;
     }
 
     async destroy() {
         if (!this.id) return;
-        const response = await fetch(`${serverURL}/classes/${this.className}/${this.id}`, {
+        const response = await fetch(serverURL + `/classes/${this.className}/${this.id}`, {
             headers: {
-                "X-Parse-Application-Id": "dev"
+                "x-parse-application-id": "dev"
             },
+            body: null,
             method: "DELETE",
             mode: "cors",
             credentials: "omit"
         });
 
-        const result = await response.json();
+        const result = await response?.json();
         if (result) {
             this.id = undefined;
-            this.createdAt = undefined;
-            this.updatedAt = undefined;
-            this.data = {};
         }
         return true;
     }
 }
+
 // CloudQuery.ts
-// cloud-query.ts
 export class CloudQuery {
     className: string;
     queryParams: Record<string, any> = { where: {} };
@@ -99,10 +94,9 @@ export class CloudQuery {
         this.className = className;
     }
 
-    include(...fields: string[]) {
-        this.queryParams["include"] = fields.join(",");
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
     }
-
     greaterThan(key: string, value: any) {
         if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
         this.queryParams["where"][key]["$gt"] = value;
@@ -124,72 +118,334 @@ export class CloudQuery {
     }
 
     equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
         this.queryParams["where"][key] = value;
     }
 
     async get(id: string) {
-        const url = `${serverURL}/classes/${this.className}/${id}`;
+        const url = serverURL + `/classes/${this.className}/${id}?`;
 
         const response = await fetch(url, {
             headers: {
-                "X-Parse-Application-Id": "dev"
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
             },
+            body: null,
             method: "GET",
             mode: "cors",
             credentials: "omit"
         });
 
-        const json = await response.json();
-        if (json.error) {
-            console.error(json.error);
-            return null;
+        const json = await response?.json();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
         }
-        return this.dataToObj(json);
+        return null
     }
 
     async find(): Promise<Array<CloudObject>> {
-        let url = `${serverURL}/classes/${this.className}?`;
+        let url = serverURL + `/classes/${this.className}?`;
 
-        let queryStr = "";
+        let queryStr = ``
         Object.keys(this.queryParams).forEach(key => {
             let paramStr = JSON.stringify(this.queryParams[key]);
-            if (key === "include") {
-                paramStr = this.queryParams[key];
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
             }
             if (queryStr) {
-                url += `&${key}=${encodeURIComponent(paramStr)}`;
+                url += `${key}=${paramStr}`;
             } else {
-                url += `${key}=${encodeURIComponent(paramStr)}`;
+                url += `&${key}=${paramStr}`;
             }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
         });
 
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}&limit=1`;
+        }
+
         const response = await fetch(url, {
             headers: {
-                "X-Parse-Application-Id": "dev"
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
             },
+            body: null,
             method: "GET",
             mode: "cors",
             credentials: "omit"
         });
 
-        const json = await response.json();
-        let list = json.results || [];
-        let objList = list.map((item: any) => this.dataToObj(item));
-        return objList;
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
     }
 
     dataToObj(exists: any): CloudObject {
         let existsObject = new CloudObject(this.className);
         Object.keys(exists).forEach(key => {
-            if (exists[key]?.__type === "Pointer") {
-                exists[key] = new CloudObject(exists[key].className);
-                exists[key].id = exists[key].objectId;
+            if (exists[key]?.__type == "Object") {
+                exists[key] = this.dataToObj(exists[key])
             }
-        });
+        })
         existsObject.set(exists);
         existsObject.id = exists.objectId;
         existsObject.createdAt = exists.createdAt;
         existsObject.updatedAt = exists.updatedAt;
         return existsObject;
     }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if (userCacheStr) {
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken: string | null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(serverURL + `/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string): Promise<CloudUser | null> {
+        const response = await fetch(serverURL + `/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(serverURL + `/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        let result = await response?.json();
+
+        if (result?.error) {
+            console.error(result?.error);
+            if (result?.error == "Invalid session token") {
+                this.clearUserCache()
+                return true;
+            }
+            return false;
+        }
+
+        this.clearUserCache()
+        return true;
+    }
+    clearUserCache() {
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(serverURL + `/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = serverURL + `/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data: any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions: any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi {
+    async fetch(path: string, body: any, options?: {
+        method: string
+        body: any
+    }) {
+
+        let reqOpts: any = {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if (body || options?.body) {
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `https://dev.fmode.cn`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/` + path
+        console.log(url, reqOpts)
+        const response = await fetch(url, reqOpts);
+        let json = await response.json();
+        return json
+    }
+}
+// color-usage.ts
+export class ColorUsage {
+  private usage: { [key: string]: number } = {};
+
+  set(color: string, count: number) {
+    this.usage[color] = count;
+  }
+
+  get(color: string): number {
+    return this.usage[color] || 0;
+  }
+
+  increment(color: string) {
+    this.usage[color] = (this.usage[color] || 0) + 1;
+  }
+
+  getAll(): { [key: string]: number } {
+    return this.usage;
+  }
 }