Skip to content

管理员页面

搜索防抖

ts
// 防抖搜索
const handleSearch = debounce(() => {
    querySearch.value = querySearch.value.trim();
    getAdminUserList()
}, 500)
vue
<div class="search-right">
    <el-input v-model="querySearch" style="width: 240px;margin-right: 4px;" placeholder="用户名" clearable
        :prefix-icon="Search" @clear='clearQuerySearch' @input="handleSearch" />
    <el-button class="search-right-button-item" @click="getAdminUserList">
        <ele-Search style="width: 1em; height: 1em; margin-right: 4px;"></ele-Search>
        <span>搜索</span>
    </el-button>
</div>

defineProps问题

ts
这通常是因为传递给子组件的 RolesList 是一个对象,而 Vue 的 props 是默认浅层侦听的。当你更新数组的内容而不是整个引用时,子组件并没有感知到变化。

问题原因
浅层侦听 (shallow):Vue 对 props 默认是浅层侦听,如果父组件更新的是对象/数组内部的内容(如 push、splice 等),子组件可能不会感知到变化。
数据初始化时机:如果你在传递数据时 RolesList 尚未赋值完成,子组件可能会接收到一个空数组。

RolesList定义类型

你需要给 RolesList 指定具体的数组元素类型。例如,假设你的角色列表每一项是一个对象,包含 idnameremark 属性,可以这样定义类型:

ts
<script lang="ts">
import { defineProps } from 'vue';

interface Role {
  id: number;
  name: string;
  remark: string;
}

const props = defineProps<{
  RolesList: Role[];
}>();
</script>

<template>
  <el-option
    v-for="item in RolesList"
    :key="item.id"
    :label="item.remark"
    :value="item.name"
  />
</template>



在 Vue 3 中使用 TypeScript 时,defineProps 默认不会推断数组中元素的具体类型,因此 item 被推断为 unknown 类型。为了解决这个问题,可以在 defineProps 中显式声明 RolesList 的类型。

解决方法:为 RolesList 定义类型
你需要给 RolesList 指定具体的数组元素类型。例如,假设你的角色列表每一项是一个对象,包含 id、name 和 remark 属性,可以这样定义类型:

修改代码如下:
vue
复制代码
<script lang="ts">
import { defineProps } from 'vue';

interface Role {
  id: number;
  name: string;
  remark: string;
}

const props = defineProps<{
  RolesList: Role[];
}>();
</script>

<template>
  <el-option
    v-for="item in RolesList"
    :key="item.id"
    :label="item.remark"
    :value="item.name"
  />
</template>
关键点解析
定义接口 Role:

通过 interface Role 定义每个角色对象的类型包括 id数字)、name字符串)、remark字符串)。
 RolesList 指定类型

使用 defineProps<{ RolesList: Role[] }>() 明确地将 RolesList 定义为一个包含 Role 对象的数组
自动推断 item 类型

因为 RolesList 的类型是 Role[],v-for 中的 item 会被推断为 Role 类型
现在item.iditem.name item.remark 都有类型提示

格式化数据

ts
interface TreeNode {
  id: number;
  label: string;
  children?: TreeNode[];
  [key: string]: any; // 允许其他字段存在
}

interface TransformedNode {
  id: number;
  value: number;
  label: string;
  children?: TransformedNode[];
}

const transformedList = ref<TransformedNode[]>([]);

// 递归处理树形结构
const transformTree = (nodes: TreeNode[]): TransformedNode[] => {
  return nodes.map((node) => ({
    id: node.id,
    value: node.id, // 设置 value 为 id
    label: node.label,
    children: node.children ? transformTree(node.children) : undefined, // 递归处理子节点
  }));
};

清空校验结果

父组件

ts

<UserForm ref="userform" :RolesList="state.RolesList" :OrganTreeList="state.OrganTreeList"
    v-model:scene="scene">
</UserForm>

const userform = ref();

// 创建用户
const handleCreateUser = () => {
    // 切换场景
    scene.value = 1
    // 重置表单数据 清空校验结果
    userform.value.resetFiledResult()
}

子组件

ts
<el-form ref="formRef"

// form对象
const formRef = ref<any>()

// 清空校验结果
const resetFiledResult = () =>{
    // 重置为初始值,并移除校验结果
    formRef.value.resetFields()
}

