liu
12 小时以前 b3383d96db5b39e03edfece495572a3120edcd61
Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
已修改14个文件
已添加1个文件
284 ■■■■■ 文件已修改
index.html 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
internal/vite-config/src/plugins/inject-app-loading/default-loading.html 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/crm/customer/index.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/post/index.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/index.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/import-result.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/index.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/customer/modules/import-form.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/record/modules/import-form.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/dv/machinery/modules/import-form.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/modules/import-form.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/modules/import-form.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/modules/import-form.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html
@@ -11,8 +11,10 @@
      name="viewport"
      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
    />
    <!-- ç”± vite æ³¨å…¥ VITE_APP_TITLE å˜é‡ï¼Œåœ¨ .env æ–‡ä»¶å†…配置 -->
    <title>%VITE_APP_TITLE%</title>
    <!-- é¦–屏标题固定为中性文案,不要把客户品牌编译进产物(否则换个环境部署就会"串品牌")。
         çœŸå®žå“ç‰Œç”±è¿è¡Œæ—¶ä¸‹å‘:系统管理 â†’ åå¥½è®¾ç½® â†’ å…¬å¸ä¿¡æ¯ï¼ˆåŽç«¯ system_company_config),
         å¯åŠ¨åŽè¦†ç›– document.title,见 src/utils/company-config.ts -->
    <title>工业AI智造平台</title>
    <link rel="icon" href="/favicon.ico" />
    <script>
      var HM_ID = '%VITE_APP_BAIDU_CODE%';
internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html
@@ -103,5 +103,6 @@
</style>
<div class="loading" id="__app-loading__">
  <span class="dot"><i></i><i></i><i></i><i></i></span>
  <div class="title">%VITE_APP_TITLE%</div>
  <!-- ä¸­æ€§æ–‡æ¡ˆï¼šåŠ è½½é¡µæ—©äºŽ JS æ‰§è¡Œï¼Œæ‹¿ä¸åˆ°è¿è¡Œæ—¶çš„公司信息,不能写死客户品牌 -->
  <div class="title">正在加载,请稍候…</div>
</div>
internal/vite-config/src/plugins/inject-app-loading/default-loading.html
@@ -109,5 +109,6 @@
</style>
<div class="loading" id="__app-loading__">
  <div class="loader"></div>
  <div class="title">%VITE_APP_TITLE%</div>
  <!-- ä¸­æ€§æ–‡æ¡ˆï¼šåŠ è½½é¡µæ—©äºŽ JS æ‰§è¡Œï¼Œæ‹¿ä¸åˆ°è¿è¡Œæ—¶çš„公司信息,不能写死客户品牌 -->
  <div class="title">正在加载,请稍候…</div>
