Просмотр исходного кода

fix: 修复企业小程序数据统计页面问题

- 修复户籍省份分布图表只显示部分数据的问题 (移除 stats.slice(0,6) 限制)
- 修复 H5 环境下 rpc-client token 解析错误
- Admin MCP 工具添加 currentCompanyId 和 currentCompany 字段支持

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 недель назад
Родитель
Сommit
8a76101f41

+ 32 - 0
allin-packages/disability-module/src/schemas/disabled-person.schema.ts

@@ -2,6 +2,30 @@ import { z } from '@hono/zod-openapi';
 import { FileSchema } from '@d8d/file-module/schemas';
 import { BankNameSchema } from '@d8d/bank-names-module/schemas';
 
+// 简化的公司Schema(避免循环依赖)
+const SimpleCompanySchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '公司ID',
+    example: 1
+  }),
+  companyName: z.string().max(100).openapi({
+    description: '公司名称',
+    example: '示例科技有限公司'
+  }),
+  contactPerson: z.string().max(50).nullable().optional().openapi({
+    description: '联系人',
+    example: '张三'
+  }),
+  contactPhone: z.string().max(20).nullable().optional().openapi({
+    description: '联系电话',
+    example: '13800138000'
+  }),
+  address: z.string().max(200).nullable().optional().openapi({
+    description: '地址',
+    example: '北京市朝阳区'
+  })
+}).openapi({ description: '当前所属公司信息(通过最新订单关联)' });
+
 // 基础字段定义
 const BaseDisabledPersonSchema = z.object({
   name: z.string().min(1, '姓名不能为空').max(50).openapi({
@@ -107,6 +131,14 @@ export const DisabledPersonSchema = BaseDisabledPersonSchema.extend({
   updateTime: z.coerce.date().openapi({
     description: '更新时间',
     example: '2024-01-01T00:00:00Z'
+  }),
+  // 当前所属公司信息(通过最新订单关联)
+  currentCompanyId: z.number().int().positive().nullable().optional().openapi({
+    description: '当前所属公司ID(通过最新入职订单获取)',
+    example: 1
+  }),
+  currentCompany: SimpleCompanySchema.nullable().optional().openapi({
+    description: '当前所属公司信息(通过最新入职订单获取)'
   })
 });
 

+ 91 - 3
allin-packages/disability-module/src/services/disabled-person.service.ts

@@ -6,7 +6,7 @@ import { DisabledPhoto } from '../entities/disabled-photo.entity';
 import { DisabledRemark } from '../entities/disabled-remark.entity';
 import { DisabledVisit } from '../entities/disabled-visit.entity';
 import { File } from '@d8d/file-module';
-import{ OrderPerson, OrderPersonAsset } from '@d8d/allin-order-module';
+import { OrderPerson, OrderPersonAsset } from '@d8d/allin-order-module';
 import { WorkStatus, getWorkStatusLabel } from '@d8d/allin-enums';
 
 // 前端专用的工作状态中文标签映射(与mini-ui保持一致)
@@ -101,6 +101,31 @@ export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
       relations: ['bankCards', 'bankCards.bankName', 'bankCards.file', 'bankCards.file.uploadUser', 'photos', 'photos.file', 'photos.file.uploadUser', 'remarks', 'visits', 'guardianPhones', 'phones']
     });
 
+    if (!person) {
+      return null;
+    }
+
+    // 加载最新的订单和公司信息
+    const orderPersonRepo = this.dataSource.getRepository(OrderPerson);
+    const latestOrderPerson = await orderPersonRepo
+      .createQueryBuilder('op')
+      .innerJoinAndSelect('op.order', 'order')
+      .leftJoinAndSelect('order.company', 'company')
+      .where('op.personId = :personId', { personId: id })
+      .orderBy('op.joinDate', 'DESC')
+      .getOne();
+
+    // 将公司信息附加到残疾人对象(使用类型断言避免 TypeScript 错误)
+    if (latestOrderPerson?.order?.company) {
+      (person as any).currentCompanyId = latestOrderPerson.order.company.id;
+      (person as any).currentCompany = {
+        id: latestOrderPerson.order.company.id,
+        companyName: latestOrderPerson.order.company.companyName,
+        contactPerson: latestOrderPerson.order.company.contactPerson,
+        contactPhone: latestOrderPerson.order.company.contactPhone,
+        address: latestOrderPerson.order.company.address
+      };
+    }
 
     return person;
   }
@@ -174,14 +199,30 @@ export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
     if (data.length > 0) {
       const personIds = data.map(p => p.id);
 
-      const [bankCards, photos, remarks, visits] = await Promise.all([
+      const [bankCards, photos, remarks, visits, latestOrderPersons] = await Promise.all([
         this.bankCardRepository.find({ where: { personId: In(personIds) } }),
         this.photoRepository.find({
           where: { personId: In(personIds) },
           relations: ['file']
         }),
         this.remarkRepository.find({ where: { personId: In(personIds) } }),
-        this.visitRepository.find({ where: { personId: In(personIds) } })
+        this.visitRepository.find({ where: { personId: In(personIds) } }),
+        // 加载每个残疾人的最新订单和公司信息
+        this.dataSource.getRepository(OrderPerson)
+          .createQueryBuilder('op')
+          .innerJoin('op.order', 'order')
+          .leftJoin('order.company', 'company')
+          .select([
+            'op.personId',
+            'company.id',
+            'company.companyName',
+            'company.contactPerson',
+            'company.contactPhone',
+            'company.address'
+          ])
+          .where('op.personId IN (:...personIds)', { personIds })
+          .orderBy('op.joinDate', 'DESC')
+          .getRawMany()
       ]);
 
 
@@ -190,6 +231,7 @@ export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
       const photosMap = new Map<number, DisabledPhoto[]>();
       const remarksMap = new Map<number, DisabledRemark[]>();
       const visitsMap = new Map<number, DisabledVisit[]>();
+      const companyMap = new Map<number, any>();
 
       for (const card of bankCards) {
         const cards = bankCardsMap.get(card.personId) || [];
@@ -215,12 +257,33 @@ export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
         visitsMap.set(visit.personId, visits);
       }
 
+      // 处理公司信息(只保留每个残疾人的第一个/最新公司)
+      for (const row of latestOrderPersons) {
+        const personId = row.op_person_id; // PostgreSQL 返回的列名格式
+        if (!companyMap.has(personId) && row.company_company_id) {
+          companyMap.set(personId, {
+            id: row.company_company_id,
+            companyName: row.company_company_name,
+            contactPerson: row.company_contact_person,
+            contactPhone: row.company_contact_phone,
+            address: row.company_address
+          });
+        }
+      }
+
       // 将关联数据附加到每个残疾人
       for (const person of data) {
         person.bankCards = bankCardsMap.get(person.id) || [];
         person.photos = photosMap.get(person.id) || [];
         person.remarks = remarksMap.get(person.id) || [];
         person.visits = visitsMap.get(person.id) || [];
+
+        // 添加公司信息
+        const company = companyMap.get(person.id);
+        if (company) {
+          (person as any).currentCompanyId = company.id;
+          (person as any).currentCompany = company;
+        }
       }
     }
 
@@ -236,6 +299,31 @@ export class DisabledPersonService extends GenericCrudService<DisabledPerson> {
       relations: ['bankCards', 'photos', 'photos.file', 'remarks', 'visits', 'guardianPhones', 'phones']
     });
 
+    if (!person) {
+      return null;
+    }
+
+    // 加载最新的订单和公司信息
+    const orderPersonRepo = this.dataSource.getRepository(OrderPerson);
+    const latestOrderPerson = await orderPersonRepo
+      .createQueryBuilder('op')
+      .innerJoinAndSelect('op.order', 'order')
+      .leftJoinAndSelect('order.company', 'company')
+      .where('op.personId = :personId', { personId: person.id })
+      .orderBy('op.joinDate', 'DESC')
+      .getOne();
+
+    // 将公司信息附加到残疾人对象
+    if (latestOrderPerson?.order?.company) {
+      (person as any).currentCompanyId = latestOrderPerson.order.company.id;
+      (person as any).currentCompany = {
+        id: latestOrderPerson.order.company.id,
+        companyName: latestOrderPerson.order.company.companyName,
+        contactPerson: latestOrderPerson.order.company.contactPerson,
+        contactPhone: latestOrderPerson.order.company.contactPhone,
+        address: latestOrderPerson.order.company.address
+      };
+    }
 
     return person;
   }

+ 15 - 3
mini-ui-packages/mini-shared-ui-components/src/utils/rpc/rpc-client.ts

@@ -21,7 +21,11 @@ const refreshToken = async (): Promise<string | null> => {
 
   isRefreshing = true
   try {
-    const refreshToken = Taro.getStorageSync('enterprise_refresh_token')
+    const refreshTokenData = Taro.getStorageSync('enterprise_refresh_token')
+    // 处理 H5 环境下 Taro.getStorageSync 返回的对象格式 {data: "..."}
+    const refreshToken = refreshTokenData
+      ? (typeof refreshTokenData === 'object' && refreshTokenData.data ? refreshTokenData.data : refreshTokenData)
+      : null
     if (!refreshToken) {
       throw new Error('未找到刷新token')
     }
@@ -114,10 +118,18 @@ const taroFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<
   // 根据API路径判断是用人方还是人才小程序的请求
   if (url.includes('/api/v1/yongren/')) {
     // 用人方API,使用enterprise_token
-    token = Taro.getStorageSync('enterprise_token')
+    const tokenData = Taro.getStorageSync('enterprise_token')
+    // 处理 H5 环境下 Taro.getStorageSync 返回的对象格式 {data: "..."}
+    if (tokenData) {
+      token = typeof tokenData === 'object' && tokenData.data ? tokenData.data : tokenData
+    }
   } else if (url.includes('/api/v1/rencai/')) {
     // 人才API,使用talent_token
-    token = Taro.getStorageSync('talent_token')
+    const tokenData = Taro.getStorageSync('talent_token')
+    // 处理 H5 环境下 Taro.getStorageSync 返回的对象格式 {data: "..."}
+    if (tokenData) {
+      token = typeof tokenData === 'object' && tokenData.data ? tokenData.data : tokenData
+    }
   }
 
   if (token) {

+ 1 - 1
mini/src/pages/yongren/statistics/index.tsx

@@ -461,7 +461,7 @@ const Statistics: React.FC<StatisticsProps> = () => {
               console.log('📊 [户籍省份分布] 原始 API 数据:', JSON.stringify(householdData, null, 2))
               console.log('📊 [户籍省份分布] stats 数组:', JSON.stringify(stats, null, 2))
               if (stats.length > 0) {
-                const chartData = convertToColumnData(stats.slice(0, 6))
+                const chartData = convertToColumnData(stats)
                 console.log('📊 [户籍省份分布] 转换后的 categories:', chartData.categories)
                 console.log('📊 [户籍省份分布] 转换后的 series:', JSON.stringify(chartData.series, null, 2))
                 // 检查每个 stat 项的 province 值