// 暴漏数据
defineExpose({UserParams,resetFiledResult})

动态按钮状态

ts
定义表单验证规则: 确保 username、passwd、organs、roles 都通过验证。
动态计算按钮是否禁用: 通过计算属性动态判断是否满足条件。
绑定按钮的 disabled 属性: 根据计算属性的结果启用或禁用按钮。
vue
<template>
  <div>
    <el-form :model="UserParams" :rules="rules" ref="userForm" @submit.native.prevent>
      <el-form-item label="用户名" prop="username">
        <el-input v-model="UserParams.username" placeholder="请输入用户名" />
      </el-form-item>
      <el-form-item label="密码" prop="passwd">
        <el-input v-model="UserParams.passwd" type="password" placeholder="请输入密码" />
      </el-form-item>
      <el-form-item label="组织机构" prop="organs">
        <el-select v-model="UserParams.organs" placeholder="请选择机构" multiple>
          <el-option label="机构A" value="A"></el-option>
          <el-option label="机构B" value="B"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="角色" prop="roles">
        <el-input v-model="UserParams.roles" placeholder="请输入角色" />
      </el-form-item>

      <el-button :disabled="isSubmitDisabled" type="primary" @click="handleSubmit">
        确定
      </el-button>
    </el-form>
  </div>
</template>

<script>
import { ref, computed } from 'vue';

export default {
  setup() {
    // 表单数据
    const UserParams = ref({
      id: '',
      username: '',
      passwd: null,
      orgId: '',
      organs: [],
      roles: '',
      mobile: '',
      nickName: '',
      email: '',
      avatar: '',
    });

    // 表单验证规则
    const rules = {
      username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
      passwd: [{ required: true, message: '请输入密码', trigger: 'blur' }],
      organs: [{ type: 'array', required: true, message: '请选择组织机构', trigger: 'change' }],
      roles: [{ required: true, message: '请输入角色', trigger: 'blur' }],
    };

    const userForm = ref(null);

    // 动态计算按钮是否禁用
    const isSubmitDisabled = computed(() => {
      // 检查必须的字段是否有值
      const { username, passwd, organs, roles } = UserParams.value;
      const isFieldsFilled = username && passwd && organs.length > 0 && roles;

      return !isFieldsFilled; // 禁用按钮条件:字段未填写
    });

    // 提交处理逻辑
    const handleSubmit = () => {
      userForm.value.validate((valid) => {
        if (valid) {
          console.log('表单验证通过,提交数据:', UserParams.value);
        } else {
          console.log('表单验证失败');
        }
      });
    };

    return {
      UserParams,
      rules,
      userForm,
      isSubmitDisabled,
      handleSubmit,
    };
  },
};
</script>

密码加解密

ts
import * as CryptoJS from 'crypto-js';

/**
 * AES 解密函数
 * @param encryptedWord 加密的 Base64 编码密文
 * @returns 解密后的明文字符串
 */
export function Decrypt(encryptedWord: string): string {
    const key = CryptoJS.enc.Utf8.parse('123123'); // 与加密时相同的密钥字符串
    const iv = CryptoJS.enc.Utf8.parse('123132'); // 与加密时相同的初始向量字符串

    // 使用 AES 解密
    const decrypted = CryptoJS.AES.decrypt(encryptedWord, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding,
    });

    // 解密后的数据是一个 WordArray,需要转换为 UTF-8 字符串
    return decrypted.toString(CryptoJS.enc.Utf8);
}

解密方法说明

ts
密钥与初始向量:

解密方法中的密钥 (key) 和初始向量 (iv) 必须与加密方法保持一致。
CryptoJS.AES.decrypt:
接受加密的字符串和密钥,用于解密数据。
返回值是一个 WordArray。
toString(CryptoJS.enc.Utf8):
将解密后的 WordArray 转换为明文字符串。

示例

ts
假设加密后的密文是 encryptedString:
const encryptedString = Encrypt('hello world'); // 加密
console.log('加密后的密文:', encryptedString);

const decryptedString = Decrypt(encryptedString); // 解密
console.log('解密后的明文:', decryptedString);

加密后的密文: [加密后的 Base64 字符串]
解密后的明文: hello world

数组的最后一个

ts
方法 2: 使用 slice 方法

const lastItem = arr.slice(-1)[0];
优点: 如果数组为空,会返回 undefined,更加安全。

