一、element-plus之70个组件
附1、标签属性名前的:
(1)无它,它后面是纯字符串,vue3中的ref和key是例外,它们后面是变量
(2)有它,它后面是js表达式!!!
附2、Vue2标签的ref属性
(1)ref="字符串",this.$refs.xxx,可用于访问DOM元素或子组件实例
(2):ref="函数",与element-plus相同
A、当元素或组件挂载到DOM上时,该函数会被调用,并且会将元素或组件的实例作为参数传递给这个函数
B、当元素或组件从DOM中移除时,该函数会再次被调用,此时传递null作为参数
附3、Vue3标签的ref属性
(1)ref="变量",xxx.value,可用于访问DOM元素或子组件实例
(2):ref="函数",
A、当元素或组件挂载到DOM上时,该函数会被调用,并且会将元素或组件的实例作为参数传递给这个函数
B、当元素或组件从DOM中移除时,该函数会再次被调用,此时传递null作为参数
附4、其它
(1)sub是"subordinate"(下级的、从属的)或"submenu"(子菜单)的缩写
/*
组件的四个配置
<div ref="btn" @click="clickButton()">我是一个按钮</div>
(1)属性名:
(2)事件名:@click,用户-点击-时-触发
(3)方法名:click,使-点击事件-触发,this.$refs.btn.click()
(4)插槽名:
*/
/*
1、Basic 基础,共10个组件
(1)Button 按钮
(2)Border 边框
(3)Color 色彩
(4)Container 布局容器。<el-header>、<el-aside>、<el-main>、<el-footer>
(5)Icon 图标
(6)Layout 布局。<el-row><el-col>
(7)Link 链接
(8)Scrollbar 滚动条
(9)Space 间距
(10)Typography 排版
2、配置,共1个组件
(11)Config Provider 全局配置
3、Form 表单,共19个组件
(12)Autocomplete 自动补全输入框
(13)Cascader 级联选择器。options(数据源),props(映射)
(14)Checkbox 多选框
(15)ColorPicker 取色器(颜色选择器)
(16)DatePicker 日期选择器
(17)DateTimePicker 日期时间选择器
(18)Form 表单
(19)Input 输入框
(20)Input Number 数字输入框
(21)Radio 单选框
(22)Rate 评分
(23)Select 选择器
(24)Select V2 虚拟列表选择器。options(数据源),props(映射)
(25)Slider 滑块
(26)Switch 开关
(27)TimePicker 时间选择器
(28)TimeSelect 时间选择
(29)Transfer 穿梭框
(30)Upload 上传
4、Data 数据展示,共21个组件
(31)Avatar 头像
(32)Badge 徽章
(33)Calendar 日历
(34)Card 卡片
(35)Carousel 走马灯
(36)Collapse 折叠面板
(37)Descriptions 描述列表
(38)Empty 空状态
(39)Image 图片
(40)Infinite Scroll 无限滚动
(41)Pagination 分页
(42)Progress 进度条
(43)Result 结果
(44)Skeleton 骨架屏
(45)Table 表格
(46)Virtualized Table 虚拟化表格
(47)Tag 标签
(48)Timeline 时间线
(49)Tree 树形控件。data(数据源),props(映射)
(50)TreeSelect 树形选择。有复选框
(51)Tree V2 虚拟化树形控件,
5、Navigation 导航,共8个组件
(52)Affix 固钉
(53)Backtop 回到顶部
(54)Breadcrumb 面包屑
(55)Dropdown 下拉菜单
(56)Menu 菜单
(57)Page Header 页头
(58)Steps 步骤条
(59)Tabs 标签页
6、Feedback 反馈,共10个组件
(60)Alert 提示
(61)Dialog 对话框
(62)Drawer 抽屉
(63)Loading 加载
(64)Message 消息提示
(65)MessageBox 消息弹框
(66)Notification 通知
(67)Popconfirm 气泡确认框
(68)Popover 弹出框(气泡卡片)
(69)Tooltip 文字提示
7、Others 其他,共1个组件
(70)Divider 分割线
*/
1、Basic 基础组件
(1)Button 按钮
<el-button type="primary">Primary</el-button>
(2)Border 边框
<template>
<table class="demo-border">
<tbody>
<tr>
<td class="text">Thickness</td>
<td class="line">Demo</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
:root {
--green:green;
}
.demo-border .line div {
width: 100%;
height: 0;
border-top: 1px solid var(--green);
}
</style>
(3)Color 色彩
(4)Container 布局容器
<template>
<div class="common-layout">
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>
</el-container>
</div>
</template>
(5)Icon 图标
<el-icon>
<Delete />
</el-icon>
(6)Layout 布局
<el-row>
<el-col :span="12"><div class="grid-content ep-bg-purple" /></el-col>
<el-col :span="12"><div class="grid-content ep-bg-purple-light" /></el-col>
</el-row>
附、Container与Layout联合展示示例
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link href="https://cdn.bootcdn.net/ajax/libs/element-plus/2.8.1/index.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.2"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/element-plus/2.8.1/index.full.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/element-plus-icons-vue/2.3.1/global.iife.js"></script>
</head>
<body>
<div id="app">
<el-container style="height: 100vh;">
<!-- 头部 -->
<el-header style="background-color: #409EFF; color: white; text-align: center; line-height: 60px;">
el-container的子级,头部区域
</el-header>
<el-container>
<!-- 侧边栏 -->
<el-aside width="300px" style="background-color: #545c64; color: white; text-align: center; line-height: 200px;">
el-container的子级,侧边栏区域
</el-aside>
<!-- 主内容区域 -->
<el-main style="background-color: #f0f2f5; padding: 20px;">
<el-row :gutter="20">
<el-col :span="12">
<div style="background-color: white; padding: 20px; border-radius: 4px;">
1/2,左侧内容
</div>
</el-col>
<el-col :span="12">
<el-row :gutter="20">
<el-col :span="12">
<div style="background-color: white; padding: 20px; border-radius: 4px;">
1/2的1/2,右侧的左侧内容
</div>
</el-col>
<el-col :span="12">
<div style="background-color: white; padding: 20px; border-radius: 4px;">
1/2的1/2,右侧的右侧内容
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</el-main>
</el-container>
</el-container>
</div>
</body>
</html>
<script>
var app = Vue.createApp({})
app.use(ElementPlus);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.mount('#app')
</script>
(7)Link 链接
<el-link href="https://element.eleme.io" target="_blank">default</el-link>
(8)Scrollbar 滚动条
通过height属性设置滚动条高度,若不设置则根据父容器高度自适应;当元素宽度大于滚动条宽度时,会显示横向滚动条
<el-scrollbar height="400px">
<p v-for="item in 20" :key="item" class="scrollbar-demo-item">{{ item }}</p>
</el-scrollbar>
(9)Space 间距
<el-space wrap>
<el-card v-for="i in 3" :key="i" class="box-card" style="width: 250px">
<template #header>
<div class="card-header">
<span>Card name</span>
<el-button class="button" text>Operation button</el-button>
</div>
</template>
<div v-for="o in 4" :key="o" class="text item">
{{ 'List item ' + o }}
</div>
</el-card>
</el-space>
(10)Typography 排版
<script lang="ts" setup>
import { isDark } from '~/composables/dark'
</script>
<template>
<div v-if="!isDark" class="demo-term-box">
<img src="/images/typography/term-helvetica.png" alt="" />
<img src="/images/typography/term-arial.png" alt="" />
</div>
<div v-else class="demo-term-box">
<img src="/images/typography/term-helvetica-dark.png" alt="" />
<img src="/images/typography/term-arial-dark.png" alt="" />
</div>
</template>
2、配置组件
(1)i18n配置,汉化(汉字、中文),i18n是Internationalization(国际化)的缩写,因为首尾字母中间有18个字母
<template>
<div>
<el-button mb-2 @click="toggle">Switch Language</el-button><br />
<el-config-provider :locale="locale">
<el-table mb-1 :data="[]" />
<el-pagination :total="100" />
</el-config-provider>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import en from 'element-plus/dist/locale/en.mjs'
const language = ref('zh-cn')
const locale = computed(() => (language.value === 'zh-cn' ? zhCn : en))
const toggle = () => {
language.value = language.value === 'zh-cn' ? 'en' : 'zh-cn'
}
</script>
(2)对按钮进行配置
<template>
<div>
<div m="b-2">
<el-checkbox v-model="config.autoInsertSpace">autoInsertSpace</el-checkbox>
</div>
<el-config-provider :button="config">
<el-button>中文</el-button>
</el-config-provider>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
const config = reactive({
autoInsertSpace: true,
})
</script>
(3)对消息进行配置
<template>
<div>
<el-config-provider :message="config">
<el-button @click="open">OPEN</el-button>
</el-config-provider>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
const config = reactive({
max: 3,
})
const open = () => {
ElMessage('This is a message.')
}
</script>
3、Form 表单组件
(1)Autocomplete 自动补全输入框
<el-autocomplete
v-model="state1"
:fetch-suggestions="querySearch"
clearable
class="inline-input w-50"
placeholder="Please Input"
@select="handleSelect"
/>
(2)Cascader 级联选择器
<el-cascader
v-model="filterForm.deptCode"
:options="centersOptions"
:props="deptCodeProps"
filterable
placeholder="全部"
style="width: 590px;"
collapse-tags
collapse-tags-tooltip
clearable
:show-all-levels="false"
@change="deptCodeChange"
/>
const centersOptions = [ //静态数据源
{ //此对象可重复
id: 'zhejiang',
name: '浙江省',
subList: [
{ //此对象可重复
id: 'hangzhou',
name: '杭州市',
subList: [
{ id: 'xihu', name: '西湖区', disabled: true }, // 禁用该选项
{ id: 'binjiang', name: '滨江区' }
]
},
]
},
],
const deptCodeProps = { //props配置
value: 'id', //值字段
label: 'name', //显示字段
children: 'subList', //子节点字段
disabled: 'disabled', //禁用字段
lazy: false, //是否动态加载
multiple: false, //是否多选
expandTrigger: 'click', //展开子菜单的方式
//以下,取值控制
emitPath: true, //默认为true,返回完整路径,如 ['父级值', '子级值', '末级值']
checkStrictly: false //默认为false,只能选择末级选项
}
(3)Checkbox 多选框
<div>
<el-checkbox v-model="checked1" label="Option 1" size="large" />
<el-checkbox v-model="checked2" label="Option 2" size="large" />
</div>
(4)ColorPicker 取色器(颜色选择器)
<div class="demo-color-block">
<span class="demonstration">With default value</span>
<el-color-picker v-model="color1" />
</div>
(5)DatePicker 日期选择器
<el-date-picker v-model="filterForm.date" type="daterange"
value-format="YYYY-MM-DD" :disabled-date="disabledDate" range-separator="—"
:shortcuts="date_shortcuts" :clearable="false"
start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
//today.setDate(),设置当前日期中的日
//today.getDate(),获取当前日期中的日
//today.getDay(),获取当前日期是一周中的第几天
//日期时间,以下简称“时间”!!!
function getDateRange(baseRange, endType = 'today', isShowTime = false) {
//以下获取:今天日期时间(7段)
const getToday = () => {
const today = new Date();
today.setHours(0, 0, 0, 0); //设置日期时间的后4位
return today;
};
const today = getToday();
//以下声明:结束时间(当天0时)
const endDate = endType === 'yesterday'
? new Date(today.setDate(today.getDate() - 1))
: new Date(today);
//以下声明:开始时间(当天0时)
let startDate;
//以下获取:开始时间(含强制覆盖结束日期)
switch (baseRange) {
//A、以下,周及其附近
//A1、以下生成:开始时间--本周的第1天(周一)
case 'weekStart':
const weekDay = endDate.getDay() || 7;
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - weekDay + 1);
break;
//A2、以下生成:上个整周的开始、结束时间(周一到周日)
case 'prevWeek':
const currentWeekMonday = new Date(today);
currentWeekMonday.setDate(today.getDate() - (today.getDay() || 7) + 1);
const lastSunday = new Date(currentWeekMonday);
lastSunday.setDate(currentWeekMonday.getDate() - 1);
const lastMonday = new Date(lastSunday);
lastMonday.setDate(lastSunday.getDate() - 6);
startDate = lastMonday;
endDate.setTime(lastSunday.getTime()); //强制覆盖结束日期为上周日
break;
//A3、以下生成:开始时间--近7的第1天
case 'last7Days':
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 6);
break;
//B、以下,月及其附近
//B1、以下生成:开始时间--本月的第1天(1号)
case 'monthStart':
startDate = new Date(endDate.getFullYear(), endDate.getMonth(), 1);
break;
//B2、以下生成:上个整月的开始、结束时间(1日到月末)
case 'prevMonth':
const prevMonthFirstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1);
const prevMonthLastDay = new Date(today.getFullYear(), today.getMonth(), 0);
startDate = prevMonthFirstDay;
endDate.setTime(prevMonthLastDay.getTime()); //强制覆盖结束日期为上月末
break;
//B3、以下生成:开始时间--近30天的第1天
case 'last30Days':
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 29);
break;
//C、以下,季及其附近
//C1、以下生成:前3个整月的开始、结束时间(首月1日到末月月末)
case 'prev3FullMonths':
const lastMonthLastDay = new Date(today.getFullYear(), today.getMonth(), 0);
const threeMonthsAgoFirstDay = new Date(lastMonthLastDay.getFullYear(), lastMonthLastDay.getMonth() - 2, 1);
startDate = threeMonthsAgoFirstDay;
endDate.setTime(lastMonthLastDay.getTime()); //强制覆盖结束日期为上月末
break;
//C2、以下生成:开始时间--近90天的第1天
case 'last90Days':
startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - 89);
break;
//以下报错:不支持的基础范围
default:
throw new Error(`不支持的基础日期范围:${baseRange},请参考函数注释传入正确值`);
}
//校验截止时间类型合法性
if (!['today', 'yesterday'].includes(endType)) {
throw new Error(`截止时间类型仅支持 'today' 或 'yesterday',当前传入:${endType}`);
}
//强制重置时间,避免误差
startDate.setHours(0, 0, 0, 0);
endDate.setHours(0, 0, 0, 0);
if (baseRange.indexOf('last')>-1 && !isShowTime) {//需要显示近多少天,且不需要显示时间
startDate.setDate(startDate.getDate() - 1);
}
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
//根据isShowTime处理返回格式
const dateArray = [formatDate(startDate), formatDate(endDate)];
if (isShowTime) {
dateArray[0] = `${dateArray[0]} 00:00:00`;
dateArray[1] = `${dateArray[1]} 23:59:59`;
}
return dateArray;
}
const date_shortcuts = [
// 以下,7天及其附近
{ text: '本周初至昨天', value: getDateRange ('weekStart', 'yesterday') },
{ text: '本周初至今天', value: getDateRange ('weekStart', 'today') },
{ text: '上个整周', value: getDateRange ('prevWeek') }, //固定范围,无需传endType
{ text: '近7天(截止到昨天)', value: getDateRange ('last7Days', 'yesterday') }, //, true
{ text: '近7天(截止到今天)', value: getDateRange ('last7Days', 'today') }, //, true
// 以下,30天及其附近
{ text: '本月初至昨天', value: getDateRange ('monthStart', 'yesterday') },
{ text: '本月初至今天', value: getDateRange ('monthStart', 'today') },
{ text: '上个整月', value: getDateRange ('prevMonth') }, //固定范围,无需传endType
{ text: '近30天(截止到昨天)', value: getDateRange ('last30Days', 'yesterday', true) }, //, true
{ text: '近30天(截止到今天)', value: getDateRange ('last30Days', 'today', true) }, //, true
// 以下,90天及其附近
{ text: '上3个整月', value: getDateRange ('prev3FullMonths', 'yesterday') }, //固定范围,无需传endType
{ text: '近90天(截止到今天)', value: getDateRange ('last90Days', 'today') }, //, true
{ text: '近90天(截止到今天)', value: getDateRange ('last90Days', 'today', true) }, //, true
{ text: '近90天(截止到昨天)', value: getDateRange ('last90Days', 'yesterday') }, //, true
{ text: '近90天(截止到昨天)', value: getDateRange ('last90Days', 'yesterday', true) }, //, true
];
date_shortcuts.forEach(item => {
console.log(item.text+ ':' +JSON.stringify(item.value));
});
(6)DateTimePicker 日期时间选择器
<el-date-picker
v-model="datetimeValue"
type="datetimerange"
range-separator="To"
start-placeholder="Start date"
end-placeholder="End date"
/>
import { ref } from 'vue'
const datetimeValue = ref<[Date, Date]>([
new Date(2000, 10, 10, 10, 10),
new Date(2000, 10, 11, 10, 10),
])
/* 1、format 日期格式
format="YYYY/MM/DD HH:mm:ss" //客户端24小时补0显示;时间字母,双写为补0,单写为不补0;
format="YYYY/MM/DD hh:mm:ss a" //客户端12小时补0显示;H,大写为24小时,小写为12小时(a为am,A为AM)
value-format="YYYY-MM-DD HH:mm:ss" //服务端24小时补0显示;取值"X"为秒,取值"x"为毫秒
value-format="YYYY-MM-DD hh:mm:ss a" //服务端12小时补0显示;
2、type 日期类型(year/month/date/datetime/ week/datetimerange/daterange)
type="date" //日期
type="datetime" //日期时间
type="datetimerange" //日期时间范围
3、range-separator="To" //范围的分隔符
4、default-value 选择器打开时,默认显示的时间
5、default-time 选择日期后,默认显示的时间,未指定时,默认为00:00:00
6、size 输入框尺寸 large/default/small
7、placeholder 非范围选择时的占位内容
8、start-placeholder 范围选择时开始日期的占位内容
9、end-placeholder 范围选择时结束日期的占位内容
*/
(7)Form 表单
A、可验证的属性
a、required:是否必填。{ required: true, message: '该字段不能为空', trigger: 'blur' }
b、max:最大值。{ max: 10, message: '长度不能超过10个字符', trigger: 'blur' }
c、min:最小值。{ min: 6, message: '长度至少为6个字符', trigger: 'blur' }
d、type:数据类型,{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
附、取值范围,'number'、'email'、'array'、'date'、'url'、'enum'、'boolean'
e、pattern:正则(必须加required:true)。{ pattern: /^[A-Za-z]+$/, message: '只能输入字母', trigger: 'blur' }
f、len:指定字段长度(优先级高于max,min)。{ len: 5, message: '长度必须为5个字符', trigger: 'blur' }
g、whitespace:是否允许空白字符。{ whitespace: true, message: '不能只输入空格', trigger: 'blur' }
B、属性配置
a、message:提示内容
b、trigger:触发条件 (change||blur)
c、validator: 自定义验证逻辑,优先级高于message
C、触发验证
a、单项触发:trigger:change||blur。el-select在失去焦点时,不触发blur事件,这是一个已知的“坑”!!!
b、整体触发:submitForm
D、清除与重置验证
//清除验证
const clearValidate = function () {
formRef.value.clearValidate()
formRef.value.clearValidate('date')
};
//重置验证
const resetField = function () {
formRef.value.resetField()
formRef.value.resetField('date')
};
E、单项验证(两种实现)
<el-form-item
label="邮箱"
prop="email"
:rules="[
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
]">
<el-input v-model="dynamicValidateForm.email"></el-input>
</el-form-item>
formRef.value.validateField('username', (valid) => {
if (valid) {
console.log('用户名校验成功');
} else {
console.log('用户名校验失败');
}
});
F、部分与整体验证
<el-form
ref="ruleForm"
:model="ruleForm"
:rules="rules"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="ruleForm.username"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="ruleForm.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age" placeholder="请输入年龄"></el-input>
</el-form-item>
<el-form-item label="兴趣" prop="interests">
<el-select
v-model="ruleForm.interests"
multiple
placeholder="请选择兴趣"
style="width: 100%;"
>
<el-option label="阅读" value="reading"></el-option>
<el-option label="运动" value="sports"></el-option>
<el-option label="音乐" value="music"></el-option>
<el-option label="旅行" value="travel"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] }
],
age: [
{ validator: validateAge, trigger: ['blur', 'change'] }
],
interests: [
{ type: 'array', required: true, message: '请至少选择一个兴趣', trigger: 'change' }
]
}
const formRef = ref(null)
//部分验证
formRef.value.validateField(['username', 'email', 'phone'], (valid) => {
if (valid) {
console.log('所有字段校验成功');
} else {
console.log('部分字段校验失败');
}
});
//全部验证
formRef.value.validate((valid, invalidFields) => {//vue3使用验证
if (valid) {
//表单验证成功,向服务器提交数据
} else {
//表单验证失败,不向服务器提交数据
}
});
G、效果实现
a、表单项只显示标签
<el-form-item label="内容配置" class="blue-label"></el-form-item>
b、表单项不显示标签
<el-form-item label-width="80px">
<template #label>
<span></span>
</template>
<el-button type="primary" size="mini" icon="el-icon-search" @click="handleQuery">查询</el-button>
</el-form-item>
c、一行显示多个表单项
<el-form :model="nowCard" :label-position="'right'" label-width="130px">
<el-row>
<el-col :span="10">
<el-form-item label="页卡名称">
<el-input
v-model="nowCard.card_name"
type="text"
maxlength="200"
placeholder="请输入页卡名称"
style="width:300px;"
:disabled="nowCard.create_user == 'system'"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="页卡序号">
<el-input
v-model="nowCard.order_index"
type="text"
maxlength="200"
:placeholder="'请输入页卡序号('+ addPagecardText +'时不输入)'"
style="width:300px;"
:disabled="nowCard.isAdd"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
(8)Input 输入框
<el-input v-model="filterForm.name" style="width: 500px" placeholder="请输入主题" clearable
@clear="clearTheme" @keyup.enter="(event)=>{enterEvent(event)}" @focus="inputFocus">
<template #append>
<el-button :icon="Search" @click="searchTheme" />
</template>
</el-input>
const searchTheme = function () {
if(isFromFiles.value) {
isFromFiles.value = false
filterForm.value.name = ''
}
page.value.pageNum = 1
listData()
}
const inputFocus = function () {
if(isFromFiles.value) {
// 正常状态下,点击清除按钮,先触发inputFocus,再触发clearTheme
// 用异步处理,先执行clearTheme,阻止发出请求;再执行inputFocus,发出请求;避免发出重复请求!!!
setTimeout(() => {
isFromFiles.value = false
filterForm.value.name = ''
page.value.pageNum = 1
listData()
})
}
}
const clearTheme = function () {
if(isFromFiles.value) return
page.value.pageNum = 1
listData()
}
(9)Input Number 数字输入框
/* 下例,如果filterForm.minTime='',则显示0;如果filterForm.minTime=null或undefined,则显示最小分钟;!!! */
<el-input-number v-model="filterForm.minTime" :min="0" @change="inputMinChange" placeholder="最小分钟" />
(10)Radio 单选框
<el-radio-group v-model="radio1" class="ml-4">
<el-radio label="1" size="large">Option 1</el-radio>
<el-radio label="2" size="large">Option 2</el-radio>
</el-radio-group>
(11)Rate 评分
<div class="demo-rate-block">
<span class="demonstration">Default</span>
<el-rate v-model="value1" />
</div>
(12)Select 选择器
附、el-dropdown和el-select的区别
A、el-dropdown:选择操作,如编辑、删除等
B、el-select:选择值
<el-select v-model="value" class="m-2" placeholder="Select" size="large">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
附、先选中的在前,后选中的在后(先选中在前)
<template>
<el-form :model="filterForm" label-width="90px" label-position="left">
<el-form-item label="中心">
<el-select v-model="filterForm.center" style="width: 220px;" multiple collapse-tags collapse-tags-tooltip clearable placeholder="全部" @change="changeCenter">
<el-option v-for="item in centerOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<div class="search-organize-three" >
<div>
<el-form-item label="传播渠道">
<el-select v-model="filterForm.channel" @change="changeChannel" style="width: 220px;" multiple collapse-tags collapse-tags-tooltip clearable placeholder="请选择" >
<el-option v-for="item in spreadChannel" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</div>
<div @click="clickExpandOrCollapse" class="div-three">
<span v-show="!isExpand">展开<el-icon><ArrowDown/></el-icon></span>
<span v-show="isExpand">收起<el-icon><ArrowUp/></el-icon></span>
</div>
</div>
<div v-show="isExpand && filterForm.channel.length>0" class="search-organize-detail">
<!-- 以下写法,是为了先选中的在前,后选中的在后 -->
<el-row v-for="(item,index) in Array(Math.ceil(filterForm.channel.length/num))" :key="item" >
<el-col :span="6" v-for="(itemIn, indexIn) in Array(num)" :key="itemIn" >
<el-form-item :label="spreadChannel[0].name" v-show="filterForm.channel.indexOf(spreadChannel[0].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[0].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect" multiple :clearable="true">
<el-option v-for="item in TVChannelOptions" :key="item.channelCode" :label="item.channelName" :value="item.channelCode" />
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[1].name" v-show="filterForm.channel.indexOf(spreadChannel[1].id) == num*index+indexIn" >
<el-select
v-model="spreadChannel[1].model"
class="search-organize-width-margin"
filterable
remote
reserve-keyword
placeholder="请输入对内广播名称"
:remote-method="remoteRadioList"
:loading="radioLoading"
>
<el-option
v-for="item in innerRadioOptions"
:key="item.columnId"
:label="item.freqName"
:value="item.columnId"
>
<span style="float: left">{{ item.freqName }}</span>
<span
style="
float: right;
color: var(--el-text-color-secondary);
font-size: 13px;
"
>
{{ item.columnId }}
</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[2].name" v-show="filterForm.channel.indexOf(spreadChannel[2].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[2].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[2].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[3].name" v-show="filterForm.channel.indexOf(spreadChannel[3].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[3].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[3].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<!-- 以下,4个 -->
<el-form-item :label="spreadChannel[4].name" v-show="filterForm.channel.indexOf(spreadChannel[4].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[4].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[4].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[5].name" v-show="filterForm.channel.indexOf(spreadChannel[5].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[5].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[5].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[6].name" v-show="filterForm.channel.indexOf(spreadChannel[6].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[6].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[6].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="spreadChannel[7].name" v-show="filterForm.channel.indexOf(spreadChannel[7].id) == num*index+indexIn" >
<el-select v-model="spreadChannel[7].model" class="search-organize-width-margin" placeholder="请选择" @change="changeSelect">
<el-option v-for="item in spreadChannel[7].childrens" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="search-organize-date">
<el-form-item label="选择日期">
<el-date-picker v-model="filterForm.date" type="daterange"
value-format="YYYY-MM-DD" range-separator="—"
:shortcuts="date_shortcuts"
start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
</div>
<div class="search-organize-button">
<div>
<el-button v-for="(item, index) in operateList" :key="index" @click="clickThreeButton(index)"
:class="operateIndex == index ? 'active-button' : ''">
{{ item.title }}
</el-button>
</div>
</div>
</el-form>
</template>
<script setup>
const num = 2;//每行显示项数
const isExpand = ref(false)
//以下,传播渠道及其子选项的获取
const spreadChannel = ref([])//传播渠道及其子选项
getAllChannel({
m_LIKE_code:"zh_",
level:1
}).then(res => {
var all = res.data;
var firstChildrens = all[0].childrens;
firstChildrens.forEach((itemUp, indexUp) => {
all.forEach((itemDown, indexDown) => {
if ( indexDown > 1 && itemUp.name == itemDown.name) {
var array = ['对外广播','APP','网站','国内社媒','海外社媒','OTT'];
itemUp.childrens = itemDown.childrens
itemUp.model = ''
if (array.indexOf(itemUp.name) != -1) {
itemUp.childrens.unshift({ name: '全部', id: '全部' })
}
}
});
});
spreadChannel.value = firstChildrens
})
</script>
(13)Select V2 虚拟列表选择器。options(数据源),props(映射)!!!
A、示例1,一边输入字符,一边向后台请求下拉选项
<el-form-item label="选择栏目" prop="columnName" >
<el-select-v2
v-model="ruleForm.columnName"
filterable remote clearable
placeholder="请输入栏目名称或栏目代码"
loading-text="正在加载,请耐心等待!"
:options="allOptions"
:loading="loading"
:remote-method="remoteMethod"
@change="select2Change"
/>
</el-form-item>
var obj = {}
const remoteMethod = (query) => {
obj.last = query;//防止出现-先请求后返回-的情况,此处同步改变标记
loading.value = true;
selectLike({
id: query,
}).then(res => {
var data = res.data;
for(var i=0 ;i< data.length; i++){
data[i].label = data[i].columnName ;
data[i].value = data[i].columnName ;
}
if(obj.last === query){//防止出现-先请求后返回-的情况,此处异步比较标记
allOptions.value = [...data];
}
loading.value = false;
})
}
B、示例2,敲击回车,创建标签(回车创建标签)
<template>
<div>
<el-dialog v-model="visible" title="添加标签" width="600px" :before-close="closeDialog">
<div class="add-label-el-select-v2">
<div style="width:100px">标签名称:</div>
<el-select-v2
v-model="newLabel"
filterable remote multiple
placeholder="请输入"
:options="allOptions"
:remote-method="remoteMethod"
:loading="loading"
:reserve-keyword="false"
allow-create
loading-text="正在加载,请耐心等待!"
@focus="handleFocus" >
<!-- @keyup.enter="enterEvent" -->
</el-select-v2>
</div>
<div class="add-label-el-button">
<el-button @click="cancel" >取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { getLabelInfo } from '@/api/sys.js'
const emit = defineEmits(['close', 'confirm'])
const props = defineProps({
show:{
type: Boolean,
default: false,
},
})
const allOptions = ref([])
const visible = ref(false)
const loading = ref(false)
const newLabel = ref([]);
const key = ref('');
function handleFocus() {
allOptions.value = []
}
const enterEvent = function (event) {
if (event.keyCode == 13) {
if (key.value) {
if (newLabel.value.indexOf(key.value) == -1) {
newLabel.value.push(key.value)
}
}
}
}
const remoteMethod = (query) => {
key.value = query
if (query) {
loading.value = true
let params = {
name: query
}
getLabelInfo(params).then((res) => {
allOptions.value = []
res.data.forEach((item) => {
item.value = item.labelName
item.label = item.labelName
if (item.label != query) {//防止出现2个相同的下拉项,比如“大海”
allOptions.value.push(item)
}
})
loading.value = false
}).catch(() => {
loading.value = false
})
}
}
const closeDialog = function () {
emit('close')
}
const cancel = function () {
emit('close')
}
const confirm = function () {
emit('confirm',newLabel.value)
}
onMounted( function() {
visible.value = props.show
});
onUpdated( function() {
visible.value = props.show
});
</script>
<style>
.add-label-el-select-v2 {
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 20px;
}
.add-label-el-button {
display: flex;
justify-content: flex-end;
}
</style>
C、vue-virtual-scroll-list的说明
a、是一个用于Vue.js的虚拟滚动组件,它可以用来处理包含大量列表项的长列表
b、该组件通过仅渲染当前视口内的列表项来提高性能,从而减少渲染的计算和内存使用
D、vue-virtual-scroll-list的原理
a、将列表分割成多个固定高度的区块
b、只渲染当前视口内的区块
c、当用户滚动时,通过重用区块来优化性能
E、vue-virtual-scroll-list的伪代码
<template>
<div
class="virtual-list"
@scroll="onScroll"
:style="{ height: height + 'px', overflow: 'auto' }"
>
<div
v-for="(item, index) in visibleItems"
:key="start + index"
:style="{ height: itemHeight + 'px' }"
>
<!-- 插入你的列表项内容 -->
{{ items[start + index] }}
</div>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
required: true
}
},
data() {
return {
start: 0,
end: 0,
height: 0
};
},
computed: {
visibleItems() {
return this.items.slice(this.start, this.end + 1);
}
},
methods: {
onScroll() {
const scrollTop = this.$el.scrollTop;
this.start = Math.floor(scrollTop / this.itemHeight);
this.end = this.start + Math.ceil(this.$el.clientHeight / this.itemHeight) + 1;
}
},
mounted() {
this.height = this.itemHeight * this.items.length;
}
};
</script>
<style>
.virtual-list {
overflow-y: scroll;
position: relative;
}
</style>
(14)Slider 滑块
<div class="slider-demo-block">
<span class="demonstration">Default value</span>
<el-slider v-model="value1" />
</div>
(15)Switch 开关
<el-switch
v-model="value2"
class="ml-2"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
active-value="100" inactive-value="0"
/>
A、后端:
a、提供给前端的数据类型为数字,
b、要求前端交互返回的数据类型也为数字
B、active-value的数据类型
<el-form-item label="账号显示状态">
<el-select v-model="dataInfo.searchForm.displayStatus" @change="getList(1)" >
<el-option :label="item.label" :value="item.value" v-for="item in showOptions" :key="item.value" />
</el-select>
</el-form-item>
<el-table-column prop="state" label="账号显示状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.displayStatus" @change="updateStatus(scope.row)"
active-value=0 inactive-value=1 //值为字符串0、1
active-value="0" inactive-value="1" //值为字符串0、1
active-value="'0'" inactive-value="'1'" //值为字符串'0'、'1'。!!!
:active-value=0 :inactive-value=1 //值为数字0、1
:active-value="0" :inactive-value="1" //值为数字0、1
:active-value="'0'" :inactive-value="'1'" //值为字符串0、1
/>
</template>
</el-table-column>
C、下拉选项的value为数字
const showOptions = [
{
label: '全部',
value: '全部',
},
{
label: '开启',
value: 0,
},
{
label: '关闭',
value: 1,
}
]
D、点击后手动恢复为原状态,确定后手动更改为新状态,成功后刷新列表
const updateStatus = (row) => {
row.displayStatus = [1, 0][row.displayStatus];//点击后手动恢复为原状态
var closeStr = '确认关闭账号显示吗?关闭后综合查询模块无法查看该账号数据。';
var openStr = '确定开启账号显示吗?开启后综合查询模块可查看该账号数据。';
var text = [closeStr,openStr][row.displayStatus];//当前为0时是开启,询问是否关闭
ElMessageBox.confirm(
text,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
customClass: 'delete'
}
).then(() => {
dataInfo.loading = true
return thirdPartyUpdate({ id: row.id, displayStatus: [1, 0][row.displayStatus] })//确定后手动更改为新状态
}).then(res => {
dataInfo.loading = false
if (res.code === 0) {
getList();//刷新列表
ElMessage.success("状态修改成功");
} else {
ElMessage.error("状态修改失败");
}
}).catch(() => {
dataInfo.loading = false
})
}
(16)TimePicker 时间选择器
<el-time-picker
v-model="value2"
arrow-control
placeholder="Arbitrary time"
/>
(17)TimeSelect 时间选择
<el-time-select
v-model="value"
start="08:30"
step="00:15"
end="18:30"
placeholder="Select time"
/>
(18)Transfer 穿梭框
<el-transfer v-model="value" :data="data" />
(19)Upload 上传
A、示例1
附、说明!!!
a、如果:on-change="uploadChange"里有手动上传;同时缺少:auto-upload="false",
b、那么会引发二次(两次、再次)上传
<el-upload
:on-change="uploadChange"
:show-file-list="false"
:auto-upload="false"
accept=".jpg,.jpeg,.png"
>
<el-button icon="plus" type="primary">上传证照</el-button>
<span style="color:gray;padding-left: 6px">请上传jpg、jpeg、png格式图片</span>
</el-upload>
const uploadChange = (file) => {
const regex = /\.(jpg|jpeg|png)$/i;
if (regex.test(file.name)) {
isShowRedText.value = false;
}else{
return isShowRedText.value = true;
}
if(!beforeUpload(file)) return
isUploading.value = true;
uploadFileTool(file,'license').then(res => {
uploadForm.imageUrl = res.data.fullUrl;
uploadName.value = file.name;
uploadFormRef.value.validateField('imageUrl');
isUploading.value = false;
})
}
B、示例2
//利用插槽定义上传按钮和说明文字
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
multiple
:limit="3"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:before-remove="beforeRemove"
:http-request="httpRequest"
>
<el-button type="primary">Click to upload</el-button>
<template #tip>
<div class="el-upload__tip">
jpg/png files with a size less than 500KB.
</div>
</template>
</el-upload>
/*
1、on-preview:点击文件列表中已上传的文件时的钩子
2、on-remove:文件列表移除文件时的钩子
3、on-success:文件上传成功时的钩子
4、on-error:文件上传失败时的钩子
5、on-progress:文件上传时的钩子
6、on-change:文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
7、on-exceed:当超出限制时,执行的钩子函数
8、before-upload:上传文件之前的钩子,参数为上传的文件,若返回false或者返回Promise且被reject,则停止上传
9、before-remove:删除文件之前的钩子,参数为上传的文件和文件列表,若返回false或者返回Promise且被reject,则停止删除
10、http-request:覆盖默认的Xhr行为,允许自行实现上传文件的请求,如切片。
*/
data() {
return {
fileList: [],
chunkSize: 1024 * 1024 * 6, // 6M 为一个切片,超过6M,则采用上传大文件的方法进行文件上传
percentage: {}, // 进度条
}
},
httpRequest(params) { // 来源:https://blog.csdn.net/qq_33547169/article/details/127807333
let {file: {size}} = params;
size > this.chunkSize ? this.uploadBigFile(params, this.chunkSize) : this.uploadFile(params)
},
uploadBigFile(params, chunkSize) {
let {file, filename, onSuccess} = params;
let {size, type, name} = file;
const chunkLength = Math.ceil(file.size / chunkSize)
let chunks = Array.from({length: chunkLength}).map((v, i) => file.slice(i * chunkSize, i * chunkSize + chunkSize))
let loadeds = [];
let chunkRequests = chunks.map((chunk, i) => {
const formData = new FormData();
formData.append(filename, chunk);
loadeds[i] = [];
const config = {
'Content-type': 'multipart/form-data',
onUploadProgress: progress => {
loadeds[i].push(progress.loaded)
this.calculationPercentage(file, i, loadeds, size);
}
}
return this.$post(this.$servers.openServer, 'tb/minio/upload', formData, config);
})
this.$axios.all(chunkRequests).then(res => {
let fileNames = res.map(({data: {minioPath}}) => minioPath)
const params = {fileName: name, contentType: type, fileSize: size, fileNames}
this.$post(this.$servers.openServer, 'tb/minio/merge', params).then(onSuccess)
})
},
uploadFile(params) {
let {file, filename, onSuccess} = params;
const formData = new FormData();
formData.append(filename, file);
const config = {
'Content-type': 'multipart/form-data',
onUploadProgress: progress => this.percentage = Math.floor(progress.loaded / progress.total * 100)
}
this.$post(this.$servers.openServer, 'tb/minio/upload', formData, config).then(onSuccess)
},
4、Data 数据展示
(1)Avatar 头像
<div v-for="size in sizeList" :key="size" class="block">
<el-avatar :size="size" :src="circleUrl" />
</div>
(2)Badge 徽章
<el-badge :value="12" class="item">
<el-button>comments</el-button>
</el-badge>
(3)Calendar 日历
<el-calendar v-model="value" />
(4)Card 卡片
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>Card name</span>
<el-button class="button" text>Operation button</el-button>
</div>
</template>
<div v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</div>
</el-card>
(5)Carousel 走马灯
<el-carousel height="150px">
<el-carousel-item v-for="item in 4" :key="item">
<h3 class="small justify-center" text="2xl">{{ item }}</h3>
</el-carousel-item>
</el-carousel>
(6)Collapse 折叠面板
<el-collapse v-model="activeNames" @change="handleChange">
<el-collapse-item title="Consistency" name="1">
<div>
Consistent with real life: in line with the process and logic of real
life, and comply with languages and habits that the users are used to;
</div>
<div>
Consistent within interface: all elements should be consistent, such
as: design style, icons and texts, position of elements, etc.
</div>
</el-collapse-item>
</el-collapse>
(7)Descriptions 描述列表
<el-descriptions title="User Info">
<el-descriptions-item label="Username">kooriookami</el-descriptions-item>
</el-descriptions>
(8)Empty 空状态
<el-empty description="description" />
<el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"/>
<el-empty v-if="showEmpty" class="content-table" style="min-height:500px" :image="emptyLogo" :image-size="220" description="选择查询条件!"/>
<div v-else class="content-table"></div>
(9)Image 图片
<div v-for="fit in fits" :key="fit" class="block">
<span class="demonstration">{{ fit }}</span>
<el-image style="width: 100px; height: 100px" :src="url" :fit="fit" />
</div>
(10)Infinite Scroll 无限滚动(无限滚动加载)
<ul
v-infinite-scroll="load"
class="list"
:infinite-scroll-disabled="disabled"
>
<li v-for="i in count" :key="i" class="list-item">{{ i }}</li>
</ul>
import { computed, ref } from 'vue'
const count = ref(10)
const loading = ref(false)
const noMore = computed(() => count.value >= 20)
const disabled = computed(() => loading.value || noMore.value) //为false,加载
//(loading.value || noMore.value)与(!loading.value && !noMore.value)等效,都为false,执行
const load = () => {
loading.value = true
setTimeout(() => {
count.value += 2
loading.value = false //为false
}, 2000)
}
(11)Pagination 分页
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
const currentPage = ref(4);
const pageSize = ref(100);
const handleSizeChange = (val) => {}
const handleCurrentChange = (val) => {}
(12)Progress 进度条
<el-progress :percentage="100" status="success" />
(13)Result 结果
不同颜色圆形背景下的,对号、错号、感叹号图标
<el-result
icon="success"
title="Success Tip"
sub-title="Please follow the instructions"
>
<template #extra>
<el-button type="primary">Back</el-button>
</template>
</el-result>
(14)Skeleton 骨架屏
<el-skeleton /> <br />
<el-skeleton style="--el-skeleton-circle-size: 100px">
<template #template>
<el-skeleton-item variant="circle" />
</template>
</el-skeleton>
(15)Table 表格
A、样式!!!
a、子表格自适应高
.content-table .el-table .el-table {
min-height: auto;
}
b、留出padding、允许换行
.el-table .cell{
padding: 4px 8px;
white-space: normal;
}
c、留出padding、不允许换行
.content-table .el-table tr td:first-child div.cell {
display: flex;
align-items: center;
justify-content: center;
}
d、禁止勾选
<el-table-column
type="selection"
width="55"
align="center"
:selectable="selectionDisabled"
:selectable="(row) => { return row.status === 2&&row.summary===2}"
/>
var selectionDisabled = function(row, index){
return row.status === 2 && row.summary === 2; // 返回false,禁止勾选
}
e、表体(不含表头)超高出现滚动条,行高超过4行出现省略号,悬停出现完整
<el-table :data="tableData" height="400">
<el-table-column align="center" prop="description" label="描述信息" show-overflow-tooltip/>
</el-table>
.search-topical-my-theme-table{
.cell {
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
}
}
f、无子级时,展开图标不显示
<template>
<el-table :data="allTableData" :cell-class-name="addClassToCell">
</template>
<script setup>
const addClassToCell = ({ row, column }) => {
if (!row.columnVOList && column.type === 'expand') {
return 'no-expand';
}
}
</script>
<style>
.no-expand.el-table_1_column_1.el-table__expand-column.el-table__cell>div {
display: none;
}
</style>
g、父表格的第1个小格的右侧边框不显示
.content-table .el-table {
td{
border-right: 1px solid #E5E7EE;
}
td:nth-child(1) {
border-right: none;
}
}
.content-table .el-table .el-table {
td:nth-child(1) {
border-right: 1px solid #E5E7EE;
}
}
B、用visibility代替v-show,以免el-popconfirm飞走
<el-table-column prop="title" label="名称">
<template #default="scope">
<div style="display: flex;" v-show="!scope.row.isShowEdit">
<div class="document-title" @click="cellClick(scope.row)">{{ scope.row.title }}</div>
<div :style="{visibility:scope.row.isShowOperator}">
<el-tooltip content="重命名" placement="top" effect="light">
<svg-icon icon-class="rename" @click="renameClick(scope.row)" class="svg-icon"></svg-icon>
</el-tooltip>
<span style="padding: 0 4px"></span>
<el-popconfirm
confirm-button-text="确定"
cancel-button-text="取消"
cancel-button-type="default"
width="180px"
:enterable="true"
placement="top"
title="你确定要删除吗?"
effect="light"
@confirm="deleteItem(scope.row)"
>
<template #reference>
<span>
<el-tooltip content="删除" placement="top" effect="light">
<svg-icon icon-class="del" class="svg-icon"></svg-icon>
</el-tooltip>
</span>
</template>
</el-popconfirm>
</div>
</div>
<div style="display: flex;" v-show="scope.row.isShowEdit">
<el-input
:ref="getRef"
v-model="scope.row.title"
placeholder="请输入"
@blur="renameBlur(scope.row)"
/>
</div>
</template>
</el-table-column>
C、点击表格每行尾栏的按钮,出现气泡,气泡里面有无序列表ul
<el-table-column label="操作" align="center">
<template #default="{ row }">
<el-popover
placement="right"
title=""
width="180"
trigger="click"
:visible="row.showPopover"
>
<div>
<!-- <div class="popover-title">此处写标题,方便添加标题样式</div> -->
<ul class="popover-list">
<li v-for="(item, idx) in centerOrg" :key="idx" class="list-item"
:style="{ color: activeIndex === idx ? '#2162DB' : '' }"
@click="clickPopoverItem(item, idx)"
>{{ item.name }}</li>
</ul>
<div class="popover-button">
<el-button size="small" @click="cancelPopover(row)">取消</el-button>
<el-button size="small" @click="confirmPopover(row)" type="primary">确认</el-button>
</div>
</div>
<template #reference>
<el-button link type="primary" @click="openPopover(row)">分配</el-button>
</template>
</el-popover>
<el-button link @click="viewHandle(row)" type="primary">查看</el-button>
</template>
</el-table-column>
const activeIndex = ref() //popover选中项索引
const activeKey = ref() //popover选中项数据
function clickPopoverItem(item, idx) {
activeIndex.value = idx;
activeKey.value = item.name;
}
const cancelPopover = (row) => {
row.showPopover = false;
};
const confirmPopover = (row) => {
row.showPopover = false;
};
const openPopover = (row) => {
activeIndex.value = null;
activeKey.value = null;
tableRef.value.data.forEach((item) => {
if( item == row){
item.showPopover = !item.showPopover;
}else{
item.showPopover = false;
}
})
};
const viewHandle = (obj)=>{
router.push({
path:'/project/column-manage/account-detail',
query:{
id: obj.columnCode,
tabIndex: currIndex.value,
}
});
}
D、element-plus的el-table过滤
a、默认过滤(一个向下的空心箭头)
1.在列中设置filters和filter-method属性即可开启该列的筛选
2.filters是一个数组
3.filter-method是一个方法,它用于决定某些数据是否显示,会传入三个参数:value,row和column
b、自定义过滤--给多个表头栏“共同”添加一个el-popover,
1.el-popover可以通过:virtual-ref出现在不同的被点击元素旁,详细用法见:virtual-ref="refName"!!!
E、element-plus的el-table排序,优先级从高到低
a、后端排序(最高优先级)
1.当列设置sortable="custom"时,点击该列触发整体的sort-change事件,参数为sortObj={prop:'字段名', order:'排序方向'}
2.此时页面不会进行任何排序操作,开发者在事件回调中告知后端排序字段和排序升降,后端返回数据,前端更新数据
b、列级自定义排序方法(sort-method)
1.当列设置sortable=true时,点击该列触发该列的sort-method方法,参数为(currentRow, nextRow)
2.返回值<0,currentRow排在nextRow前面;返回值>0,nextRow排在currentRow前面;返回值=0,currentRow和nextRow位置不变!!!
c、列级排序字段指定(sort-by)
1.当列设置sortable=true时,点击该列触发该列的sort-by值,该值可以是字符串或数组
2.如果是字符串,可以是嵌套字段或计算属性
d、默认排序规则(最低优先级)
1.当列设置sortable=true时,该列没有sort-method方法、sort-by值,点击该列,触发prop字段进行默认排序
2.字符串按字典序;数字按数值大小;日期是特殊的字符串,按字典序,其格式必须为YYYY-MM-DD,实证案例如下!!!
3.<el-table :data="B"><el-table-column prop="A" label="日期" sortable></el-table-column></el-table>
const B=ref([{A:'2023-01-15'},{A:'2023-02-10'},{A:'2023-1-5'},{A:'2023/01/05'},{A:'January 16,2023'}]);
</el-table>
F、element-plus的el-table勾选
a、勾选事件
1.@select,当用户手动勾选单行Checkbox时触发的事件,selection(当前表格所有选中的行), row(当前行)
2.@select-all,当用户手动勾选全选Checkbox时触发的事件,selection(当前表格所有选中的行)
3.@selection-change:当用户手动勾选单行或全选Checkbox时触发的事件,selection(当前表格所有选中的行)
b、勾选方法
1.toggleRowSelection,代码切换单选和单不选,tableRef.value.toggleRowSelection(tableRef.value[1], true);
2.toggleAllSelection,代码切换当前页全选和全不选,tableRef.value.toggleAllSelection(true);
3.getSelectionRows,代码获取当前页选中的行,tableRef.value.getSelectionRows();
4.clearSelection,代码清空当前页选中的行,tableRef.value.clearSelection();
c、其它事件和方法
1.@expand-change:当用户手动展开或者关闭时触发的事件,row(当前行), expandedRows(当前表格所有展开的行)
2.clearSort,代码清空当前页生效的排序,tableRef.value.clearSort()
d、简单跨页勾选的实现方案(没有子表格、没有父子表格所有页全选)
<el-table
//以下使用默认的全(不)选与半选
:data="allTableData"
row-key="columnCode" //指定行数据的唯一标识
@selection-change="tableSelectionChange" //1次勾选(无论单个还是批量),1次触发!!!
>
<el-table-column
type="selection"
reserve-selection //配合row-key使用,实现跨页/数据刷新时保留选择状态
/>
</el-table>
const multipleSelection = ref([])
const isShowModifyPop = ref(false)
const isFirstPlay = ref(null)
const tableSelectionChange = (allSelectedItems) => { //由于设置了reserve-selection,参数包含“跨页”选中的数据
multipleSelection.value = allSelectedItems.map(item => item.columnCode)
console.log('当前选中的数据:', multipleSelection.value)
}
e、复杂跨页勾选的实现方案(有子表格、有父子表格所有页全选)
1.indeterminate和v-model的取值说明!!!
1.1,为true时,复选框显示半选状态,忽略v-model值
1.2,为false时,复选框状态由v-model控制。v-model为true时,显示全选状态;v-model为false时,显示未选状态
2.需求,6种勾选
2.1,包含所有页全选、单页全选、单项勾选
2.2,包含单项下所有子页全选、单子页全选、单子项勾选
3.方案示例
<el-checkbox
:indeterminate="isHalf"
v-model="isAll"
@change="allPageSelect"
/>
<el-table></el-table>
import { reactive, computed } from 'vue';
const total = ref(0);
const obj = reactive({
hasExclude: false,
selectIds: [
{ //上面hasExclude为false,selectIds为选中项组成的数组
id1: 'id1',
hasExclude: false,
selectIds: ['id1-1','id1-2'],
},{
id1: 'id2',
hasExclude: true,
excludeIds: ['id2-1','id2-2'],
}
],
excludeIds: [],
});
const obj = reactive({
hasExclude: true,
selectIds: [],
excludeIds: [
{ //上面hasExclude为true,excludeIds为排除项组成的数组
id1: 'id1',
hasExclude: false,
selectIds: ['id1-1','id1-2'],
},{
id1: 'id2',
hasExclude: true,
excludeIds: ['id2-1','id2-2'],
}
],
});
const isHalf = computed(() => {
return obj.hasExclude
? (obj.excludeIds.length > 0 && obj.excludeIds.length < total.value)
: false;
});
const isAll = computed(() => {
return obj.hasExclude
? obj.excludeIds.length === 0
: obj.selectIds.length > 0 && obj.selectIds.length === total.value;
});
4.方案说明。@selection-change事件,1次勾选(无论单个还是批量),1次触发!!!
4.1,根据后端返回的数据给total赋值
total.value = data.total;
4.2,计算属性
isHalf,给父子表格添加所有页半选
isAll,给父子表格添加所有页全选
4.3,所有页全选-初始化
obj.selectIds = [];
obj.excludeIds = [];
obj.hasExclude = false;
4.4,所有页全选-初始化后
后来在@selection-change事件中,维护obj.selectIds
4.5,所有页全选-勾选
立即在allPageSelect中,执行初始化,并修改obj.hasExclude = true;
后来在@selection-change事件中,维护obj.excludeIds
4.6,所有页全选-取消勾选
立即在allPageSelect中,执行初始化
后来在@selection-change事件中,维护obj.selectIds
5.前端给后端的总参数对象obj
var obj = {
isHalf: false, //为false,表示未勾选所有页全选;为true,表示已勾选所有页全选
ids: [
{ //上面isHalf:为false,ids为选中项组成的数组;为true,ids为排除项组成的数组
isHalf: false,
ids: ['id1','id2'],
},{
isHalf: false,
ids: ['id1','id2'],
}
],
}
G、表头倒计时(表格倒计时)
<template>
<div class="el-col-right-title">
<div class="el-col-right-title-item el-col-right-title-current">我的订单</div>
</div>
<div class="el-col-right-order-select">
<div
class="el-col-right-order-select-item"
:class="{'el-col-right-order-select-item-current':orderStatus == item.state }"
v-for="item in orderTabs" :key="item.state"
@click="clickOrderState(item)"
>{{ item.name }}</div>
</div>
<el-table
v-for="(tablePay, indexRow) in rowPay"
:data="tablePay"
:header-cell-style="headerCellStyle"
:cell-class-name="tableCellClassName"
:span-method="spanMethod"
class="profile-el-table"
:key="indexRow"
>
<el-table-column
v-for="(item, indexCol) in colPay[indexRow]"
:key="indexCol"
:prop="item.prop"
:width="item.width"
:label="item.label"
>
<template #header v-if="indexCol === 4"><!-- 表头,第5栏。若下面为空,则为空 -->
<span v-if="item.orderStatus===1">
<span v-if="new Date().getTime() < item.expireTime">
<span>待支付,剩余时间</span>
<span class="forpay-countdown">{{item.label}}</span>
</span>
<span v-else>
<span>订单已失效</span>
</span>
</span>
<span v-else-if="item.orderStatus===2" style="color: #0058E6;">{{item.label}}</span>
<span v-else>
<span>{{item.label}}</span>
</span>
</template>
<template #default="scope" v-if="indexCol === 1"><!-- 表体,第2栏。若下面为空,则用默认值 -->
<div v-if="scope.row.isColspan" style="text-align: right;"><!-- 表体,第2行。若下面为空,则为空 -->
<span v-if="scope.row.orderStatus===1">
<span v-if="new Date().getTime() < scope.row.expireTime">
<span>
<span>应付</span>
<span style="color:#DD0000">¥{{ scope.row.aaa }}</span>
</span>
<span style="padding-left: 40px;">
<el-button @click="cancelOrder(scope.row)" style="font-size: 12px;color:#000000;font-weight: 600;">取消订单</el-button>
<el-button @click="immediatePay(scope.row)" style="background-color: #0058E6;color:#ffffff;font-size: 12px;">立即支付</el-button>
</span>
</span>
</span>
<span v-else-if="scope.row.orderStatus===2">
<span>共1件产品,总计</span>
<span style="color:#DD0000">¥{{ scope.row.aaa }}</span>
</span>
</div>
</template>
</el-table-column>
</el-table>
<div class="noData" v-show="rowPay.length===0">暂无数据</div>
<div class="profile-el-pagination" v-show="total>0">
<span class="profile-el-pagination-span">共{{total}}条</span>
<el-pagination
layout=" ->, prev, pager, next"
:total="total"
@current-change="clickOrderpagination"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getOrderData, distanceDateTime } from '@/api/profile';
import { orderCancel } from '@/api/pay';
import * as filters from '@/filter';
import useUserStore from '@/store/modules/user';
const userStore = useUserStore();
const userSid = userStore.user && userStore.user.zxktUser && userStore.user.zxktUser.sid;
var total = ref(0)
var orderStatus = ref(0)
var timerArray = reactive([])
var colPay = reactive([]);
var rowPay = reactive([]);
var orderTabs = reactive([
{
name: "全部订单",
state: 0,
},
{
name: "待付款",
state: 1,
},
{
name: "支付成功",
state: 2,
},
]);
var spanMethod = function({ row, column, rowIndex, columnIndex }){
if (rowIndex === 1 && columnIndex === 1) {
return {
rowspan: 1,
colspan: 4
};
}
};
var headerCellStyle = reactive({color:'#b2b8c4',background:'#eaeef5'});
var tableCellClassName = function({ row, column, rowIndex, columnIndex}){
if (rowIndex === 1 && columnIndex === 0) {
return 'grayColor'
}else{
return 'blackColor'
}
};
//以下切换订单Tab
var clickOrderState = function(item){
orderStatus.value = item.state
var page = item.page||1
var allData = {
page: page,
size: 10,
userId: userSid,
};
if(orderStatus.value == 1 ||orderStatus.value == 2){
allData.orderStatus = orderStatus.value
}
var totalIn = total
getOrderData(allData).then(function(result){
totalIn.value = result.data.total;
colPay.length = 0;
rowPay.length = 0;
var list = result.data.list;
for(var i = 0; i<list.length; i++){
var item = list[i];
var singleCol = [//每个表头有5列
{
prop: 'date',
label: '下单时间:' + distanceDateTime(item.orderTime).clientDateTime,
width: '280px'
},
{
prop: 'price',
label: '原价(元)',
},
{
prop: 'payAmount',
label: item.orderStatus===2 ? '实付(元)' : '应付(元)',
},
{
prop: 'favor',
label: '优惠金额',
},
{
prop: 'forPay',
label: ['','','支付成功','已退款','退款失败','支付失败','订单已关闭'][item.orderStatus],
width: '204px',
expireTime: item.expireTime,
orderStatus: item.orderStatus,
},
];
var singleRow = [//每个表体有2行
{
date: item.collectionTitle,
price: filters.fmoney(item.price),
payAmount: filters.fmoney(item.payAmount),
favor: filters.fmoney(item.price - item.payAmount),
forPay: item.forPay,
},
{
date: '订单号:' + item.orderNo,
isColspan: true,
totalNum: item.totalNum,
aaa: filters.fmoney(item.payAmount),//aaa不能换成payAmount
orderNo: item.orderNo,
sid: item.sid,
payMethod: item.payMethod,
expireTime: item.expireTime,
orderStatus: item.orderStatus,
}
];
colPay.push(singleCol)
rowPay.push(singleRow)
}
if(timerArray.length>0){
for(var i = 0; i < timerArray.length; i++){
clearInterval(timerArray[i]);
}
}
var objArray = []
timerArray.length = 0;
for(var i = 0; i < colPay.length; i++){
var obj = colPay[i][colPay[i].length-1];//获取每个表头的最后一项,里面包含-倒计时区域
var total = 0;
if(obj.orderStatus != 1 ) return
if(obj.expireTime < new Date().getTime() ) return
countDown(obj)//立即执行一次倒计时,以免-倒计时区域-出现1秒钟的空白
objArray.push(obj)//存储所有的-倒计时区域
//当有定时器清除时,clickOrderState会再次执行,与定时器相关的数据会重新生成
var interval = setInterval(function(){//for循环结束时,objArray存储了所有的-倒计时区域,又过1秒钟后,多个定时器同时执行
var index = total%objArray.length;//获取本定时器对应的-倒计时区域的序号,即定时器已运行次数与定时器总个数的模,
countDown(objArray[index])//执行倒计时,传入倒计时区域
total++
},1000)
obj.interval = interval
timerArray.push(interval)
}
})
}
//以下是倒计时
var countDown = function (obj) {
/* console.log( '倒计时正在运行,请判断是否必要!!!' ); */
function addZero(number) {
return number.toString()[1] ? number : "0" + number;
};
var nowMilliseconds = new Date().getTime();
var futureMilliseconds = obj.expireTime;
if(nowMilliseconds >= futureMilliseconds){
obj.label = '定时器出错了';
clearInterval(obj.interval);
clickOrderState({state: orderStatus.value})
return
}
var seconds = Math.floor((futureMilliseconds - nowMilliseconds)/1000)
var str = "";
var day = Math.floor(seconds / 86400);
var hour = Math.floor((seconds % 86400) / 3600);
var minute = Math.floor(((seconds % 86400) % 3600) / 60);
var second = Math.floor(((seconds % 86400) % 3600) % 60);
if ( day > 0) {
str += day + "天";
}
if ( day > 0 || hour > 0) {
str += addZero(hour) + "小时";
}
if ( day > 0 || hour > 0 || minute > 0) {
str += addZero(minute) + "分";
}
str += addZero(second) + "秒";
obj.label = str;
}
var clickOrderpagination = function(num){
clickOrderState({
state: orderStatus.value,
page: num,
})
}
var cancelOrder = function(row){
var data ={
orderNo: row.orderNo,
userId: userSid,
};
orderCancel(data).then(function(){
clickOrderState({state: orderStatus.value})
})
}
import { useRouter } from "vue-router"
const router = useRouter()
var immediatePay = function(row){
var payMethod = {
WECHAT: 1,
ALIPAY: 2,
};
router.push({
path: '/pay',
query: {
collectionId: row.collectionId,
channel: payMethod[row.payMethod],
expiretime: row.expireTime,
}
})
}
const emit = defineEmits(['refreshPage'])
onMounted(function() {
clickOrderState({state:0})
emit('refreshPage',router.currentRoute.value.name)
});
onBeforeUnmount(function() {
if(timerArray.length>0){
for(var i = 0; i < timerArray.length; i++){
clearInterval(timerArray[i]);
}
}
});
</script>
<style lang="scss">
.blackColor{
color:#000000;
}
.grayColor{
color:#707889;
}
</style>
(16)Virtualized Table 虚拟化表格
<template>
<el-table-v2
:columns="columns"
:data="data"
:width="700"
:height="400"
fixed
/>
</template>
<script lang="ts" setup>
const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
Array.from({ length }).map((_, columnIndex) => ({
...props,
key: `${prefix}${columnIndex}`,
dataKey: `${prefix}${columnIndex}`,
title: `Column ${columnIndex}`,
width: 150,
}))
const generateData = (
columns: ReturnType<typeof generateColumns>,
length = 200,
prefix = 'row-'
) =>
Array.from({ length }).map((_, rowIndex) => {
return columns.reduce(
(rowData, column, columnIndex) => {
rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
return rowData
},
{
id: `${prefix}${rowIndex}`,
parentId: null,
}
)
})
const columns = generateColumns(10)
const data = generateData(columns, 1000)
</script>
(17)Tag 标签
<el-tag class="ml-2" type="warning">Tag 4</el-tag>
(18)Timeline 时间线
<template>
<el-timeline>
<el-timeline-item
v-for="(activity, index) in activities"
:key="index"
:timestamp="activity.timestamp"
>
{{ activity.content }}
</el-timeline-item>
</el-timeline>
</template>
<script lang="ts" setup>
const activities = [
{
content: 'Event start',
timestamp: '2018-04-15',
},
{
content: 'Approved',
timestamp: '2018-04-13',
},
{
content: 'Success',
timestamp: '2018-04-11',
},
]
</script>
(19)Tree 树形控件
<el-tree
ref="treeRef"
node-key="id"
show-checkbox
:check-strictly="true"
:data="treeData"
:props="defaultProps"
/>
(20)TreeSelect 树形选择。含有下拉菜单的树形选择器,结合了el-tree和el-select两个组件的功能,有复选框
<el-tree-select
v-model="value"
:data="data"
:render-after-expand="false"
show-checkbox
multiple //复选框
/>
(21)Tree V2 虚拟化树形控件
<el-tree-v2 :data="data" :props="props" :height="208" />
5、Navigation 导航
(1)Affix 固钉
<el-affix :offset="120">
<el-button type="primary">Offset top 120px</el-button>
</el-affix>
(2)Backtop 回到顶部
<el-backtop :right="100" :bottom="100" />
(3)Breadcrumb 面包屑
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item>
<el-breadcrumb-item><a href="/">promotion management</a></el-breadcrumb-item>
<el-breadcrumb-item>promotion list</el-breadcrumb-item>
<el-breadcrumb-item>promotion detail</el-breadcrumb-item>
</el-breadcrumb>
</template>
附、面包屑二次封装
A、使用,ContentContainer
<ContentContainer :titles="[route.query.report]" :back="goList"></ContentContainer>
<ContentContainer :titles="[fatherId?'编辑消息':'新建消息']" :back="backTabOne"></ContentContainer>
B、定义,ContentContainer
<script setup>
import Breadcrumb from './breadcrumb.vue'
const props = defineProps({
back: {
type: Function,
required: false,
default: null
},
showBack:{
type:Boolean,
default:true
},
titles:{
type:Array,
default:() => {
return[]
}
}
});
const back = function(){
if(props.back){
props.back()
}else{
history.go(-1)
}
}
</script>
<template>
<div class="content-container">
<Breadcrumb @back="back" :titles="titles" :show-back="showBack"></Breadcrumb>
<slot></slot>
</div>
</template>
C、定义,Breadcrumb
<script setup>
import { ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const breadcrumbData = ref([]);
const props = defineProps({
showBack:{
type:Boolean,
default:true
},
titles:{
type:Array,
default:() => {
return null
}
}
});
const titles= ref(null)
const getBreadcrumbData = () => {
breadcrumbData.value = route.matched.filter(
(item) => item.name
);
if(titles.value){
titles.value.forEach((item,index) => {
if(item){
breadcrumbData.value[breadcrumbData.value.length - (index+1)].name = item
}
})
}
};
watch(
route,
() => {
titles.value =null
getBreadcrumbData();
},
{
immediate: true,
}
);
watch(
props.titles,
() => {
titles.value = props.titles
getBreadcrumbData();
},
{
immediate: true,
}
);
const emit = defineEmits(['back'])
const handleBack = (event) => {
emit('back')
}
</script>
<template>
<div class="breadcrumb-warp">
<el-icon class="breadcrumb-back" @click="handleBack" v-if="showBack">
<Back/>
</el-icon>
<el-breadcrumb separator="|">
<el-breadcrumb-item
v-for="(item, index) in breadcrumbData"
:key="index" :to="{ path: item.path }" >{{ item.name }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
(4)Dropdown 下拉菜单
附、el-dropdown和el-select的区别
A、el-dropdown:选择操作,如编辑、删除等
B、el-select:选择值
<template>
<el-dropdown @command="handleCommand">
<el-button>
操作菜单<i class="el-icon-arrow-down"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="delete" divided>删除</el-dropdown-item>/*divided给该选项的上方添加一条水平线,使菜单结构更清晰*/
<el-dropdown-item command="download" disabled>下载(禁用)</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
const handleCommand = (command) => {//这个参数就是el-dropdown-item标签的command属性值
switch (command) {
case 'edit':
ElMessage.success('点击了编辑');
break;
case 'delete':
ElMessageBox.confirm('确认删除?', '提示')
.then(() => ElMessage.success('删除成功'))
.catch(() => ElMessage.info('已取消'));
break;
default:
console.log(command);
}
};
</script>
(5)Menu 菜单
A、正常用例
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
>
<el-menu-item index="1">Processing Center</el-menu-item>
<el-sub-menu index="2">
<template #title>Workspace</template>
<el-menu-item index="2-1">item one</el-menu-item>
<el-menu-item index="2-2">item two</el-menu-item>
<el-menu-item index="2-3">item three</el-menu-item>
<el-sub-menu index="2-4">
<template #title>item four</template>
<el-menu-item index="2-4-1">item one</el-menu-item>
<el-menu-item index="2-4-2">item two</el-menu-item>
<el-menu-item index="2-4-3">item three</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item-group title="Group One">
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"
:class="{
'active': activeIndex === '4',
'disable': !route.enabled
}"
>Orders</el-menu-item>
</el-menu>
handleSelect(index) {
activeIndex.value = index;
}
B、组件,递归多级菜单,来源:deepseek,请以elementplus的el-menu为基准,封装一个可以递归的多级菜单组件
a、定义组件//RecursiveMenu.vue
<template>
<el-menu
:default-active="activeMenu"
:mode="mode"
:collapse="collapse"
:background-color="backgroundColor"
:text-color="textColor"
:active-text-color="activeTextColor"
@select="handleSelect"
>
<template v-for="item in menuData" :key="item.path">
<el-sub-menu v-if="item.children && item.children.length" :index="item.path">
<template #title>/* 子菜单的标题 */
<el-icon v-if="item.meta?.icon">
<component :is="item.meta.icon" />
</el-icon>
<span>{{ item.meta?.title }}</span>
</template>
<recursive-menu /* 子菜单的子级,此处正常情况下应该是el-sub-menu */
:menu-data="item.children"
:mode="mode"
:collapse="collapse"
:background-color="backgroundColor"
:text-color="textColor"
:active-text-color="activeTextColor"
@select="handleSelect"
/>
</el-sub-menu>
<el-menu-item v-else :index="item.path">
<el-icon v-if="item.meta?.icon">
<component :is="item.meta.icon" />
</el-icon>
<template #title>{{ item.meta?.title }}</template>
</el-menu-item>
</template>
</el-menu>
</template>
b、使用组件
<template>
<div class="menu-container">
<recursive-menu
:menu-data="menuList"
mode="vertical"
:collapse="isCollapse"
active-text-color="#ffd04b"
@select="handleMenuSelect"
/>
</div>
</template>
(6)Page Header 页头
<el-page-header @back="onBack">
<template #breadcrumb>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: './page-header.html' }">
homepage
</el-breadcrumb-item>
<el-breadcrumb-item
><a href="./page-header.html">route 1</a></el-breadcrumb-item
>
<el-breadcrumb-item>route 2</el-breadcrumb-item>
</el-breadcrumb>
</template>
<template #content>
<div class="flex items-center">
<el-avatar
class="mr-3"
:size="32"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
/>
<span class="text-large font-600 mr-3"> Title </span>
<span
class="text-sm mr-2"
style="color: var(--el-text-color-regular)"
>
Sub title
</span>
<el-tag>Default</el-tag>
</div>
</template>
<template #extra>
<div class="flex items-center">
<el-button>Print</el-button>
<el-button type="primary" class="ml-2">Edit</el-button>
</div>
</template>
<el-descriptions :column="3" size="small" class="mt-4">
<el-descriptions-item label="Username">kooriookami</el-descriptions-item>
<el-descriptions-item label="Telephone">18100000000</el-descriptions-item>
<el-descriptions-item label="Place">Suzhou</el-descriptions-item>
<el-descriptions-item label="Remarks">
<el-tag size="small">School</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Address">No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province
</el-descriptions-item>
</el-descriptions>
<p class="mt-4 text-sm">
Element Plus team uses <b>weekly</b> release strategy under normal
circumstance, but critical bug fixes would require hotfix so the actual
release number <b>could be</b> more than 1 per week.
</p>
</el-page-header>
(7)Steps 步骤条
A、原组件
<el-steps :active="active" finish-status="success">
<el-step title="Step 1" />
<el-step title="Step 2" />
<el-step title="Step 3" />
</el-steps>
B、自定义组件实现个性需求
<script setup>
import { computed, ref } from 'vue';
const props = defineProps({
active: {
type: Number,
default: 0,
},
type: {
type: String,
default: '',
}
});
const myActive = computed(() => props.active)
const myType = computed(() => props.type)
const steps = ref([])
const bgstyle = function (index) {
if (index === myActive.value) {
return 'active'
} else if (index < myActive.value) {
return 'finished'
} else {
return ''
}
}
function init(){
if (myType.value === 'edit') {
steps.value = [
{
title: '报表范围',
description: '各中心数据范围',
},
{
title: '编辑基础信息',
description: '基础信息',
},
{
title: '编辑完成',
description: '编辑成功',
}
]
} else {
steps.value = [
{
title: '选择报表范围',
description: '选择各中心数据',
},
{
title: '基础信息设置',
description: '创建报表',
},
{
title: '完成创建',
description: '创建成功',
}
]
}
}
init()
</script>
<template>
<div class="report-steps">
<div class="report-steps-step" v-for="item, index in steps" :key="index">
<div class="report-steps-step-num" :class="bgstyle(index)">
<div class="report-steps-step-num-text">
{{ index + 1 }}
</div>
</div>
<div class="report-steps-step-info">
<div class="report-steps-step-info-title">
{{ item.title }}
</div>
<div class="report-steps-step-info-description">
{{ item.description }}
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.report-steps {
display: flex;
justify-content: center;
&-step {
width: 180px;
height: 80px;
position: relative;
margin: 0 70px;
&-num {
position: absolute;
left: 0;
top: 0;
width: 60px;
height: 50px;
background: #AEBAD0;
box-shadow: 6px 6px 20px 0px rgba(119, 120, 121, 0.09);
border-radius: 10px 0px 0px 10px;
backdrop-filter: blur(5px);
&.active {
background: #4582FF;
box-shadow: 6px 6px 20px 0px rgba(69, 84, 103, 0.09);
}
&.finished {
background: #35B765;
box-shadow: 6px 6px 20px 0px rgba(70, 105, 90, 0.09);
}
&-text {
width: 40px;
text-align: center;
padding-top: 11px;
font-family: DINCondensed, DINCondensed;
font-weight: bold;
font-size: 20px;
color: #FFFFFF;
}
}
&-info {
width: 140px;
height: 80px;
margin-left: 39px;
padding: 16px 0 0 28px;
background: rgba(240, 244, 250, 0.8);
border-radius: 0px 10px 10px 10px;
backdrop-filter: blur(5px);
font-family: PingFangSC, PingFang SC;
&-title {
color: #223253;
font-size: 14px;
font-weight: 600;
margin-bottom: 10px;
}
&-description {
color: #223253;
font-size: 13px;
color: #848FA5;
}
}
}
}
</style>
(8)Tabs 标签页
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
A、需求与实现
需求:点击el-tab-pane,向后台发送请求,请求结果没返回前,禁止点击el-tab-pane,
实现:将el-tab-pane的disabled属性与loading绑定
说明:该方案同样适用于由div和el-button模拟的选项卡中
B、带权限的选项卡
<div class="tabs">
<ul>
<li v-for="(item, index) in accountNumber"
@click="clickAccount(item, index)" :key="index"
:class="index == currIndex ? 'active' : ''"
:style="$hasPermit('project:column:account:distributed')? null:{'margin-left':'0'}"
v-show="(index==0 && $hasPermit('project:column:account:distributed'))||
(index==1 && $hasPermit('project:column:account:undistributed'))"
>
{{ item }}
</li>
<span class="number" v-show="$hasPermit('project:column:account:undistributed')">
{{ twoAccount.unAssignCount }}
</span>
</ul>
</div>
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
const currIndex = ref(route.query.tabIndex||0)
if(!proxy.$hasPermit('project:column:account:distributed'))currIndex.value = 1;
6、Feedback 反馈组件
(1)Alert 提示
Alert不是浮层元素,不会自动消失或关闭
<el-alert title="success alert" type="success" />
(2)Dialog 对话框
(2-1)示例
<el-dialog
v-model="dialogVisible"
title="Tips"
width="30%"
:before-close="handleClose"
>
<span>This is a message</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
</span>
</template>
</el-dialog>
(2-2)弹窗上下固定,中间滚动
<el-dialog
title="详情"
:visible.sync="dialogPreviewVisible"
width="1150px"
>
<div style="height:640px;overflow: auto;">
<AlbumPreview :sid="item.f_id"></AlbumPreview>
</div>
<el-button>确定</el-button>
</el-dialog>
(2-3)弹窗作为子组件(传值传参传数据)
A、需求
a、父组件控制子组件的隐现
b、子组件只有一个弹窗
c、弹窗只有一个表单
B、实现
a、父组件属性传参
b、子组件获取参数,显示弹窗和表单
c、在点击按钮后,或点击叉号时,发射事件(携带数据),
d、父组件向后台发送数据,然后)成功后,属性传参,关闭弹窗
注、在子组件里,给接收的属性直接赋值,违背了单向数据流的原则
注、在子组件里,给接收的属性解构并初始化为本组件的变量,然后可以给该变量重新赋值
(3)Drawer 抽屉
<el-drawer
v-model="drawer"
title="I am the title"
:direction="direction"
:before-close="handleClose"
>
<span>Hi, there!</span>
</el-drawer>
(4)Loading 加载
<el-table v-loading="loading" :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
(5)Message 消息提示
Message用于主动操作后的反馈提示
Notification用于系统级通知的被动提醒
<template>
<el-button :plain="true" @click="open">Show message</el-button>
</template>
<script lang="ts" setup>
import { h } from 'vue'
import { ElMessage } from 'element-plus'
const open = () => {
ElMessage('this is a message.')
}
const open = () => {
ElMessage({
message: 'Warning, this is a warning message.',
type: 'warning',
})
}
</script>
(6)ElMessageBox,消息弹出框。默认从中间出现,中断用户操作
略:详情搜“ElMessageBox.alert、ElMessageBox.confirm、ElMessageBox.prompt”
(7)Notification 通知
Message用于主动操作后的反馈提示
Notification用于系统级通知的被动提醒
<template>
<el-button plain @click="open"> Success </el-button>
</template>
<script lang="ts" setup>
import { ElNotification } from 'element-plus'
const open = () => {
ElNotification({
title: 'Success',
message: 'This is a success message',
type: 'success',//可有、可无、可选
})
}
</script>
(8)Popconfirm 气泡确认框,在被点击元素旁边弹出一个简单的气泡确认框
<template>
<el-popconfirm title="Are you sure to delete this?">
<template #reference>
<el-button>Delete</el-button>
</template>
</el-popconfirm>
</template>
(9)Popover 弹出框(气泡卡片),在被点击元素旁边弹出一个简单气泡卡片
//附、el-popover可以通过:virtual-ref出现在不同的被点击元素旁
<el-popover
placement="top-start"
title="Title"
:width="200"
trigger="hover"
content="this is content, this is content, this is content"
>
<template #reference>
<el-button class="m-2">Hover to activate</el-button>
</template>
</el-popover>
(10)Tooltip 文字提示
A、在被悬停元素旁边弹出一个简单的气泡卡片
<el-tooltip
class="box-item"
effect="dark"
content="Top Left prompts info"
placement="top-start"
>
<el-button>top-start</el-button>
</el-tooltip>
B、下拉项,选中太多,悬停满行时,右侧边框不出现
.el-popper.is-light {
//写在局部不生效,只能写在全局。YANSHOU-10690
margin-right: 10px;
}
C、溢出1行显示,!!!来源,bcbf-web
a,组件定义,CctvEllipsis
<template>
<el-tooltip placement="top" :disabled="!isShowTooltip" :content="content" effect="dark">
<div class="ellipsis-text" @mouseenter="visibilityChange($event)">
<slot></slot>
</div>
</el-tooltip>
</template>
<script setup>
const isShowTooltip = ref(false);
const content = ref('')
// 判断是否显示 溢出的文本 el-tooltip
const visibilityChange = (event) => {
const ev = event.target;
const evWeight = ev.scrollWidth;
const contentWeight = ev.clientWidth;
console.log(ev)
// console.log(ev, evWeight, contentWeight, 1);
if (evWeight > contentWeight) {
// 实际宽度 > 可视宽度 文字溢出
isShowTooltip.value = true;
content.value = ev.innerHTML
} else {
// 否则为不溢出
isShowTooltip.value = false;
}
};
</script>
<style lang="scss" scoped>
.ellipsis-text {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;/* 文本溢出部分,用省略号 */
white-space: nowrap;
}
</style>
b,组件使用,CctvEllipsis
import CctvEllipsis from '../base/cctv-ellipsis.vue'
<el-table-column prop="pathName" label="申领部门" align="center">
<template #default="scope">
<CctvEllipsis>
{{ scope.row.pathName }}
</CctvEllipsis>
</template>
</el-table-column>
D、溢出2行显示,来源,bcbf-web
a,组件定义,Cctv2Ellipsis
<template>
<el-tooltip
:content="content"
placement="top"
effect="dark"
:disabled="!isShowTooltip"
>
<div class="cctv2-ellipsis" @mouseenter="visibilityChange($event)">
<slot></slot>
</div>
</el-tooltip>
</template>
<script setup>
const {acctId} = defineProps({
acctId:{
type:String,
default:''
},
})
const isShowTooltip = ref(false);
const content = ref('')
const visibilityChange = (event) => {
const element = event.currentTarget;
const clone = element.cloneNode(true);
clone.textContent = element.textContent;
var num = 0;
//以下,悬停异常!!!
if(clone.textContent === 'Сэтгүүлч Д.Хүчитбаатар') num = 1;
if(clone.textContent === '马来语-KenaliBeijing主页') num = 14;
if(clone.textContent === '菲律宾语-FilipinoService') num = 4;
if(clone.textContent === '菲律宾语-CRI Filipino Service') {
clone.textContent = clone.textContent.replace(/ /g,'-')
}
clone.style.cssText = `
position: absolute;
visibility: hidden;
font-size: 14px;
width: ${element.offsetWidth+3+num}px;
height: auto;
-webkit-line-clamp: none;
overflow-wrap: break-word;
`;
document.body.appendChild(clone);
console.log( clone.textContent, clone.offsetHeight, element.offsetHeight);
const isOverflowing = clone.offsetHeight > element.offsetHeight;
content.value = acctId||element.textContent;
if(isOverflowing) content.value = acctId + ( acctId? '\xa0'.repeat(4)+'账号名称:' : '') + element.textContent;
document.body.removeChild(clone);
isShowTooltip.value = acctId||isOverflowing;
};
</script>
<style lang="scss">
.cctv2-ellipsis {
display: -webkit-box; /* 弹性盒子 */
-webkit-box-orient: vertical; /* 子元素垂直排列 */
-webkit-line-clamp: 2; /* 超出2行,显示... */
overflow: hidden;
line-height: 1.5; /* 行高,影响整体高度 */
/* 以下多余 */
text-overflow: ellipsis;
vertical-align: middle;
}
</style>
b,组件使用,Cctv2Ellipsis
<el-table-column label="栏目名称" prop="columnName" align="center">
<template #default="scope">
<Cctv2Ellipsis>
<div
@click="openColumnDetail(scope.row, scope.row.isBold)"
:style="{
color: scope.row.isBold? '':'#2162DB',
cursor: scope.row.isBold? '':'pointer',
fontWeight: scope.row.isBold? 'bold':'',
width: scope.row.changeNumber>1? 'auto':'100%',
}"
>{{scope.row.columnName}}</div>
</Cctv2Ellipsis>
<span v-if="scope.row.changeNumber>1">
<el-icon v-if="scope.row.isDown && !scope.row.isLoading" @click="addOrDelTableData(scope.row, scope.$index, true)">
<arrow-down class="el-icon-arrow"/>
</el-icon>
<el-icon v-if="!scope.row.isDown && !scope.row.isLoading" @click="addOrDelTableData(scope.row, scope.$index, false)">
<arrow-right class="el-icon-arrow"/>
</el-icon>
</span>
<el-button :loading='scope.row.isLoading' type="text" class="el-icon-load"></el-button>
</template>
</el-table-column>
7、Others 其他
(1)Divider 分割线
<el-divider content-position="left">Rabindranath Tagore</el-divider>
<el-divider>
<el-icon>
<star-filled />
</el-icon>
</el-divider>
<el-divider content-position="right">Rabindranath Tagore</el-divider>
二、各种提示组件的区别(各种浮层)
附、常见提示10种,分类如下
A、全局提示:ElNotification(右)、ElMessage(上)、ElMessageBox(中)
B、弹出层提示:el-dialog、el-drawer
C、元素关联提示:el-badge、el-tooltip、el-popconfirm、el-dropdown、el-popover
1、全局提示
附、el-alert,指定类型,仅通过标签属性
A、直接渲染在页面的指定位置,无JS调用方式
B、<el-alert type="success" title="内容"></el-alert>
(1)ElNotification,系统提示。从右侧出现,3秒后自动消失。支持打点调用或对象配置
A、打点调用,如 ElNotification.success('内容')ElNotification.success({message: '内容' })
B、对象配置,如 ElNotification({ type: 'success', message: '内容' }))
C、type取值,error、warning、info
(2)ElMessage,操作提示。从顶部出现,3秒后自动消失。支持打点调用或对象配置
A、打点调用,如 ElMessage.success('内容')、ElMessage.success({message: '内容' })
B、对象配置,如 ElMessage({ type: 'success', message: '内容' }))
C、type取值,error、warning、info
(3)ElMessageBox,消息弹出框。默认从中间出现,中断用户操作,点击按钮后关闭。分别对应消息提示、确认、输入场景
附、为略提供,搜“ElMessageBox.alert、ElMessageBox.confirm、ElMessageBox.prompt”
A、用法,配置dangerouslyUseHTMLString:true时,第一个参数支持(字符串形式的)HTML标签
a、ElMessageBox.alert,消息提示,模拟alert,
b、ElMessageBox.confirm,确认消息,模拟confirm。常用于删除前询问!!!,见C示例
c、ElMessageBox.prompt,提交内容,模拟prompt
B、原生弹窗说明
alert:
1、弹窗有确定按钮,阻塞线程,
2、alert后面的代码,点击确认后才执行
confirm:
1、弹窗有两个按钮(确定和取消),阻塞线程,
2、confirm后面的代码,点击按钮后才执行,
3、点击确定返回true,点击取消返回false
prompt:
1、弹窗有一个输入框、两个按钮(确定和取消),阻塞线程
2、prompt后面的代码,点击按钮后才执行,
3、点击确定返回输入内容,为空返回空字符串,点击取消返回null
C、示例,
const open2 = () => {
ElMessageBox.confirm(
'是否删除?',
'Warning',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
ElMessage({
type: 'success',
message: '删除成功!',
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '删除失败!',
})
})
}
2、弹出层提示
(1)弹窗提示
<el-dialog
title="复杂对话框"
:visible.sync="dialogPreviewVisible"
width="1150px"
>
<div style="height:640px;overflow: auto;">
<AlbumPreview :sid="item.f_id"></AlbumPreview>
</div>
<el-button>确定</el-button>
</el-dialog>
(2)抽屉提示
<template>
<el-button @click="drawerVisible = true">打开抽屉</el-button>
<el-drawer v-model="drawerVisible" title="基础抽屉" size="30%">
<span>这里是抽屉内容,可以放任意组件或HTML</span>
</el-drawer>
</template>
<script setup>
import { ref } from 'vue';
const drawerVisible = ref(false);
</script>
3、元素关联提示,它们的插槽是正常展示的内容,
(1)徽标提示,el-badge
<el-badge :value="100" :max="99" class="item"><!-- 显示为 99+ -->
<el-button>通知</el-button>
</el-badge>
(2)文字提示,el-tooltip。隐现方式,hover
<el-tooltip content="Bottom center" placement="bottom" effect="light">{/* effect,风格/主题,dark(默认)和light */}
<el-button>Light</el-button>
</el-tooltip>
<el-tooltip placement="top">
<div slot="content">多行信息<br/>第二行信息</div>
<el-button>Top center</el-button>
</el-tooltip>
(3)确认提示,el-popconfirm。隐现方式,点击按钮
<template>
<el-popconfirm
title="这是一段内容确认?"
confirm-button-text="确认"
cancel-button-text="取消"
icon="el-icon-info"//这是图标,!!!
icon-color="red"
@confirm="handleConfirm"
@cancel="handleCancel"
>
<el-button slot="reference">点击我</el-button>/* 不起作用 */!!!
<template #reference>/* 起作用 */
<el-button type="primary">系统误报</el-button>
</template >
</el-popconfirm>
</template>
<script>
export default {
methods: {
handleConfirm() {
console.log('确认操作');
},
handleCancel() {
console.log('取消操作');
}
}
}
</script>
(4)菜单提示,el-dropdown
附、el-dropdown和el-select的区别!!!
a、el-dropdown:选择操作,如编辑、删除等
b、el-select:选择值
<template>
<el-dropdown @command="handleCommand">
<el-button>
操作菜单<i class="el-icon-arrow-down"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="delete" divided>删除</el-dropdown-item>/*divided给该选项的上方添加一条水平线,使菜单结构更清晰*/
<el-dropdown-item command="download" disabled>下载(禁用)</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
const handleCommand = (command) => {//这个参数就是el-dropdown-item标签的command属性值
switch (command) {
case 'edit':
ElMessage.success('点击了编辑');
break;
case 'delete':
ElMessageBox.confirm('确认删除?', '提示')
.then(() => ElMessage.success('删除成功'))
.catch(() => ElMessage.info('已取消'));
break;
default:
console.log(command);
}
};
</script>
(5)卡片提示,el-popover。隐现方式,trigger(触发,hover、click、focus)或v-model
A、以下用法一,常规用法
<el-popover :visible="isShowModifyPop && multipleSelection.length>0" placement="bottom" :width="160">
<p>请选择播发种类</p>
<div style="padding: 10px 0">
<el-select v-model="isFirstPlay" placeholder="全部">
<el-option v-for="item in playStates" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="plain" @click="cancelBatchModify">取消</el-button>
<el-button size="mini" type="plain" @click="submitBatchModify">确定</el-button>
</div>
<template #reference>
<el-button @click="changeBatchModify" :disabled="multipleSelection.length==0">批量人工修订</el-button>
</template>
</el-popover>
<script setup>
const isShowModifyPop = ref(false)
</script>
B、以下用法二,el-popover可以通过:virtual-ref出现在不同的被点击元素旁!!!
<el-table-column label="" prop="columnLengthMinutesStr" align="center">
<template #header>
<div class="cell-header">
<span>节目时长</span>
<el-icon ref="timeLengthRef" @click.stop="relateFilter(timeLengthRef, 'timeLength')"
:style="{ color: filterForm.minTime||filterForm.maxTime? '#2162DB' : '#85888e' }" class="icon"
>
<Filter />
</el-icon>
</div>
</template>
<template #default="scope">
<span>{{ scope.row.channelName }}</span>
</template>
</el-table-column>
<!-- 以下写在el-table的下面,与之平级 -->
<el-popover
:visible="visible" //控制弹窗是否可见,绑定到visible变量
:teleported="false" //设置弹窗是否使用teleport传送到body上,false表示不传送
:virtual-ref="refName" //绑定虚拟触发元素的引用名称,用于定位计算
width="fit-content" //设置弹窗宽度为自适应内容大小
placement="bottom" //设置弹窗相对于触发元素的位置,这里是显示在下方
virtual-triggering //启用虚拟触发模式,使用JS计算定位而非真实DOM元素
popper-class="organize-filter-popover" //为弹窗添加自定义CSS类,用于样式定制
>
<el-icon class="popover-close" @click="visible = false">
<Close />
</el-icon>
<div class="filter-popover">
<div v-if="showKey === 'timeLength'" style="padding: 3px 0 2px 0;">
<el-input-number v-model="filterForm.minTime" :min="0" @change="inputMinChange" placeholder="最小分钟" />
—
<el-input-number v-model="filterForm.maxTime" :min="0" @change="inputMaxChange" placeholder="最大分钟" />
</div>
<el-select v-if="showKey === 'produceManage'" v-model="filterForm.produceManage" placeholder="全部">
<el-option v-for="item in playStates" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-if="showKey === 'systemCompute'" v-model="filterForm.systemCompute" placeholder="全部">
<el-option v-for="item in playStates" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-if="showKey === 'modifySystem'" v-model="filterForm.modifySystem" placeholder="全部">
<el-option v-for="item in playStates" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="mt">
<el-button type="info" link @click="columnResetFilter">重置</el-button>
<el-button type="primary" link @click="columnFilter">筛选</el-button>
</div>
</div>
</el-popover>
<script setup>
const timeLengthRef = ref()
const produceManageRef = ref()
const systemComputeRef = ref()
const modifySystemRef = ref()
const visible = ref(false) //控制弹窗隐现
const refName = ref(null) //控制弹窗显示位置
const showKey = ref(undefined) //控制弹窗显示区域
//表头过滤-过滤栏目与过滤弹窗关联
const relateFilter = (ref, key) => {
if (showKey.value !== key) visible.value = false //本表头连续第1次点击时,类似于初始化,隐藏弹窗
refName.value = ref //把表头与弹窗关联起来
showKey.value = key //把表头与弹窗显示区域关联起来
visible.value = !visible.value //切换弹窗显示状态
}
//表头过滤-栏目重置过滤
const columnResetFilter = () => {
filterForm[showKey.value] = '';
visible.value = false;
page.value.pageNum = 1
listData()
}
//表头过滤-栏目过滤
const columnFilter = () => {
visible.value = false;
page.value.pageNum = 1
listData()
}
//表头过滤-栏目过滤-节目时长
const inputMinChange = (num) => {
if (filterForm.maxTime < num) filterForm.maxTime = num
}
const inputMaxChange = (num) => {
if (filterForm.minTime > num) filterForm.minTime = num
}
</script>
4、其它提示(属各种提示组件)
(1)el-alert,正文,不属于浮层元素,不会自动消失或关闭
<div style="max-width: 600px">
<el-alert title="Success alert" type="success" />
<el-alert title="Info alert" type="info" />
<el-alert title="Warning alert" type="warning" />
<el-alert title="Error alert" type="error" />
</div>
(2)ElTour,分步引导用户操作的高亮浮层
<template>
<div>
<!-- 目标元素(需要被高亮的DOM节点) -->
<el-button ref="step1">第一步</el-button>
<el-input ref="step2" placeholder="第二步" />
<el-table ref="step3" :data="tableData" style="width: 100%"></el-table>
<!-- 启动引导的按钮 -->
<el-button @click="startTour">开始引导</el-button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElTour } from 'element-plus';
const step1 = ref();
const step2 = ref();
const step3 = ref();
const tableData = ref([{ name: '示例数据' }]);
const steps = [
{
target: step1.value?.$el, //绑定目标元素的DOM节点
content: '这是第一步,点击按钮试试',
placement: 'bottom', //提示位置:top/bottom/left/right
},
{
target: step2.value?.$el,
content: '请在输入框中填写内容',
arrow: false, //隐藏箭头
},
{
target: step3.value?.$el,
content: '这是数据表格区域',
mask: { color: 'rgba(0,0,0,0.5)' }, //自定义遮罩颜色
},
];
const startTour = () => {
const tour = ElTour.create(steps, {
closeOnClickOutside: false, //禁止点击外部关闭
onFinish: () => console.log('引导完成!'),
onClose: () => console.log('用户手动关闭'),
});
tour.start();
};
</script>
三、各种选择组件的区别
附、el-dropdown和el-select的区别
A、el-dropdown:选择操作,如编辑、删除等
B、el-select:选择值
1、单级选择组件
(1)Select,选择器
<el-select
v-model="filterForm.broadcastStatus"
@change="changeType"
>
<el-option v-for="item in broadcastStatusOptions" :key="item.name" :label="item.name" :value="item.name" />
</el-select>
(2)Select V2,虚拟列表选择器。懒加载,通过filterable和remote-method实现。
附、懒加载说明,一边输入,一边触发remoteMethod,发送请求,用户把返回结果赋值给下拉选项
<el-select-v2
v-model="value"
:options="options"
:props="props"
filterable
:remote-method="remoteMethod"
/>
const props = {
label: 'name',
value: 'id',
children: 'children',
}
2、多级选择组件
(1)Cascader,级联选择器
A、懒加载,一边点击,一边触发lazyLoad,发送请求,用户把返回结果赋值给下一级
B、向右水平展示层级,适用于少量数据
C、获取选择结果(分多级和末级),用v-model和emitPath!!!
D、示例
<el-cascader
v-model="selectedValues"
:options="allOptions" //级联选择器-数据源(数组类型,每级默认需包含value/label字段,children字段用于嵌套子选项)
:props="allProps" //自定义节点字段映射(如{label:'name', value:'id', children:'subItems'},用于适配自定义数据结构)
placeholder="请选择"
//模板中的懒加载配置,覆盖props中的配置,优先级更高。此种写法更好
:lazy="true"
:lazy-load="lazyLoad"
/>
const allOptions = [
{
label: '一级选项A',
id: 1,
isLeaf: false, //false表示有子节点;true表示无子节点
children: []
},
{
id: 2,
label: '一级选项B',
isLeaf: true
}
];
const allProps = {
//以下,指定“属性字段”对应的“allOptions字段”
label: 'label',
value: 'id',
leaf: 'isLeaf',
children: 'children',
//以下,props中的懒加载配置,会被模板中的配置覆盖
lazy: true,
lazyLoad: lazyLoad,
//以下,取值控制
emitPath: true, //默认为true,返回完整路径,如 ['父级值', '子级值', '末级值']
checkStrictly: false //默认为false,只能选择末级选项
};
(2)Tree,树形控件
A、懒加载,既可以写在标签里,也可以写在props里
B、向下缩进展示层级,适用于大量数据
C、获取选择结果(分多级全选、末级选择、半选),用!!!
a、treeRef.value.getHalfCheckedNodes()
b、treeRef.value.getCheckedNodes(leafOnly?, includeHalfChecked?)
D、示例-配置说明
<el-tree
ref="treeRef"
:data="treeData" //树形结构-数据源(数组类型,每个节点默认需包含value/label字段,children字段用于嵌套子选项)
:props="treeProps" //自定义节点字段映射(如{label:'name', value:'id', children:'subItems'},适配自定义数据结构)
node-key="id" //指定节点唯一标识字段(如id,用于选中/展开/拖拽等操作的精准定位)
//节点展开/折叠配置
//default-expanded-keys的值,!!!
// 1.如果后端返回字符串'20000001',前端写成数字20000001,
// 2.那么线下本地环境可以展开,线上服务器环境没有展开
:default-expanded-keys="[1, 3]" //初始化时默认展开的节点ID数组(需与node-key对应)
:expand-on-click-node="true" //是否点击节点文本时触发展开/折叠(默认true,设为false则仅通过箭头操作)
:auto-expand-parent="true" //选中子节点时是否自动展开所有父节点(默认true)
//节点选择(复选框)配置
show-checkbox //是否显示节点复选框(开启多选功能)
:default-checked-keys="[2, 4]" //初始化时默认勾选的节点ID数组(需配合show-checkbox使用)
:check-strictly="false" //是否关闭父子节点勾选联动(默认false,true则父子勾选状态独立)
:check-on-click-node="false" //是否点击节点文本时触发勾选(默认false,true则点击文本=点击复选框)
@check-change="checkManyChange" //节点勾选状态变化的通用监听,任何节点的勾选状态变化都触发该事件。1次点击,1到多次触发!!!
@check="checkSingleChange" //用户交互的精确监听,只有直接点击的节点会触发该事件,1次点击1次触发
//懒加载配置(大数据场景)
lazy //是否启用懒加载模式(初始只加载根节点,点击节点时加载子节点)
:load="loadNode" //懒加载回调函数(参数为node和resolve,需通过resolve传递子节点数据)
//样式与自定义渲染
:indent="16" //相邻层级节点的水平缩进量(单位:px,默认16)
:icon="customIcon" //自定义节点图标(可传组件/字符串,或函数动态返回)
:render-content="renderNodeContent" //自定义节点内容渲染函数(自定义节点内HTML结构)
empty-text="暂无数据" //数据源为空时的提示文本
//拖拽功能配置
draggable //是否开启节点拖拽功能(支持节点位置调整)
:allow-drop="handleAllowDrop" //拖拽时判断目标节点是否允许放置(返回布尔值,控制拖拽规则)
:allow-drag="handleAllowDrag" //判断节点是否允许被拖拽(返回布尔值,控制可拖拽节点范围)
//节点过滤(搜索)配置
:filter-node-method="filterNode" //节点过滤方法(配合外部输入框,实现树形节点搜索过滤)
filter="" //过滤关键词(需配合filter-node-method,动态传入搜索值触发过滤)
/>
E、示例-效果展示
<el-tree
ref="treeRef"
:data="treeData"
:props="treeProps"
node-key="id"
show-checkbox
@check-change="checkManyChange"
@check="checkSingleChange"
/>
const treeData = [
{
id: 1,
label: '公司总部',
children: [
{
id: 11,
label: '技术部',
children: [
{
id: 111,
label: '前端团队',
children: [
{ id: 1111, label: 'React小组', isLeaf: true },
{ id: 1112, label: 'Vue小组', isLeaf: true },
]
},
{
id: 112,
label: '后端团队',
children: [
{ id: 1121, label: 'Java开发组', isLeaf: true },
{ id: 1122, label: 'Python开发组', isLeaf: true },
]
}
]
},
{
id: 12,
label: '产品部',
disabled: true,
children: [
{
id: 121,
label: '产品设计组',
children: [
{ id: 1211, label: 'UI设计团队', isLeaf: true },
{ id: 1212, label: '交互设计团队', isLeaf: true },
]
},
{
id: 122,
label: '产品管理组',
children: [
{ id: 1221, label: '需求分析团队', isLeaf: true },
{ id: 1222, label: '产品规划团队', isLeaf: true },
]
}
]
}
]
},
{
id: 2,
label: '分公司',
children: [
{
id: 21,
label: '市场部',
children: [
{
id: 211,
label: '数字营销组',
children: [
{ id: 2111, label: 'SEO优化团队', isLeaf: true },
{ id: 2112, label: 'SEM投放团队', isLeaf: true },
]
},
{
id: 212,
label: '品牌推广组',
children: [
{ id: 2121, label: '品牌策划团队', isLeaf: true },
{ id: 2122, label: '公关活动团队', isLeaf: true },
]
}
]
},
{
id: 22,
label: '销售部',
children: [
{
id: 221,
label: '大客户销售',
children: [
{ id: 2211, label: '金融行业组', isLeaf: true },
{ id: 2212, label: '政府行业组', isLeaf: true },
]
},
{
id: 222,
label: '渠道销售',
children: [
{ id: 2221, label: '代理商管理', isLeaf: true },
{ id: 2222, label: '合作伙伴拓展', isLeaf: true },
]
},
]
},
]
},
{
id: 3,
label: '合作单位',
children: [
{
id: 31,
label: '战略合作伙伴',
children: [
{ id: 311, label: '腾讯云', isLeaf: true },
{ id: 312, label: '阿里云', isLeaf: true },
]
},
{
id: 32,
label: '技术合作伙伴',
children: [
{ id: 321, label: '微软', isLeaf: true },
{ id: 322, label: 'Oracle', isLeaf: true },
]
}
]
},
{
id: 4,
label: '海外分部',
children: [
{
id: 41,
label: '北美分部',
children: [
{ id: 411, label: '硅谷研发中心', isLeaf: true },
{ id: 412, label: '纽约办事处', isLeaf: true },
]
},
{
id: 42,
label: '欧洲分部',
children: [
{ id: 421, label: '伦敦办事处', isLeaf: true },
{ id: 422, label: '柏林技术中心', isLeaf: true },
]
}
]
}
];
const treeProps = {
//以下,指定“属性字段”对应“treeData字段”
label: 'label',
children: 'children',
disabled: 'disabled',
isLeaf: 'isLeaf', //用于判断是否为叶子节点(没有子节点的节点)
class: 'nodeClass'
};
let i=0, j=0;
const checkManyChange = function (data, checked, indeterminate) {
//1.data,当前节点的原始数据对象
//2.checked,当前节点是否被勾选(true/false)
//3.indeterminate,当前节点是否处于半选状态(子节点部分勾选时为true,全选/全不选为false)
i++;
/* console.log('checkManyChange'+i, data, checked, indeterminate); */
};
const checkSingleChange = function (data, event) {
//1.data,被点击节点对应的数据对象
//2.event,树目前的选中状态对象,包含四个属性
// 2.1.halfCheckedNodes:目前半选中的节点所组成的数组,节点上游的节点
// 2.2.halfCheckedKeys:目前半选中的节点的key所组成的数组,节点上游的节点的key
// 2.3.checkedNodes:目前被选中的节点所组成的数组,节点自身及下游的节点
// 2.4.checkedKeys:目前被选中的节点的key所组成的数组,节点自身及下游的节点的key
j++;
/* console.log('checkSingleChange'+j, data, event); */
getFinalSelection();
};
//以下,获取选择结果(分多级全选、末级选择、半选)!!!
const treeRef = ref(null);
const getFinalSelection = function() {
const halfCheckedNodes = treeRef.value.getHalfCheckedNodes(); //获取勾选结果-半选
const halfCheckedIds = halfCheckedNodes.map(node => node.id);
const allCheckedNodes = treeRef.value.getCheckedNodes(); //获取勾选结果-所有全选
const allCheckedIds = allCheckedNodes.map(node => node.id);
const checkedLeafNodes = treeRef.value.getCheckedNodes(true,false); //获取勾选结果-末级选择
const checkedLeafIds = checkedLeafNodes.map(node => node.id);
//后端应根据层级给id命名,方便后端识别,比如id="2-3-4",指第1级的第2项-下级的第3项-下级的第4项
}
3、多级选择组件-进阶
(1)TreeSelect,树形选择
A、懒加载,既可以写在标签里,也可以写在props里
B、结合了Tree的缩进展开和Select的选项悬浮
<el-tree-select
v-model="selectedValue"
ref="treeRef"
node-key="id"
show-checkbox
multiple
filterable
:data="treeData"
:props="props"
:lazy="true"
:load="loadNode"
:default-checked-keys="defaultCheckedKeys"
@check-change="handleCheckChange"
:default-expanded-keys="defaultExpandedKeys"
@node-expand="handleNodeExpand"
>
</el-tree-select>
const props = {
children: 'children',
label: 'label',
lazy: true,
load: loadNode;
}
(2)Tree V2,虚拟化树形控件
A、懒加载,既可以写在标签里,也可以写在props里
B、仅渲染可见节点
<el-tree-v2
:data="treeData"
:props="props"
lazy
:load="loadNode"
show-checkbox
:default-checked-keys="defaultCheckedKeys"
@check-change="handleCheckChange"
:default-expanded-keys="defaultExpandedKeys"
@node-expand="handleNodeExpand"
/>
const props = {
children: 'children',
label: 'label',
isLeaf: 'isLeaf',
lazy: true,
load: loadNode,
};