最近在做一个后台,记录一些问题和学习的成果
更改element ui主题
UI图跟默认主题不一样的话可以去修改主题,大概操作就是官网配置然后导入文件到项目中.具体可以搜索关键字:element ui定制主题
后台顶部的排版问题
目前我得到两种方案栅格排版 两种方案的清空表单都是使用的如下方式.
this.formTop = this.$options.data.call(this).formTop;
一:以前一直使用的el-form然后使用自带的el-row-el-col栅格排版
具体方案(暂空)
二: 直接使用el-input 然后el-row-el-row栅格排版
具体方案
<template>
<div class="app-container">
<!-- 主体部分 -->
<template v-if="isHome">
<!-- 搜索框 -->
<el-card>
<div class="search">
<div class="search_r">
<el-form class="elForm" :model="formTop" label-width="auto">
<el-row>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem w-all h-all">
<div class="info w-all h-all">
<div class="name">姓名:</div>
<el-input
v-model="formTop.name"
placeholder="请输入客户姓名"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem w-all h-all">
<div class="info w-all h-all">
<div class="name">电话:</div>
<el-input
v-model="formTop.tel"
placeholder="请输入客户电话"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">性别:</div>
<el-select
clearable
v-model="formTop.sex"
placeholder="请选择客户性别"
>
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">拒绝产品:</div>
<el-input
v-model="formTop.rejectedProduct"
placeholder="请输入已拒绝产品"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">归属人:</div>
<el-input
v-model="formTop.salesmanName"
placeholder="请输入归属人"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">婚姻状况:</div>
<el-select
clearable
v-model="formTop.marital"
placeholder="请选择客户婚姻状况"
>
<el-option label="未婚" value="未婚"></el-option>
<el-option label="已婚" value="已婚"></el-option>
</el-select>
</div></div
></el-col>
<template v-if="isMore">
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">客户类型:</div>
<el-select
clearable
v-model="formTop.type"
placeholder="请选择客户类型"
>
<el-option label="男" value="shanghai"></el-option>
<el-option label="女" value="beijing"></el-option>
</el-select>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">状态:</div>
<el-select
clearable
v-model="formTop.status"
placeholder="请选择客户状态"
>
<el-option label="未分配[跟进]" value="0"></el-option>
<el-option label="未分配[邀约]" value="1"></el-option>
<el-option label="未分配[签约]" value="2"></el-option>
</el-select>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">情况备注:</div>
<el-input
v-model="formTop.remark"
placeholder="请输入情况备注"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">备注1:</div>
<el-input
v-model="formTop.extraRemarkOne"
placeholder="请输入情况备注1"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">备注2:</div>
<el-input
v-model="formTop.extraRemarkTwo"
placeholder="请输入情况备注2"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">身份证号:</div>
<el-input
v-model="formTop.idCard"
placeholder="请输入客户身份证号"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">地址:</div>
<el-input
v-model="formTop.address"
placeholder="请输入客户常驻地址"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">客户标签:</div>
<el-input
v-model="formTop.label"
placeholder="请输入客户标签"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">推荐产品:</div>
<el-input
v-model="formTop.recommendProductName"
placeholder="请输入推荐产品名称"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">推荐额度:</div>
<el-input
v-model="formTop.recommendProductAmount"
placeholder="请输入推荐产品额度"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">推荐银行:</div>
<el-input
v-model="formTop.recommendProductBank"
placeholder="请输入推荐产品银行"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">推荐类型:</div>
<el-select
clearable
v-model="formTop.recommendProductType"
placeholder="请选择推荐产品类型"
>
<el-option label="抵押" value="0"></el-option>
<el-option label="信贷" value="1"></el-option>
</el-select>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">成交类型:</div>
<el-select
clearable
v-model="formTop.transactionProductType"
placeholder="请选择成交产品类型"
>
<el-option label="抵押" value="0"></el-option>
<el-option label="信贷" value="1"></el-option>
</el-select>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">成交产品:</div>
<el-input
v-model="formTop.transactionProductName"
placeholder="请输入成交产品名称"
></el-input>
</div>
</div>
</el-col>
<!-- <el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">成交期限:</div>
<el-input
v-model="formTop.tel"
placeholder="请输入成交产品期限"
></el-input>
</div>
</div>
</el-col> -->
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">成交额度:</div>
<el-input
v-model="formTop.transactionProductAmount"
placeholder="请输入成交产品额度"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">成交银行:</div>
<el-input
v-model="formTop.transactionProductBank"
placeholder="请输入成交产品银行"
></el-input>
</div>
</div>
</el-col>
<!-- <el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">还款金额:</div>
<el-input
v-model="formTop.tel"
placeholder="请输入成交产品还款金额"
></el-input>
</div>
</div>
</el-col>
<el-col :xl="6" :lg="8" :md="12" :sm="12">
<div class="gpitem">
<div class="info w-all h-all">
<div class="name">还款日期:</div>
<el-date-picker
v-model="formTop.tel"
type="date"
placeholder="请选择成交产品还款日期"
>
</el-date-picker>
</div>
</div>
</el-col> -->
</template>
</el-row>
</el-form>
</div>
<div class="search_l">
<div>
<el-button
@click.native="onSubmit"
type="primary"
icon="el-icon-search"
>查询</el-button
>
</div>
<div>
<el-button @click="resetForm" icon="el-icon-refresh"
>重置</el-button
>
</div>
</div>
</div>
<div class="expansion">
<span @click="isMoreHandler"
>{{ expansionText
}}<i
:class="isMore ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"
></i
></span>
</div>
</el-card>
<!-- 中条 -->
<displayCard
@fastHandler="fastHandler"
:showArray="showArray"
></displayCard>
<!-- 列表 -->
<el-card>
<div style="margin-bottom: 20px" class="tabTopBtu">
<!-- 按钮 -->
<div>
<el-button @click.native="handleAddClick" type="primary"
>新增客户</el-button
>
</div>
<div class="flex">
<!-- <el-button>列设置</el-button> -->
<zdyColumn
:columnSettings="columnSettings"
:category="pageKey"
@refreshList="refreshList"
></zdyColumn>
</div>
</div>
<div v-loading="tebLoading">
<el-table
:data="tableData"
ref="crmtTab"
v-loading="loading"
style="width: 100%"
:cell-style="{ height: '73px' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection"></el-table-column>
<template v-for="item in columnSettings">
<el-table-column
:key="item.id"
v-if="item.isShow"
:prop="item.field"
:label="item.description"
align="center"
>
</el-table-column>
</template>
<el-table-column
fixed="right"
label="操作"
align="center"
width="120"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="handleViewClick(scope.row)"
>查看</el-button
>
<el-button
@click="aAndModify(scope.row)"
type="text"
size="small"
>修改</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<!-- @size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="currentPage1"-->
<div style="margin: 20px 0px">
<el-pagination
:page-size="page.pageSize"
background
@current-change="handleCurrentChange"
layout="->,total,prev, pager, next"
:total="total"
></el-pagination>
</div>
</el-card>
</template>
<!-- 详情部分 -->
<template v-else>
<!-- 详情 -->
<followUp @goHome="goHome"></followUp>
</template>
<!-- 新增修改 -->
<clientEd ref="clientEd"></clientEd>
</div>
</template>
<style lang="scss" scoped>
// 搜索组件样式
::v-deep.search {
display: flex;
.el-form-item__label {
word-wrap: break-word;
}
.search_r {
width: 85%;
input {
width: 220px;
}
}
.search_l {
width: 15%;
height: 100px;
border-left: 1px solid #e5e6eb;
display: flex;
flex-flow: column;
align-items: center;
justify-content: space-around;
& > div {
margin-top: 10px;
}
}
}
.tabTopBtu {
display: flex;
justify-content: space-between;
}
.elForm {
width: 100%;
flex-wrap: wrap;
// justify-content: space-between;
// justify-content: flex-start;//默认值
display: flex;
padding: 0px 5px;
.gpitem {
margin-bottom: 20px;
// flex: 30%;
display: flex;
align-items: center;
justify-content: center;
.info {
display: flex;
align-items: center;
justify-content: center;
.name {
text-align: right;
margin-right: 12px;
// width: auto;
// min-width: 170px;
min-width: calc(100% - 230px);
color: #303133;
font-weight: 700;
font-size: 14px;
}
}
}
}
.w-all {
width: 100%;
}
.h-all {
height: 100%;
}
</style>
总结:总的来说都差不多,只需要关注一下长度不一致的情况(一般是下拉框还有时间日期组件).下次希望使用json来配置这块,成为个人组件库的一部分.
列设置
这个方案在网上很多,我们使用的方案有很多瑕疵.我感觉比较好的有后端有两个接口,一个设置列设置的显示隐藏状态,一个就是当前返回的请求列表字段中有这个字段的布尔值,这样前端的操作性非常大,可以循环判断这个布尔值(需要后端带上这个对象字段的汉字名称)然后是否展示也可以直接老样子写模板.需要注意的是一般要使用v-if去控制否则无法生效,可能是组件的css问题.
这里大概介绍下我们的方案做一个反面案例
后端会给三个接口
- 设置整个系统的列设置.这个接口我们叫做列设置.作用主要是勾选列之后将勾选的字段设置进入当前可展示的接口数据内.
- 获取当前页面的所有字段,这个接口可以获取这个页面能展示的所有字段.当然是对象数组的数据结构,内部要有可以跟请求回来的列表数据对应的prpo值
- 获取本页面当前可展示的字段.这个接口跟第一个接口是联动的,第一个接口设置了之后这边才会出现(这也是缺点非常大的地方,很不合理)
接下来请求到数据后会利用当前页面的所有字段充当列设置的模板字段
这里我直接封装了一个组件代码如下
<template>
<!--列设置按钮-->
<div>
<el-button
style="margin-left: 20px"
@click="drawer = true"
icon="el-icon-s-operation"
>列设置</el-button
>
<el-drawer
title="列设置"
:visible.sync="drawer"
:direction="direction"
:before-close="handleClose"
>
<el-switch
style="margin: 20px"
v-model="isAll"
inactive-text="全选"
active-color="#13ce66"
@change="changeColumnAll"
>
</el-switch>
<!-- 展示列表 -->
<el-table :data="columns" style="width: 100%">
<el-table-column
type="index"
align="center"
label="列名"
width="100"
></el-table-column>
<el-table-column
prop="description"
align="center"
label="显示名称"
width="150"
></el-table-column>
<el-table-column label="显示" align="center">
<template slot-scope="scope">
<el-switch
v-model="scope.row.isShow"
active-color="#13ce66"
@change="changeColumn"
></el-switch>
</template>
</el-table-column>
</el-table>
</el-drawer>
</div>
</template>
<script>
// 按需引入lodash深拷贝
import cloneDeep from "lodash/cloneDeep";
import { menuUpdate } from "@/api/column";
export default {
name: "zdyColumn",
props: {
columnSettings: { //传入的列设置数据
type: Array,
default: () => [],
},
category: { //因为需要分页面去设置字段这里是设置的页面分类值
type: String,
default: "",
},
},
data() {
return {
drawer: false,
direction: "rtl",
// 表格展示配置
columns: [],
requestData: {//后端的要求必须包上一个requestData
codes: [],
category: "",
},
isRefresh: false, //选中状态改变
isAll: false,
};
},
watch: {
columnSettings: {
immediate: true, // watch侦听操作内的函数会立刻被执行
deep: true,
handler(newColumns) {
this.columns = cloneDeep(newColumns);//第一步就深拷贝传入的列设置数据
this.linkage(this.columns); //给全选按钮赋值
this.requestData.category = this.category;
},
},
},
methods: {
// 关闭回调
handleClose() {
this.drawer = false;
this.changeHandler();
},
//在此发请求修改列设置(在关闭回调中使用)
async changeHandler() {
if (!this.isRefresh) {
//如果根本没修改过不发请求
return;
}
this.isRefresh = false;
this.requestData.codes = this.columns
.filter((item) => item.isShow)
.map((element) => element.id);
try {
const { message } = await menuUpdate({ requestData: this.requestData });
this.$message({
message: message,
type: "success",
});
// 刷新父组件页面
this.$emit("refreshList");
} catch (error) {
console.log(error);
}
},
// 选中状态改变的回调
changeColumn() {
if (!this.isRefresh) {
this.isRefresh = true;
}
this.linkage(this.columns); //给全选按钮赋值
},
// 全选
changeColumnAll(value) {
if (!this.isRefresh) {
this.isRefresh = true;
}
if (value) {
this.columns.forEach((item) => {
item.isShow = true; //复杂数据类型会被修改
});
} else {
this.columns.forEach((item) => {
item.isShow = false; //复杂数据类型会被修改
});
}
},
// 全选联动
linkage(value) {
const isAll = value.some((item) => item.isShow === false);
if (!isAll) {
this.isAll = true;
} else {
this.isAll = false;
}
},
},
};
</script>
<style lang="scss" scoped></style>
请求回来的数据用工具函数做了处理代码如下
// 从系统所有菜单中筛选当前菜单的字段
// 参数:所有字段,筛选关键字比如0,1
export const filterField = (field, params) => {
return field.filter((item) => item.catagory === params);
};//这个地方的处理主要是这个数据是后端给的接口获取的当前页面字段需要自己去筛选,
//默认返回全系统的,也就是系统比较小,大一点的话应该无法使用这种方案需要后端提前处理好
//列设置字段
// 简洁版
//参数:当前页面全部字段,当前页面已经设置的字段
export const filterColumn = (all, part) => {
return all.map((item) => {
return {
...item,
//使用some方法找数组中是否有这个属性,找到返回true找不到返回false
isShow: part.some((element) => element.description === item.description),
};
});
};
//这个地方这么处理主要是后端未添加是否显示的标识字段我只能自己加一个
还有非常多的方案可以自行去搜索,最好还是可以本地缓存减少服务器压力的同时也能快速的进行列设置
字段转义
前后端交互中我们有大量的字段可能是给后端传数字前端展示汉字,比如性别的0和1传给后端但是前端展示男和女.但是回显的时候如果后端给我们直接返回0和1我们就需要去做转义.一般的情况下我们会去模板中做三元运算符的转义或者是加入表格组件之前处理.但是这样需要请求后端接口获取这个值的对应关系.我们这个项目后端统一给了一个接口,返回的0和1就是key值可以直接取出来,非常好用. 下图为接口数据结构
这个接口的请求我放在了获取基本信息之后,并未做持久化而是随着用户刷新来重新请求.(在路由守卫中做了处理),然后存入vuex,这样就可以每个页面都使用了
if (hasToken) {
if (to.path === "/login") {
// if is logged in, redirect to the home page
next({ path: "/" });
NProgress.done();
} else {
const hasGetUserInfo = store.getters.name;
if (hasGetUserInfo) {
next();
} else {
try {
// get user info
await store.dispatch("user/getInfo");
await store.dispatch("user/getPull");//获取下拉对应的数据
// next();
next({ ...to, replace: true });
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch("user/resetToken");
Message.error(error || "Has Error");
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
}
工具函数代码
export const escapePro = (field) => {
return store.state.user.mySelect[field]
? store.state.user.mySelect[field].name
: field; //有?就找name,没有就返回原值
};//直接使用vuex
export const escape = (online, field) => {
// return field;
return online[field] ? online[field].name : field; //有?就找name,没有就返回原值
};//值由外部传入
后续发现这其实是一种处理这种业务的方式,比如字典处理,一个接口传递不同的值来获取不同的数据列表
列表请求的分页问题
我们这个系统需要很多的快捷跳转(点击某个按钮就做筛选),但是接口并不统一而是需要去掉用别的接口,所幸返回值基本相同不用特殊处理.这样会有一个分页问题,假如你点了快捷查询的按钮那么分页的时候就不能调用列表查询的接口.所以我们可以用一个变量来存储请求列表数据的方法,在查询或者点击快捷查询的时候将本函数的地址赋值过去.然后在分页请求的时候直接调用这个变量就可以了
新增编辑区分问题
- 以往我区分新增编辑的时候总喜欢通过按钮传参来去区分,比如新增的按钮传"新增"编辑的传"编辑".然后去修改弹窗的title.这样在点击按钮的时候去区分是有用的,当然我们也可以不区分而是绑定通过不同的事件去区分.
- 一般我们会把新增或者编辑的表单划分为一个单独的组件.这个组件的新增编可能会共用一个点击按钮一个点击事件.平常我是用计算属性去判断title来区分要去调用那个接口,但现在我发现这样是比较多余的,完全可以判断id是否存在来区分新增还是编辑.
数据入参前的处理
- 当我们提交一个表单数据之前可能需要多次的去处理这个数据,但是依然可能无法通过后端的校验或者需求的变更,从而需要再次的去修改代码.这时候我们在处理提交数据的过程中保持源数据的干净就十分重要!无论如何不要去修改源数据而是去处理源数据的拷贝,这样重复的提交中结果是恒定的,而不会因为上次的提交失败造成数据修改的副作用导致修改代码非常困难!