示例:
const arr = [1, 2, 3, 4];
const lastItem = arr.slice(-1)[0];
console.log(lastItem); // 输出: 4

使用闭包传递参数

在 Vue 3 中,如果 :before-change 回调函数不能直接传递参数,可以通过以下方法来控制指定的开关,而不是所有开关:

方法 1: 使用闭包传递参数
为每个开关的 before-change 动态绑定一个闭包函数,在闭包中传递指定的参数。
ts
<template>
  <el-switch
    v-for="item in switches"
    :key="item.id"
    v-model="item.value"
    :before-change="switchBeforeChangeWrapper(item.id)"
  />
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const switches = ref([
      { id: 1, value: false },
      { id: 2, value: true },
      { id: 3, value: false },
    ]);

    // 闭包函数包装
    const switchBeforeChangeWrapper = (id) => {
      return (newValue) => switchBeforeChange(id, newValue);
    };

    // 具体的 before-change 回调函数
    const switchBeforeChange = (id, newValue) => {
      console.log(`Switch with ID ${id} is changing to ${newValue}`);
      // 控制逻辑
      return new Promise((resolve) => {
        setTimeout(() => {
          // 模拟异步检查,比如确认弹窗
          resolve(newValue); // 同意更改
        }, 1000);
      });
    };

    return {
      switches,
      switchBeforeChangeWrapper,
    };
  },
};
</script>

实际代码

vue
<el-table-column prop="inotice" label="短信通知" width="120">
<template #default="scope">
    <el-switch v-model="scope.row.inotice" :loading="scope.row.switchLoading"
        :before-change="switchBeforeChangeWrapper(scope.row.id)" @change="switchChange" />
</template>
</el-table-column>
ts
// 在源数据中添加字段 用于控制加载效果
// 获取管理用户列表
const getAdminUserList = async () => {
    // query参数
    const params = {
        current: currentPage.value,
        size: pageSize.value,
        uname: querySearch.value,
        orgs: organsStr
    }
    // 发送请求
    try {
        await reqAdminUserList(params).then((res) => {
            // 存储数据
            totalCount.value = res.list.total
            // 遍历-并添加加载字段
            const tmpList = res.list.records.map((item: any) => {
                item.switchLoading = false
                return item
            })
            AdminUserList.value = tmpList

        })
    } catch (error) {
        console.error(error)
    }
}

// 闭包函数包装
const switchBeforeChangeWrapper = (id: any) => {
    return () => switchBeforeChange(id);
}

// 开关切换状态前回调
const switchBeforeChange = (id: any) => {
    AdminUserList.value.forEach((element: any) => {
        if (element.id === id) {
            element.switchLoading = true
        }
    });
    return new Promise<boolean>((resolve, reject) => {
        setTimeout(() => {
            AdminUserList.value.forEach((element: any) => {
                if (element.id === id) {
                    element.switchLoading = false
                }
            });
            // 消息提示
            // 提示消息
            ElNotification({
                title: '成功',
                message: `切换成功`,
                type: 'success',
            })
            return resolve(true)
        }, 1000)
    })
}

el-switch转换1和0

ts
配合 :active-value  :inactive-value
可以通过 el-switch 的 active-value 和 inactive-value 来控制绑定的值,将字符串 '1''0' 转换为布尔值 truefalse


<template>
  <el-switch
    v-model="scope.row.inotice"
    :active-value="'1'"
    :inactive-value="'0'"
  />
</template>

剪切板插件

ts
可以通过 clipboard.js 插件实现复制当前选中行对象的账号、密码,并生成自定义格式和自定义网址的功能。

npm install clipboard

安装 clipboard.js 安装 clipboard.js 插件:

npm install clipboard

创建按钮和绑定事件 为每一行数据生成一个“复制”按钮,按钮会触发复制操作。

自定义复制内容 使用 clipboard.js 的 text 参数,自定义需要复制的内容(如账号、密码、格式和网址)。

示例代码

vue
<template>
  <div>
    <el-table :data="tableData" border style="width: 100%">
      <el-table-column prop="account" label="账号" />
      <el-table-column prop="password" label="密码" />
      <el-table-column label="操作">
        <template #default="scope">
          <!-- 复制按钮 -->
          <el-button
            type="primary"
            size="small"
            class="copy-btn"
            :data-account="scope.row.account"
            :data-password="scope.row.password"
            @click="handleCopy(scope.row)"
          >
            复制
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import Clipboard from 'clipboard';

export default {
  data() {
    return {
      // 示例表格数据
      tableData: [
        {
          account: 'user1',
          password: 'password1',
        },
        {
          account: 'user2',
          password: 'password2',
        },
      ],
    };
  },
  mounted() {
    // 初始化 Clipboard
    this.clipboard = new Clipboard('.copy-btn', {
      text: (trigger) => {
        const account = trigger.getAttribute('data-account');
        const password = trigger.getAttribute('data-password');
        // 自定义格式化内容
        return `账号: ${account}\n密码: ${password}\n网址: https://example.com`;
        // 格式化内容2
          return `
        --------------------------------------------
        账号: ${account}
        密码: ${password}
        网址: http://jdgz.xwydl.com/show/temper/
        --------------------------------------------
          `.trim(); 
      },
    });

    // 复制成功提示
    this.clipboard.on('success', (e) => {
      this.$message.success('复制成功!');
      e.clearSelection();
    });

    // 复制失败提示
    this.clipboard.on('error', () => {
      this.$message.error('复制失败,请重试!');
    });
  },
  beforeDestroy() {
    // 销毁 Clipboard 实例
    if (this.clipboard) {
      this.clipboard.destroy();
    }
  },
  methods: {
    // 处理点击复制事件(可以扩展逻辑)
    handleCopy(row) {
      console.log('复制内容:', row);
    },
  },
};
</script>
ts
点击表格中的“复制”按钮时,将复制如下格式的内容到剪贴板:

makefile
复制代码
账号: user1
密码: password1
网址: https://example.com
成功时弹出提示框“复制成功!”。
ts
自定义扩展
动态网址

如果网址需要动态生成,可以在 text 方法中根据行数据生成。

return `账号: ${account}\n密码: ${password}\n网址: https://yourwebsite.com/${account}`;

复制多个字段
可以扩展复制的内容,添加更多字段。

样式优化
使用 el-tooltip 添加鼠标悬停提示,比如显示“点击复制”。

<el-tooltip content="点击复制" placement="top">
  <el-button type="primary" size="small" class="copy-btn" ...>
    复制
  </el-button>
</el-tooltip>

遍历10个div

vue
<template>
  <div style="padding: 10px;" v-for="(_, index) in 10" :key="index">
    <div style="display: flex; align-items: center; justify-content: space-between; height: 30px;">
      <el-skeleton-item
        v-for="itemWidth in skeletonWidths"
        :key="itemWidth + index"
        variant="text"
        :style="{ marginBottom: '16px', width: `${itemWidth}px`, height: '30px', marginLeft: itemWidth === skeletonWidths[0] ? '0' : '50px' }"
      />
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "SkeletonTable",
  setup() {
    // 定义每个 skeleton-item 的宽度
    const skeletonWidths = [150, 150, 150, 150, 150, 80, 180, 150];

    return {
      skeletonWidths,
    };
  },
});
</script>

el-tabel同时绑定两个数组

ts
你可以通过一个计算属性来实现这一逻辑。当 rackTableData 有数据时显示它,没有数据时显示 roomTableData。

以下是具体实现方式:
vue
<script setup>
import { computed } from 'vue';
const props = defineProps(['rackTableData', 'roomTableData']);

// 创建一个计算属性,根据 rackTableData 的有无数据动态返回 tableData
const tableData = computed(() => {
  return props.rackTableData && props.rackTableData.length > 0
    ? props.rackTableData
    : props.roomTableData;
});
</script>

<template>
  <el-table :data="tableData" style="width: 100%;">
    <!-- Table 内容 -->
  </el-table>
</template>

el-statistic字体样式设置

vue
<el-statistic :value="outputValue" :value-style="{ fontSize: '24px', color: '#42b983', fontWeight: 'bold' }">
    <template #title>
        <div style="display: inline-flex; align-items: center">
            Ratio of men to women
            <el-icon style="margin-left: 4px" :size="12">
                <Male />
            </el-icon>
        </div>
    </template>
    <template #suffix >
        <el-icon style="vertical-align: -0.125em;font-size: 20px;">
            <Odometer />
        </el-icon>
    </template>
</el-statistic>

vertical-align

