Element-Ui
el-table 自动滚动
- setTimeout 实现滚动
<template>
<div class="rtc-table" @mouseenter="stopScroll" @mouseleave="startScroll">
<el-table
ref="rollTable"
:data="tableData"
style="width: 100%"
v-bind="tableConfig"
>
<template slot="empty"> 暂无数据 </template>
<el-table-column
v-if="showIndex"
type="index"
:index="indexMethod"
label="序号"
align="center"
>
</el-table-column>
<el-table-column
v-for="item in propsList"
:key="`${item.prop}_${item.label}`"
:prop="item.prop"
:label="item.label"
:width="item.width"
:align="item.align || 'center'"
v-bind="item.otherConfig"
>
<template v-if="item.child">
<el-table-column
v-for="sitem in item.child"
:key="`${sitem.prop}_${sitem.label}`"
:prop="sitem.prop"
:label="sitem.label"
:width="sitem.width"
:align="sitem.align || 'center'"
v-bind="sitem.otherConfig"
>
<template slot-scope="scope">
<slot :name="sitem.slotName" :scope="scope">{{
scope.row[sitem.prop]
}}</slot>
</template>
</el-table-column>
</template>
<template slot-scope="scope">
<slot :name="item.slotName" :scope="scope">{{
scope.row[item.prop]
}}</slot>
</template>
<template #[item.tableSlotName]="scope">
<slot :name="item.tableSlotName" :scope="scope">{{
scope.column.label
}}</slot>
</template>
</el-table-column>
</el-table>
<div v-if="showPagination" class="table-bottom mt-20">
<slot name="table-bottom"></slot>
<div class="pagination" style="text-align: right">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagingInfo.pageIndex"
:page-sizes="[10, 20, 50]"
:page-size="pagingInfo.pageSize"
:total="pagingInfo.total"
v-bind="pagingConfig"
>
</el-pagination>
</div>
</div>
</div>
</template>
<script>
export default {
name: "RtcTable",
props: {
tableData: {
type: Array,
default: () => [],
},
tableConfig: {
type: Object,
default: () => {
return {};
},
},
propsList: {
type: Array,
default: () => [],
required: true,
},
showIndex: {
type: Boolean,
default: false,
},
showPagination: {
type: Boolean,
default: false,
},
pagingInfo: {
type: Object,
default: () => ({
pageIndex: 1,
pageSize: 10,
total: 0,
}),
},
pagingConfig: {
type: Object,
default: () => ({
layout: "total, sizes, prev, pager, next, jumper",
}),
},
scroll: {
type: Boolean,
default: false,
},
handle: {
type: Boolean,
default: false,
},
},
data() {
return {
scrollT: 0,
scrollTime: null,
};
},
computed: {
isAutoScroll() {
// 是否自动滚动
return this.scroll && !this.handle;
},
canScroll() {
// 是否可以滚动
return (
this.isAutoScroll &&
Array.isArray(this.tableData) &&
this.tableData.length > 0
);
},
},
watch: {
canScroll(nv) {
if (nv) {
console.log("canScroll");
this.toScroll();
}
},
},
methods: {
indexMethod(index) {
return index + 1;
},
handleSizeChange(val) {
this.$emit("update:pagingInfo", {
...this.pagingInfo,
pageSize: val,
pageIndex: 1,
});
this.$emit("pagingChange", {
...this.pagingInfo,
pageSize: val,
pageIndex: 1,
});
},
handleCurrentChange(val) {
this.$emit("update:pagingInfo", { ...this.pagingInfo, pageIndex: val });
this.$emit("pagingChange", { ...this.pagingInfo, pageIndex: val });
},
toScroll() {
this.stopScroll();
if (!this.tableData.length || !this.scroll || !this.$refs.rollTable) {
// if (!this.$refs.rollTable) {
// this.$emit("end");
// }
this.$emit("notScroll");
return;
}
let divData = this.$refs.rollTable.bodyWrapper;
if (this.handle && divData.clientHeight >= divData.scrollHeight) {
this.$emit("notScroll");
return;
}
let initTop = 0;
if (this.scrollT < 0) {
initTop = this.scrollT * -1;
}
// this.scrollTime = setInterval(() => {
// divData.scrollTop += 1;
// initTop++;
// if (initTop >= divData.offsetTop / 2) {
// initTop = 0;
// }
// divData.style.Top = "-" + initTop + "px";
// if (divData.clientHeight + divData.scrollTop == divData.scrollHeight) {
// divData.scrollTop = 0;
// this.$emit("end");
// }
// }, 100);
const scrollFn = () => {
divData.scrollTop += 1;
initTop++;
if (initTop >= divData.offsetTop / 2) {
initTop = 0;
}
divData.style.Top = "-" + initTop + "px";
if (divData.clientHeight + divData.scrollTop == divData.scrollHeight) {
divData.scrollTop = 0;
this.$emit("end");
}
this.$nextTick(() => {
this.scrollTime = setTimeout(scrollFn, 100);
});
};
scrollFn();
},
stopScroll() {
this.scrollTime && clearTimeout(this.scrollTime);
},
startScroll() {
if (!this.scroll || !this.$refs.rollTable) return;
let target = this.$refs.rollTable.bodyWrapper;
this.scrollT = target.offsetTop;
this.toScroll();
},
autoScroll() {
if (this.scroll && !this.handle) this.toScroll();
},
},
beforeDestroy() {
this.stopScroll();
},
};
</script>
<style lang="scss" scoped>
.rtc-table {
.table-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
.pagination {
flex: 1;
}
}
::v-deep .el-table {
tr,
td {
border-bottom: none;
}
}
}
</style>
- requestAnimationFrame 实现滚动
<template>
<div class="rtc-table" @mouseenter="stopScroll" @mouseleave="startScroll">
<el-table
ref="rollTable"
:data="tableData"
style="width: 100%"
v-bind="tableConfig"
>
<template slot="empty"> 暂无数据 </template>
<el-table-column
v-if="showIndex"
type="index"
:index="indexMethod"
label="序号"
align="center"
>
</el-table-column>
<el-table-column
v-for="item in propsList"
:key="`${item.prop}_${item.label}`"
:prop="item.prop"
:label="item.label"
:width="item.width"
:align="item.align || 'center'"
v-bind="item.otherConfig"
>
<template v-if="item.child">
<el-table-column
v-for="sitem in item.child"
:key="`${sitem.prop}_${sitem.label}`"
:prop="sitem.prop"
:label="sitem.label"
:width="sitem.width"
:align="sitem.align || 'center'"
v-bind="sitem.otherConfig"
>
<template slot-scope="scope">
<slot :name="sitem.slotName" :scope="scope">{{
scope.row[sitem.prop]
}}</slot>
</template>
</el-table-column>
</template>
<template slot-scope="scope">
<slot :name="item.slotName" :scope="scope">{{
scope.row[item.prop]
}}</slot>
</template>
<template #[item.tableSlotName]="scope">
<slot :name="item.tableSlotName" :scope="scope">{{
scope.column.label
}}</slot>
</template>
</el-table-column>
</el-table>
<div v-if="showPagination" class="table-bottom mt-20">
<slot name="table-bottom"></slot>
<div class="pagination" style="text-align: right">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pagingInfo.pageIndex"
:page-sizes="[10, 20, 50]"
:page-size="pagingInfo.pageSize"
:total="pagingInfo.total"
v-bind="pagingConfig"
>
</el-pagination>
</div>
</div>
</div>
</template>
<script>
export default {
name: "RtcTable",
props: {
tableData: {
type: Array,
default: () => [],
},
tableConfig: {
type: Object,
default: () => {
return {};
},
},
propsList: {
type: Array,
default: () => [],
required: true,
},
showIndex: {
type: Boolean,
default: false,
},
showPagination: {
type: Boolean,
default: false,
},
pagingInfo: {
type: Object,
default: () => ({
pageIndex: 1,
pageSize: 10,
total: 0,
}),
},
pagingConfig: {
type: Object,
default: () => ({
layout: "total, sizes, prev, pager, next, jumper",
}),
},
scroll: {
type: Boolean,
default: false,
},
handle: {
type: Boolean,
default: false,
},
},
data() {
return {
scrollT: 0,
scrollTime: null,
lastTime: 0,
interval: 100,
};
},
computed: {
isAutoScroll() {
// 是否自动滚动
return this.scroll && !this.handle;
},
canScroll() {
// 是否可以滚动
return (
this.isAutoScroll &&
Array.isArray(this.tableData) &&
this.tableData.length > 0
);
},
},
watch: {
canScroll(nv) {
if (nv) {
console.log("canScroll");
this.toScroll();
}
},
},
methods: {
indexMethod(index) {
return index + 1;
},
handleSizeChange(val) {
this.$emit("update:pagingInfo", {
...this.pagingInfo,
pageSize: val,
pageIndex: 1,
});
this.$emit("pagingChange", {
...this.pagingInfo,
pageSize: val,
pageIndex: 1,
});
},
handleCurrentChange(val) {
this.$emit("update:pagingInfo", { ...this.pagingInfo, pageIndex: val });
this.$emit("pagingChange", { ...this.pagingInfo, pageIndex: val });
},
toScroll() {
this.stopScroll();
if (!this.tableData.length || !this.scroll || !this.$refs.rollTable) {
this.$emit("notScroll");
return;
}
let divData = this.$refs.rollTable.bodyWrapper;
if (this.handle && divData.clientHeight >= divData.scrollHeight) {
this.$emit("notScroll");
return;
}
let initTop = 0;
if (this.scrollT < 0) {
initTop = this.scrollT * -1;
}
const scrollFn = (currentTime) => {
if (!this.lastTime) this.lastTime = currentTime;
const elapsed = currentTime - this.lastTime;
if (elapsed > this.interval) {
this.lastTime = currentTime - (elapsed % this.interval);
divData.scrollTop += 1;
initTop++;
if (initTop >= divData.offsetTop / 2) {
initTop = 0;
}
divData.style.Top = "-" + initTop + "px";
if (
divData.clientHeight + divData.scrollTop ==
divData.scrollHeight
) {
divData.scrollTop = 0;
this.$emit("end");
}
}
this.scrollTime = requestAnimationFrame(scrollFn);
};
scrollFn();
},
stopScroll() {
this.scrollTime && cancelAnimationFrame(this.scrollTime);
},
startScroll() {
if (!this.scroll || !this.$refs.rollTable) return;
let target = this.$refs.rollTable.bodyWrapper;
this.scrollT = target.offsetTop;
this.toScroll();
},
autoScroll() {
if (this.scroll && !this.handle) this.toScroll();
},
},
beforeDestroy() {
this.stopScroll();
},
};
</script>
<style lang="scss" scoped>
.rtc-table {
.table-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
.pagination {
flex: 1;
}
}
::v-deep .el-table {
tr,
td {
border-bottom: none;
}
}
}
</style>
Button 组件 type = "text" 时,拓展状态样式
$--color-danger: #f56c6c !default;
$--color-warning: #e6a23c !default;
$custom-colors: (
"danger": $--color-danger,
"warning": $--color-warning,
);
// 拓展状态样式 text-danger, text-warning
@each $name, $color in $custom-colors {
.el-button--text.text-#{$name} {
color: $color !important;
&:hover {
color: lighten($color, 5%) !important;
}
&:active {
color: $color !important;
}
}
}
定制主题
- 创建
element-variables.scss文件,内容如下:
$--color-primary: #1fa3b0 !default;
$--color-danger: #f56c6c !default;
$--color-warning: #e6a23c !default;
/* 改变 icon 字体路径变量,必需 */
$--font-path: "~element-ui/lib/theme-chalk/fonts";
@import "~element-ui/packages/theme-chalk/src/index";
- 在
main.js中引入element-variables.scss文件:
import Vue from "vue";
import ElementUI from "element-ui";
import "./element-variables.scss";
Vue.use(ElementUI);
- vue-cli 生产环境打包,element-ui 字体图标有时会乱码
问题描述
开发环境正常,生产环境乱码(不是必现,打开控制台查看字体图标时会出现乱码,关闭控制台后恢复正常)。
生产环境打包, 所有 el-icon 的样式中 content 属性的值为 "" 或者是其他乱码字符,如:
.el-icon-delete:before { content: ""; }
解决方案:
- 安装
css-unicode-loader
npm install css-unicode-loader --save-dev
- 修改
vue.config.js文件,增加css-unicode-loader配置:
// vue.config.js
module.exports = {
configureWebpack: (config) => {
if (process.env.NODE_ENV === "production") {
const sassLoader = require.resolve("sass-loader");
config.module.rules
.filter((rule) => rule.test.toString().includes("scss"))
.forEach((rule) => {
rule.oneOf.forEach((oneOfRule) => {
const sassIndex = oneOfRule.use.findIndex(
(item) => item.loader === sassLoader
);
oneOfRule.use.splice(sassIndex, 0, {
loader: require.resolve("css-unicode-loader"),
});
});
});
}
},
css: {
loaderOptions: {
sass: {
sassOptions: {
quietDeps: true, // 禁用终端输出 sass 警告信息
},
},
},
},
};
定制主题(完整)
element-variables.scss文件内容:
:root {
--color-white: #fff;
// --color-black: #000;
// 1fa3b0
// --color-primary: #1fa3b0;
--color-primary: #2e57e5;
--color-primary-light-1: color-mix(
in srgb,
var(--color-white) 10%,
var(--color-primary)
);
--color-primary-light-2: color-mix(
in srgb,
var(--color-white) 20%,
var(--color-primary)
);
--color-primary-light-3: color-mix(
in srgb,
var(--color-white) 30%,
var(--color-primary)
);
--color-primary-light-4: color-mix(
in srgb,
var(--color-white) 40%,
var(--color-primary)
);
--color-primary-light-5: color-mix(
in srgb,
var(--color-white) 50%,
var(--color-primary)
);
--color-primary-light-6: color-mix(
in srgb,
var(--color-white) 60%,
var(--color-primary)
);
--color-primary-light-7: color-mix(
in srgb,
var(--color-white) 70%,
var(--color-primary)
);
--color-primary-light-8: color-mix(
in srgb,
var(--color-white) 80%,
var(--color-primary)
);
--color-primary-light-9: color-mix(
in srgb,
var(--color-white) 90%,
var(--color-primary)
);
--color-success: #13b881;
--color-warning: #fcb242;
--color-danger: #e75e5e;
--color-info: #909399;
}
// 覆盖 sass 内置的 mix 函数,使得其支持var变量
@function mix($color1, $color2, $p: 50%) {
@return color-mix(in srgb, $color1 $p, $color2);
}
/* 改变主题色变量 */
$--color-white: var(--color-white) !default;
// $--color-black: var(--color-black) !default;
$--color-primary: var(--color-primary) !default;
$--color-primary-light-1: var(--color-primary-light-1) !default;
$--color-primary-light-2: var(--color-primary-light-2) !default;
$--color-primary-light-3: var(--color-primary-light-3) !default;
$--color-primary-light-4: var(--color-primary-light-4) !default;
$--color-primary-light-5: var(--color-primary-light-5) !default;
$--color-primary-light-6: var(--color-primary-light-6) !default;
$--color-primary-light-7: var(--color-primary-light-7) !default;
$--color-primary-light-8: var(--color-primary-light-8) !default;
$--color-primary-light-9: var(--color-primary-light-9) !default;
$--color-success: var(--color-success) !default;
$--color-warning: var(--color-warning) !default;
$--color-danger: var(--color-danger) !default;
$--color-info: var(--color-info) !default;
/* 改变 icon 字体路径变量,必需 */
$--font-path: "~element-ui/lib/theme-chalk/fonts";
@import "~element-ui/packages/theme-chalk/src/index";
- 创建
themeColor.vue,动态修改 CSS 变量:
<template>
<el-color-picker v-model="themeColor" @change="updateThemeColor" />
</template>
<script>
export default {
data() {
return {
themeColor: localStorage.getItem("themeColor") || "#409EFF",
predefineColors: [
"#ff4500",
"#ff8c00",
"#ffd700",
"#90ee90",
"#00ced1",
"#1e90ff",
"#c71585",
"rgba(255, 69, 0, 0.68)",
"rgb(255, 120, 0)",
"hsv(51, 100, 98)",
"hsva(120, 40, 94, 0.5)",
"hsl(181, 100%, 37%)",
"hsla(209, 100%, 56%, 0.73)",
"#c7158577",
],
};
},
methods: {
handleThemeChange(color) {
// 动态修改CSS变量
const root = document.documentElement;
root.style.setProperty("--color-primary", color);
// 扩展其他变量(如辅助色)
const successColor = this.generateSuccessColor(color);
root.style.setProperty("--color-success", successColor);
localStorage.setItem("themeColor", color);
},
generateSuccessColor(primary) {
// 基于主色生成辅助色算法[6](@ref)
return tinycolor(primary).spin(120).toHexString();
},
updateThemeColor(color) {
if (!color) {
color = "#2e57e5";
this.themeColor = color;
}
const root = document.documentElement;
root.style.setProperty("--color-primary", color);
localStorage.setItem("themeColor", color);
},
},
// mounted() {
// const savedColor = localStorage.getItem("themeColor");
// if (savedColor) this.handleThemeChange(savedColor);
// },
};
</script>
<style></style>
图标打包问题
config.module
.rule("scss")
.oneOfs.values()
.forEach((oneOf) => {
oneOf
.use("css-unicode-loader")
.loader(require.resolve("css-unicode-loader"))
.before("sass-loader");
});
module.exports = defineConfig({
publicPath: "/",
outputDir: "dist",
assetsDir: "static",
lintOnSave: isDev,
productionSourceMap: false,
transpileDependencies: isProd ? transpileDependencies : [],
devServer: {
client: {
overlay: false,
},
port: port,
//代理本地接口地址
proxy: {
[process.env.VUE_APP_BASE_API]: {
// http://192.168.1.106:18351
// 204 37
target: "http://192.168.1.12:5227/",
changeOrigin: true,
pathRewrite: {
["^" + process.env.VUE_APP_BASE_API]: "",
},
},
},
//before: require('./mock/mock-server.js')
},
css: {
loaderOptions: {
sass: {
sassOptions: {
quietDeps: true,
},
},
},
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
"@": resolve("src"),
},
fallback: {
path: require.resolve("path-browserify"),
process: require.resolve("process/browser"),
},
},
plugins: [
// 全局注入 process 变量
new webpack.ProvidePlugin({
process: "process/browser",
}),
],
},
chainWebpack(config) {
config.module.rule("svg").uses.clear();
config.module.rule("svg").exclude.add(resolve("src/icons")).end();
config.module
.rule("icons")
.test(/\.svg$/)
.include.add(resolve("src/icons"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({ symbolId: "icon-[name]" })
.end();
// set preserveWhitespace
config.module
.rule("vue")
.use("vue-loader")
.loader("vue-loader")
.tap((options) => {
options.compilerOptions.preserveWhitespace = true;
return options;
})
.end();
config.module
.rule("scss")
.oneOfs.values()
.forEach((oneOf) => {
oneOf
.use("css-unicode-loader")
.loader(require.resolve("css-unicode-loader"))
.before("sass-loader");
});
config.when(isProd, (config) => {
config.optimization.splitChunks({
chunks: "all",
cacheGroups: {
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial", // only package third parties that are initially dependent
},
elementUI: {
name: "chunk-elementUI", // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
},
commons: {
name: "chunk-commons",
test: resolve("src/components"), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true,
},
},
});
});
},
});
