上传代码的时候,记得把一些全局的配置包括:package.json,以及一些console.log代码删除,其次考虑代码冗余,不得随意篡改老代码及其样式,保证样式的美观,仿照别人代码进行配置的同时进行代码优化,减少不必要的操作,运用框架的时候,尽量少用面向对象的原生方法来获取数据,例如:获取table组件的某个单元格信息,第一时间想到的必然是事件代理,获取点击target,通过向上查找父节点的兄弟节点来获取目标单元格的数据,没有充分利用组件的方便,而且通过事件代理触发的变更只是前端页面上的变更,并不能做到数据的变更,而是对页面标签的一些属性的属性值进行表面上的更改,当页面刷新的时候,之前操作所修改的值就会消失,所以我们要充分利用组件提供的record以及scope.row属性,若为antd-vue组件即可使用record.xxx来获取指定单元格的数据,若为饿了么UI,则可以使用scope.row.xxx来获取改行单元格的数据,以减少代码的冗余性,提高代码的可读性。多次做分页功能,当需要做改变页面展示条数的时候,antd-vue需要的onTableChange可以如此写:
onTableChange({current,pageSize}){this.pageSize:pageSize,this.getDataList(current)}(参数有大括号是因为table组件动态绑定的pagination属性是一个对象,具体见项目)下边的请求方法要把pageNum和pageSize传入。事件BUS不建议或者不用,因为事件bus需要全局引入,组件传值可以使用this.$emit(“事件姓名”,所传的数据),在接收的那一边将事件注册在组件上,可以实现组件之间传值,减少代码的冗余,参数在页面展示的时候需要注意:当dom刚加载的时候,没有参数的情况下,传的值是undefined,会降低用户的体验度,解决方法:增加三元判断,如果值存在即显示值,若不存在,即赋一个默认值为0或者""(依据情况而定)。交流中获得的知识:router.resolve类似与router-link实现打开一个新页面进行跳转。切记,给接口传参数的时候,必须传一个对象,而不是一个数组!若要限制一个日期组件的时间区间。可以使用moment函数,例如:uploadTime:[moment().subtract(90, ‘days’), moment()],设置完之后,可以实现选择了前面的日期之后,会自动的选择后90天的日期。当你想禁用今天后面的日期被选中的时候可以使用日期框的disabledDate方法,其中的操作如下:
disabledDate (current) { // 禁用 今天以后的日期
return current >= moment().endOf(‘day’)
},
当点击Link文本根据获取的url进行跳转的时候,我们可以使用window.open(record.xxxxxUrl)进行页面的跳转(此处拿antd-vue进行举例),展示数据的时候要注意,如果接收的某个字段没有数据,换个说法就是如果值为空,则需要给它赋一个默认值,这样做的原因是:如果这是个table组件,个别单元格没有值,那么就会缩在一起,可能导致表头和值不匹配,这是需要写个三元表达式对接收的值进行判断,如果值是空,则设置一个默认值为空字符串,如果值存在,则展示该值。
经验总结:
对比饿了么UI和antd-vue的table组件,饿了么UI将column写在vue页面,antd-vue可以采用动态绑定将column以:columns=columns的方式,可以在当前页进行定义,也可以写一个columns.js文件进行引入,数据插入采用插槽,例如:scopedSlots: { customRender: ‘xxx’ },
当涉及到父子传值的时候,可以将对应的值绑定在子组件上,在子组件上可以如此操作,例如:
父组件中在子组件上如此绑定:
:inviteResultList = “inviteResultList”
在子组件中:
props: {
visible: Boolean,
inviteResultList: {
type: Array,
default: () => [],
},
测试接口的时候,多打开控制台查看Network数据包,出错根据状态码进行分析,获取不到数据的时候,一看状态码,二看preview参数,提高效率。既然使用组件,就充分利用组件文档里的API,减少多余的操作,提高工作效率。当后端接口传过来一个status为一个数字,但你需要展示的是一个string文字状态的时候,可以写一个js进行转换,类似于:
function makeOption (obj) {
return Object.entries(obj).map(([key, val]) => ({ label: val, value: key }))
}
export const signalMap = {
0: ‘短途高活’,
1: ‘非高活’,
2: ‘中高活’
}
export const signalList = makeOption(signalMap)
当一个dialog被一到两个页面复用,并且,两个页面所展示的columns不一样,可以使用lodash中的remove方法:
首先引入lodash方法:
import { remove } from ‘lodash’
columns () {
const displayColumns = cloneDeep(columns)
if (this.from === ‘signIn’) {
remove(displayColumns, (column) => (
column.key === ‘inviteRemark’
))
}
return displayColumns
}
},
当需要对数组对象进行深拷贝的时候可以使用lodash的clonedeep方法进行深拷贝,例如利用深拷贝,对查询按钮进行操作:
首先同上lodash的remove方法进行引入
handleSubmit () {
if (!this.validate()) return
this.cacheForm = cloneDeep(this.form)
this.getTableList(1)
},
喇叭需求经验总结:在表头添加任意icon和tooltip怎么实现?
在columns.js文件中对应column的scopeslots:{customRender:‘xxxxx’}中添加字段后变为:{ customRender: ‘stickerInfo’, filterIcon: ‘filterIcon’ },并且添加属性:filterDropdownVisible: true, filterDropdown: true。
在.vue文件中相应部分添加如下代码,此处拿问好icon举例:
下面展示一些 内联代码片
。
<template slot="filterIcon">
<a-tooltip placement="bottom">
<template slot="title">
近30天的接单/完单数据
</template>
<a-icon type="question-circle"/>
</a-tooltip>
</template>
此时,该问号icon在该表头框的最右边,那么如何修改样式把该icon紧挨着文件?
首先打开控制台,查看他的element组成,尝试着直接在.vue文件中通过其class名进行修改(基本失败,多次尝试),这时我们会发现是因为权重不够,那么尝试这添加类名增加权重(无用功),或者在该样式的尾部加上!important将此样式的权重改为最高,发现也是无用功,这时应该在assets的common.css中写全局引入样式,问题找到了,现在看怎么改,我们不难发现,该icon是个嵌套在块级盒子里的span标签,并且该标签添加了绝对定位脱离了文档流,这时只需取消right:0即可,只需一条命令right:unset即可取消脱离文档流之后icon所在标签位置在该单元格的最右边。
当你使用的是antd-vue做需求,且需求上要求的是table的列表项可筛选,那么如何实现?
首先查找文档,不难发现,table组件有一个rowSelection属性,使得列表项可选择,接收的是一个对象,即在table组件中加入::row-selection=“rowSelection”,接着传入一个对象,对象通常包括如下属性和属性值,以及方法:
hideDefaultSelections 去掉全选,反选两个默认选项 接收boolean,默认为false
selectedRowKeys 指定选中项的key数组,需要和onchange进行配合 接受一个string类型
onChange 选中项发生变化时触发的回调 Function(selectedRowKeys, selectedRows)
onSelect 用户手动选择/取消某列触发的回调 Function(record, selected, selectedRows)
例如邀约优化中的指派删除的逻辑:
下面展示一些 内联代码片
。
onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
if (selectedRowKeys.length == this.tableData.length) {
this.selectUserIds = []
this.selectUserMobilesForSms = []
this.assignSelectItems = []
this.deleteSelectItems = []
for (let i = 0; i < selectedRows.length; i++) {
this.selectUserIds.push(selectedRows[i].driverInfo.userId)
this.selectUserMobilesForSms.push(
selectedRows[i].driverInfo.driverMobileForSms
)
const deleteItem = {
driverId: selectedRows[i].driverInfo.userId
}
// 指派
const assignItem = {
userId: selectedRows[i].driverInfo.userId,
storeId: selectedRows[i].storeId,
taskId: selectedRows[i].taskId,
taskStatus: selectedRows[i].taskStatus,
phone:
selectedRows[i].driverInfo.driverMobileForSms.slice(3, -3) || "",
}
this.assignSelectItems.push(assignItem)
this.deleteSelectItems.push(Object.values(deleteItem))
}
} else if (selectedRowKeys.length == 0) {
this.selectUserIds = []
this.selectUserMobilesForSms = []
this.assignSelectItems = []
this.deleteSelectItems = []
}
},
onSelectRows (record, selected) {
if (
this.selectedRowKeys.length == 0 ||
this.selectedRowKeys.length == this.tableData.length
) {
return
}
if (selected) {
// 选中,记录userId和mobile
this.selectUserIds.push(record.driverInfo.userId)
this.selectUserMobilesForSms.push(record.driverInfo.driverMobileForSms)
const deleteItem = {
driverId: record.driverInfo.userId
}
// 指派
const assignItem = {
userId: record.driverInfo.userId,
storeId: record.storeId,
taskId: record.taskId,
taskStatus: record.taskStatus,
phone: record.driverInfo.driverMobileForSms.slice(3, -3) || "",
}
this.assignSelectItems.push(assignItem)
this.deleteSelectItems.push(Object.values(deleteItem))
} else {
// 取消选中,删除userId和mobile
const userIdValue = record.driverInfo.userId
const userMobileValue = record.driverInfo.driverMobileForSms
this.selectUserIds.length &&
this.selectUserIds.map((item, index) => {
if (item == userIdValue) {
this.selectUserIds.splice(index, 1)
}
})
this.selectUserMobilesForSms.length &&
this.selectUserMobilesForSms.map((item, index) => {
if (item == userMobileValue) {
this.selectUserMobilesForSms.splice(index, 1)
}
})
this.assignSelectItems.length &&
this.assignSelectItems.map((item, index) => {
if (item.userId == userIdValue) {
this.assignSelectItems.splice(index, 1)
}
})
this.deleteSelectItems.length && this.deleteSelectItems.map((item, index) => {
if (item == userIdValue) {
this.deleteSelectItems.splice(index, 1)
}
})
}
},
当项目运行时,利用项目运行后的ip地址进行登录,若反复登录不上,首先检查host的配置,若不行,则检查vue.config.js,试着改变端口号,防止由于端口号被占用,其次可能是ip地址未加入到本机host当中去,可以将host改成自己配置的host的ip地址。
Tips:
input框的type有很多种,可以根据需求设置type值,例如:上传文件可以将type设置成"file",变成改变年月的可以将type设置成"datetime-local"(h5新属性),甚至还能修改type的值使之变成各类样式的button包括reset、submit等等
刷新时如果提交数据的动作,则会出现对话框,解决方法:
window.location.href=window.location.href;
window.location.reload;
饿了么上传文件的模板:
<el-form ref="form" :inline="true">
<el-form-item label="导入:">
<input type="file" ref="uploadFile" accept="application/xls,application/vnd.ms-excel,application/x-excel" style="width: 180px;">
<el-button size="small" type="primary" @click="submitForm">提交</el-button>
</el-form-item>
</el-form>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<span>导入完成</span>
<span slot="footer" class="dialog-footer">
<a href="/cargo-admin/cargoAssociateWord/downloadLatestImportData" target="_blank">
<el-button type="primary" @click="confirmHandle">确定</el-button>
</a>
</span>
</el-dialog>
JS部分:
// An highlighted block
async submitForm (e) {
let file = this.$refs.uploadFile.files[0]
if (!file) {
this.$message.error('请先选择文件')
return
}
this.$message({ type: 'success', message: '导入中,请稍后!' })
let res = await importFile(file)
this.dialogVisible = true
},
饿了么ui分页问题,模仿其他页面做分页的时候,未注意接口返回的字段里有没有pageNumber和pageSize,导致currentPage始终传默认值1,table组件能够实现变化,但是页码始终跳回第一页,甚至未显示末页的页码,显示的是infinity,解决方法1:让负责接口的后端添加两个返回的字段分别对应pageSize和pageNumber,接收后赋值给pagination中的参数,解决方法2:给pageSize传一个默认值(大于0,尽量为10的倍数,美观),currentPage给一个默认值,从接口接收一个total或者数据的length即可。当创建了本地分支但远程没有该分支的时候,可以使用代码:git push --set-upstream origin 本地分支名,在远程创建一个和本地名字一样的分支源,接着按照代码提交流程进行代码的提交,若有代码更新,切记,先拉取代码,再进行缓存,提交!!!git status查看代码状态,git branch -r查看远程分支,git branch -a查看所有分支,git checkout -b myRelease origin/Release,作用是checkout远程的Release分支,在本地起名为myRelease分支,并切换到本地的myRelase分支
使用pm及dmpt系统,pm创建模块,填写ip地址及权限码,以及审批人之类,创建好之后本地运行项目后打开的网页即有该模块。dmpt系统,项目完成填写项目变更。
检视代码,发现多处method中的方法都加上了async,await,async,await在方法上的作用,不难知道,async,await是用于解决无限嵌套的promise请求,将异步请求以同步的方式执行,那么货源后台项目中为什么要在方法前面使用async,await呢?通过试验得出,由于很多method的触发会有$message的弹窗出现,如果不加上async,await的话,可能能够完成原来方法中的操作,但是看不到弹窗结果,甚至不能及时的刷新table,获取最新的数据。进行模糊搜索的时候,可以尝试去封装一个模糊搜索的组件,所谓的模糊搜索就是一个input框,在里面输入关键字的时候会出现一个下拉框,下拉框里的字段从后端接口获取,其中都是包含该关键字的字段,例如对user进行模糊搜索的组件模板(可以进行修改变成其他内容的模糊搜索,通过增删props里的值,以及按需修改方法名,可以封装成需求的模糊搜索):
<template>
<a-select
mode="multiple"
label-in-value
:value="value"
:placeholder="placeholder"
:filter-option="false"
:not-found-content="fetching ? undefined : null"
:size="size"
:maxTagCount="maxTagCount"
style="min-width: 200px"
@search="fetchUser"
@change="handleChange"
>
<a-spin
v-if="fetching"
slot="notFoundContent"
size="small"
/>
<a-select-option
v-for="d in data"
:key="d.value"
>
{{ d.text }}
</a-select-option>
</a-select>
</template>
<script>
import debounce from 'lodash/debounce'
import Config from '@/config'
import Server from '@/extend/Server'
import { mapState } from 'vuex'
export default {
name: 'SearchUser',
props: {
roleId: { // 【运营负责人1】【大区经理2】【城市经理3】【邀约员4】【培训师5】【前台6】
type: Number | String,
default: null,
},
size: {
type: String,
default: 'default',
},
maxTagCount: {
type: Number,
default: 1,
},
multiple: {
type: Boolean,
default: false,
},
value: {
type: Array,
default: () => [],
},
placeholder: {
type: String,
default: '请输入工号或姓名搜索',
},
storeIdArray: {
type: Array,
default: () => []
},
echoJobNumber: {
type: Boolean,
default: false
},
hideStoreIdList: {
type: Boolean,
default: false
},
sourceType: {
type: Number | String,
default: ''
},
},
data () {
this.lastFetchId = 0
this.fetchUser = debounce(this.fetchUser, 800)
return {
data: [],
fetching: false,
}
},
computed: {
...mapState(['storeInfoList'])
},
methods: {
fetchUser (value) {
if (!value) return
if (!this.multiple && this.value.length === 1) return
this.lastFetchId += 1
const fetchId = this.lastFetchId
this.data = []
this.fetching = true
let storeIdList = []
if (this.hideStoreIdList) {
storeIdList = null
} else {
storeIdList = this.storeIdArray && this.storeIdArray.length ? this.storeIdArray : this.storeInfoList.map((store) => store.id)
}
// 集中处理Payload
let payload = {
storeIdList: storeIdList,
keyWord: value,
roleId: this.roleId,
}
if (this.sourceType || this.sourceType === 0) {
payload = {
...payload,
sourceType: this.sourceType,
}
}
Server({
url: `${Config.apiHost}/api/v1/user/fuzzy/list`,
method: 'POST',
needLoading: false,
ignoreCode: true,
data: payload
}).then(({ data }) => {
if (fetchId !== this.lastFetchId) {
return
}
const userList = (data.data || []).map(user => ({
...user,
text: `${user.name}(${user.jobNumber})`,
value: this.echoJobNumber ? user.jobNumber : user.id.toString(),
})) || []
this.data = userList
if (!this.data.length) this.$message.warning('未搜索到用户')
this.fetching = false
}).catch(e => {
this.$message.warning('未搜索到用户')
this.fetching = false
})
},
handleChange (value) {
value = value.map(element => ({
...this.data.find(user => user.value === element.key),
...element,
}))
this.$emit('input', value)
this.$emit('change', value, this.data)
Object.assign(this, {
data: [],
fetching: false,
})
},
},
}
</script>
codeReview结果总结:
导入外来的文件时,观察有没有返回message类似于:errorMsg字段,将次字段作为this.$message的提示字段
查询的按钮不要使用submit关键字,lodash的get方法:_.get(object, path, [defaultValue]),可以通过该方法,获取目标对象里的某个特点键的值,例如在API的异步请求中使用lodash的get方法:
export const getAbnormalWordList = (pageNumber, pageSize, badCaseName) => {
let param = {
pageNumber: pageNumber,
pageSize: pageSize,
badCaseName: “”
}
if (badCaseName) {
param.badCaseName = badCaseName
}
return Server({
url: ‘badCargoName/badCargoNameList’,
method: ‘post’,
data: param
}).then(resp => {
return {
list: get(resp, ‘data.list’, []),
total: Number(get(resp, ‘data.totalCount’, 0)),
}
})
}
不熟悉react,怎么实现跳转老后台的vue页面?解决:首先在config文件里添加出menu菜单行,其次在siderMenu中的baseRoute写上跳转老后台的路由,path写死,写成老后台对应页面的ip地址,target写为_blank以打开新页面的方式打开对应页面(投机)
已经权限系统配置完了侧栏菜单的权限,需求完成,并且正确打包部署到dev环境,但仅在本地看到新增的侧栏,在dev环境下看不到,这是什么原因?解决:由于dev网页的本地缓存未清理,导致每次登陆都是第一次登陆的信息。
学习:在日历组件上动态渲染数据在对应日期上:
首先它是通过v-for遍历到每个日期格子的。
getListData(value) {
let listData;
//遍历数组
this.testList.forEach(e => {
//让数组的date与遍历到日历的那天的日期值相匹配
if (e.date === value.format(“YYYY-MM-DD”)) {
listData = e.listData;
}
});
return listData || [];
},
从接口获取到日期数据,但我们仅仅想要的部分是月和日部分,怎么办?解决:之前我们使用的是{record.xxxxx | datetime}获取到的是"年月日"的形式,经过尝试,获取"月日"的方法是这样的{record.xxxxx | daytime}从而实现获取日月部分。
需求中要求在今天之前的日期不展示更改框,大于等于今天的展示,怎么实现?方法:首先获取指定时间以及当前时间的时间戳,即Date.parse(new Date()),之后将两者时间戳相减除以天数:(10006060*24)即var day = parseInt((a2-a1)/ (1000 * 60 * 60 * 24)),如果为正的话就显示更改框,如果为负数的话就不显示更改框
let day = new Date()
day = day.toLocaleDateString()
if (day == value.format(“YYYY-MM-DD”)){
this.today = true
console.log(this.today)
}else {
this.today = false
}
获取当前时间戳后如何转换成正常的形式?解决:
let day = new Date()
day = day.toLocaleDateString().replace(///g, ‘-’)
日历控件如何实现在当日的日期上展示“今日”二字?解决:
<template slot="dateCellRender" slot-scope="value">
<div>
<span>{{ getMonthData(value) }}</span>
</div>
</template>
getMonthData (value) {
let day = new Date().getDate()
let month = new Date().getMonth() + 1
let year = new Date().getFullYear()
if (day - 10 < 0) {
day = "0" + day
}
if (month - 10 < 0) {
month = "0" + month
}
let days = year + "-" + month + "-" + day
console.log(days)
let slotData
let obj = {
date: days,
text: "今日"
}
if (obj.date === value.format("YYYY-MM-DD")) {
slotData = obj.text
}
return slotData || ""
},
如何获取一段时间内的所有日期?解决:
用项目举例,项目要求当在日期选择框里选完日期的时候,能够获取到日期区间内所有的日期,并且按照轮询渲染到表格组件上(这部分还没做,待补充)代码如下:
首先是组件部分,选择最为简单的日期选择框:
<a-form-item label="开始排班">
<a-date-picker
v-model="onDutyTime"
ref="onDutyTime"
class="date-pick-input"
allowClear
@change="changeDate"
/>
</a-form-item>
其次是逻辑部分:
getAll(begin, end) {
let arr1= begin.split("-");
let arr2= end.split("-");
let arr1_= new Date();
// let arrTime = [];
arr1_.setUTCFullYear(arr1[0], arr1[1] - 1, arr1[2]);
let arr2_= new Date();
arr2_.setUTCFullYear(arr2[0], arr2[1] - 1, arr2[2]);
let unixDb = arr1_.getTime();
let unixDe = arr2_.getTime();
for (let k = unixDb; k <= unixDe;) {
this.arrTime.push(this.datetimeparse(k, 'MM-DD'));
k = k + 24 * 60 * 60 * 1000;
}
},
datetimeparse (timestamp, format, prefix) {
if (typeof timestamp =='string'){
timestamp=Number(timestamp)
};
//转换时区
let currentZoneTime = new Date (timestamp);
let currentTimestamp = currentZoneTime.getTime ();
let offsetZone = currentZoneTime.getTimezoneOffset () / 60;//如果offsetZone>0是西区,西区晚
let offset = null;
//客户端时间与服务器时间保持一致,固定北京时间东八区。
offset = offsetZone + 8;
currentTimestamp = currentTimestamp + offset * 3600 * 1000
let newtimestamp = null;
if (currentTimestamp) {
if (currentTimestamp.toString ().length === 13) {
newtimestamp = currentTimestamp.toString ()
} else if (currentTimestamp.toString ().length === 10) {
newtimestamp = currentTimestamp + '000'
} else {
newtimestamp = null
}
} else {
newtimestamp = null
}
;
let dateobj = newtimestamp ? new Date (parseInt (newtimestamp)) : new Date ()
let YYYY = dateobj.getFullYear ()
let MM = dateobj.getMonth () > 8 ? dateobj.getMonth () + 1 : '0' + (dateobj.getMonth () + 1)
let DD = dateobj.getDate () > 9 ? dateobj.getDate () : '0' + dateobj.getDate ()
let HH = dateobj.getHours () > 9 ? dateobj.getHours () : '0' + dateobj.getHours ()
let mm = dateobj.getMinutes () > 9 ? dateobj.getMinutes () : '0' + dateobj.getMinutes ()
let ss = dateobj.getSeconds () > 9 ? dateobj.getSeconds () : '0' + dateobj.getSeconds ()
let output = '';
let separator = '/'
if (format) {
separator = format.match (/-/) ? '-' : '/'
output += format.match (/yy/i) ? YYYY : ''
output += format.match (/MM/) ? (output.length ? separator : '') + MM : ''
output += format.match (/dd/i) ? (output.length ? separator : '') + DD : ''
output += format.match (/hh/i) ? (output.length ? ' ' : '') + HH : ''
output += format.match (/mm/) ? (output.length ? ':' : '') + mm : ''
output += format.match (/ss/i) ? (output.length ? ':' : '') + ss : ''
} else {
output += YYYY + separator + MM + separator + DD
}
output = prefix ? (prefix + output) : output
return newtimestamp ? output : ''
},
changeDate (date) {
let days = new Date(date)
console.log(date.format("YYYY-MM-DD"));
let day1 = date.format("YYYY-MM-DD")
days.setDate(days.getDate()+this.number.value)
let day = days.getDate()
let month = days.getMonth() + 1
let year = days.getFullYear()
if (day - 10 < 0) {
day = "0" + day
}
if (month - 10 < 0) {
month = "0" + month
}
let da = year + "-" + month + "-" + day
console.log(da)
this.getAll(day1,da)
console.log(this.arrTime)
},
这样能够成功获取到日期区间内的所有日期,渲染后期待补充!
如何将日历组件作限制,将当日之前的日期置为灰色?解决:
两种方法:1.修改底层,添加让当日日期之前日期置灰的API(鄙人不才,不会,看了下底层是TS,果断放弃治疗),2.用一个块级元素改为透明色,用绝对定位将当日之前所有的灰色大框框定位到日历的框框上,也能达到效果,不过有瑕疵,就是选中的时候那个蓝色不是很明显,因为被半透明的灰色中和掉了(甭管了,先实现效果!没有效果都是空谈!实在不行可以修改全局的样式,将选中呈现的颜色加深一些)实现方法如下:
<a-calendar @select="select">
<template slot="dateCellRender" slot-scope="value">
<div :class="getBackColor(value)"> // 主要部分
<span v-if="getDutyData(value)">{{ getDutyData(value) }}</span>
<li v-for="item in getListData(value)" :key="item.content">
<a-badge :status="item.type" :text="item.content" />
</li>
</div>
</template>
</a-calendar>
逻辑部分:
getBackColor (value) {
return value < moment().startOf('day') ? "events-bofore" : "events"
},
css样式部分:
.events-bofore {
position: absolute;
left: 5px;
top: 0;
right: 5px;
bottom: 0;
width: calc(100%-10px);
height: 100%;
background-color: lightgrey;
opacity: 0.5;
}
.events {
list-style: none;
margin: 0;
padding: 0;
}
通过返回的三元表达式进行判断,勉强实现当日之前的日子置灰,另外还有一个想法:为了比较完美的还原wiki项目上的需求,我们是否可以通过事件,在选中的时候将置灰块级元素盒子的背景色置为transparent,来达到近乎完美还原项目需求?(通过实验证明,在下不才,通过ref修改样式未能成功,待后面解决补充)
日期成功渲染到对应框,并且经过改动能够用少量代码实现各种日期区间的展示,之前出现了这样的问题:通过CSDN的方法所获取到的日期段的日期是一整个数组,数组中的元素是一个个字符串,若要通过record渲染到table上的话,会出现错误,该错误大概意思是需要的是一个对象,而我传送的是一个个字符串,虽然能够成功渲染到页面上(仅仅是一竖排),但是出现的84个同样的错误,也就是渲染的问题,而且要求轮询渲染(以7天为一竖列,超过7天另起一列进行渲染)进行排列,所以会牵扯到两个字符串日期的拼接,以及对不同日期段的长度进行不同的渲染(这边我投机取巧了采用一次性渲染到位5列,这样渲染的缺点是可能会出现不存在的日期显示的是undefined,这里可以采用一组判断,下面会解释),下面是代码展示:
let con = {}
let tem = []
if (this.arrTime.length) {
for (let i = 0; i < 7; i++) {
this.arrTime[i] = this.arrTime[i] !== undefined ? this.arrTime[i] : " "
this.arrTime[i + 7] = this.arrTime[i + 7] !== undefined ? this.arrTime[i + 7] : " "
this.arrTime[i + 14] = this.arrTime[i + 14] !== undefined ? this.arrTime[i + 14] : " "
this.arrTime[i + 21] = this.arrTime[i + 21] !== undefined ? this.arrTime[i + 21] : " "
this.arrTime[i + 28] = this.arrTime[i + 28] !== undefined ? this.arrTime[i + 28] : " "
let rowDate = JSON.parse(JSON.stringify(this.arrTime[i] + ’ ’ + this.arrTime[i + 7] + ’ ’ + this.arrTime[i + 14] + ’ ’ + this.arrTime[i + 21] + ’ ’ + this.arrTime[i + 28]))
tem.push(rowDate)
con = { …tem }
this.tableData.push(con)
tem = []
con = {}
}
console.log(this.tableData)
} else {
this.tableData = []
}
由于不存在该日期的话会出现undefined,所以这里我们采用三元判断当不存在的时候将对应的值置" "
两个相同格式的日期之间是可以直接作比较的,例如需求中的如果日期相同则渲染一个字段:e.date === value.format(“YYYY-MM-DD”)即格式都为’2021-xx-xx’是能够直接比较出大小
在衔接两个字符串之间我们常常用+" "+中间来个空格进行拼接,但是为了做到美观,我们可能需要多个空格,那么如何做到呢?在html部分我们可以使用 进行添加空格,那么在js里我们如何在字符串之间添加空格?解决:字符串1+’\xa0\xa0\xa0\xa0’+字符串2进行拼接,根据想要的间隔大小进行个数的添加。
1月8日个人总结:
如果返回的值是这样的:[{…}, {…}, {…}, {…}, {…}, {…}, {…}, ob: Observer],那么handleChange(value)其中value打印下来的是每个对象的索引值,即0,1,那么怎么拿到值呢?解决:将value值作为索引带入tableData里即:this.tableData[value][0]
页面即一些效果完成了,现在出现了一个问题,如何将这个页面上的数据发给接口?点击上次排班的配置如何将数据渲染到select多选框?
1月11日到12日总结:
进度条组件与页面联动,并实现父子传值如何实现?解决:
<template>
<div>
<a-steps :current="current">
<a-step v-for="item in steps" :key="item.title" :title="item.title" />
</a-steps>
<div class="steps-content">
<Info v-if="this.current === 0" :userInfo='userInfo' />
<competitorInfo v-if="this.current === 1" />
<sticker v-if="this.current === 2" />
<extension v-if="this.current === 3" />
</div>
<div class="steps-action">
<a-button v-if="current > 0" style="margin-right: 8px" @click="prev">
上一步
</a-button>
<a-button
v-if="current == steps.length - 1"
type="primary"
@click="$message.success('Processing complete!')"
>
提交
</a-button>
<a-button v-if="current < steps.length - 1" type="primary" @click="next">
下一步
</a-button>
</div>
</div>
</template>
<script>
import Info from './components/Info'
import competitorInfo from './components/competitorInfo'
import sticker from './components/sticker'
import extension from './components/extension'
export default {
name: "uploadInfo",
data() {
return {
userInfo: {
applyId: '',
driverUserId: '',
driverName: '',
driverMobile: ''
},
current: 0,
steps: [
{
title: '合同上传',
},
{
title: '竞品信息',
},
{
title: '车贴张贴',
},
{
title: '推广信息',
},
],
};
},
components: {
Info,
competitorInfo,
sticker,
extension
},
mounted () {
this.getRecord()
},
methods: {
next() {
this.current++;
},
prev() {
this.current--;
},
getRecord () {
this.userInfo.driverMobile = this.$route.query.driverMobile
this.userInfo.driverName = this.$route.query.driverName
this.userInfo.driverUserId = this.$route.query.driverUserId
this.userInfo.applyId = this.$route.query.applyId
}
},
};
</script>
<style scoped>
.steps-content {
margin-top: 16px;
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
padding-top: 80px;
}
.steps-action {
margin-top: 24px;
}
</style>
介于第一个合同审核的跳转页,如何实现这样的页面?(这里复用了别人封装的upload组件,也有一些自己的改良,这里拿出一部分进行举例)
下面展示一些 内联代码片
。
<a-form-item label="身份证">
<div class="upload-container">
<upload-item
:src="src1"
:disabled="disabled"
:img-business-type="1"
:bottomFooter="ImgTypeAndDescMap[1]"
@setLoading="setLoading"
@getUploadError="getUploadError"
@setTarget="setTarget1"
/>
<upload-item
:src="src2"
:disabled="disabled"
:img-business-type="2"
:bottom-footer="ImgTypeAndDescMap[2]"
@setLoading="setLoading"
@getUploadError="getUploadError"
@setTarget="setTarget2"
/>
</div>
</a-form-item>
此处的<upload-item>是封装的组件,具体如下:
<template>
<div class="clearfix">
<a-upload
class="upload"
:disabled="disabled"
:custom-request="customRequest"
:file-list="fileList"
:beforeUpload="beforeUpload"
:withCredentials="true"
list-type="picture-card"
accept=".jpg,jpeg,.png"
@preview="handlePreview"
@change="handleChange"
>
<div v-if="!uploadError && fileList.length < uploadNum && (computeValid() || !fileList.length)">
<a-icon type="plus" />
<div class="ant-upload-text">
上传
</div>
<div>{{ isOther ? '其他图片' : '' }}</div>
</div>
</a-upload>
<div v-if="bottomFooter" class="bottom-footer">{{ bottomFooter }}</div>
<div v-else class="bottom-footer"> </div>
</div>
</template>
<script>
import api from '../api/index'
export default {
name: "upload-item",
components: {
},
props: {
disabled: {
type: Boolean,
default: false,
},
uploadNum: {
type: Number,
default: 1,
},
src: {
type: Array,
default: () => []
},
imgBusinessType: {
type: String | Number,
default: '',
},
bottomFooter: {
type: String,
default: '',
},
isOther: {
type: Boolean,
default: false
}
},
watch: {
src: {
handler (v) {
// 此处为 fileList 赋初始值
this.fileList = v.map((item, index) => ({
...item,
name: item.picName || `${new Date()}`, // 非常必须
uid: item.uid || `${new Date()}-${index}`,
url: item.picUrl || '',
}))
},
deep: true,
},
},
data () {
return {
fileList: [],
UID: '', // 记录上传错误图片的UID
uploadError: false, // 是否有图片上传错误
}
},
methods: {
customRequest ({ onSuccess, onError, file }) {
this.setLoading(true)
const formData = new FormData()
formData.append("uploadVersionImage", file)
// 此处后端要求,上传图片和车贴版本图片上传地址保持一致
api.getUploadVersionImage(formData).then(res => {
file.networkUrl = res.data.data.url
this.setImgItem(file)
onSuccess(null, file)
}).catch(err => {
onError(err)
}).finally(() => {
this.setLoading(false)
})
},
beforeUpload (file) { // 图片上传前的校验
return new Promise((resolve, reject) => {
const isValidImg = file.type === 'image/png' || file.type === 'image/jpeg'
if (!isValidImg) {
this.$message.warning('只能上传.png, .jpg, .jpeg 格式的图片')
return reject(new Error('只能上传.png, .jpg, .jpeg 格式的图片'))
}
return resolve(true)
})
},
handleChange ({ file, fileList }) {
if (file.status === 'error') {
this.UID = file.uid
this.uploadError = true
} else if (file.status === 'removed' && this.UID === file.uid) {
this.uploadError = false
}
this.$emit('getUploadError', this.uploadError, this.imgBusinessType)
this.fileList = fileList
this.echoTarget(this.fileList)
},
setImgItem (imgItem) {
for (let index = 0; index < this.fileList.length; index++) {
if (this.fileList[index].uid === imgItem.uid) {
this.fileList[index].url = imgItem.url || imgItem.networkUrl
}
}
this.echoTarget(this.fileList)
},
echoTarget (target) {
this.$emit('setTarget', target, this.imgBusinessType)
},
setLoading (loading) {
this.$emit('setLoading', loading)
},
async handlePreview (file) { // 预览图片
this.$imageview.show(file.url || file.originFileObj.networkUrl, 0, this.bottomFooter || '')
},
computeValid () { // 校验所有图片是否上传失败,有删除失败的图片,则引导用户先删除失败的图片
if (this.uploadError) {
this.$message.warning('上传失败,请删除后重新上传')
return false
}
return true
},
},
}
</script>
<style scoped>
.upload {
display: flex;
flex-direction: row;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
.bottom-footer {
width: 102px;
line-height: 13px; /* 比 font-size 略大即可 */
text-align: center;
color: #999999;
font-size: 12px;
}
</style>
之后可以拿来复用,里面的上传之前的判断逻辑很值得学习!
排班需求上要求根据用户选择的起始日期以及排班天数,动态形成日期+select的列表(已实现),并且根据用户选择的排班人,生成列表数据,传送给后端,暴露出问题:我才用的是失去焦点事件来收集数据,因为在我看来通过绑定value和onchange事件会造成牵一发而动全身的问题,但是这样会出现当用户选完该行后,再来改的话会生成两条时间相同但人不同的数据,所以我想了个笨法子:采用数组的filter方法,将相同的和不相同的过滤出来,对过滤出来相同的,取最后一条,最后全部合并到一个数组里,代码如下:
onBlur (record) {
console.log(this.selectOnes.length)
console.log(record[0].slice(0,11).split(" "))
let payload = {
time:record[0].slice(0,11),
selectPerson:this.selectOnes
}
this.obj.push(payload)
let a = JSON.parse(JSON.stringify(this.obj))
let temSame = a.filter(item => {
if (item.time !== payload.time) {
return item
}
return
})
let temDiff = a.filter(item => {
if (item.time === payload.time) {
return item
}
return
})
if (temDiff.length > 1) {
this.obj = []
this.obj.push(temDiff[temDiff.length - 1])
this.obj = […this.obj, …temSame]
}
console.log(JSON.parse(JSON.stringify(this.obj)))
this.selectOnes = ‘’
},
但此时又会出现另一个问题:我的数据是这样的,后面要通过数组方法把时间拆分遍历和值班人拼起来,但是拼接完成后,他再要修改,我的过滤就判断不了了,因为我的过滤逻辑不太行,只能对存储的时间和整行时间比,那么怎么办?解决,将拼接操作放在点击保存时弹出气泡确定的时候,代码如下:
confirmOnDuty (e) {
console.log(e)
let tem = JSON.parse(JSON.stringify(this.obj))
if (this.arrTime.length >= 7) {
for (let i = 0; i < 7; i++) {
let temTime = tem[i].time.split(’ ‘)
let len = tem[i].time.split(’ ‘).length
let people = tem[i].selectPerson
for (let j = 0; j < len; j++) {
let params = {
time: temTime[j],
person: people
}
this.totalInfo.push(params)
}
}
} else if (this.arrTime.length < 7) {
let temTime = tem[i].time
let len = tem[i].time.split(’ ‘).length
let people = tem[i].selectPerson
let params = {
time: temTime,
person: people
}
this.totalInfo.push(params)
}
console.log(this.totalInfo)
this. m e s s a g e . s u c c e s s ( ′ 排 班 成 功 ′ ) t h i s . message.success('排班成功') this. message.success(′排班成功′)this.router.push(’/cityManagement/scheduleManagement’)
},
<template>
<div class="clearfix">
<!-- <img v-if="src" :src="src" alt="" class="img-con"> -->
<a-upload
:custom-request="customRequest"
list-type="picture-card"
:file-list="fileList"
>
<div v-if="fileList.length < 1">
<a-icon type="plus" />
<div class="ant-upload-text">上传</div>
</div>
</a-upload>
<div class="bottom-footer">{{label}}</div>
<!-- <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal> -->
</div>
</template>
<script>
import api from './api/index'
export default {
name: 'FileUpload',
props: {
label: String,
value: String
},
data () {
return {
src: '',
fileList: []
}
},
watch: {
value (val) {
this.src = val
}
},
methods: {
// handleChange ({ file, fileList }) {
// // if (file.status === 'error') {
// // this.UID = file.uid
// // this.uploadError = true
// // } else if (file.status === 'removed' && this.UID === file.uid) {
// // this.uploadError = false
// // }
// console.log(fileList, 333)
// this.$emit('input', this.fileList[0].url)
// },
customRequest ({ onSuccess, onError, file }) {
const formData = new FormData()
formData.append("uploadVersionImage", file)
// 此处后端要求,上传图片和车贴版本图片上传地址保持一致
api
.getUploadVersionImage(formData)
.then((res) => {
this.src = res.data.data.url
this.$emit('input', this.src)
onSuccess(null, file)
})
.catch((err) => {
onError(err)
})
},
},
}
</script>
<style scoped>
.bottom-footer {
width: 102px;
line-height: 13px; /* 比 font-size 略大即可 */
text-align: center;
color: #999999;
font-size: 12px;
}
</style>
页面落实:
H5部分:
<file-upload v-model="srcIdentiFront" label="身份证正面"></file-upload>
<file-upload v-model="srcIdentiBack" label="身份证反面"></file-upload>
逻辑部分:
import FileUpload from './FileUpload.vue'
watch: {
srcIdentiFront (val) {
console.log(123, val)
},
srcIdentiBack (val) {
console.log(123, val)
},
}
当需要动态获取多行input或者select这一类的框框中的文本可以这样,项目需求动态根据排期来创建select:
下面展示一些 内联代码片
。
<template slot="onDutyPeople" slot-scope="text, record ,index">
<SearchUser v-model="userList[index]"/>
</template>
let people = this.userList?.[i]?.[0]?.name || null
scoped看起来很好用,当时在Vue项目中,当我们引入第三方组件库时(如使用element-ui),需要在局部组件中修改第三方组件库样式,而又不想去除scoped属性造成组件之间的样式覆盖。这时我们可以通过特殊的方式穿透scoped。
1、stylus的样式穿透 使用 >>>
.wrapper >>> .swiper-pagination-bullet-active
background: #fff复制代码
2、sass和less的样式穿透 使用 /deep/
// 语法
外层 /deep/ 第三方组件 {
样式
}
// eg
.wrapper /deep/ .swiper-pagination-bullet-active{
background: #fff;
}