Commit 48cfd035 authored by sheng du's avatar sheng du
Browse files

init

parent 89dffcd8
No related merge requests found
Pipeline #802 canceled with stages
src/assets/img/nav/serviceData.png

558 Bytes

src/assets/img/nav/serviceDataActive.png

619 Bytes

src/assets/img/nav/spaceIndex.png

901 Bytes

src/assets/img/nav/spaceIndexActive.png

885 Bytes

src/assets/img/property.png

497 Bytes

src/assets/img/save.png

288 Bytes

src/assets/img/sub-header-bg.png

73.2 KB

src/assets/img/test-card-photo.png

43.7 KB

src/assets/img/user.png

1.66 KB

<template>
<div class="app-header">
<div class="header-left"><img src="@/assets/img/logo.png" alt="" /></div>
<div class="header-right">
<span class="welcome">欢迎来到数字空间!</span>
<img src="@/assets/img/user.png" alt="" />
<span class="user-name">刘云</span>
<img src="@/assets/img/login-out.png" alt="" />
</div>
</div>
</template>
<script>
import dayjs from "dayjs";
// import { appName } from "@/config";
// import { asyncRoutesSort } from "@/router";
// import cloneDeep from "lodash/cloneDeep";
// console.log(asyncRoutes);
export default {
name: "AppHeader",
data() {
return {
now: {
date: "",
time: "",
week: "",
},
// appName,
timmer: null,
// isCollapse: false,
};
},
computed: {
// menus() {
// return cloneDeep(asyncRoutesSort);
// },
// activeRouteName() {
// return this.$route.matched[0]?.name;
// },
},
created() {
this.timmer = setInterval(() => this.updateTime(), 1000);
},
beforeDestroy() {
if (this.timmer) clearInterval(this.timmer);
},
methods: {
updateTime() {
const now = dayjs();
this.now = {
date: now.format("YYYY-MM-DD"),
time: now.format("HH:mm:ss"),
week: now.format("dddd"),
};
},
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.app-header {
width: 100%;
height: 80px;
display: flex;
background-color: #2171ff;
color: #fff;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 0;
}
.header-left {
padding-left: calc(50% - 600px);
}
.header-right {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
padding-right: 33px;
height: 30px;
.avatar {
width: 28px;
height: 28px;
margin-left: 20px;
}
.user-name {
padding: 0 10px;
}
.date {
padding-right: 20px;
}
}
</style>
<template>
<div class="back-title">
<i class="el-icon-arrow-left" @click="backRouter"></i>
<span>{{ title }}</span>
</div>
</template>
<script>
export default {
name: "SubHeader",
data() {
return {};
},
props: {
title: {
type: String,
default: () => "标题",
},
},
computed: {},
watch: {},
methods: {
backRouter() {
this.$router.back(-1);
},
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.back-title {
height: 56px;
line-height: 36px;
padding-top: 20px;
// cursor: pointer;
flex-shrink: 0;
span {
padding-left: 5px;
}
i {
cursor: pointer;
}
}
</style>
<template>
<div class="app-bread-crumb">
{{ pageName }}
<!-- <el-tabs
v-model="activeName"
@tab-click="handleClick"
@edit="handleTabsEdit"
>
<el-tab-pane
:label="item.meta.title"
:name="item.path"
v-for="item in pathList"
closable
:key="item.path"
></el-tab-pane>
</el-tabs> -->
<slot></slot>
</div>
</template>
<script>
export default {
name: "BreadCrumb",
data() {
return {
activeName: "second",
pathList: [],
};
},
computed: {
paths() {
const matched = this.$route.matched.filter(
(item) => item.meta && item.meta.title
);
this.changeRouter(matched);
// console.log(matched.filter((item) => item.meta && item.meta.title));
return matched.filter((item) => (item.meta ? item.meta.title : ""));
},
pageName() {
return this.$route.matched[this.$route.matched.length - 1]?.meta?.title;
},
},
watch: {
paths() {
this.$nextTick(() => {
// console.log(this.paths);
});
},
},
methods: {
changeRouter(matched) {
// console.log(matched);
if (matched.length > 1) {
matched = [matched[matched.length - 1]];
}
if (matched[0].meta.disTab) {
return;
}
//从左侧导航打开页面时添加在头部导航,没有则只定位
let paths = this.pathList.map((item) => item.path);
if (paths.includes(matched[0].path)) {
this.activeName = matched[0].path;
} else {
this.pathList = [...this.pathList, ...matched];
this.activeName = matched[0].path;
}
// this.pathList = [...this.pathList, ...matched];
// console.log(this.pathList);
},
handleTabsEdit(targetName, action) {
// console.log(targetName);
if (action === "remove") {
if (this.pathList.length == 1) {
this.$message.info("已经是最后的页面了,无法关闭");
return;
}
//删除缓存页面
let findItem = this.pathList.find((item) => item.path == targetName);
// console.log(findItem);
this.$store.dispatch("app/removeCacheRoutes", findItem.name);
if (findItem.parent) {
this.$store.dispatch("app/removeCacheRoutes", findItem.parent.name);
}
//从列表去掉
this.pathList = this.pathList.filter((item) => item.path != targetName);
if (this.$route.path == targetName) {
//关闭当前页面跳转到最后一个页面
this.$router.replace(this.pathList[this.pathList.length - 1].path);
// this.activeName = matched[0].path;
}
}
},
handleClick(tab) {
// console.log(tab.name, event);
this.$router.push(tab.name);
},
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.app-bread-crumb {
display: flex;
// margin-bottom: 20px;
// margin-bottom: 15px;
align-items: center;
justify-content: space-between;
background-color: #fff;
height: 50px;
height: 28px;
line-height: 28px;
font-size: 18px;
font-weight: 700;
color: rgba(49, 130, 255, 1);
// padding: 0 16px;
.app-home {
display: flex;
font-size: 20px;
color: #8d8d8d;
margin-right: 20px;
&:hover {
color: $--color-primary;
}
}
&::v-deep {
.el-tabs {
max-width: 80%;
}
.el-tabs__nav-prev,
.el-tabs__nav-next {
height: 50px;
line-height: 50px;
}
.el-tabs__header {
margin: 0;
height: 100%;
}
.el-tabs__nav-wrap {
&::after {
display: none;
}
}
.el-tabs__item,
.el-tabs,
.el-tabs__nav {
height: 50px;
line-height: 50px;
}
.router-link-active {
font-weight: 500;
color: #333333;
&:hover {
color: $--color-primary;
}
}
.el-breadcrumb__separator {
color: #333333;
margin: 0;
}
}
}
</style>
<script>
export default {
functional: true,
render(h, { props, children, data }) {
return (
<div class="c__empty" {...data}>
{children || [
<svg-icon icon-class="empty" />,
<p>{props.tips || "暂无数据"}</p>
]}
</div>
);
}
};
</script>
<style lang="scss">
.c__empty {
text-align: center;
color: #8c8c8c;
.svg-icon {
font-size: 60px;
}
}
</style>
<script>
/*
@author
@name FuncComponent
@desc render函数组件【因为template中写jsx不规范】
@props params any render函数所需参数
renderFunc function render函数
@emit
*/
export default {
name: "FuncComponent",
props: {
params: {
type: [String, Number, Boolean, Array, Object, undefined]
},
renderFunc: {
type: Function,
required: true
}
},
render() {
return this.renderFunc(this.params);
}
};
</script>
<template>
<div class="ImgUploader">
<div class="img-list">
<div class="img-item" v-for="(item, index) in imgList" :key="index">
<img :src="item" alt="" />
<div class="mask">
<i class="el-icon-search" @click="preView(item)"></i>
<i class="el-icon-delete" @click="deleteImg(item, index)"></i>
</div>
</div>
</div>
<el-upload
action=""
:before-upload="
(file) => {
beforeUpload(file);
return false;
}
"
v-if="!readOnly"
>
<i class="el-icon-plus"></i>
<span>上传图片</span>
</el-upload>
<el-image-viewer
v-if="dialogVisible"
:on-close="
() => {
dialogVisible = false;
}
"
:url-list="[dialogImageUrl]"
/>
<!-- <el-dialog :visible.sync="dialogVisible" width="600px" append-to-body>
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog> -->
</div>
</template>
<script>
import ElImageViewer from "element-ui/packages/image/src/image-viewer";
export default {
name: "ImgUploader",
components: { ElImageViewer },
data: () => {
return {
dialogImageUrl: "",
dialogVisible: false,
};
},
props: {
value: {
type: Array,
default: () => [],
},
readOnly: {
type: Boolean,
default: () => false,
},
},
computed: {
imgList: {
get: function() {
return this.value;
},
set: function(newValue) {
this.$emit("input", newValue);
},
},
},
methods: {
async beforeUpload(file) {
// console.log(file);
let url = await this.$utils.file2Base64(file);
this.imgList = [...this.value, url];
return false;
// console.log(url);
// this.$set(this.form, field, url);
// return false;
// const reader = new FileReader();
// reader.readAsDataURL(file);
// reader.onload = () => {
// const base64 = reader.result;
// // console.log(base64);
// this.imgList = [...this.value, base64];
// };
// reader.onerror = (error) => {
// console.log(error);
// };
},
deleteImg(item, index) {
this.imgList = this.imgList.filter((fitem, findex) => findex != index);
},
preView(item) {
this.dialogImageUrl = item;
this.dialogVisible = true;
},
},
};
</script>
<style lang="scss" scoped>
.ImgUploader {
display: flex;
flex-wrap: wrap;
max-width: 100%;
// padding-top: 24px;
.img-list {
display: flex;
.img-item {
width: 72px;
height: 72px;
border: 1px solid #e5e5e5;
overflow: hidden;
margin-right: 20px;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
position: relative;
.mask {
display: none;
width: 100%;
position: absolute;
height: 100%;
left: 0;
top: 0;
color: #fff;
font-size: 22px;
background-color: rgba($color: #000000, $alpha: 0.3);
justify-content: space-around;
padding: 0 20px;
align-items: center;
::v-deep {
i {
cursor: pointer;
}
}
}
&:hover {
.mask {
display: flex;
}
}
img {
width: 100%;
height: 100%;
}
}
}
::v-deep {
.el-upload {
width: 72px;
height: 72px;
border: 1px dashed #e5e5e5;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
font-size: 12px;
line-height: 22px;
color: #808080;
span {
padding-top: 6px;
}
i {
font-size: 18px;
}
}
}
}
</style>
<template>
<el-dialog
:title="title"
width="720px"
append-to-body
:visible.sync="dialogVisible"
:before-close="beforeClose"
class="PubDialog"
>
<div class="dialog-main">
<div class="slot-box">
<slot></slot>
</div>
</div>
<!-- <span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span> -->
</el-dialog>
</template>
<script>
export default {
name: "VideoDialog",
data: () => {
return {
// dialogVisible: false,
};
},
props: {
value: {
type: Boolean,
default: () => false,
},
title: {
type: String,
default: () => "标题",
},
},
computed: {
dialogVisible: {
get: function(that) {
return that.value;
},
set: function(newValue) {
this.$emit("input", newValue);
},
},
},
methods: {
beforeClose() {
// console.log('beforeClose');
// this.dialogVisible = false;
this.$emit("beforeClose");
},
},
};
</script>
<style lang="scss" scoped>
// .PubDialog {
::v-deep {
.el-dialog {
border-radius: 8px;
}
.el-dialog__header {
height: 52px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 0 20px;
border-bottom: 1px solid #e5e5e5;
.el-dialog__title {
font-size: 14px;
color: #000;
}
.el-dialog__headerbtn .el-dialog__close {
font-size: 20px;
color: #c4c4c4;
}
}
}
// .el-dialog__header {
// padding: 0;
// }
// .el-dialog__body {
// padding: 24px;
// background: #041740;
// // border: 3px solid #c6e7ff;
// box-shadow: inset 0px 0px 26px #289aff;
// color: #fff;
// position: relative;
// min-height: 460px;
// .dialog-back {
// position: absolute;
// z-index: 1;
// width: 100%;
// height: 100%;
// left: 0;
// top: 0;
// img {
// width: 100%;
// height: 100%;
// }
// }
// }
// .dialog-btns {
// display: flex;
// justify-content: flex-end;
// padding-top: 24px;
// }
// .el-button {
// height: 28px;
// line-height: 28px;
// padding: 0 30px;
// }
// .el-button--primary.is-plain {
// background-color: transparent;
// border-color: #1e81ff;
// &:hover {
// background-color: #1e81ff;
// color: #fff;
// }
// }
// }
// .dialog-main {
// position: relative;
// z-index: 2;
// .header {
// width: 100%;
// height: 52px;
// background: linear-gradient(90deg, #289aff 0%, rgba(#289aff, 0) 100%);
// padding: 0 20px;
// display: flex;
// justify-content: space-between;
// align-items: center;
// font-size: 18px;
// font-weight: bold;
// letter-spacing: 2px;
// .box-icon {
// margin-right: 12px;
// }
// .close {
// width: 25px;
// height: 25px;
// cursor: pointer;
// }
// }
// }
// .slot-box {
// padding: 34px 26px;
// }
// }
</style>
<script>
// import { appName } from "@/config";
import { asyncRoutesSort } from "@/router";
import cloneDeep from "lodash/cloneDeep";
// import logo from "@/assets/img/logo.png";
export default {
name: "SideBar",
data() {
return {
// appName,
};
},
props: {
isCollapse: {
type: Boolean,
default: () => false,
},
},
computed: {
menus() {
let allMenu = this.treeMap(cloneDeep(asyncRoutesSort));
console.log(allMenu);
// let findFatherItem = allMenu;
return allMenu;
// let findFatherItem = allMenu.find(
// (item) => item.name == this.mainMenuName
// );
// if (findFatherItem) {
// return findFatherItem.children || [];
// } else {
// return [];
// }
},
mainMenuName() {
return this.$route.matched[0]?.name;
},
activeRouteName() {
const { name } = this.$route;
// console.log(name, this.$route);
if(this.$route?.meta?.activeName){
return this.$route.meta.activeName
}
return name;
},
},
methods: {
treeMap(list) {
// console.log(list);
let res = list.map((item) => {
if (item.children) {
return {
...item,
children: this.treeMap(item.children),
};
} else {
return {
...item,
};
}
});
// console.log(res);
res = res.filter((item) => !item.meta.hidden);
return res.length ? res : null;
},
routeTo(indexs) {
let path = "";
let route = this.menus;
indexs = indexs.split("-");
indexs.forEach((index) => {
route = route[index] || route.children[index];
if (/^\//.test(route.path)) {
path = path + route.path;
} else {
path = path + "/" + route.path;
}
});
// path = "/" + path;
// path = "/" + this.mainMenuName + path;
// console.log(path);
this.$router.push(path);
},
getImgPath(iconImg) {
try {
let file = require(`../../../assets/img/nav/${iconImg}.png`);
return file;
} catch (error) {
return "";
}
// return require(`../../../assets/img/nav/${iconImg}.png`);
// <span class="title-span" slot="title">
// <div class="icon-box">
// {item.meta.iconImg ? (
// <img src={getImgPath(item.meta.iconImg)}></img>
// ) : (
// <span></span>
// )}
// </div>
// {item.meta.title}
// </span>
},
renderMenus(menus, propIndex = "") {
let getImgPath = this.getImgPath;
return menus.map((item, index) => {
return item.children ? (
<el-submenu index={item.name} key={propIndex + index}>
<span class="title-span" slot="title">
{item.meta.icon ? <i class={item.meta.icon}></i> : <span></span>}
{item.meta.title}
</span>
{item.children
? this.renderMenus(item.children, propIndex + index + "-")
: null}
</el-submenu>
) : (
<el-menu-item
key={propIndex + index}
index={item.name}
onClick={() => this.routeTo(String(propIndex + index))}
>
{item.meta.iconImg ? (
<span class="title-span" slot="title">
<div class="icon-box">
{item.meta.iconImg ? (
<div class="flex-center">
<img
src={getImgPath(item.meta.iconImg)}
class="img-icon"
></img>
<img
src={getImgPath(
item.meta.iconImgActive
? item.meta.iconImgActive
: item.meta.iconImg
)}
class="img-icon-active"
></img>
</div>
) : (
<span></span>
)}
</div>
{item.meta.title}
</span>
) : (
<div>
{item.meta.icon ? (
<i class={item.meta.icon}></i>
) : (
<span></span>
)}
{item.meta.title}
</div>
)}
</el-menu-item>
);
});
},
},
render() {
const menus = this.menus;
// const appName = this.appName;
return (
<div class={menus.length ? "side-bar" : "side-bar hide"}>
<div class="side-bar-body">
<el-menu
collapse={this.isCollapse}
defaultActive={this.activeRouteName}
// default-active="1-4-1"
>
{this.renderMenus(menus)}
</el-menu>
</div>
</div>
);
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.side-bar {
width: 240px;
height: calc(100%);
border-radius: 8px;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
display: flex;
flex-shrink: 0;
flex-direction: column;
transition: all 0.2s;
background-color: #fff;
position: relative;
padding-top: 14px;
&.hide {
width: 0;
}
::v-deep {
.el-menu {
width: 240px;
background: transparent;
&.el-menu--collapse {
width: 60px;
}
}
}
}
.side-bar-body {
width: 100%;
height: 100%;
display: block;
&::v-deep {
.el-menu {
border-right: none;
}
.el-submenu__title {
transition: all 0.3s;
width: 200px;
height: 35px;
line-height: 35px;
background: rgba(82, 162, 255, 0.2);
color: #5e5e5e;
font-size: 14px;
display: flex;
}
// li {
// line-height: 40px;
// }
.el-menu-item {
transition: all 0.3s;
width: 200px;
height: 35px;
line-height: 35px;
// background: rgba(82, 162, 255, 0.2);
background: transparent;
border-radius: 8px;
color: #5e5e5e;
font-size: 14px;
display: flex;
align-items: center;
margin: 6px auto;
}
.el-menu-item:hover,
.el-menu-item:focus,
.el-submenu__title:hover,
.el-submenu__title:focus {
color: #3182ff;
position: relative;
i {
color: #3182ff;
}
.icon-box {
img {
&.img-icon {
display: none;
}
&.img-icon-active {
display: inline-block;
}
}
}
}
.el-menu-item.is-active,
.el-submenu .el-menu-item.is-active,
.el-submenu__title.is-active {
background: rgba($color: #52a2ff, $alpha: 0.2);
color: #3182ff;
position: relative;
i {
color: #3182ff;
}
}
.el-menu-item [class^="el-icon-"] {
margin-right: 10px;
}
.el-submenu__title i {
}
}
}
.title-span {
display: flex;
align-items: center;
}
.icon-box {
display: flex;
align-items: center;
vertical-align: middle;
// width: 40px;
height: 100%;
margin-left: 3px;
margin-right: 14px;
img {
width: 18px;
height: 18px;
&.img-icon-active {
display: none;
}
&.img-icon {
display: inline-block;
}
}
}
::v-deep {
.is-active {
.icon-box {
img {
&.img-icon {
display: none;
}
&.img-icon-active {
display: inline-block;
}
}
}
}
}
</style>
<template>
<div class="sub-header">
<div class="user-info flex">
<div class="avatar">
<img src="@/assets/img/avatar-test.png" alt="" />
</div>
<div class="info">
<div>您好,欢迎登录~</div>
<div class="name">李云</div>
<div class="level">
<span>认证等级:</span>
<div class="level-box">四级认证</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "SubHeader",
data() {
return {};
},
computed: {},
watch: {},
methods: {},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.sub-header {
width: 1200px;
height: 196px;
flex-shrink: 0;
background: url("../../../assets/img/sub-header-bg.png");
background-size: 100% 100%;
color: #fff;
.user-info {
align-items: center;
height: 100%;
width: 100%;
padding-left: 54px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
box-shadow: 0px 2px 6px rgba($color: #000000, $alpha: 0.2);
img {
width: 100%;
height: 100%;
}
}
.info {
padding-left: 20px;
font-size: 18px;
line-height: 28px;
.name {
font-size: 20px;
}
.level {
font-size: 12px;
color: rgba($color: #fff, $alpha: 0.7);
display: flex;
align-items: center;
.level-box {
width: 67px;
height: 20px;
opacity: 1;
border-radius: 10px;
background: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
color: rgba(0, 59, 161, 1);
}
}
}
}
</style>
<template>
<el-form
class="table-header"
ref="form"
v-if="paramsGroups || params.length || showHeader"
>
<div class="table-header-row">
<!-- <slot v-if="$slots.default"></slot> -->
<template v-if="paramsGroups">
<!-- 选择 -->
<el-form-item class="group-item" v-if="paramsGroups.select">
<el-select
class="label-select"
v-model="select.key"
@change="changeHandler($event, 'select')"
>
<el-option
v-for="item in paramsGroups.select"
:value="item.value"
:label="item.label"
:key="item.value"
>
</el-option>
</el-select>
<component
class="value-input"
@change="updateData()"
v-model="select.val"
:is="select.component"
v-if="select.component"
v-bind="{ ...select.elAttrs }"
>
</component>
<el-select
v-else
class="value-input"
v-model="select.val"
@change="updateData()"
>
<el-option
v-for="item in paramsGroups.options[select.key]"
:label="item.label"
:value="item.value"
:key="item.value"
>
</el-option>
</el-select>
</el-form-item>
<!-- 输入 -->
<el-form-item class="group-item" v-if="paramsGroups.inputs">
<el-select
class="label-select"
v-model="inputs.key"
@change="changeHandler($event, 'input')"
>
<el-option
v-for="item in paramsGroups.inputs"
:label="item.label"
:value="item.value"
:key="item.value"
>
</el-option>
</el-select>
<el-input
class="value-input"
maxlength="20"
v-model="inputs.val"
@change="updateData()"
></el-input>
</el-form-item>
<!-- 范围 -->
<el-form-item class="group-item" v-if="paramsGroups.ranges">
<el-select
class="label-select"
v-model="ranges.label"
@change="changeHandler($event, 'ranges')"
>
<el-option
v-for="item in paramsGroups.ranges"
:value="item.label"
:label="item.label"
:key="item.label"
>
</el-option>
</el-select>
<component
@change="updateData()"
v-model="ranges.val"
:is="ranges.component"
v-bind="{ ...ranges.elAttrs }"
>
</component>
</el-form-item>
</template>
<template v-else>
<div
v-for="(item, index) in params"
:key="index"
style="display: inline-block;"
>
<el-form-item :class="{ empty: !item.label }" :label="item.label">
<FuncComponent
v-if="item.renderFunc"
:renderFunc="item.renderFunc"
></FuncComponent>
<component
v-else
:type="item.type"
clearable
size="small"
@change="search(1)"
:options="item.options"
v-model="data[item._key]"
v-bind="{ ...item.elAttrs }"
:placeholder="item.placeholder"
:is="item.component || 'el-input'"
>
<template v-if="item.component === 'el-select'">
<template v-if="options[item._key]">
<el-option
v-for="(_it, _i) in options[item._key]"
:key="index + '-' + _i"
:label="_it.label"
:value="_it.value"
></el-option>
</template>
<template v-else>
<el-option
v-for="(_it, _i) in item.options"
:key="index + '-' + _i"
:label="_it.label"
:value="_it.value"
></el-option>
</template>
</template>
</component>
</el-form-item>
</div>
</template>
<template v-if="paramsGroups || params.length">
<el-form-item class="btn-item">
<el-button
v-if="searchBtn"
type="primary"
size="small"
@click="search()"
>查询</el-button
>
</el-form-item>
<el-form-item class="btn-item">
<el-button
v-if="resetBtn"
size="small"
type="primary"
@click="reset"
plain
>重置</el-button
>
</el-form-item>
</template>
<slot name="tableHeaderSlot"></slot>
</div>
<!-- <div class="btns">
</div> -->
<div class="btn-right"><slot name="btnRight"></slot></div>
</el-form>
</template>
<script>
/*
@author
@name TableHeader
@desc 表头筛选组件
@props params Array<
label string label
default string|number 默认值
key string form的key
type string 组件传递的type值
component string 组件名称,如:el-input
placeholder string placeholder
renderFunc function:VNode 自定义渲染函数
elAttrs object element对应组件的属性(配置)
options object<{ el-select时的options
label string 选项label
value string 选项value
}>
}>
paramsGroups object< 参数组
inputs Array<{ 输入参数(默认第一个
label string 选择的label
value string 选择的value
}>
select Array<{ 选择参数(默认第一个
label string 选择的label
value string 选择的value
elAttrs object 对应组件的属性
component string 指定组件的名称,优先使用该配置
}>
ranges Array<{
label string 选择的label
keys Array<string> 参数对应的key,顺序排列
elAttrs object 对应组件的属性
component string 对应的组件名称
}>
options object<Val> select对应的选项对象,key为select对应的value
Array<{ select对应的选项
label string 选项的label
value strine 选项的value
}>
>
searchBtn boolean 是否显示搜索按钮[true]
resetBtn boolean 是否显示重置按钮[true]
showHeader boolean 是否在没有headers的时候显示头部
@slot slot 接受一个默认插槽
@desc header内容优先级 slot> paramsGroup >params
@emit
*/
// 不使用 import {} 是因为App里面的AreaSelect中依赖LinkageSelect,会产生循环引用
import cloneDeep from "lodash/cloneDeep";
import FuncComponent from "@/components/App/FuncComponent";
export default {
name: "TableHeader",
components: {
FuncComponent,
},
props: {
params: {
type: Array,
default: () => [],
},
options: {
type: Object,
default: () => {
return {};
},
},
showHeader: {
type: Boolean,
default: true,
},
searchBtn: {
type: Boolean,
default: true,
},
resetBtn: {
type: Boolean,
default: true,
},
paramsGroups: {
type: Object,
default: null,
},
},
data() {
return {
data: {},
inputs: {
key: "",
val: "",
},
select: {
key: "",
val: "",
elAttrs: {},
component: "",
},
ranges: {
val: [],
keys: [],
elAttrs: {},
component: "",
},
};
},
created() {
this.initParams();
this.initParamsGroups();
},
methods: {
reset() {
console.log(this.data);
for (let p in this.data) {
// if (Array.isArray(this.data[p])) {
// // const index = parseInt(p.substring(6));
// // if (this.params[index] && this.params[index].default) {
// // this.$set(this.data, p, this.params[index].default);
// // } else {
// // this.$set(this.data, p, []);
// // }
// this.$set(this.data, p, []);
// }
if (/^param-/.test(p)) {
const index = parseInt(p.substring(6));
if (this.params[index] && this.params[index].default) {
this.$set(this.data, p, this.params[index].default);
} else {
this.$set(this.data, p, []);
}
} else if (Array.isArray(this.data[p])) {
this.$set(this.data, p, []);
} else if (typeof this.data[p] === "object") {
this.$set(this.data, p, {});
} else {
this.$set(this.data, p, "");
}
}
this.inputs.val = "";
this.select.val = "";
this.ranges.val = [];
this.$emit("search");
},
search(force) {
// console.log(this.data);
if (force) {
if (!this.searchBtn) {
this.$emit("search");
}
} else {
this.$emit("search");
}
},
initParams() {
const data = {};
this.params.forEach((item, index) => {
item.elAttrs = item.elAttrs || {};
if (typeof item.key === "string") {
data[item.key] = item.default || "";
item._key = item.key;
} else {
item._key = "param-" + index;
data["param-" + index] = item.default || "";
}
});
this.data = data;
// console.log(this.params);
// console.log(this.data);
},
initParamsGroups() {
if (!this.paramsGroups) return;
if (this.paramsGroups.inputs && this.paramsGroups.inputs.length) {
this.inputs = {
val: "",
key: this.paramsGroups.inputs[0].value,
};
}
if (this.paramsGroups.select && this.paramsGroups.select.length) {
this.select = {
val: "",
key: this.paramsGroups.select[0].value,
elAttrs: this.paramsGroups.select[0].elAttrs || {},
component: this.paramsGroups.select[0].component || "",
};
}
if (this.paramsGroups.ranges && this.paramsGroups.ranges.length) {
const range = this.paramsGroups.ranges[0];
this.ranges = {
keys: range.keys,
label: range.label,
component: range.component,
elAttrs: range.elAttrs || {},
val: range.keys.map(() => ""),
};
}
this.updateData(true);
},
changeHandler(value, type) {
if (type === "input") {
this.$set(this.inputs, "val", "");
} else if (type === "select") {
this.$set(this.select, "val", "");
const item = this.paramsGroups.select.find((it) => it.value === value);
this.$set(this.select, "component", item.component);
this.$set(this.select, "elAttrs", item.elAttrs || {});
} else {
const item = this.paramsGroups.ranges.find((it) => it.label === value);
const val = item.keys.map(() => "");
this.$set(this.ranges, "val", val);
this.$set(this.ranges, "keys", item.keys);
this.$set(this.ranges, "component", item.component);
this.$set(this.ranges, "elAttrs", item.elAttrs || {});
}
this.updateData();
},
// stop:boolean 是否暂停搜索
updateData(stop = false) {
const paramsRanges = {};
let selectVal = this.select.val;
// console.log(this.ranges);
// setTimeout(() => {
// console.log(this.ranges);
// }, 2000);
this.ranges.keys.forEach((key, index) => {
paramsRanges[key] = this.ranges.val ? this.ranges.val[index] : "";
});
if (
selectVal === "全部" &&
this.select.component === "ModelAreaTreeSelect"
) {
selectVal = "";
}
this.data = {
[this.inputs.key]: this.inputs.val,
[this.select.key]: selectVal,
...paramsRanges,
};
if (stop) return;
this.search(1);
},
setParamsVal(o) {
const data = cloneDeep(this.data);
for (let p in o) {
const index = this.params.findIndex(
(item) => JSON.stringify(item.key) === p
);
if (index === -1) continue;
data[this.params[index]._key] = o[p];
}
this.data = data;
},
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/variables.scss";
.label-select {
::v-deep {
.el-input__inner {
border: none;
padding-left: 0;
border-radius: 2px;
background-color: transparent;
}
}
}
.table-header {
padding: 22px 15px 0 0;
::v-deep {
.el-tag.el-tag--info {
display: flex;
max-width: 90px;
}
}
// display: flex;
// .btns {
// margin-top: 15px;
// }
.table-header-row {
display: flex;
flex: 1;
margin-right: -30px;
flex-wrap: wrap;
}
.table-header-btns {
.el-button {
width: 56px;
height: 32px;
background: #0071e3;
border-radius: 2px;
line-height: 32px;
padding: 0;
}
}
.table-header-btns {
display: flex;
}
&::v-deep {
.el-form-item {
display: flex;
margin-bottom: 0;
align-items: center;
margin-right: 30px;
margin-bottom: 27px;
height: 32px;
&.btn-item {
margin-right: 10px;
}
}
.el-form-item__label {
// width: 96px;
font-size: 12px;
height: 32px;
line-height: 32px;
padding-right: 22px;
}
.el-form-item__content {
.el-input,
.el-select {
width: 180px;
height: 32px;
display: flex;
.el-input__inner {
height: 32px;
line-height: 30px;
border-radius: 2px;
background-color: transparent;
}
.el-input__suffix {
top: 1px;
height: 30px;
line-height: 30px;
}
}
.el-date-editor {
height: 32px;
display: flex;
padding: 0 10px;
}
}
.el-select .el-input .el-select__caret,
.el-cascader .el-input .el-icon-arrow-down {
line-height: 30px;
}
.empty {
.el-form-item__label {
display: none;
}
}
.area-select {
width: 318px;
}
.flex-input {
.el-select {
width: 96px;
}
.el-input {
margin-left: 0;
}
}
.group-item {
// flex: 1;
margin-right: 20px;
&:last-child {
flex: 2;
}
.el-form-item__content {
width: 100%;
display: flex;
}
.el-select:first-child {
width: 120px;
}
.el-slider {
flex: 1;
height: 32px;
margin-left: 20px;
}
.el-slider__runway {
margin: 13px 0;
}
.el-date-editor--daterange.el-input__inner,
.el-date-editor--datetimerange.el-input__inner {
// flex: 1;
// width: auto;
border-radius: 2px;
width: 480px;
}
}
.table_select {
display: flex;
align-items: center;
padding-top: 15px;
.select_title {
font-size: 14px;
margin-right: 15px;
color: $--color-text-regular;
}
}
}
}
.btn-right {
margin-bottom: 18px;
}
</style>
<template>
<div class="base-table">
<div class="base-table-header">
<TableHeader
:params="params"
:options="options"
@search="loadData(1)"
ref="refTableHeader"
:resetBtn="resetBtn"
:searchBtn="searchBtn"
:paramsGroups="paramsGroups"
:showHeader="showHeader"
>
<slot name="tableHeader"></slot>
<template slot="tableHeaderRight">
<slot name="tableHeaderRight"></slot>
</template>
<template slot="btnRight">
<slot name="btnRight"></slot>
</template>
<template slot="tableHeaderSlot">
<slot name="tableHeaderSlot"></slot>
</template>
</TableHeader>
</div>
<div class="add-line"><slot name="tableAdd"></slot></div>
<div class="base-table-body" ref="refTableBody" v-loading="loading">
<el-table
:height="height"
:data="tableData"
ref="refInnerTable"
size="small"
stripe
v-bind="{ ...table }"
@sort-change="sortChange"
@selection-change="$emit('selection-change', $event)"
>
<el-table-column
v-if="table.selection"
type="selection"
width="55"
:selectable="checkboxSelect"
>
</el-table-column>
<template v-for="(column, index) in tableColumns">
<el-table-column
v-if="column.renderFun"
v-bind="{ ...column }"
:key="index"
>
<template slot-scope="scope">
<FuncComponent
:params="scope"
:renderFunc="column.renderFun"
></FuncComponent>
</template>
</el-table-column>
<template v-else>
<el-table-column
:key="index"
v-bind="{ ...column }"
></el-table-column>
</template>
</template>
</el-table>
</div>
<div v-if="pagination" class="base-table-footer" ref="refTableFooter">
<slot name="tableFooter"></slot>
<el-pagination
background
:total="total"
:current-page="page"
:page-size="pageSize"
:page-sizes="pageSizes"
prev-text="上一页"
next-text="下一页"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="prev,pager,next"
>
</el-pagination>
</div>
</div>
</template>
<script>
/*
@author
@name Table
@desc 基础表格组件[搜索条件+内容+翻页区域]
@props table object<{ 表格配置,参考el-table;无需定义data,否则将覆盖组件内data,仅扩展以下属性
page number 默认页码[1]
pageSize number 默认页码条数[10]
selection boolean 是否可筛选
}>
checkboxSelect function 当selection为true即可筛选时的禁用
params Array<{ 表格请求所需参数,组件将自动添加页码
component string 全局组件 global-component 及 TableHeader 已导入组件
label string 表单项label
type string 组件type属性
key string 参数key
placeholder string placeholder
options array<{
label string label
value string value
}>
}>
options object<{ options
label string label
value string value
}>
paramsGroups object< 参数组
inputs Array<{ 输入参数(默认第一个
label string 选择的label
value string 选择的value
}>
select Array<{ 选择参数(默认第一个
label string 选择的label
value string 选择的value
}>
ranges Array<{
label string 选择的label
keys Array<string> 参数对应的key,顺序排列
component string 对应的组件名称
}>
options object<Val> select对应的选项对象,key为select对应的value
Array<{ select对应的选项
label string 选项的label
value strine 选项的value
}>
>
headers array<{ 自定义表头
field string 字段名
dataField string 字段名
fieldName string 字段中文名
sort boolean 是否可排序
sortBy String/Array/Function(row, index) 参考el-table
sortMethod Function(a, b) 参考el-table
}>
columns object<{ 表格列配置,参考el-table;如接口返回表头,则将会对表头进行合并,无需重复定义label,如果headers中未包含colums中的属性,则会将未包含的属性放置到最后。仅扩展以下属性
renderFun function render函数,scope 作为入参
}>
pageKeys object<{ 页码和每页数量的key
page string 页码 default page
size string 每页数量 default pageSize
}>
fetcher function 获取数据的方法
searchBtn boolean 是否显示搜索按钮[true]
resetBtn boolean 是否显示重置按钮[true]
showHeader boolean 是否在没有headers的时候显示头部
pagination boolean 是否需要分页[true]
@slot tableHeader 表头slot
tableHeaderRight 表头搜索按钮旁slot,用于添加按钮等
tableFooter 表脚内容,用于添加底部按钮等
@refMethod setParamsVal object 设置params参数值,key为params的key(必须转JSON),val为params的值,会自动根据key的类型进行赋值
@emit
@waring 上方部分属性暴露出来需要直接绑定,切勿全部使用 elAttr ,存在属性覆盖导致某些属性绑定失败
*/
import TableHeader from "./TableHeader";
import FuncComponent from "@/components/App/FuncComponent";
export default {
name: "Table",
props: {
table: {
type: Object,
default: () => ({}),
},
params: {
type: Array,
default: () => [],
},
options: {
type: Object,
default: () => ({}),
},
headers: {
type: Array,
default: () => [],
},
columns: {
type: Object,
default: () => ({}),
},
fetcher: {
type: Function,
},
searchBtn: {
type: Boolean,
default: true,
},
resetBtn: {
type: Boolean,
default: true,
},
pageKeys: {
type: Object,
default: () => ({
page: "current",
size: "size",
}),
},
paramsGroups: {
type: Object,
default: () => null,
},
pagination: {
type: Boolean,
default: true,
},
showHeader: {
type: Boolean,
default: false,
},
checkboxSelect: {
type: Function,
default: () => {
return true;
},
},
},
components: {
TableHeader,
FuncComponent,
},
data() {
return {
total: 0,
fixed: false,
height: null,
tableData: [],
fieldName: "",
fieldSort: "",
loading: false,
tableColumns: [],
page: this.table.page || 1,
pageSize: this.table.pageSize || 10,
pageSizes: this.table.pageSizes || [10, 20, 50, 100],
};
},
activated() {
this.calcHeight();
this.scrollTop();
},
mounted() {
for (let p in this.columns) {
if (this.columns[p].fixed) {
this.fixed = true;
break;
}
}
if (this.fetcher) this.loadData();
if (this.fixed) this.scrollHandler(1);
window.addEventListener("resize", this.calcHeight);
},
beforeDestroy() {
if (this.fixed) this.scrollHandler(0);
window.removeEventListener("resize", this.calcHeight);
},
methods: {
mergeColumns(res) {
if (this.headers.length) {
const fields = [];
const tableColumns = this.headers.map((item) => {
fields.push(item.field);
// console.log(item);
return {
...item,
prop: item.field,
// sortable: item.sort,
label: item.fieldName,
"sort-by": item.sortBy,
...this.columns[item.field],
"sort-method": item.sortMethod,
// sortable: item.sort ? "custom" : false,
// sortable:'custom',
type: item.field === "index" ? "index" : null,
width: item.field === "index" ? "62px" : item.width,
index: item.field === "index" ? this.indexMethod : null,
};
});
for (let p in this.columns) {
if (!fields.includes(p)) {
tableColumns.push(this.columns[p]);
}
}
this.tableColumns = tableColumns;
// console.log(this.tableColumns);
} else {
if (!this.tableColumns.length) {
const fields = [];
const tableColumns = res.headers.map((item) => {
fields.push(item.field);
return {
...item,
prop: item.field,
// sortable: item.sort,
label: item.fieldName,
"sort-by": item.sortBy,
...this.columns[item.field],
"sort-method": item.sortMethod,
type: item.field === "index" ? "index" : null,
width: item.field === "index" ? "62px" : item.width,
index: item.field === "index" ? this.indexMethod : null,
};
});
for (let p in this.columns) {
if (!fields.includes(p)) {
tableColumns.push(this.columns[p]);
}
}
this.tableColumns = tableColumns;
}
}
},
async loadData(page) {
this.loading = true;
if (page) this.page = page;
try {
const params = {
[this.pageKeys.page]: this.page,
[this.pageKeys.size]: this.pageSize,
};
if (this.fieldSort) {
params.orderBy = this.fieldName;
params.orderByType = this.fieldSort;
}
if (this.$refs.refTableHeader) {
const data = this.$refs.refTableHeader.data;
for (let p in data) {
if (/^param-/.test(p)) {
const index = parseInt(p.substring(6));
if (this.params[index] && Array.isArray(this.params[index].key)) {
this.params[index].key.forEach((key, _index) => {
params[key] = data[p]?.[_index] || "";
});
} else {
for (let _p in data[p]) {
params[_p] = data[p][_p];
}
}
} else {
params[p] = data[p];
// eslint-disable-next-line valid-typeof
if (typeof data[p] == "object" || typeof data[p] == "array") {
params[p] = data[p].join(",");
}
}
}
// console.log(data);
}
//console.log(params);
let res = await this.fetcher(params);
setTimeout(() => {
if (!res) {
res = { data: [] };
}
this.tableData = res.data || res.records;
this.total = res.total;
this.loading = false;
this.mergeColumns(res);
this.calcHeight();
this.scrollTop();
}, 300);
} catch (e) {
// console.log(e);
this.loading = false;
}
},
//手动修改data
async changeData(data) {
this.tableData = data;
this.mergeColumns();
this.calcHeight();
this.scrollTop();
// console.log('手动修改data',data);
},
sortChange({ column, prop, order }) {
// console.log(column, prop, order);
this.fieldName = column.sortBy || prop;
if (order === "ascending") {
this.fieldSort = "ASC";
} else if (order === "descending") {
this.fieldSort = "DESC";
} else {
this.fieldSort = order;
}
this.handleCurrentChange(1);
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.loadData();
},
handleCurrentChange(page) {
this.page = page;
this.loadData();
},
// 序号
indexMethod(index) {
// return (this.page - 1) * this.pageSize + index + 1;
return index + 1;
},
// 设置params参数值
setParamsVal(val) {
this.$refs.refTableHeader.setParamsVal(val);
},
// 计算高度
calcHeight() {
this.height = "";
setTimeout(() => {
const _offsetHeight = this.$refs.refTableBody.offsetHeight;
this.height = _offsetHeight - 4 + "px";
}, 1);
},
// 滚动到顶部
scrollTop() {
const innerTable = this.$refs.refInnerTable.$el;
const bodyWrapper = innerTable.querySelector(".el-table__body-wrapper");
bodyWrapper.scrollTop = 0;
},
// 滚动处理函数
scrollHandler(bind) {
const _self = this;
const innerTable = this.$refs.refInnerTable.$el;
const doLayout = () => {
_self.$nextTick(() => _self.$refs.refInnerTable.doLayout());
};
const bodyWrapper = innerTable.querySelector(".el-table__body-wrapper");
if (bind) {
bodyWrapper.addEventListener("scroll", doLayout);
} else {
bodyWrapper.removeEventListener("scroll", doLayout);
}
},
clearSelection() {
this.$refs.refInnerTable.clearSelection();
},
toggleSelection(rows) {
if (rows) {
rows.forEach((row) => {
this.$refs.refInnerTable.toggleRowSelection(row);
});
} else {
this.$refs.refInnerTable.clearSelection();
}
},
},
};
</script>
<style lang="scss" scoped>
.base-table {
height: 100%;
display: flex;
overflow: hidden;
position: relative;
flex-direction: column;
::v-deep {
.el-table {
background-color: transparent;
tr {
background-color: transparent;
}
.el-table__row {
td {
&:first-child {
border-left: 1px solid #e5e5e5;
}
&:last-child {
border-right: 1px solid #e5e5e5;
}
}
}
}
.el-table th.el-table__cell {
background-color: #e3edfc;
color: #182233;
}
.el-table__fixed::before,
.el-table__fixed-right::before {
display: none;
}
.el-table::before {
display: none;
}
}
}
.base-table-body {
flex: 1;
overflow: hidden;
// padding-bottom: 72px;
background-color: #fff;
// border-bottom: 1px solid #e4e7ed;
}
.base-table-footer {
bottom: 0;
width: 100%;
padding: 15px 0;
background-color: #fff;
&::v-deep {
.el-pagination {
padding: 0px 5px;
display: flex;
justify-content: flex-start;
font-weight: initial;
}
.el-pager li,
.el-pagination button {
min-width: 28px;
margin: 0 4px;
border-radius: 2px;
}
.el-pagination .btn-prev,
.el-pagination .btn-next {
padding-right: 6px;
padding-left: 6px;
}
.el-pagination .el-pagination__sizes .el-input__inner {
border: none;
background: #eee;
color: #666666;
}
}
}
</style>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment