本文档说明了如何在 yss-project 中集成企业微信认证 (WxworkAuthGuard) 和动态数据服务 (FmodeParse)。
已在 src/app/app.routes.ts 中为所有主要路由添加了 WxworkAuthGuard:
import { WxworkAuthGuard } from 'fmode-ng/social';
// 客服路由
{
  path: 'customer-service',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
// 设计师路由
{
  path: 'designer',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
// 组长路由
{
  path: 'team-leader',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
// 财务路由
{
  path: 'finance',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
// 人事路由
{
  path: 'hr',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
// 管理员路由
{
  path: 'admin',
  canActivate: [WxworkAuthGuard],
  children: [...]
}
已在主要页面组件中集成了 WxworkAuth 类:
管理员仪表板 (src/app/pages/admin/dashboard/dashboard.ts)
import { WxworkAuth, FmodeQuery, FmodeObject, FmodeUser } from 'fmode-ng/core';
private initAuth(): void {
  this.wxAuth = new WxworkAuth({
    cid: 'cDL6R1hgSi'  // 公司帐套ID
  });
}
private async authenticateAndLoadData(): Promise<void> {
  const { user } = await this.wxAuth.authenticateAndLogin();
  if (user) {
    console.log('✅ 管理员登录成功:', user.get('username'));
    await this.loadDashboardData();
  }
}
客服仪表板 (src/app/pages/customer-service/dashboard/dashboard.ts)
// 同样的认证模式,加载咨询统计数据
private async loadConsultationStats(): Promise<void> {
  const consultationQuery = new FmodeQuery('Consultation');
  consultationQuery.equalTo('status', 'new');
  const newConsultations = await consultationQuery.count();
  this.stats.newConsultations.set(newConsultations);
}
设计师仪表板 (src/app/pages/designer/dashboard/dashboard.ts)
// 同样的认证模式,加载任务数据
已在 src/app/app.ts 中初始化了 FmodeParse:
import { FmodeParse } from 'fmode-ng/core';
private initParse(): void {
  try {
    const Parse = FmodeParse.with("nova");
    console.log('✅ FmodeParse 初始化成功');
  } catch (error) {
    console.error('❌ FmodeParse 初始化失败:', error);
  }
}
项目统计
private async loadProjectStats(): Promise<void> {
  const projectQuery = new FmodeQuery('Project');
  // 总项目数
  const totalProjects = await projectQuery.count();
  this.stats.totalProjects.set(totalProjects);
  // 进行中项目数
  projectQuery.equalTo('status', '进行中');
  const activeProjects = await projectQuery.count();
  this.stats.activeProjects.set(activeProjects);
}
用户统计
private async loadUserStats(): Promise<void> {
  // 设计师统计
  const designerQuery = new FmodeQuery('Profile');
  designerQuery.equalTo('role', 'designer');
  const designers = await designerQuery.count();
  this.stats.totalDesigners.set(designers);
}
收入统计
private async loadRevenueStats(): Promise<void> {
  const orderQuery = new FmodeQuery('Order');
  orderQuery.equalTo('status', 'paid');
  const orders = await orderQuery.find();
  let totalRevenue = 0;
  for (const order of orders) {
    const amount = order.get('amount') || 0;
    totalRevenue += amount;
  }
  this.stats.totalRevenue.set(totalRevenue);
}
咨询统计
private async loadConsultationStats(): Promise<void> {
  // 新咨询数
  const consultationQuery = new FmodeQuery('Consultation');
  consultationQuery.equalTo('status', 'new');
  consultationQuery.greaterThanOrEqualTo('createdAt', new Date(new Date().setHours(0,0,0,0)));
  const newConsultations = await consultationQuery.count();
  this.stats.newConsultations.set(newConsultations);
}
src/app/shared/components/upload-component/)直接使用 NovaStorage.withCid(cid).upload() 方法,无需额外服务层。
功能特性:
核心实现:
import { NovaStorage, NovaFile } from 'fmode-ng/core';
// 初始化存储服务
private async initStorage(): Promise<void> {
  const cid = localStorage.getItem('company') || 'cDL6R1hgSi';
  this.storage = await NovaStorage.withCid(cid);
}
// 上传文件
const uploaded: NovaFile = await this.storage.upload(file, {
  prefixKey: 'project/pid/', // 可选的路径前缀
  onProgress: (p) => console.log('进度:', p.total.percent),
});
使用示例:
import { UploadComponent, UploadResult } from './shared/components/upload-component/upload.component';
@Component({
  standalone: true,
  imports: [UploadComponent]
})
export class MyComponent {
  onUploadComplete(results: UploadResult[]) {
    results.forEach(result => {
      if (result.success) {
        console.log('上传成功:', result.file?.url);
        // 保存文件信息到数据库
        this.saveFileInfo(result.file);
      }
    });
  }
  private async saveFileInfo(file: NovaFile) {
    // 保存 key, url, name, type, size, metadata, md5 等信息
    console.log('文件信息:', {
      key: file.key,
      url: file.url,
      name: file.name,
      size: file.size
    });
  }
}
<app-upload-component
  [accept]="image/*"
  [multiple]="true"
  [maxSize]="10"
  [prefixKey]="'demo/images/'"
  [showPreview]="true"
  (uploadComplete)="onUploadComplete($event)">
</app-upload-component>
示例组件: src/app/shared/components/upload-example/ 提供了完整的使用示例和配置选项演示。
在订单分配阶段 (src/modules/project/pages/project-detail/stages/stage-order.component) 中集成了项目文件管理功能:
核心功能:
NovaStorage.withCid(cid).upload() 上传文件ProjectFile 表projects/{projectId}/ 前缀实现代码:
// 初始化 NovaStorage
private async initStorage(): Promise<void> {
  const cid = localStorage.getItem('company') || this.cid || 'cDL6R1hgSi';
  this.storage = await NovaStorage.withCid(cid);
}
// 上传文件到项目目录
const uploaded: NovaFile = await this.storage.upload(file, {
  prefixKey: `projects/${this.projectId}/`,
  onProgress: (p) => { /* 进度回调 */ }
});
// 保存到 ProjectFile 表
const projectFile = new Parse.Object('ProjectFile');
projectFile.set('project', this.project.toPointer());
projectFile.set('name', file.name);
projectFile.set('url', uploaded.url);
projectFile.set('key', uploaded.key);
projectFile.set('uploadedBy', this.currentUser.toPointer());
projectFile.set('source', source); // 标记来源
await projectFile.save();
企业微信拖拽支持:
// 检测企业微信环境
if (typeof window !== 'undefined' && (window as any).wx) {
  this.wxFileDropSupported = true;
  this.initWxWorkFileDrop();
}
// 监听拖拽事件
document.addEventListener('drop', (e) => {
  if (this.isWxWorkFileDrop(e)) {
    this.handleWxWorkFileDrop(e);
  }
});
ProjectFile - 项目文件表
Project - 项目表
Profile - 用户档案表
Consultation - 咨询表
Order - 订单表
AfterSales - 售后表
Images - 图片表
对于新页面,按以下步骤添加认证:
import { WxworkAuth, FmodeQuery, FmodeObject, FmodeUser } from 'fmode-ng/core';
@Component({...})
export class NewPageComponent {
  private wxAuth: WxworkAuth;
  private currentUser: FmodeUser | null = null;
  constructor() {
    this.initAuth();
  }
  private initAuth(): void {
    this.wxAuth = new WxworkAuth({
      cid: 'cDL6R1hgSi'
    });
  }
  async ngOnInit(): Promise<void> {
    await this.authenticateAndLoadData();
  }
  private async authenticateAndLoadData(): Promise<void> {
    const { user } = await this.wxAuth.authenticateAndLogin();
    if (user) {
      this.currentUser = user;
      await this.loadData();
    }
  }
}
// 基本查询
const query = new FmodeQuery('Project');
const projects = await query.find();
// 条件查询
query.equalTo('status', '进行中');
query.greaterThan('createdAt', new Date('2025-01-01'));
// 排序
query.descending('createdAt');
// 分页
query.limit(20);
query.skip(0);
// 计数
const count = await query.count();
// 关联查询
const userQuery = new FmodeQuery('Profile');
userQuery.include('projects');
// 创建新对象
const project = new FmodeObject('Project');
project.set('name', '新项目');
project.set('status', '进行中');
const savedProject = await project.save();
// 更新对象
savedProject.set('status', '已完成');
await savedProject.save();
// 删除对象
await savedProject.destroy();
import { UploadComponent, UploadResult } from '../shared/components/upload-component/upload.component';
@Component({
  standalone: true,
  imports: [UploadComponent]
})
export class MyComponent {
  async onUploadComplete(results: UploadResult[]) {
    // 处理上传结果
    for (const result of results) {
      if (result.success && result.file) {
        console.log('上传成功:', result.file.url);
        // 保存文件信息到数据库
        await this.saveFileInfo(result.file);
      }
    }
  }
  async onUploadError(error: string) {
    console.error('上传失败:', error);
  }
  private async saveFileInfo(file: NovaFile) {
    // 保存文件信息到 Attachment 表或其他相关表
    console.log('保存文件:', file.key, file.url);
  }
}
所有数据操作都包含错误处理,在API调用失败时会自动降级到模拟数据:
try {
  await this.loadDashboardData();
} catch (error) {
  console.error('❌ 数据加载失败:', error);
  // 降级到模拟数据
  this.loadMockData();
}
cid: 'cDL6R1hgSi' 在 WxworkSDK.companyMap 中有对应配置NovaStorage.withCid(cid) 自动初始化,无需手动配置 Providerstorage/company/<cid>/<prefixKey>/<YYYYMMDD>/<HHmmss-rand>-<name>prefixKey 参数指定文件存储路径前缀,如 'project/pid/'