JXL组件:jxl-form基于el-form表单封装的组件

JxlForm 表单

满足开发者采用配置的方式,快速搭建表单的需求。

基于 ElementUI 的 el-form、el-row 和 el-col 标签封装。
基于自封组件的 jxl-form-item 标签封装。

<template>
  <div>
    <el-form :id="formId" ref="form" :model="form" :rules="rules" v-bind="$attrs" v-on="$listeners" @submit.native.prevent>
      <template v-if="group">
        <div v-for="(groupItem, groupKey) in items" :key="groupKey" :class="['el-form-group', indent ? '' : 'no-indent']">
          <template v-if="isVisible(groupItem)">
            <div class="el-form-group-title">{{ groupItem.title }}</div>
            <div class="el-form-group-items">
              <el-row v-bind="layout.row">
                <template v-for="(item, key) in groupItem.items">
                  <template v-if="isVisible(item)">
                    <el-col :key="key" v-bind="[getLayout(item), item.colAttrs]">
                      <jxl-form-item :item="item" :colon="colon" :is-query="isQuery" :input-space="inputSpace" :form="form" @query="queryHandle">
                        <template v-if="$scopedSlots[getLabelExtraSlotName(item)]" v-slot:labelExtra>
                          <slot :name="getLabelExtraSlotName(item)" :row="item" />
                        </template>
                        <template v-if="$scopedSlots[getTipSlotName(item)]" v-slot:tipContent>
                          <slot :name="getTipSlotName(item)" :row="item" />
                        </template>
                        <template v-if="$scopedSlots[getItemSlotName(item, key)]" v-slot:content>
                          <slot :name="getItemSlotName(item, key)" :row="item" />
                        </template>
                      </jxl-form-item>
                    </el-col>
                  </template>
                </template>
              </el-row>
            </div>
          </template>
        </div>
      </template>
      <template v-else>
        <el-row v-bind="layout.row">
          <template v-for="(item, key) in items">
            <template v-if="isVisible(item)">
              <el-col :key="key" v-bind="[getLayout(item), item.colAttrs]">
                <jxl-form-item :item="item" :colon="colon" :is-query="isQuery" :input-space="inputSpace" :form="form" @query="queryHandle">
                  <template v-if="$scopedSlots[getLabelExtraSlotName(item)]" v-slot:labelExtra>
                    <slot :name="getLabelExtraSlotName(item)" :row="item" />
                  </template>
                  <template v-if="$scopedSlots[getTipSlotName(item)]" v-slot:tipContent>
                    <slot :name="getTipSlotName(item)" :row="item" />
                  </template>
                  <template v-if="$scopedSlots[getItemSlotName(item, key)]" v-slot:content>
                    <slot :name="getItemSlotName(item, key)" :row="item" />
                  </template>
                </jxl-form-item>
              </el-col>
            </template>
          </template>
        </el-row>
      </template>
      <slot />
    </el-form>
    <slot name="form-footer" />
  </div>
</template>
<script>
import JxlFormItem from '@/components/libs/Form/JxlFormItem'
import mixin from '@/components/libs/mixin'

export default {
  name: 'JxlForm',
  components: { JxlFormItem },
  mixins: [mixin],
  props: {
    /**
     * 表单ID
     */
    formId: {
      type: String,
      default: undefined
    },
    /**
     * 表单数据
     */
    form: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 表单是否分组
     */
    group: {
      type: Boolean,
      default: false
    },
    /**
     * 是否显示冒号
     */
    colon: {
      type: Boolean,
      default: true
    },
    /**
     * 分组模式下标题与子项缩进
     */
    indent: {
      type: Boolean,
      default: true
    },
    /**
     * 是否为表单查询模式
     * 监听 change 事件,回调为 query 事件
     */
    isQuery: {
      type: Boolean,
      default: false
    },
    /**
     * 输入空格
     * 可选值:none,auto, valid
     * none: 不能输入空格
     * auto: 自由输入空格, 默认值
     * valid: 不能连续输入空格
     */
    inputSpace: {
      type: String,
      default: 'auto'
    },
    /**
     * 表单配置
     */
    items: {
      type: [Object, Array],
      require: true,
      default: null
    },
    /**
     * 校验规则
     */
    rules: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 表单布局
     */
    layout: {
      type: Object,
      default: () => {
        return {
          row: {},
          col: {}
        }
      }
    },
    /**
     * 查询表单方法
     */
    query: {
      type: Function,
      default: null
    }
  },
  methods: {
    /**
     * 提交表单
     */
    validate(callback) {
      this.$refs.form.validate(callback)
    },
    /**
     * 重置表单
     */
    resetFields() {
      // checkbox全选效果需要手动去除标记
      this.checkAllClear() // 清除全选
      // 等待去除标记后数据刷新再重置
      this.$nextTick(() => {
        this.$refs.form.resetFields()
      })
    },
    /** 验证某个字段 */
    validateField(filed, callback) {
      this.$refs.form.validateField(filed, callback)
    },
    /**
     * 移除表单项的校验结果。
     * 传入待移除的表单项的 prop 属性或者 prop 组成的数组,
     * 如不传则移除整个表单校验结果
     * @param props array | string | undefined
     */
    clearValidate(props) {
      this.$refs.form.clearValidate(props)
    },
    /**
     * 清除全选
     */
    checkAllClear() {
      Object.keys(this.items).map(key => {
        const checkAll = this.items[key].checkAll
        if (this._typeof(checkAll) === Object) {
          checkAll.value = false
          checkAll.indeterminate = false
        }
      })
    },
    /**
     * 获取列布局
     * @param item
     * @returns {object}
     */
    getLayout(item) {
      if (item.layout && item.layout.col && this._typeof(item.layout.col) === Object) return item.layout.col
      if (this.layout.col && this._typeof(this.layout.col) === Object) return this.layout.col
      return {}
    },
    /**
     * 获取条目插槽名称
     * @param item
     * @returns {*}
     */
    getLabelExtraSlotName(item) {
      return this._get(item, 'labelExtraSlotName')
    },
    /**
     * 获取提示组件插槽名称
     * @param item
     * @returns {*}
     */
    getTipSlotName(item) {
      return this._get(item, 'tip.prompt.content.slotName')
    },
    /**
     * 获取表单条目组件插槽名称
     * @param item
     * @param key
     * @returns {*}
     */
    getItemSlotName(item, key) {
      if (this._typeof(item.slotName) === String && this.isValid(item.slotName)) return item.slotName
      if (item.slot === true) {
        if (this._typeof(item.prop) === String && this.isValid(item.prop)) return item.prop
        if (this._typeof(key) === String && this.isValid(key)) return key
      }
      return undefined
    },
    /**
     * 查询表单处理
     */
    queryHandle() {
      this.$emit('query')
    }
  }
}
</script>
<style lang="less">
.el-form {
  position: relative;

  &.el-form--label-top {
    .el-form-item {
      .el-form-item__label {
        display: block;
        position: relative;
        width: 100%;

        &:after {
          display: none;
        }
      }
    }
  }
  .el-form-item {
    .el-form-item__label {
      font-weight: 400;
    }
    .el-input__inner {
      border-color: #d0d7dd;
      &:focus {
        border-color: #1890ff;
      }
    }
  }
}
</style>
<style lang="less" scoped>
  .el-form-group {
    margin-bottom: 24px;

    &:last-child {
      margin-bottom: 0;
    }
    &.no-indent {
      .el-form-group-title {
        padding-left: 0;

        &:before {
          display: none;
        }
      }
      .el-form-group-items {
        padding-left: 0;
      }
    }
    .el-form-group-title {
      position: relative;
      white-space: nowrap;
      text-overflow: ellipsis;
      color: #303233;
      font-weight: 600;
      font-size: 16px;
      line-height: 1.5;
      padding-left: 12px;
      margin-bottom: 20px;

      &:before {
        display: block;
        content: '';
        position: absolute;
        left: 0;
        top: 3px;
        width: 2px;
        height: 13px;
        background-color: #1890ff;
      }
    }
    .el-form-group-items {
      padding-left: 12px;

      &.no-indent {
        padding-left: 0;
      }
    }
  }
  /deep/ .el-col:last-child {
    .description-item {
      margin-bottom: 0;
    }
  }
</style>

JxlFormItem 表单项

满足开发者采用配置的方式,快速搭建表单的需求。

基于 ElementUI 的 el-form-item 标签封装。

<template>
  <el-form-item :label="item.label" :prop="item.prop" :rules="rules" :required="item.required" :class="[colon ? 'has-colon' : '']" v-bind="item.itemAttrs">
    <template v-if="item.label" slot="label">
      <!--条目名称-->
      <span>{{ item.label }}</span>
      <!--条目额外的内容-->
      <slot v-if="$scopedSlots['labelExtra']" name="labelExtra" :row="item" />
      <!--条目提示符-->
      <jxl-tip v-if="item.tip" :options="item.tip">
        <template v-if="_get(item, 'tip.prompt.content.slotName')" v-slot:content>
          <slot v-if="$scopedSlots['tipContent']" name="tipContent" :row="item" />
        </template>
      </jxl-tip>
    </template>
    <!--自定义组件-->
    <slot v-if="$scopedSlots['content']" name="content" :row="item" />
    <!--对象组件-->
    <template v-else-if="_typeof(item.component) === Object">
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--输入框-->
    <template v-else-if="item.component === 'el-input'">
      <el-input v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--计数器-->
    <template v-else-if="item.component === 'el-input-number'">
      <el-input-number v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--选择器-->
    <template v-else-if="item.component === 'el-select'">
      <el-select v-model="form[item.prop]" :name="item.prop" value="" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events">
        <el-option v-if="item['optionAll']" :label="item['optionAll'].label || '全部'" :value="item['optionAll'].value || ''" />
        <template v-for="(option, index) in item.options">
          <el-option v-if="isVisible(option)" :key="index" :label="getOptionLabel(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs" :value="getOptionValue(option)" />
        </template>
      </el-select>
    </template>
    <!--开关-->
    <template v-else-if="item.component === 'el-switch'">
      <el-switch v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--多选框-->
    <template v-else-if="item.component === 'el-checkbox'">
      <template v-if="_typeof(item.options) === Array">
        <el-checkbox v-if="_typeof(item.checkAll) === Object" v-model="item.checkAll.value" :name="item.prop + 'All'" :indeterminate="item.checkAll.indeterminate" v-bind="item.checkAll.attrs" @change="checkAllChange(item)">{{ item.checkAll.label || item.checkAll.name }}</el-checkbox>
        <el-checkbox-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" @change="checkedChange(item)">
          <template v-for="(option, index) in item.options">
            <el-checkbox v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-checkbox>
          </template>
        </el-checkbox-group>
      </template>
      <template v-else>
        <el-checkbox v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">{{ item.optionName }}</el-checkbox>
      </template>
    </template>
    <!--单选框-->
    <template v-else-if="item.component === 'el-radio'">
      <el-radio-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">
        <template v-if="item['optionButton']">
          <template v-for="(option, index) in item.options">
            <el-radio-button v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio-button>
          </template>
        </template>
        <template v-else>
          <template v-for="(option, index) in item.options">
            <el-radio v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio>
          </template>
        </template>
      </el-radio-group>
    </template>
    <!--滑块-->
    <template v-else-if="item.component === 'el-slider'">
      <el-slider v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--时间选择器-固定时间点-->
    <template v-else-if="item.component === 'el-time-select'">
      <el-time-select v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--时间选择器-任意时间点-->
    <template v-else-if="item.component === 'el-time-picker'">
      <el-time-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--日期选择器-->
    <template v-else-if="item.component === 'el-date-picker'">
      <el-date-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--评分-->
    <template v-else-if="item.component === 'el-rate'">
      <el-rate v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--颜色选择器-->
    <template v-else-if="item.component === 'el-color-picker'">
      <el-color-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--穿梭框-->
    <template v-else-if="item.component === 'el-transfer'">
      <el-transfer v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--级联选择器-->
    <template v-else-if="item.component === 'el-cascader'">
      <el-cascader v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" v-on="item.events" />
    </template>
    <!--上传-->
    <template v-else-if="item.component === 'el-upload'">
      <el-upload v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="Object.assign(item.attrs, item.events)" :action="item.attrs.action">
        <template v-if="$scopedSlots[item.uploadSlot]">
          <slot :name="item.uploadSlot" :row="item" />
        </template>
        <template v-else>
          <img v-if="form.avatar" :src="form.avatar" class="avatar" alt="">
          <i v-else class="el-icon-plus avatar-uploader-icon" />
        </template>
      </el-upload>
    </template>
    <!--具名组件-->
    <template v-else>
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <div v-if="item.extra" class="el-form-item-extra">{{ item.extra }}</div>
  </el-form-item>
</template>

<script>
import mixin from '@/components/libs/mixin'
import JxlTip from '@/components/libs/Tip/JxlTip'
import { _typeof } from '@/utils/libs/support'
import { preventVoidInput } from '@/components/libs/util'

