使用Vue2+ElementUI+scss开发在线问卷页面

遇到的坑

Vue

  1. Vue下不同页面匹配不同layout

    • 坑点:当我在layout布局页面中为块级元素使用class=header的标签后,所有匹配逻辑会失效,查元素检查器发现该layout被直接应用在页面最外层(Vue甚至没有挂载到App)

      • 解决方法:换个自定义class名

        * 解决方法 * 学习
  2. Vue数组变动的渲染

    深入Vue响应式原理 :出于性能的考虑,Vue不会对数组[index]= newVal 数组.length=newLength进行响应,需要使用

    1
    2
    vue.$set(array,index,newVal)
    vm.items.splice(newLength)

    等写法触发状态更新。

    在实际应用中,我们可以在数据改变时利用@change等钩子函数同步一下即可。如下所示

    1
    2
    3
    4
    handleAnswerChange(index: number): void {
    // 解决Vue响应式原理中数组更新视图不渲染问题
    this.$set(this.array, index, this.array[index])
    }
  3. 自己编写的可拖拽组件:查看代码

  4. 利用ElementUI的照片墙样式,定制自己的图片上传组件:根据限制数量限制图片上传按钮的出现,自定义http-request

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <template>
    <div class="upload-pic">
    <el-upload
    action=""
    list-type="picture-card"
    :file-list="imgFileList"
    :limit="limit"
    :class="{ hide: hideUpload }"
    ref="upload"
    name="files"
    :on-preview="handlePreview"
    :before-upload="handleBeforeUpload"
    :http-request="handleUpload"
    >

    <i slot="default" class="el-icon-plus"></i>
    <div slot="file" slot-scope="{ file }">
    <img class="el-upload-list__item-thumbnail" :src="file.url" />
    <span class="el-upload-list__item-actions">
    <span
    class="el-upload-list__item-preview"
    @click="handlePreview(file)"
    >

    <i class="el-icon-zoom-in"></i>
    </span>
    <span
    v-if="!disabled"
    class="el-upload-list__item-delete"
    @click="handleRemove(file)"
    >

    <i class="el-icon-delete"></i>
    </span>
    </span>
    </div>
    <div v-if="slotText" slot="tip" class="el-upload__tip">
    {{ slotText }}
    </div>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
    <img width="100%" :src="dialogImageUrl" />
    </el-dialog>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    <script lang="ts">
    /**
    * @file 带缩略图的单张照片墙组件
    * @todo 如果需要扩展为多张照片墙,需修改imgUrl的类型为Array<string>
    */

    import { IObject } from "@/@types/common"
    import { uploadFileUser } from "@/api/api"
    import { Component, Vue, Prop, Watch } from "vue-property-decorator"
    import { InnerFile } from "@/@types/common"

    @Component
    export default class UploadPic extends Vue {
    @Prop()
    imgType!: string

    @Prop()
    imgUrl!: string

    @Prop({ default: 1, required: false })
    limit!: number

    @Prop({ default: "该值", required: false })
    validateText!: string

    @Prop({ default: "", required: false })
    slotText!: string

    dialogImageUrl = ""
    dialogVisible = false
    hideUpload = false
    disabled = false //是否显示删除按钮
    imgFileList: InnerFile[] = []

    mounted(): void {
    this.handleimgUrl()
    }

    handleimgUrl(): void {
    if (this.imgUrl && this.imgUrl != "defaultimg") {
    const fileName = this.imgUrl.match("[^/]+(?!.*/)")
    const fileType = fileName ? fileName[0] : ""
    const name = this.$store.getters.userInfo.orgId
    this.imgFileList.push({
    name: this.imgType + "_" + name + "_" + fileType,
    url: this.imgUrl
    })
    this.hideUpload = true
    }
    }

    handleUpload(config: IObject): void {
    // 上传时不显示多余的上传框
    this.hideUpload = true
    let fd = new FormData()
    fd.append("businessName", "organization")
    fd.append("file", config.file)
    fd.append("type", this.imgType)
    uploadFileUser(fd).then(res => {
    if (res.code === 200) {
    this.$message.success("上传成功")
    this.$emit("update:imgUrl", res.data)
    } else {
    this.$message.error(res.msg)
    this.hideUpload = false
    }
    })
    }
    handlePreview(file: InnerFile): void {
    this.dialogImageUrl = file.url ? file.url : ""
    this.dialogVisible = true
    }

    handleRemove(file: InnerFile): void {
    const el = this.$refs.upload as IObject
    el.handleRemove(file)
    this.$emit("update:imgUrl", "")
    // HACK 删除后才加载添加框 safari加载520,chrome加载1000
    // setTimeout(() => {
    this.hideUpload = false
    // }, 520)
    }
    handleBeforeUpload(file: File): boolean {
    const isPic = file.type === "image/png" || "image/jpeg"
    const isLt2M = file.size / 1024 / 1024 < 10
    if (!isPic) {
    this.$message.error("上传图片只能是 JPG 或 PNG 格式!")
    }
    if (!isLt2M) {
    this.$message.error("上传头像图片大小不能超过 10MB!")
    }
    return isPic && isLt2M
    }
    }
    </script>
    1
    2
    3
    4
    5
    6
    7
    <style lang="scss">
    .upload-pic {
    .hide .el-upload--picture-card {
    display: none;
    }
    }
    </style>