ts
CSS 中,vertical-align 是一个用于设置元素的垂直对齐方式的属性,主要用于 内联元素、内联块级元素、表格单元格的对齐。
ts
语法
vertical-align: value;

常见取值

  1. 基于文字基线对齐
    • baseline(默认值):元素的基线与其父元素的基线对齐。
    • top:元素顶部与其父元素内容区的顶部对齐。
    • bottom:元素底部与其父元素内容区的底部对齐。
    • middle:元素的垂直中心与其父元素的基线加上 1/2 x 行高对齐。
  2. 与父容器对齐
    • sub:将元素对齐到父元素下标的位置。
    • super:将元素对齐到父元素上标的位置。
    • text-top:将元素对齐到父元素文本顶部。
    • text-bottom:将元素对齐到父元素文本底部。
  3. 百分比值
    • 使用百分比值(例如 50%),相对于元素的行高进行偏移。

使用场景

  1. 内联元素对齐 如果一个图标(如 <img>)嵌入到文字中,图标默认可能会和文字基线对齐。可以使用 vertical-align 来调整其位置:

    html
    <span>文本 <img src="icon.png" style="vertical-align: middle;"> 文本</span>
  2. 表格内容对齐 表格中的内容可以通过 vertical-align 设置:

    html
    <table>
      <tr>
        <td style="vertical-align: top;">顶部对齐</td>
        <td style="vertical-align: middle;">中间对齐</td>
        <td style="vertical-align: bottom;">底部对齐</td>
      </tr>
    </table>
  3. 块级元素中无效 如果 vertical-align 用在块级元素(如 <div>),是无效的。通常需要结合 flexboxgrid 布局实现垂直对齐。

示例

html
<div style="line-height: 50px;">
  <span style="vertical-align: top;">顶部对齐</span>
  <span style="vertical-align: middle;">中间对齐</span>
  <span style="vertical-align: bottom;">底部对齐</span>
</div>

注意事项

  • vertical-align 是专门为内联内容设计的,不能直接用于块级元素。
  • 如果需要让整个块级元素垂直居中,可以结合 flexbox 或其他布局技术
css
display: flex;
align-items: center;
justify-content: center;
height: 100px;

el-滚动条

vue
<el-scrollbar class="timeline-scrollbar">
    <el-timeline style="max-width: 600px">
        <el-timeline-item timestamp="2018/4/12" placement="top">
            <el-card>
                <h4>Update Github template</h4>
                <p>Tom committed 2018/4/12 20:46</p>
            </el-card>
        </el-timeline-item>
        <el-timeline-item timestamp="2018/4/3" placement="top">
            <el-card>
                <h4>Update Github template</h4>
                <p>Tom committed 2018/4/3 20:46</p>
            </el-card>
        </el-timeline-item>
        <el-timeline-item timestamp="2018/4/2" placement="top">
            <el-card>
                <h4>Update Github template</h4>
                <p>Tom committed 2018/4/2 20:46</p>
            </el-card>
        </el-timeline-item>
    </el-timeline>
</el-scrollbar>

<style scoped lang="scss">
.timeline-scrollbar {
	height: 300px;
	/* 固定高度 */
	border: 1px solid #ebeef5;
	/* 边框(可选) */
	padding: 10px;
	/* 内边距(可选) */
}
</style>

手动写滚动条

vue
<div class="timeline-container">
    <el-timeline>
      <el-timeline-item
        v-for="item in timelineData"
        :key="item.id"
        :timestamp="item.timestamp"
        placement="top"
      >
        {{ item.content }}
      </el-timeline-item>
    </el-timeline>
  </div>
</template>
css
.timeline-container::-webkit-scrollbar {
  width: 6px; /* 滚动条宽度 */
}

.timeline-container::-webkit-scrollbar-thumb {
  background-color: #c0c4cc; /* 滚动条颜色 */
  border-radius: 3px; /* 圆角 */
}

.timeline-container::-webkit-scrollbar-track {
  background-color: #f5f5f5; /* 滚动条轨道颜色 */
}


.timeline-container {
  height: 300px; /* 固定高度 */
  overflow-y: auto; /* 启用垂直滚动条 */
  border: 1px solid #ebeef5; /* 可选,方便观察容器边界 */
  padding: 10px;
}
</style>

限制文字长度

ts
可以通过 HTMLCSS 实现限制文字长度,并在鼠标悬停时显示完整内容
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>文字长度限制</title>
  <style>
    .text-container {
      max-width: 200px; /* 限制最大宽度 */
      white-space: nowrap; /* 不换行 */
      overflow: hidden; /* 超出部分隐藏 */
      text-overflow: ellipsis; /* 显示省略号 */
      position: relative;
      cursor: pointer;
    }

    /* 悬停时显示完整文字 */
    .text-container:hover::after {
      content: attr(data-full-text); /* 显示完整文字内容 */
      position: absolute;
      top: 100%; /* 放置在文字下方 */
      left: 0;
      background-color: #fff;
      color: #000;
      border: 1px solid #ccc;
      padding: 5px;
      white-space: normal; /* 允许换行 */
      z-index: 10;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      max-width: 300px; /* 限制弹框宽度 */
    }
  </style>
</head>
<body>
  <div class="text-container" data-full-text="河南省郑州市管城回族区郑州市城东航海路口机房-复建21084">
    河南省郑州市管城回族区郑州市城东航海路口机房-复建21084
  </div>
</body>
</html>

文字限制和省略号 使用以下 CSS 属性来限制文字长度并显示省略号:

  • max-width: 限制最大宽度。
  • white-space: nowrap: 禁止换行。
  • overflow: hidden: 隐藏超出部分。
  • text-overflow: ellipsis: 超出部分显示为省略号。

完整文字显示

  • 借助 data-full-text 属性存储完整文字内容。
  • hover::after 伪元素在鼠标悬停时显示完整文字。
  • position: absolute 用于定位完整文字弹框。

自适应弹框 弹框宽度受 max-width 限制,文字过长时可换行显示。

Vue示例

vue
<template>
  <div
    class="text-container"
    :data-full-text="text"
  >
    {{ text }}
  </div>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      required: true,
    },
  },
};
</script>

<style>
.text-container {
  max-width: 200px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  position: relative;
  cursor: pointer;
}

.text-container:hover::after {
  content: attr(data-full-text);
  position: absolute;
  top: 100%;
  left: 0;
  background-color: #fff;
  color: #000;
  border: 1px solid #ccc;
  padding: 5px;
  white-space: normal;
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  max-width: 300px;
}
</style>

使用el-tooltip实现

vue
<template>
  <div class="text-container">
    <el-tooltip
      class="item"
      effect="dark"
      :content="text"
      placement="top"
    >
      <span class="ellipsis-text">{{ text }}</span>
    </el-tooltip>
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: "河南省郑州市管城回族区郑州市城东航海路口机房-复建21084",
    };
  },
};
</script>

<style>
.text-container {
  max-width: 200px; /* 限制最大宽度 */
}

.ellipsis-text {
  display: inline-block;
  max-width: 100%; /* 适应容器宽度 */
  white-space: nowrap; /* 不换行 */
  overflow: hidden; /* 超出部分隐藏 */
  text-overflow: ellipsis; /* 显示省略号 */
  cursor: pointer;
}
</style>
ts
el-tooltip 的作用:

鼠标悬停时弹出完整内容。
content 属性用于设置要显示的完整文字。
placement 设置弹框位置(如 top、bottom 等)。
ellipsis-text 的作用:

使用 CSS 的 text-overflow: ellipsis 实现文字超出时显示省略号。
容器宽度:

max-width 限制文字显示区域的宽度,防止弹框和文字样式不一致。
vue
<template>
  <div>
    <div
      class="text-container"
      v-for="(item, index) in items"
      :key="index"
    >
      <el-tooltip
        class="item"
        effect="dark"
        :content="item"
        placement="top"
      >
        <span class="ellipsis-text">{{ item }}</span>
      </el-tooltip>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        "河南省郑州市管城回族区郑州市城东航海路口机房-复建21084",
        "北京市朝阳区建国路88号院2号楼",
        "上海市浦东新区世纪大道100号",
        "广州市天河区珠江新城华强路66号",
      ],
    };
  },
};
</script>

<style>
.text-container {
  max-width: 200px;
  margin-bottom: 10px; /* 每个项目间距 */
}

.ellipsis-text {
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
</style>
ts
省略号:超出容器宽度时显示省略号。
鼠标悬停:完整内容在 el-tooltip 中显示,样式与 Element Plus 保持一致。
响应式:max-width 和 text-overflow 确保组件能适应动态内容和不同宽度。