export default {
  name: 'JxlFormItem',
  components: { JxlTip },
  mixins: [mixin],
  props: {
    /**
     * 表单数据
     */
    form: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 表单项配置
     */
    item: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 校验规则
     */
    rules: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 是否显示冒号
     */
    colon: {
      type: Boolean,
      default: true
    },
    /**
     * 是否为表单查询模式
     * 监听 change 事件,回调为 query 事件
     */
    isQuery: {
      type: Boolean,
      default: false
    },
    /**
     * 输入空格
     * 可选值:none,auto, valid
     * none: 不能输入空格
     * auto: 自由输入空格, 默认值
     * valid: 不能连续输入空格
     */
    inputSpace: {
      type: String,
      default: 'auto'
    },
    /**
     * 查询表单方法
     */
    query: {
      type: Function,
      default: null
    }
  },
  data() {
    return {
      inputPlaceholder: ['el-input', 'el-input-number'], // 输入提示
      selectPlaceholder: ['el-select', 'el-time-select', 'el-time-picker', 'el-date-picker', 'el-cascader'] // 选择提示
    }
  },
  methods: {
    /**
     * 处理多选
     * @param item 绑定的表单对象
     */
    checkedChange(item) {
      const checkedCount = this.form[item.prop].length
      if (this._typeof(item.checkAll) === Object) {
        item.checkAll.value = checkedCount === item.options.length
        item.checkAll.indeterminate = checkedCount > 0 && checkedCount < item.options.length
      }
    },
    /**
     * 处理全选
     * @param item 绑定的表单对象
     */
    checkAllChange(item) {
      this.form[item.prop] = item.checkAll.value ? this.getValues(item.options) : []
      item.checkAll.indeterminate = false
    },
    /**
     * 获取对象列表的值,返回一个值得列表
     * @param array
     * @returns {array}
     */
    getValues(array) {
      return array.map(option => {
        if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
        if ('value' in option) return option['value']
        return option['id']
      })
    },
    /**
     * 获取选项数值
     * @param option
     */
    getOptionValue(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
      if ('value' in option) return option['value']
      return option['id']
    },
    /**
     * 获取选项标签
     * @param option
     * @returns {*}
     */
    getOptionLabel(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['label']]
      if ('label' in option) return option['label']
      return option['name']
    },
    /**
     * 插入属性
     * @param item
     */
    insertAttrs(item) {
      const attrs = _typeof(item.attrs) === Object ? item.attrs : {}
      if ('placeholder' in attrs) return attrs
      if (this.inputPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请输入' + item.label })
      if (this.selectPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请选择' + item.label })
      return attrs
    },
    /**
     * 输入处理
     * @param val
     */
    inputHandle(val) {
      if (!['valid', 'none'].includes(this.inputSpace)) return
      this.form[this.item.prop] = preventVoidInput({ text: val, trimAll: this.inputSpace === 'none' })
    },
    /**
     * 改变处理
     * @param val
     */
    changeHandle(val) {
      if (!this.isQuery) return
      this.$emit('query')
    }
  }
}
</script>
<style lang="less">
.el-form-item {
  &.has-colon {
    .el-form-item__label:after {
      content: ":";
      position: relative;
      left: 4px;
      top: -1px;
      padding-right: 4px;
      font-weight: 600;
    }
  }
}
</style>
<style lang="less" scoped>
  /deep/ .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  /deep/ .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  /deep/ .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  /deep/ .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
  .el-form-item-extra {
    margin-top: 4px;
    line-height: 1.5;
    min-height: 24px;
    clear: both;
    color: #00000073;
    font-size: 12px;
    transition: color .3s cubic-bezier(.215,.61,.355,1);
  }
</style>

JxlFormItem 表单项

满足开发者采用配置的方式,快速搭建表单的需求。

基于 ElementUI 的 el-form-item 标签封装。

<template>
  <el-form-item :label="item.label" :prop="item.prop" :rules="rules" :required="item.required" :class="[colon ? 'has-colon' : '']" v-bind="item.itemAttrs">
    <template v-if="item.label" slot="label">
      <!--条目名称-->
      <span>{{ item.label }}</span>
      <!--条目额外的内容-->
      <slot v-if="$scopedSlots['labelExtra']" name="labelExtra" :row="item" />
      <!--条目提示符-->
      <jxl-tip v-if="item.tip" :options="item.tip">
        <template v-if="_get(item, 'tip.prompt.content.slotName')" v-slot:content>
          <slot v-if="$scopedSlots['tipContent']" name="tipContent" :row="item" />
        </template>
      </jxl-tip>
    </template>
    <!--自定义组件-->
    <slot v-if="$scopedSlots['content']" name="content" :row="item" />
    <!--对象组件-->
    <template v-else-if="_typeof(item.component) === Object">
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--输入框-->
    <template v-else-if="item.component === 'el-input'">
      <el-input v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--计数器-->
    <template v-else-if="item.component === 'el-input-number'">
      <el-input-number v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--选择器-->
    <template v-else-if="item.component === 'el-select'">
      <el-select v-model="form[item.prop]" :name="item.prop" value="" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events">
        <el-option v-if="item['optionAll']" :label="item['optionAll'].label || '全部'" :value="item['optionAll'].value || ''" />
        <template v-for="(option, index) in item.options">
          <el-option v-if="isVisible(option)" :key="index" :label="getOptionLabel(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs" :value="getOptionValue(option)" />
        </template>
      </el-select>
    </template>
    <!--开关-->
    <template v-else-if="item.component === 'el-switch'">
      <el-switch v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--多选框-->
    <template v-else-if="item.component === 'el-checkbox'">
      <template v-if="_typeof(item.options) === Array">
        <el-checkbox v-if="_typeof(item.checkAll) === Object" v-model="item.checkAll.value" :name="item.prop + 'All'" :indeterminate="item.checkAll.indeterminate" v-bind="item.checkAll.attrs" @change="checkAllChange(item)">{{ item.checkAll.label || item.checkAll.name }}</el-checkbox>
        <el-checkbox-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" @change="checkedChange(item)">
          <template v-for="(option, index) in item.options">
            <el-checkbox v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-checkbox>
          </template>
        </el-checkbox-group>
      </template>
      <template v-else>
        <el-checkbox v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">{{ item.optionName }}</el-checkbox>
      </template>
    </template>
    <!--单选框-->
    <template v-else-if="item.component === 'el-radio'">
      <el-radio-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">
        <template v-if="item['optionButton']">
          <template v-for="(option, index) in item.options">
            <el-radio-button v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio-button>
          </template>
        </template>
        <template v-else>
          <template v-for="(option, index) in item.options">
            <el-radio v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio>
          </template>
        </template>
      </el-radio-group>
    </template>
    <!--滑块-->
    <template v-else-if="item.component === 'el-slider'">
      <el-slider v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--时间选择器-固定时间点-->
    <template v-else-if="item.component === 'el-time-select'">
      <el-time-select v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--时间选择器-任意时间点-->
    <template v-else-if="item.component === 'el-time-picker'">
      <el-time-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--日期选择器-->
    <template v-else-if="item.component === 'el-date-picker'">
      <el-date-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--评分-->
    <template v-else-if="item.component === 'el-rate'">
      <el-rate v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--颜色选择器-->
    <template v-else-if="item.component === 'el-color-picker'">
      <el-color-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--穿梭框-->
    <template v-else-if="item.component === 'el-transfer'">
      <el-transfer v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--级联选择器-->
    <template v-else-if="item.component === 'el-cascader'">
      <el-cascader v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" v-on="item.events" />
    </template>
    <!--上传-->
    <template v-else-if="item.component === 'el-upload'">
      <el-upload v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="Object.assign(item.attrs, item.events)" :action="item.attrs.action">
        <template v-if="$scopedSlots[item.uploadSlot]">
          <slot :name="item.uploadSlot" :row="item" />
        </template>
        <template v-else>
          <img v-if="form.avatar" :src="form.avatar" class="avatar" alt="">
          <i v-else class="el-icon-plus avatar-uploader-icon" />
        </template>
      </el-upload>
    </template>
    <!--具名组件-->
    <template v-else>
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <div v-if="item.extra" class="el-form-item-extra">{{ item.extra }}</div>
  </el-form-item>
</template>

<script>
import mixin from '@/components/libs/mixin'
import JxlTip from '@/components/libs/Tip/JxlTip'
import { _typeof } from '@/utils/libs/support'
import { preventVoidInput } from '@/components/libs/util'

export default {
  name: 'JxlFormItem',
  components: { JxlTip },
  mixins: [mixin],
  props: {
    /**
     * 表单数据
     */
    form: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 表单项配置
     */
    item: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 校验规则
     */
    rules: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 是否显示冒号
     */
    colon: {
      type: Boolean,
      default: true
    },
    /**
     * 是否为表单查询模式
     * 监听 change 事件,回调为 query 事件
     */
    isQuery: {
      type: Boolean,
      default: false
    },
    /**
     * 输入空格
     * 可选值:none,auto, valid
     * none: 不能输入空格
     * auto: 自由输入空格, 默认值
     * valid: 不能连续输入空格
     */
    inputSpace: {
      type: String,
      default: 'auto'
    },
    /**
     * 查询表单方法
     */
    query: {
      type: Function,
      default: null
    }
  },
  data() {
    return {
      inputPlaceholder: ['el-input', 'el-input-number'], // 输入提示
      selectPlaceholder: ['el-select', 'el-time-select', 'el-time-picker', 'el-date-picker', 'el-cascader'] // 选择提示
    }
  },
  methods: {
    /**
     * 处理多选
     * @param item 绑定的表单对象
     */
    checkedChange(item) {
      const checkedCount = this.form[item.prop].length
      if (this._typeof(item.checkAll) === Object) {
        item.checkAll.value = checkedCount === item.options.length
        item.checkAll.indeterminate = checkedCount > 0 && checkedCount < item.options.length
      }
    },
    /**
     * 处理全选
     * @param item 绑定的表单对象
     */
    checkAllChange(item) {
      this.form[item.prop] = item.checkAll.value ? this.getValues(item.options) : []
      item.checkAll.indeterminate = false
    },
    /**
     * 获取对象列表的值,返回一个值得列表
     * @param array
     * @returns {array}
     */
    getValues(array) {
      return array.map(option => {
        if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
        if ('value' in option) return option['value']
        return option['id']
      })
    },
    /**
     * 获取选项数值
     * @param option
     */
    getOptionValue(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
      if ('value' in option) return option['value']
      return option['id']
    },
    /**
     * 获取选项标签
     * @param option
     * @returns {*}
     */
    getOptionLabel(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['label']]
      if ('label' in option) return option['label']
      return option['name']
    },
    /**
     * 插入属性
     * @param item
     */
    insertAttrs(item) {
      const attrs = _typeof(item.attrs) === Object ? item.attrs : {}
      if ('placeholder' in attrs) return attrs
      if (this.inputPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请输入' + item.label })
      if (this.selectPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请选择' + item.label })
      return attrs
    },
    /**
     * 输入处理
     * @param val
     */
    inputHandle(val) {
      if (!['valid', 'none'].includes(this.inputSpace)) return
      this.form[this.item.prop] = preventVoidInput({ text: val, trimAll: this.inputSpace === 'none' })
    },
    /**
     * 改变处理
     * @param val
     */
    changeHandle(val) {
      if (!this.isQuery) return
      this.$emit('query')
    }
  }
}
</script>
<style lang="less">
.el-form-item {
  &.has-colon {
    .el-form-item__label:after {
      content: ":";
      position: relative;
      left: 4px;
      top: -1px;
      padding-right: 4px;
      font-weight: 600;
    }
  }
}
</style>
<style lang="less" scoped>
  /deep/ .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  /deep/ .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  /deep/ .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  /deep/ .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
  .el-form-item-extra {
    margin-top: 4px;
    line-height: 1.5;
    min-height: 24px;
    clear: both;
    color: #00000073;
    font-size: 12px;
    transition: color .3s cubic-bezier(.215,.61,.355,1);
  }
</style>

JxlFormItem 表单项

满足开发者采用配置的方式,快速搭建表单的需求。

基于 ElementUI 的 el-form-item 标签封装。

<template>
  <el-form-item :label="item.label" :prop="item.prop" :rules="rules" :required="item.required" :class="[colon ? 'has-colon' : '']" v-bind="item.itemAttrs">
    <template v-if="item.label" slot="label">
      <!--条目名称-->
      <span>{{ item.label }}</span>
      <!--条目额外的内容-->
      <slot v-if="$scopedSlots['labelExtra']" name="labelExtra" :row="item" />
      <!--条目提示符-->
      <jxl-tip v-if="item.tip" :options="item.tip">
        <template v-if="_get(item, 'tip.prompt.content.slotName')" v-slot:content>
          <slot v-if="$scopedSlots['tipContent']" name="tipContent" :row="item" />
        </template>
      </jxl-tip>
    </template>
    <!--自定义组件-->
    <slot v-if="$scopedSlots['content']" name="content" :row="item" />
    <!--对象组件-->
    <template v-else-if="_typeof(item.component) === Object">
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--输入框-->
    <template v-else-if="item.component === 'el-input'">
      <el-input v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--计数器-->
    <template v-else-if="item.component === 'el-input-number'">
      <el-input-number v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--选择器-->
    <template v-else-if="item.component === 'el-select'">
      <el-select v-model="form[item.prop]" :name="item.prop" value="" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" @input="inputHandle" v-on="item.events">
        <el-option v-if="item['optionAll']" :label="item['optionAll'].label || '全部'" :value="item['optionAll'].value || ''" />
        <template v-for="(option, index) in item.options">
          <el-option v-if="isVisible(option)" :key="index" :label="getOptionLabel(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs" :value="getOptionValue(option)" />
        </template>
      </el-select>
    </template>
    <!--开关-->
    <template v-else-if="item.component === 'el-switch'">
      <el-switch v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" @input="inputHandle" v-on="item.events" />
    </template>
    <!--多选框-->
    <template v-else-if="item.component === 'el-checkbox'">
      <template v-if="_typeof(item.options) === Array">
        <el-checkbox v-if="_typeof(item.checkAll) === Object" v-model="item.checkAll.value" :name="item.prop + 'All'" :indeterminate="item.checkAll.indeterminate" v-bind="item.checkAll.attrs" @change="checkAllChange(item)">{{ item.checkAll.label || item.checkAll.name }}</el-checkbox>
        <el-checkbox-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" @change="checkedChange(item)">
          <template v-for="(option, index) in item.options">
            <el-checkbox v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-checkbox>
          </template>
        </el-checkbox-group>
      </template>
      <template v-else>
        <el-checkbox v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">{{ item.optionName }}</el-checkbox>
      </template>
    </template>
    <!--单选框-->
    <template v-else-if="item.component === 'el-radio'">
      <el-radio-group v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" @change="changeHandle" v-on="item.events">
        <template v-if="item['optionButton']">
          <template v-for="(option, index) in item.options">
            <el-radio-button v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio-button>
          </template>
        </template>
        <template v-else>
          <template v-for="(option, index) in item.options">
            <el-radio v-if="isVisible(option)" :key="index" :label="getOptionValue(option)" :disabled="isDisabled(option.attrs)" v-bind="option.attrs">{{ getOptionLabel(option) }}</el-radio>
          </template>
        </template>
      </el-radio-group>
    </template>
    <!--滑块-->
    <template v-else-if="item.component === 'el-slider'">
      <el-slider v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--时间选择器-固定时间点-->
    <template v-else-if="item.component === 'el-time-select'">
      <el-time-select v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--时间选择器-任意时间点-->
    <template v-else-if="item.component === 'el-time-picker'">
      <el-time-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--日期选择器-->
    <template v-else-if="item.component === 'el-date-picker'">
      <el-date-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" @change="changeHandle" v-on="item.events" />
    </template>
    <!--评分-->
    <template v-else-if="item.component === 'el-rate'">
      <el-rate v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--颜色选择器-->
    <template v-else-if="item.component === 'el-color-picker'">
      <el-color-picker v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--穿梭框-->
    <template v-else-if="item.component === 'el-transfer'">
      <el-transfer v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <!--级联选择器-->
    <template v-else-if="item.component === 'el-cascader'">
      <el-cascader v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="insertAttrs(item)" v-on="item.events" />
    </template>
    <!--上传-->
    <template v-else-if="item.component === 'el-upload'">
      <el-upload v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="Object.assign(item.attrs, item.events)" :action="item.attrs.action">
        <template v-if="$scopedSlots[item.uploadSlot]">
          <slot :name="item.uploadSlot" :row="item" />
        </template>
        <template v-else>
          <img v-if="form.avatar" :src="form.avatar" class="avatar" alt="">
          <i v-else class="el-icon-plus avatar-uploader-icon" />
        </template>
      </el-upload>
    </template>
    <!--具名组件-->
    <template v-else>
      <component :is="item.component" v-model="form[item.prop]" :name="item.prop" :disabled="isDisabled(item.attrs)" v-bind="item.attrs" v-on="item.events" />
    </template>
    <div v-if="item.extra" class="el-form-item-extra">{{ item.extra }}</div>
  </el-form-item>
</template>

<script>
import mixin from '@/components/libs/mixin'
import JxlTip from '@/components/libs/Tip/JxlTip'
import { _typeof } from '@/utils/libs/support'
import { preventVoidInput } from '@/components/libs/util'

export default {
  name: 'JxlFormItem',
  components: { JxlTip },
  mixins: [mixin],
  props: {
    /**
     * 表单数据
     */
    form: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 表单项配置
     */
    item: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 校验规则
     */
    rules: {
      type: Object,
      require: true,
      default: null
    },
    /**
     * 是否显示冒号
     */
    colon: {
      type: Boolean,
      default: true
    },
    /**
     * 是否为表单查询模式
     * 监听 change 事件,回调为 query 事件
     */
    isQuery: {
      type: Boolean,
      default: false
    },
    /**
     * 输入空格
     * 可选值:none,auto, valid
     * none: 不能输入空格
     * auto: 自由输入空格, 默认值
     * valid: 不能连续输入空格
     */
    inputSpace: {
      type: String,
      default: 'auto'
    },
    /**
     * 查询表单方法
     */
    query: {
      type: Function,
      default: null
    }
  },
  data() {
    return {
      inputPlaceholder: ['el-input', 'el-input-number'], // 输入提示
      selectPlaceholder: ['el-select', 'el-time-select', 'el-time-picker', 'el-date-picker', 'el-cascader'] // 选择提示
    }
  },
  methods: {
    /**
     * 处理多选
     * @param item 绑定的表单对象
     */
    checkedChange(item) {
      const checkedCount = this.form[item.prop].length
      if (this._typeof(item.checkAll) === Object) {
        item.checkAll.value = checkedCount === item.options.length
        item.checkAll.indeterminate = checkedCount > 0 && checkedCount < item.options.length
      }
    },
    /**
     * 处理全选
     * @param item 绑定的表单对象
     */
    checkAllChange(item) {
      this.form[item.prop] = item.checkAll.value ? this.getValues(item.options) : []
      item.checkAll.indeterminate = false
    },
    /**
     * 获取对象列表的值,返回一个值得列表
     * @param array
     * @returns {array}
     */
    getValues(array) {
      return array.map(option => {
        if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
        if ('value' in option) return option['value']
        return option['id']
      })
    },
    /**
     * 获取选项数值
     * @param option
     */
    getOptionValue(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['value']]
      if ('value' in option) return option['value']
      return option['id']
    },
    /**
     * 获取选项标签
     * @param option
     * @returns {*}
     */
    getOptionLabel(option) {
      if (this._typeof(option) === String) return option
      if (this._typeof(this.item.optionSource) === Object) return option[this.item.optionSource['label']]
      if ('label' in option) return option['label']
      return option['name']
    },
    /**
     * 插入属性
     * @param item
     */
    insertAttrs(item) {
      const attrs = _typeof(item.attrs) === Object ? item.attrs : {}
      if ('placeholder' in attrs) return attrs
      if (this.inputPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请输入' + item.label })
      if (this.selectPlaceholder.includes(item.component)) return Object.assign(attrs, { placeholder: '请选择' + item.label })
      return attrs
    },
    /**
     * 输入处理
     * @param val
     */
    inputHandle(val) {
      if (!['valid', 'none'].includes(this.inputSpace)) return
      this.form[this.item.prop] = preventVoidInput({ text: val, trimAll: this.inputSpace === 'none' })
    },
    /**
     * 改变处理
     * @param val
     */
    changeHandle(val) {
      if (!this.isQuery) return
      this.$emit('query')
    }
  }
}
</script>
<style lang="less">
.el-form-item {
  &.has-colon {
    .el-form-item__label:after {
      content: ":";
      position: relative;
      left: 4px;
      top: -1px;
      padding-right: 4px;
      font-weight: 600;
    }
  }
}
</style>
<style lang="less" scoped>
  /deep/ .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  /deep/ .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  /deep/ .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  /deep/ .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
  .el-form-item-extra {
    margin-top: 4px;
    line-height: 1.5;
    min-height: 24px;
    clear: both;
    color: #00000073;
    font-size: 12px;
    transition: color .3s cubic-bezier(.215,.61,.355,1);
  }
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容