</div>
src/api/crm/customer/index.ts
@@ -44,6 +44,13 @@
    file: File;
    updateSupport: boolean;
  }
  /** å®¢æˆ·å¯¼å…¥ç»“æžœ */
  export interface CustomerImportRespVO {
    createCustomerNames?: string[]; // æ–°å¢žæˆåŠŸçš„å®¢æˆ·å
    updateCustomerNames?: string[]; // æ›´æ–°æˆåŠŸçš„å®¢æˆ·å
    failureCustomerNames?: Record<string, string>; // å¯¼å…¥å¤±è´¥çš„客户名及原因
  }
}
/** æŸ¥è¯¢å®¢æˆ·åˆ—表 */
@@ -88,7 +95,10 @@
/** å¯¼å…¥å®¢æˆ· */
export function importCustomer(data: CrmCustomerApi.CustomerImportReqVO) {
  return requestClient.upload('/crm/customer/import', data);
  return requestClient.upload<CrmCustomerApi.CustomerImportRespVO>(
    '/crm/customer/import',
    data,
  );
}
/** èŽ·å–å®¢æˆ·ç²¾ç®€ä¿¡æ¯åˆ—è¡¨ */
src/api/system/post/index.ts
@@ -13,6 +13,13 @@
    remark: string;
    createTime?: Date;
  }
  /** å²—位导入结果 */
  export interface PostImportRespVO {
    createPostNames?: string[]; // æ–°å¢žæˆåŠŸçš„å²—ä½å
    updatePostNames?: string[]; // æ›´æ–°æˆåŠŸçš„å²—ä½å
    failurePostNames?: Record<string, string>; // å¯¼å…¥å¤±è´¥çš„岗位名及原因
  }
}
/** æŸ¥è¯¢å²—位列表 */
@@ -69,8 +76,11 @@
/** å¯¼å…¥å²—位 */
export function importPost(file: File, updateSupport: boolean) {
  return requestClient.upload('/system/post/import', {
    file,
    updateSupport,
  });
  return requestClient.upload<SystemPostApi.PostImportRespVO>(
    '/system/post/import',
    {
      file,
      updateSupport,
    },
  );
}
src/api/system/user/index.ts
@@ -26,6 +26,13 @@
  export interface UserSimple extends User {
    id: number;
  }
  /** ç”¨æˆ·å¯¼å…¥ç»“æžœ */
  export interface UserImportRespVO {
    createUsernames?: string[]; // æ–°å¢žæˆåŠŸçš„ç”¨æˆ·å
    updateUsernames?: string[]; // æ›´æ–°æˆåŠŸçš„ç”¨æˆ·å
    failureUsernames?: Record<string, string>; // å¯¼å…¥å¤±è´¥çš„用户名及原因
  }
}
/** æŸ¥è¯¢ç”¨æˆ·ç®¡ç†åˆ—表 */
@@ -80,10 +87,13 @@
/** å¯¼å…¥ç”¨æˆ· */
export function importUser(file: File, updateSupport: boolean) {
  return requestClient.upload('/system/user/import', {
    file,
    updateSupport,
  });
  return requestClient.upload<SystemUserApi.UserImportRespVO>(
    '/system/user/import',
    {
      file,
      updateSupport,
    },
  );
}
/** ç”¨æˆ·å¯†ç é‡ç½® */
src/utils/import-result.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
import type { VNodeChild } from 'vue';
import { h } from 'vue';
import { message, notification } from 'ant-design-vue';
export interface ImportResultNotifyOptions {
  /** ä¸šåŠ¡ä¸»ä½“ï¼Œç”¨äºŽæ‹¼æ ‡é¢˜ä¸Žå»ºè®®ï¼Œå¦‚ã€Œè®¾å¤‡ã€ã€Œå®¢æˆ·ã€ */
  subject: string;
  /** æ–°å¢žæˆåŠŸçš„æ¡æ•° */
  createCount?: number;
  /** æ›´æ–°æˆåŠŸçš„æ¡æ•° */
  updateCount?: number;
  /** å¤±è´¥æ˜Žç»†ï¼škey ä¸ºç¼–码/名称,value ä¸ºåŽç«¯å¤±è´¥åŽŸå›  */
  failures?: null | Record<string, string>;
  /** ã€Œä¸å­˜åœ¨ã€ç±»å¤±è´¥çš„落点提示,如「【设备类型】【车间】【工作站】中的对应编码」 */
  masterDataHint?: string;
}
/**
 * ç»Ÿä¸€å±•示 Excel å¯¼å…¥ç»“果。
 *
 * åªè¦åŽç«¯è¿”回了失败明细,就绝不能再提示「操作成功」——改为常驻通知逐行列出失败原因,
 * å¹¶æŒ‰å¤±è´¥åŽŸå› ç»™å‡ºä¸‹ä¸€æ­¥åŠ¨ä½œï¼›å…¨éƒ¨æˆåŠŸæ—¶æ‰æç¤ºæˆåŠŸã€‚
 *
 * @returns å®žé™…写入的条数(新增 + æ›´æ–°ï¼‰ï¼Œä¾›è°ƒç”¨æ–¹å†³å®šæ˜¯å¦éœ€è¦åˆ·æ–°åˆ—表
 */
export function notifyImportResult(
  options: ImportResultNotifyOptions,
): number {
  const {
    subject,
    createCount = 0,
    updateCount = 0,
    failures,
    masterDataHint,
  } = options;
  const failureEntries = Object.entries(failures ?? {});
  const importedCount = createCount + updateCount;
  if (failureEntries.length === 0) {
    message.success(`导入成功:新增 ${createCount} æ¡ï¼Œæ›´æ–° ${updateCount} æ¡`);
    return importedCount;
  }
  const nodes: VNodeChild[] = [
    h(
      'div',
      `新增成功 ${createCount} æ¡ï¼Œæ›´æ–°æˆåŠŸ ${updateCount} æ¡ï¼Œå¤±è´¥ ${failureEntries.length} æ¡ï¼š`,
    ),
    ...failureEntries.map(([name, reason]) => h('div', `${name}:${reason}`)),
    ...buildFailureHints(failureEntries.map(([, reason]) => reason), {
      subject,
      masterDataHint,
    }).map((hint) => h('div', { class: 'mt-2 text-red-500' }, hint)),
  ];
  const notify = importedCount === 0 ? notification.error : notification.warning;
  notify({
    message:
      importedCount === 0
        ? `${subject}导入失败:${failureEntries.length} è¡Œå…¨éƒ¨æœªå¯¼å…¥`
        : `导入完成,部分${subject}导入失败(成功 ${importedCount} æ¡ / å¤±è´¥ ${failureEntries.length} æ¡ï¼‰`,
    description: () => h('div', nodes),
    duration: null,
  });
  return importedCount;
}
/** æŒ‰å¤±è´¥åŽŸå› å½’çº³ä¸‹ä¸€æ­¥åŠ¨ä½œï¼Œé¿å…ç”¨æˆ·åªçœ‹åˆ°ã€Œå¤±è´¥ã€å´ä¸çŸ¥é“æ”¹å“ªé‡Œ */
function buildFailureHints(
  reasons: string[],
  { subject, masterDataHint }: Pick<ImportResultNotifyOptions, 'masterDataHint' | 'subject'>,
): string[] {
  const hints = new Set<string>();
  for (const reason of reasons) {
    if (reason.includes('不存在')) {
      hints.add(
        `提示「不存在」的:请先维护好${masterDataHint ?? '对应主数据'},或把 Excel ä¸­çš„值改成已存在的,再重新导入。`,
      );
    } else if (reason.includes('已经存在') || reason.includes('已存在')) {
      hints.add(
        `提示「已存在」的:请修改 Excel ä¸­çš„${subject}名称或编码,或勾选「支持更新」后重新导入以覆盖已有数据。`,
      );
    } else if (reason.includes('不能为空') || reason.includes('为空')) {
      hints.add('提示「不能为空」的:请补齐 Excel ä¸­å¯¹åº”列的内容后重新导入。');
    }
  }
  return [...hints];
}
src/utils/index.ts
@@ -1,5 +1,6 @@
import type { Recordable } from '@vben/types';
export * from './import-result';
export * from './rangePickerProps';
export * from './routerHelper';
src/views/crm/customer/modules/import-form.vue
@@ -4,11 +4,11 @@
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { Button, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { importCustomer, importCustomerTemplate } from '#/api/crm/customer';
import { $t } from '#/locales';
import { notifyImportResult } from '#/utils';
import { useImportFormSchema } from '../data';
@@ -34,15 +34,25 @@
    // æäº¤è¡¨å•
    const data = await formApi.getValues();
    try {
      await importCustomer({
      const importData = await importCustomer({
        ownerUserId: data.ownerUserId,
        file: data.file,
        updateSupport: data.updateSupport,
      });
      // å…³é—­å¹¶æç¤º
      const createCount = importData.createCustomerNames?.length || 0;
      const updateCount = importData.updateCustomerNames?.length || 0;
      // å…³é—­å¹¶æŒ‰å¯¼å…¥ç»“果提示:仅在确有数据写入时通知列表刷新
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
      if (createCount + updateCount > 0) {
        emit('success');
      }
      notifyImportResult({
        subject: '客户',
        createCount,
        updateCount,
        failures: importData.failureCustomerNames,
      });
    } finally {
      modalApi.unlock();
    }
src/views/hrm/attendance/record/modules/import-form.vue
@@ -31,7 +31,8 @@
      emit('success');
      // åŽç«¯è¿”回导入结果(含失败原因,例如“以下员工不存在...;需补充手机号...”)
      if (res && res.length > 0) {
        message[res.includes('未') || res.includes('不完整') ? 'warning' : 'success'](res);
        // å¤±è´¥æ—¶åŽç«¯ä¼šæ‹¼ä¸Šã€Œæœªèƒ½å¯¼å…¥ï¼šã€æ˜Žç»†æ®µï¼Œç”¨å®ƒåˆ¤å®šï¼Œé¿å…æˆåŠŸæ–‡æ¡ˆé‡Œå‡ºçŽ°ã€Œæœªã€å°±è¯¯æŠ¥è­¦å‘Š
        message[res.includes('未能导入') ? 'warning' : 'success'](res);
      } else {
        message.success('导入成功');
      }
src/views/mes/dv/machinery/modules/import-form.vue
@@ -6,7 +6,7 @@
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { Button, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
@@ -14,6 +14,7 @@
  importMachineryTemplate,
} from '#/api/mes/dv/machinery';
import { $t } from '#/locales';
import { notifyImportResult } from '#/utils';
import { useImportFormSchema } from '../data';
@@ -44,14 +45,21 @@
    try {
      const result = await importMachinery(data.file, data.updateSupport);
      const importData = result as MesDvMachineryApi.MachineryImportRespVO;
      let text = `上传成功数量:${importData.createCodes?.length || 0};`;
      text += `更新成功数量:${importData.updateCodes?.length || 0};`;
      text += `更新失败数量:${Object.keys(importData.failureCodes || {}).length};`;
      message.info(text);
      // å…³é—­å¹¶æç¤º
      const createCount = importData.createCodes?.length || 0;
      const updateCount = importData.updateCodes?.length || 0;
      // å…³é—­å¹¶æŒ‰å¯¼å…¥ç»“果提示:仅在确有数据写入时通知列表刷新
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
      if (createCount + updateCount > 0) {
        emit('success');
      }
      notifyImportResult({
        subject: '设备',
        createCount,
        updateCount,
        failures: importData.failureCodes,
        masterDataHint: '【设备类型】【车间】【工作站】中的对应编码',
      });
    } finally {
      modalApi.unlock();
    }
src/views/mes/md/item/modules/import-form.vue
@@ -6,11 +6,12 @@
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { Button, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { importItem, importItemTemplate } from '#/api/mes/md/item';
import { $t } from '#/locales';
import { notifyImportResult } from '#/utils';
import { useImportFormSchema } from '../data';
@@ -41,23 +42,21 @@
    try {
      const result = await importItem(data.file, data.updateSupport);
      const importData = result as MesMdItemApi.ItemImportRespVO;
      let text = `上传成功数量:${importData.createCodes?.length || 0};`;
      for (const code of importData.createCodes || []) {
        text += `< ${code} >`;
      }
      text += `更新成功数量:${importData.updateCodes?.length || 0};`;
      for (const code of importData.updateCodes || []) {
        text += `< ${code} >`;
      }
      text += `更新失败数量:${Object.keys(importData.failureCodes || {}).length};`;
      for (const code in importData.failureCodes || {}) {
        text += `< ${code}: ${importData.failureCodes?.[code]} >`;
      }
      message.info(text);
      // å…³é—­å¹¶æç¤º
      const createCount = importData.createCodes?.length || 0;
      const updateCount = importData.updateCodes?.length || 0;
      // å…³é—­å¹¶æŒ‰å¯¼å…¥ç»“果提示:仅在确有数据写入时通知列表刷新
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
      if (createCount + updateCount > 0) {
        emit('success');
      }
      notifyImportResult({
        subject: '物料',
        createCount,
        updateCount,
        failures: importData.failureCodes,
        masterDataHint: '【物料分类】中的对应编码',
      });
    } finally {
      modalApi.unlock();
    }
src/views/system/post/modules/import-form.vue
@@ -4,11 +4,11 @@
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { Button, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { importPost, importPostTemplate } from '#/api/system/post';
import { $t } from '#/locales';
import { notifyImportResult } from '#/utils';
import { useImportFormSchema } from '../data';
@@ -34,11 +34,21 @@
    // æäº¤è¡¨å•
    const data = await formApi.getValues();
    try {
      await importPost(data.file, data.updateSupport);
      // å…³é—­å¹¶æç¤º
      const importData = await importPost(data.file, data.updateSupport);
      const createCount = importData.createPostNames?.length || 0;
      const updateCount = importData.updatePostNames?.length || 0;
      // å…³é—­å¹¶æŒ‰å¯¼å…¥ç»“果提示:仅在确有数据写入时通知列表刷新
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
      if (createCount + updateCount > 0) {
        emit('success');
      }
      notifyImportResult({
        subject: '岗位',
        createCount,
        updateCount,
        failures: importData.failurePostNames,
      });
    } finally {
      modalApi.unlock();
    }
src/views/system/user/modules/import-form.vue
@@ -4,11 +4,11 @@
import { useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, message, Upload } from 'ant-design-vue';
import { Button, Upload } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { importUser, importUserTemplate } from '#/api/system/user';
import { $t } from '#/locales';
import { notifyImportResult } from '#/utils';
import { useImportFormSchema } from '../data';
@@ -34,11 +34,21 @@
    // æäº¤è¡¨å•
    const data = await formApi.getValues();
    try {
      await importUser(data.file, data.updateSupport);
      // å…³é—­å¹¶æç¤º
      const importData = await importUser(data.file, data.updateSupport);
      const createCount = importData.createUsernames?.length || 0;
      const updateCount = importData.updateUsernames?.length || 0;
      // å…³é—­å¹¶æŒ‰å¯¼å…¥ç»“果提示:仅在确有数据写入时通知列表刷新
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
      if (createCount + updateCount > 0) {
        emit('success');
      }
      notifyImportResult({
        subject: '用户',
        createCount,
        updateCount,
        failures: importData.failureUsernames,
      });
    } finally {
      modalApi.unlock();
    }
vite.config.ts
@@ -13,10 +13,9 @@
  const appApiUrl = company?.apiUrl ?? '';
  const appName = company?.name ?? '';
  const isDev = mode === 'development';
  if (appName) {
    // è¦†ç›–浏览器标签页标题:Vite çš„ resolveEnv ä¼˜å…ˆè¯» process.env,从而替换 index.html é‡Œçš„ %VITE_APP_TITLE%
    process.env.VITE_APP_TITLE = appName;
  }
  // è¿™é‡Œä¸è¦å†æŠŠ appName å†™è¿› VITE_APP_TITLE。
  // é¦–屏标题、加载页文案已固定为中性文案,品牌统一由运行时「公司信息」下发;
  // ä¸€æ—¦åœ¨è¿™é‡Œè¦†ç›–,某个客户的名称就会被编译进 index.html / åŠ è½½é¡µï¼Œæ¢ä¸ªçŽ¯å¢ƒéƒ¨ç½²å°±"串品牌"。
  console.log('[company]', companyKey, '-> name:', appName, 'logo:', appLogo, 'api:', appApiUrl || '(from env)');
  const defineVars: Record<string, string> = {