gaoluyang
8 天以前 caddfd7efaabf86eb2348ae33d9b7a2281a2b167
fix(user): 导入失败明细改用弹窗展示,用户页字段统一叫「用户账号」

- import-result.ts:导入失败明细从 notification(上百行长消息糊满屏幕)
改成弹窗展示,弹窗内列表可滚动;全部成功时仍用轻提示
- 用户管理:列表列 / 新增表单 / 搜索 / 分配角色的「用户名称」统一为
「用户账号」,与后端校验、导入模板的叫法保持一致
- 导入弹窗补充说明:模板列(用户账号、用户昵称、部门、岗位等)以及
「是否覆盖」不会修改已有用户的密码

Co-Authored-By: Claude Code <noreply@anthropic.com>
已修改2个文件
70 ■■■■■ 文件已修改
src/utils/import-result.ts 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/data.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/import-result.ts
@@ -2,7 +2,7 @@
import { h } from 'vue';
import { message, notification } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
export interface ImportResultNotifyOptions {
  /** 业务主体,用于拼标题与建议,如「设备」「客户」 */
@@ -20,8 +20,10 @@
/**
 * 统一展示 Excel 导入结果。
 *
 * 只要后端返回了失败明细,就绝不能再提示「操作成功」——改为常驻通知逐行列出失败原因,
 * 只要后端返回了失败明细,就绝不能再提示「操作成功」——改为弹窗逐行列出失败原因,
 * 并按失败原因给出下一步动作;全部成功时才提示成功。
 *
 * 失败明细可能上百行,所以用弹窗(弹窗内列表可滚动),避免长消息糊满屏幕。
 *
 * @returns 实际写入的条数(新增 + 更新),供调用方决定是否需要刷新列表
 */
@@ -43,26 +45,41 @@
    return importedCount;
  }
  const nodes: VNodeChild[] = [
  const hints = buildFailureHints(
    failureEntries.map(([, reason]) => reason),
    { subject, masterDataHint },
  );
  const content: VNodeChild = h('div', [
    h(
      'div',
      `新增成功 ${createCount} 条,更新成功 ${updateCount} 条,失败 ${failureEntries.length} 条:`,
      `新增成功 ${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)),
  ];
    h(
      'div',
      {
        class:
          'mt-2 max-h-72 overflow-y-auto rounded border border-solid border-gray-200 px-3 py-2 text-sm leading-6',
      },
      failureEntries.map(([name, reason]) =>
        h('div', { class: 'py-0.5' }, `${name}:${reason}`),
      ),
    ),
    ...hints.map((hint) =>
      h('div', { class: 'mt-2 text-red-500' }, hint),
    ),
  ]);
  const notify = importedCount === 0 ? notification.error : notification.warning;
  notify({
    message:
  const openModal = importedCount === 0 ? Modal.error : Modal.warning;
  openModal({
    title:
      importedCount === 0
        ? `${subject}导入失败:${failureEntries.length} 行全部未导入`
        : `导入完成,部分${subject}导入失败(成功 ${importedCount} 条 / 失败 ${failureEntries.length} 条)`,
    description: () => h('div', nodes),
    duration: null,
        : `导入完成:成功 ${importedCount} 条,失败 ${failureEntries.length} 条`,
    content,
    width: 680,
    centered: true,
    okText: '知道了',
  });
  return importedCount;
}
@@ -70,7 +87,10 @@
/** 按失败原因归纳下一步动作,避免用户只看到「失败」却不知道改哪里 */
function buildFailureHints(
  reasons: string[],
  { subject, masterDataHint }: Pick<ImportResultNotifyOptions, 'masterDataHint' | 'subject'>,
  {
    subject,
    masterDataHint,
  }: Pick<ImportResultNotifyOptions, 'masterDataHint' | 'subject'>,
): string[] {
  const hints = new Set<string>();
  for (const reason of reasons) {
src/views/system/user/data.ts
@@ -26,10 +26,10 @@
    },
    {
      fieldName: 'username',
      label: '用户名称',
      label: '用户账号',
      component: 'Input',
      componentProps: {
        placeholder: '请输入用户名称',
        placeholder: '请输入用户账号(4-30 位数字、字母)',
      },
      rules: 'required',
    },
@@ -204,7 +204,7 @@
    },
    {
      fieldName: 'username',
      label: '用户名称',
      label: '用户账号',
      component: 'Input',
      componentProps: {
        disabled: true,
@@ -241,7 +241,7 @@
      label: '用户数据',
      component: 'Upload',
      rules: 'required',
      help: '仅允许导入 xls、xlsx 格式文件',
      help: '仅允许导入 xls、xlsx 格式文件;请先下载导入模板,按模板列(用户账号、用户昵称、部门、岗位等)填写',
    },
    {
      fieldName: 'updateSupport',
@@ -252,7 +252,7 @@
        unCheckedChildren: '否',
      },
      rules: z.boolean().default(false),
      help: '是否更新已经存在的用户数据',
      help: '勾选后,用户账号已存在的行会更新该用户信息(不会修改其密码)',
    },
  ];
}
@@ -262,10 +262,10 @@
  return [
    {
      fieldName: 'username',
      label: '用户名称',
      label: '用户账号',
      component: 'Input',
      componentProps: {
        placeholder: '请输入用户名称',
        placeholder: '请输入用户账号',
        allowClear: true,
      },
    },
@@ -306,7 +306,7 @@
    },
    {
      field: 'username',
      title: '用户名称',
      title: '用户账号',
      minWidth: 120,
    },
    {