作为一名新晋码农,以下是我个人的工作心得,希望能帮到刚步入编程殿堂和使用vue-cli和ant-design-vue UI组件库的童鞋们

作为一名新晋码农,以下是我个人的工作心得,希望能帮到刚步入编程殿堂和使用vue-cli和ant-design-vue UI组件库的童鞋们

      • 之前需求总结:
      • 12.29错误总结:
      • 12.30日总结:
      • 12.31日总结:
      • 2021.1.4日当日总结:
      • 2021.1.5总结:
      • 1.6日个人总结:
      • 1.7日个人总结:
      • 1月14日总结:
      • 1月15号总结:在龙哥的帮助下完成了upload组件的封装:
      • 1月20日总结:
      • 1月21日
      • 1月25日


之前需求总结:

上传代码的时候,记得把一些全局的配置包括: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)
          }
        })
      }
    },

12.29错误总结:

当项目运行时,利用项目运行后的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
    },

12.30日总结:

饿了么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系统,项目完成填写项目变更。

12.31日总结:

检视代码,发现多处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)),
}
})
}

2021.1.4日当日总结:

不熟悉react,怎么实现跳转老后台的vue页面?解决:首先在config文件里添加出menu菜单行,其次在siderMenu中的baseRoute写上跳转老后台的路由,path写死,写成老后台对应页面的ip地址,target写为_blank以打开新页面的方式打开对应页面(投机)
已经权限系统配置完了侧栏菜单的权限,需求完成,并且正确打包部署到dev环境,但仅在本地看到新增的侧栏,在dev环境下看不到,这是什么原因?解决:由于dev网页的本地缓存未清理,导致每次登陆都是第一次登陆的信息。

2021.1.5总结:

学习:在日历组件上动态渲染数据在对应日期上:
首先它是通过v-for遍历到每个日期格子的。

  • //数组格式如下所示,因此我们可以在数据库这样写入每日的内容,date对应那一天,listData对应具体内容,包含的type为内容提示,content为文字。 testList: [ { date: "2020-12-01", listData: [ { type: "warning", content: "This is warning event." }, { type: "success", content: "This is usual event." }, { type: "error", content: "This is error event." }, ], }, { date: "2020-12-05", listData: [{ type: "warning", content: "This is warning event." }], }, { date: "2020-12-08", listData: [ { type: "warning", content: "This is warning event." }, { type: "error", content: "This is error event." }, ], }, ],
  • 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 || ""
          },
    

    1.6日个人总结:

    如何获取一段时间内的所有日期?解决:
    用项目举例,项目要求当在日期选择框里选完日期的时候,能够获取到日期区间内所有的日期,并且按照轮询渲染到表格组件上(这部分还没做,待补充)代码如下:
    首先是组件部分,选择最为简单的日期选择框:

    <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修改样式未能成功,待后面解决补充)

    1.7日个人总结:

    日期成功渲染到对应框,并且经过改动能够用少量代码实现各种日期区间的展示,之前出现了这样的问题:通过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">&nbsp;</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>
    

    之后可以拿来复用,里面的上传之前的判断逻辑很值得学习!

    1月14日总结:

    排班需求上要求根据用户选择的起始日期以及排班天数,动态形成日期+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’)
    },

    1月15号总结:在龙哥的帮助下完成了upload组件的封装:

    <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)
        },
    }
    
    

    1月20日总结:

    当需要动态获取多行input或者select这一类的框框中的文本可以这样,项目需求动态根据排期来创建select:
    下面展示一些 内联代码片

    <template slot="onDutyPeople" slot-scope="text, record ,index">
          <SearchUser v-model="userList[index]"/>
    </template>
    

    1月21日

    let people = this.userList?.[i]?.[0]?.name || null

    1月25日

    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;
    }

你可能感兴趣的:(vue.js,javascript,html5,es6)