JavaScript

  1. 可拖动组件

    坑点:鼠标拖拽绑定到元素会有卡顿,触屏拖拽不会

    解决方法:绑定到窗口

  2. 窗口自适应变化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <template>
    <div
    class="drag__wrapper"
    ref="dragRef"
    :style="{ top: pos.y + 'px', left: pos.x + 'px' }"
    >

    <slot name="float" />
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    <script lang="ts">
    import { env } from "echarts"
    import { Component, Vue, Prop } from "vue-property-decorator"
    @Component
    export default class FloatCounter extends Vue {
    @Prop({
    required: false
    })
    position!: {
    top: number
    left: number
    }
    @Prop({
    required: true
    })
    dragId!: string
    pos = {
    x: 0,
    y: 0
    }

    // 可拖拽边界值
    maxX = 0
    maxY = 0
    isDown = false
    mounted(): void {
    // 组件大小
    const el = this.$refs.dragRef as Element
    const rect = el.getBoundingClientRect()
    // 组件父元素位置
    const elParent = el.parentElement as HTMLElement
    // 组件能移动的位置
    this.maxX = document.body.clientWidth - rect.width
    this.maxY = document.body.clientHeight - rect.height
    if (!this.position) {
    this.handleSlide(
    elParent.offsetLeft + elParent.clientWidth - rect.width,
    elParent.offsetTop + elParent.clientHeight - rect.height
    )
    } else {
    this.handleSlide(this.position.left, this.position.top)
    }

    const drag = document.getElementById(this.dragId) as HTMLElement
    let isMove = false
    let mouseX = 0
    let mouseY = 0
    // 鼠标拖拽 绑定到document,否则容易出现延迟
    drag.onmousedown = e => {
    isMove = true
    const event = e || window.event
    const rect = el.getBoundingClientRect()
    this.maxX = document.body.clientWidth - rect.width
    this.maxY = document.body.clientHeight - rect.height
    // 获取鼠标的位置,兼容多浏览器
    mouseX = event.pageX ? event.pageX : event.clientX
    mouseY = event.pageY ? event.pageY : event.clientY
    // 获取当前元素位置
    mouseX -= drag.getBoundingClientRect().left
    mouseY -= drag.getBoundingClientRect().top
    document.onmousemove = e => {
    if (isMove) {
    const event = e || window.event
    const ox = event.pageX ? event.pageX : event.clientX
    const oy = event.pageY ? event.pageY : event.clientY

    this.handleSlide(
    Math.max(0, Math.min(ox - mouseX, this.maxX)),
    Math.max(0, Math.min(oy - mouseY, this.maxY))
    )
    }
    }
    }
    document.onmouseup = () => {
    isMove = false
    document.onmousemove = null
    }
    // 触屏拖拽 绑定到对象本身
    drag.addEventListener("touchstart", e => {
    isMove = true
    const rect = el.getBoundingClientRect()
    this.maxX = document.body.clientWidth - rect.width
    this.maxY = document.body.clientHeight - rect.height
    // 获取触点的位置
    const event = e || window.event
    const touch = event.touches[0]
    mouseX = touch.clientX - drag.getBoundingClientRect().left
    mouseY = touch.clientY - drag.getBoundingClientRect().top
    })
    drag.addEventListener("touchmove", e => {
    if (isMove) {
    const event = e || window.event
    const touch = event.touches[0]
    const ox = touch.pageX ? touch.pageX : touch.clientX
    const oy = touch.pageY ? touch.pageY : touch.clientY
    this.handleSlide(
    Math.max(0, Math.min(ox - mouseX, this.maxX)),
    Math.max(0, Math.min(oy - mouseY, this.maxY))
    )
    e.preventDefault()
    }
    })
    drag.addEventListener("touchend", () => {
    isMove = false
    })

    // 自适应窗口变化
    window.onresize = () => {
    return (() => {
    const rect = el.getBoundingClientRect()
    this.maxX = document.body.clientWidth - rect.width
    this.maxY = document.body.clientHeight - rect.height
    if (!this.position) {
    const elParent = el.parentElement as HTMLElement
    this.handleSlide(
    elParent.offsetLeft + elParent.clientWidth - rect.width,
    elParent.offsetTop + elParent.clientHeight - rect.height
    )
    }
    })()
    }
    }

    // 移动组件位置
    handleSlide(desX: number, desY: number): void {
    this.pos.x = desX
    this.pos.y = desY
    }
    }
    </script>

ElementUI

  1. 选择框

    1. el-radio:选项超出宽度自动换行,换行后自动缩进和顶端对齐:点击跳转到样式部分查看代码

    2. el-checkbox:存在bug,使用时需绑定checked选中状态才能实时响应

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <el-checkbox
      v-for="answer in question.answer"
      :key="answer.option"
      :label="answer.option"
      class="horizon"
      :checked="checked"
      @change="checked = !checked"
      >
      {{ answer.content }}
      </el-checkbox>
  2. El-Image:相对路径引用

    1
    2
    3
    4
    <el-image
    :src="require('@/assets/questionair/top.png')"
    fit="fill"
    >
    </el-image>

样式

  1. 块元素的垂直居中:老生常谈

    网络上大量“7种垂直居中的方法”“14种垂直居中的方法”,实际运用中大量绝对定位的方法都可以忽略不计。常用的几种方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // span块
    .span{
    display: inline-block; // 转为块元素
    text-align: center; // 水平居中
    line-height:值为所需对齐的div块高度; // 垂直居中
    }
    // flex布局
    .divParent{
    display: flex;
    justify-content: center; // 水平
    align-items: center; // 垂直
    }
    // 定位居中
    .name{
    background:#eee;
    position:absolute;
    left:50%;
    top:50%;
    transform: translate(-50%,-50%);
    }

    其他还有:利用table的方法

  2. 文本溢出自动换行,应用white-space属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    .el-radio__label {
    font-size: 14px;
    color: #5e6166;
    line-height: 22px;
    // 解决选项溢出问题
    width: 100%;
    text-overflow: ellipsis;
    white-space: normal;
    // 解决溢出行缩进
    display: inline-block;
    vertical-align: top;
    }
    .el-radio__input {
    // 对其label文字
    margin-top: 2px;
    }
  3. 行内块inline-block与行内文本span的对齐

    字体衬线导致即使设置了字体的line-height与块元素height相同,使用verticle-align:middle后两者也不能再同一水平线上。可以使用顶端对齐块元素向下偏移2px的方法

组件

  1. 排序拖拽组件:sortable draggable

  2. markdown组件

    1. vue-markdown-editor

    2. markdown-it-vue

title:使用Vue2+ElementUI+scss开发在线问卷页面

author:Anne416wu

link:https://www.annewqx.top/posts/38332/

publish time:2021-10-22

update time:2022-07-20


 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×