看過了大多數文章,都是講解的知識點,但知識點的話我們很容易查詢各種檔案、書籍了解到,但實用的技巧就很難, 需要自己作業有一定的經歷,經常封裝各種組件,思考才能得來,本文的講解是我本人真實的專案經歷總結出來的,目前在我司這些組件在所有的專案中都能用
我們封裝的目的就是為了方便使用,簡化操作,而不用寫一大代碼
本環境說明是針對 Vue+ElementUI 講解的,在 react 中也很實用, 我主要講解的是一個思路
注意:在vue中組件封裝、我們要熟悉父子組件的傳值方式$attrs和$listeners,以及vue的render寫法,有很多必須要用jsx,用模板實作起來太麻煩, 如果你對這些不了解,建議去了解下,有很多關于這些的技術文章,本文就不在此過多說了
$attrs包含了父作用域中不作為props被識別 (且獲取) 的特性系結 (class和style除外)$listeners獲取父組件傳遞進來的所有事件函式- 子組件通過
this.$slots拿到插槽內容,拿默認插槽this.$slots.default, 拿<div slot="header"></slot>通過this.$slots.header jsx中作用域插槽傳參
<el-table-column
{...{ props: column }}
scopedSlots={{
default: scoped => {
const row = scoped.row
const prop = column.prop
const value = row[prop]
if (column.render) {
return column.render(h, { value, row, isEdit, index: scoped.$index })
}
return <span>{value}</span>
},
}}
></el-table-column>
我們就從一個后臺專案講解開始
提交按鈕
我們在提交的時候, 為了防止重復提交, 很多是用防抖或者節流, 但是這樣不好, 但網路很慢的時候, 還是會出現重復提交, 還有就是沒有狀態,就用戶不知道怎么到底提交了沒,都沒有一個狀態
我們要做的應該是給按鈕添加一個加載狀態,當點擊的時候,在提交中有一個loading狀態,el-button 自帶的就有一個 loading屬性

我們傳參的時候, 傳遞一個 submit提交函式,是一個Promise,但promise狀態改變的時候我們就把 loading狀態修改為false
封裝代碼
<template>
<el-button v-bind="$attrs" :loading="loading" @click="click">
<slot></slot>
</el-button>
</template>
<script>
export default {
name: "SubmitButton",
props: {
// 提交函式、
submit: {
type: Function,
require: true,
},
},
data() {
return {
loading: false,
};
},
methods: {
async click() {
try {
this.loading = true;
await this.submit();
} finally {
this.loading = false;
}
},
},
};
</script>
使用
我們在頁面中使用
<el-form ref="form" :model="dataForm" :rules="rules">
<el-form-item label="用戶名" prop="username">
<el-input placeholder="請輸入用戶名" v-model="dataForm.username"></el-input>
</el-form-item>
<el-form-item>
<SubmitButton type="primary" :submit="formSubmit">提 交</SubmitButton>
</el-form-item>
</el-form>
export default {
data() {
return {
dataForm: {
username: "",
},
rules: {
username: [
{
required: true,
message: "請輸入用戶名",
}
]
}
}
},
methods: {
validate() {
return this.$refs.form.validate();
},
async formSubmit() {
// 表單驗證
await this.validate();
// 提交表單資料
await this.$http.post('/api/xxxx', this.dataForm)
this.$message.success('提交成功!')
this.$router.back()
},
},
};
這樣我們只需要傳一天提交函式, 回傳一個 Promise 就可以了,點擊按鈕時就會有加載影片,加載中的時候,按鈕是禁用狀態,我們再次點擊提交,是不生效的,就防止了重復提交
對話框、抽屜
一般是一個關閉按鈕, 一個確定按鈕,點擊確定按鈕的時候要去呼叫介面
點擊關閉按鈕我們直接關閉,點擊確定按鈕是, 參照 提交按鈕,其實我們的對話框、抽屜這些, 都是在傳入一個提交函式``submit,這個函式回傳一個Promise`
- 當從
pending狀態變為reslove時,我們就關閉對話框, - 當從
pending狀態變為reject時,就提交的時候報錯了什么的, 我們不要關閉對話框
對話框代碼如下,抽屜和對話框差不多的
封裝代碼
<template>
<el-dialog v-bind="$attrs">
<template v-if="$slots.title">
<slot name="title" slot="title"></slot>
</template>
<slot></slot>
<template slot="footer">
<el-button @click="close">取 消</el-button>
<SubmitButton type="primary" :submit="btnSubmit">確 定</SubmitButton>
</template>
</el-dialog>
</template>
<script>
import SubmitButton from "@/components/SubmitButton"
export default {
name: "Dialog",
components: { SubmitButton },
props: {
submit: Function
},
methods: {
close() {
this.$emit("update:visible", false)
},
async btnSubmit() {
await this.submit()
this.close()
},
},
}
</script>
使用
<template>
<Dialog :visible.sync="visible" :submit="dialogSubmit">
<h3 slot="title">洗掉提示</h3>
你確定洗掉這個用戶嗎?
</Dialog>
</template>
<script>
export default {
data() {
return {
visible: true,
}
},
methods: {
async dialogSubmit() {
// 洗掉操作
await this.$http.post('/api/xxxx?id=xxxxx')
this.$message.success('洗掉成功!')
this.$emit('change')
},
},
}
</script>
資料字典
資料字典就是對存盤值的描述,比如用1表示男,0表示女、而我們傳給后端的資料就傳0和1, 后端回傳的時候也只回傳0和1、我們要根據0和1去匹配男女
如果是多選的話, 我們一般都是以逗號拼接,例如這樣傳 1,3,5, 很少會傳陣列
兩種資料字典
-
后臺配置的資料字典:一般我們要根據資料字典的code獲取資料字典選項值,比如有一個選擇學歷的下拉選單,我們就要通過下面的這個字典編碼
Education去查詢選項值,之后把拿到的資料渲染出來
-
前端界面寫死的資料字典:列如

封裝
我們傳給后端的的是資料字典的值,后端回傳的時候也是回傳的資料字典的值,查看詳情的時候我們也需要根據資料字典的值去匹配,拿到文本
我們先思考想怎么用,封裝一個組件叫 DictSelectTag , 用 label顯示文字、value表示關聯的值
根據資料字典code獲取資料字典項options
const promiseCache = new Map()
/**
* 根據資料字典code獲取資料字典項options
*/
function byDictCodeGetOptions(dictCode) {
const key = 'dict_' + dictCode
let promise = promiseCache.get(key)
// 當前promise快取中沒有 該promise
if (!promise) {
promise = request.get('/api/dict/' + dictCode).then(
({ data }) => {
return data.map(item => {
item.value = item.itemValue
item.label = item.itemName
return item
})
},
error => {
// 在請求回來后,如果出現問題,把promise從cache中洗掉 以避免第二次請求繼續出錯S
promiseCache.delete(key)
return Promise.reject(error)
}
)
promiseCache.set(key, promise)
}
// 回傳promise
return promise
}
另外就是針對前端頁面寫死的,我們想的是傳一個 options過去,想傳的型別是如下這樣
/** [{value: '禁用', label: '禁用' }, '啟用'] */
/** 這種格式 { 1:'禁用', 2: '啟用' } */
/** 這種格式 { 1:'禁用', 2: {label: '啟用', value: 2} } */
/** 這種格式 { 1:'禁用', 2: {label: '啟用'} } */
根據用戶傳入的options得到我們想要的options
根據用戶傳的options得到我們需要的options,我們還可以通過傳入props, 通過 props.label指定文字 key, props.value 指定value的可以
/**
* 根據options獲取完整的options
* @param {*} options
* @param {*} props
* @returns
*/
export function getDictOptions(options, props) {
if (!options) {
return []
}
const labelKey = (props && props.label) || "label"
const valueKey = (props && props.value) || "value"
/** [{value: '禁用', label: '禁用' }, '啟用'] */
if (Array.isArray(options)) {
return options.map((item) => {
if (typeof item === "string") {
return { value: item, label: item }
}
// object
return {
...item,
label: item[labelKey],
value: item[valueKey],
}
})
} else if (typeof options === "object") {
/** 這種格式 { 1:'禁用', 2: '啟用' } */
/** 這種格式 { 1:'禁用', 2: {label: '啟用', value: 2} } */
/** 這種格式 { 1:'禁用', 2: {label: '啟用'} } */
return Object.keys(options).map((key) => {
const item = options[key]
if (typeof item == "string") {
return { value: key, label: item }
}
// object
return {
...item,
label: item[labelKey],
value: item[valueKey] || key,
}
})
} else {
throw new TypeError("傳入型別不對")
}
}
組件代碼
我們封裝的組件要分為查看狀態和編輯狀態,編輯狀態常有的組件用 radio,checkbox,select,還有一個 radioButton,不過這個用的很少
DictSelectTag.vue
<template>
<!-- 查看狀態 -->
<span v-if="isLook">
{{ getText() || empty }}
</span>
<el-radio-group v-bind="$attrs" v-else-if="tagType === 'radio'" v-on="listeners" :value="getValue" :disabled="disabled" :id="id">
<el-radio v-for="(item, key) in dictOptions" :key="key" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
<el-select
v-bind="$attrs"
:id="id"
v-else-if="tagType === 'select'"
:placeholder="placeholder"
:disabled="disabled"
:value="getValue"
v-on="listeners"
>
<el-option v-for="(item, key) in dictOptions" :key="key" :value="item.value" :label="item.label"></el-option>
</el-select>
<el-checkbox-group
v-bind="$attrs"
:id="id"
v-else-if="tagType === 'checkbox'"
:placeholder="placeholder"
:disabled="disabled"
:value="getValue"
v-on="listeners"
>
<el-checkbox v-for="(item, key) in dictOptions" :key="key" :label="item.value">{{ item.label }}</el-checkbox>
</template>
<script>
import { getDictOptions, isEmpty, byDictCodeGetOptions } from '@/utils'
export default {
name: 'DictSelectTag',
props: {
/**
* 字典資料、沒有的話就使用code去查詢
* @type { Array<{ label:string, value: string | number }> | Object<[value]: string> }
*/
options: [Array, Object],
/** 字典 code */
dictCode: {
type: String,
},
value: [String, Number, Array],
/** 型別 */
type: {
type: String,
default: 'select',
validator: val => {
return ['radio', 'select', 'radio', 'checkbox'].indexOf(val) !== -1
},
},
/** 值是以逗號分割 */
commaSplit: Boolean,
/** 知道 label,和 value 的 key { label: 'labelK', value: 'value' } */
props: Object,
/** 是否是查看 */
isLook: Boolean,
/** 資料為空時顯示什么 */
empty: {
// type: String,
default: '-',
},
/** 分割字串,分割符,多個值的時候查看時以什么分割 */
separator: {
type: String,
default: ' / ',
},
placeholder: String,
disabled: Boolean,
id: String,
},
data() {
return {
dictOptions: [],
}
},
computed: {
tagType() {
return this.type
},
getValue() {
if (this.commaSplit) {
if (this.value) return this.value.split(',')
return []
}
return this.value != null ? this.value.toString() : undefined
},
listeners() {
return {
...this.$listeners,
input: this.onInput,
}
},
},
watch: {
dictCode: {
immediate: true,
handler() {
this.initDictData()
},
},
options: 'initDictData',
},
methods: {
initDictData() {
if (this.options) {
this.dictOptions = getDictOptions(this.options, this.props)
} else {
byDictCodeGetOptions(this.dictCode).then(result => (this.dictOptions = Object.freeze(result)))
}
},
onInput(val) {
if (this.commaSplit && Array.isArray(val)) {
val = val.join(',')
}
this.$emit('input', val)
},
// 查看的時候
getText() {
const { dictOptions } = this
const valueArr = isEmpty(this.value) ? [] : String(this.value).split(',')
return valueArr
.map(value => {
const find = dictOptions.find(item => item.value == value)
return (find && find.label) || value
})
.join(this.separator)
},
},
}
</script>
使用
<el-form-item label="上市階段">
<dict-select-tag
v-model="dataForm.listedProgress"
multiple
collapse-tags
dict-code="record_listed_stage"
clearable
@change="handleSearch"
></dict-select-tag>
</el-form-item>
<el-form-item label="備案型別">
<dict-select-tag
dictCode="-"
multiple
collapse-tags
:options="recordTypeOptions"
v-model="dataForm.choiceType"
clearable
@change="handleSearch"
></dict-select-tag>
</el-form-item>

<el-form-item label="企業資質" prop="aptitude" width="200">
<dict-select-tag
type="checkbox"
dictCode="governmentIdentification"
v-model="dataForm.aptitude"
:isLook="isLook"
></dict-select-tag>
</el-form-item>

限制輸入框封裝
很多輸入限制,比如只能輸入數字,只能輸入小數

使用
<!--- 只能輸入 正整數 --->
<InputLimit inputLimit="number" v-model="age" placeholder="請輸入年齡"></InputLimit>
<!--- 只能輸入 小數、包含正整數、開頭只能為一個零 --->
<InputLimit inputLimit="decimal" v-model="money" placeholder="請輸入金額"></InputLimit>
<!-- 7位整數4位小數 -->
<InputLimit :inputLimit="/^(0|[1-9]\d{0,6})(\.\d{0,4})?$/" v-model="investMoney" placeholder="請輸入">
<template slot="suffix">萬元</template>
</InputLimit>
代碼
@/utils/index.js
const _toString = Object.prototype.toString
export function isRegExp(v) {
return _toString.call(v) === "[object RegExp]"
}
/** 正整數 */
export const intergerRE = /^\d*$/
/** 小數、包含正整數、開頭只能為一個零 */
export const decimalRE = /^(0|[1-9]\d*)(\.\d*)?$/
import { isRegExp, decimalRE, intergerRE } from "@/utils"
const limitList = [
/* 小數、包含正整數、開頭只能為一個零 */
{ name: "decimal", regexp: decimalRE },
/* 正整數 */
{ name: "number", regexp: intergerRE },
]
let timer
export default {
name: 'ElInputLimit',
props: {
value: {
type: [String, Number],
},
// 輸入限制、 decimal | number
// 優先順序,RegExp ==> limitList
inputLimit: {
type: [String, RegExp],
validator: function (val) {
if (val) {
if (isRegExp(val)) {
return true
}
return !!limitList.find(item => item.name === val)
} else {
return true
}
},
},
// el-tooltip 的 props,如果傳字串就是 傳 content
tooltip: {
type: [String, Object],
},
},
data() {
return {
showValue: this.value,
vRegexp: '',
tooltipVisible: false,
tooltipProps: {},
}
},
watch: {
value(val) {
this.showValue = val
},
inputLimit: 'setRegexp',
tooltip: {
immediate: true,
handler(val) {
const options = typeof val === 'string' ? { content: val } : val
this.tooltipProps = {
effect: 'light',
content: '不能輸入該字符',
placement: 'bottom',
...options,
}
},
},
},
created() {
this.setRegexp()
},
methods: {
setRegexp() {
const { inputLimit } = this
if (!inputLimit) {
this.vRegexp = ''
return
}
if (isRegExp(inputLimit)) {
this.vRegexp = inputLimit
return
}
if (inputLimit) {
const limit = limitList.find(item => item.name === inputLimit)
if (limit) {
this.vRegexp = limit.regexp
} else {
this.vRegexp = ''
}
}
},
onInput(val) {
clearTimeout(timer)
if (val !== undefined && val !== '') {
const re = this.vRegexp
if (re) {
// 輸入不合法(不在規則內)的字符
if (!re.test(val)) {
this.tooltipVisible = true
timer = setTimeout(() => {
this.tooltipVisible = false
}, 2000)
return
}
}
}
this.tooltipVisible = false
this.$emit('input', val)
},
onBlur(event) {
this.tooltipVisible = false
this.$emit('blur', event)
},
},
render() {
return (
<el-tooltip value={this.tooltipVisible} manual {...{ props: this.tooltipProps }}>
<el-input
value={this.showValue}
// 傳遞 attrs,解決 el-input 有部分收集的是 $attrs
{...{ props: this.$attrs, attrs: this.$attrs, on: { ...this.$listeners, input: this.onInput, blur: this.onBlur } }}
>
{Object.entries(this.$slots).map(([name, slot]) => (
<template slot={name}>{slot}</template>
))}
</el-input>
</el-tooltip>
)
},
}
日期范圍選擇器
element-ui的默認日期范圍選擇器系結的是一個陣列,我們傳給后端時還需要來回轉比較麻煩
封裝之后的使用如下代碼就可以了
<date-range-picker :startTime.sync="dataForm.startTime" :endTime.sync="dataForm.endTime"></date-range-picker>
封裝如下
<template>
<el-date-picker
v-bind="$attrs"
v-model="date"
:value-format="valueFormat"
:type="type"
@change="change"
v-on="$listeners"
:start-placeholder="startPlaceholder"
:end-placeholder="endPlaceholder"
>
</el-date-picker>
</template>
<script>
export default {
name: 'DateRangePicker',
props: {
type: {
// datetimerange
type: String,
default: 'daterange'
},
startTime: [Number, String, Date],
endTime: [Number, String, Date],
valueFormat: {
type: String,
default: 'yyyy-MM-dd HH:mm:ss'
},
startPlaceholder: {
type: String,
default: '開始日期'
},
endPlaceholder: {
type: String,
default: '結束日期'
}
},
data() {
return {
date: ''
}
},
watch: {
startTime: 'watchDateChangeHandler',
endTime: 'watchDateChangeHandler'
},
created() {
this.watchDateChangeHandler()
},
methods: {
change(val) {
let startTime = ''
let endTime = ''
if (val && Array.isArray(val)) {
startTime = val[0]
endTime = val[1]
}
this.$emit('update:startTime', startTime)
this.$emit('update:endTime', endTime)
this.$emit('change')
},
watchDateChangeHandler() {
const { startTime, endTime } = this
if ((startTime === 0 || startTime) && endTime) {
this.date = [startTime, endTime]
} else {
this.date = null
}
}
}
}
</script>
表單封裝
后臺管理專案中,想一般表單有編輯模式和查看模式,大體上查看模式和編輯模式是一樣的布局
我們使用element-ui 的表單,都知道表單寫著很麻煩,大體里面就是Input輸入框,Select 選擇器,單選框,多選框,時間選擇器這些,但每一次要寫一個 el-from-item,需要指定label,prop, 而里面的具體控制元件還要系結,寫 placeholder,寫rules
可能表單要考慮回應式布局,那么 el-from-item 還需要 el-col 包裹
先看看我封裝的在專案中使用的效果吧
效果預覽
使用代碼

對應頁面查看狀態
能支持回應式的,縮小的時候欄位單獨一行

對應頁面編輯狀態

封裝代碼
FormItem
import { formatDate, getValueByPath, isEmpty, setValueByPath } from '@/utils'
export default {
name: 'ZFormItem',
inheritAttrs: false,
props: {
// 可以手動傳 model 值,否則使用 el-form 上面的 model
model: Object,
// el-form-item 的 label
label: String,
// 關聯的值、和 el-form-item 的 prop 一樣, 可以這樣傳 'a.b.c'
prop: String,
// el-form-item 的rules
rules: [Object, Array],
// 是否必填
required: Boolean,
labelWidth: [String, Number],
// 型別, 默認值: 有dictCode和options時默認為select,否則為text(input)
type: {
type: String,
validator: val => {
return ['text', 'textarea', 'radio', 'checkbox', 'select', 'date'].indexOf(val) !== -1
},
},
// 字典code,如果有字典code, 就是用dict-select-tag
dictCode: String,
options: [Array, Object],
placeholder: String,
// 資料未空時顯示什么, 可傳 vnode
empty: {
// default: '',
},
// layout 布局
layout: [Number, Object], // el-row span 值、如果使用了 Object, 那么就系結
// layout :{ span:12, xs: 24: lg: 12 }
// 單位-我這個比較特殊(萬元、人)
unit: {},
// 時間輸入框的 type
dateType: {
type: String,
},
},
computed: {
tagType() {
if (!this.type) {
if (this.options || this.dictCode) {
return 'select'
} else {
return 'text'
}
} else {
return this.type
}
},
form() {
if (this.model) return {}
let parent = this.$parent
let parentName = parent.$options.componentName
while (parentName !== 'ElForm') {
parent = parent.$parent
parentName = parent.$options.componentName
}
return parent
},
isEdit() {
if (this.form) {
return !this.form.$attrs?.isLook
}
return true
},
dataForm() {
if (this.model) {
return this.model
}
return this.form.model || {}
},
realValue: {
get() {
// return this.dataForm[this.prop]
return getValueByPath(this.dataForm, this.prop)
},
set(val) {
// this.model[this.prop] = val
// this.$set(this.dataForm, this.prop, val)
setValueByPath(this.dataForm, this.prop, val, true)
},
},
showPlaceholder() {
if (this.placeholder) {
return this.placeholder
}
if (this.tagType === 'text' || this.tagType === 'textarea') {
return '請輸入' + this.label
}
return '請選擇' + this.label
},
},
methods: {
isEmpty,
renderFormInner() {
const { dictCode, options, isEdit } = this
// 資料字典
if (dictCode || options) {
return <dict-select-tag {...{ props: this.$attrs, on: this.$listeners }} dict-code={dictCode} options={options} v-model={this.realValue} type={this.tagType} is-look={!isEdit}></dict-select-tag>
}
if (this.tagType === 'date') {
// 時間范圍選擇器
if (this.dateType?.includes('range')) {
const formats = {
daterange: 'yyyy-MM-dd',
datetimerange: 'yyyy-MM-dd HH:mm',
}
const format = this.$attrs.format || formats[this.dateType]
if (isEdit) {
return <DateRangePicker {...{ props: this.$attrs, on: this.$listeners }} type={this.dateType} value-format="timestamp"></DateRangePicker>
}
return (
<span>
{formatDate(this.$attrs.startTime, format)} 至 {formatDate(this.$attrs.endTime, format)}
</span>
)
}
if (isEdit) {
return <el-date-picker v-model={this.realValue} {...{ props: this.$attrs, on: this.$listeners }} value-format="timestamp" type={this.dateType || 'date'} placeholder="選擇日期"></el-date-picker>
}
return <span>{formatDate(this.realValue, this.$attrs.format)}</span>
}
// 編輯
if (this.isEdit) {
// 輸入框 input
return (
<ElInputLimit {...{ props: this.$attrs, attrs: this.$attrs, on: this.$listeners }} type={this.tagType} v-model={this.realValue} placeholder={this.showPlaceholder}>
{
// this.$slots.suffix && <span slot="suffix">{this.$slots.suffix}</span>
this.unit && (
<span slot="suffix" class="unit suffix">
{this.unit}
</span>
)
}
</ElInputLimit>
)
} else {
// 輸入框 input
return <span>{this.isEmpty(this.realValue) ? this.empty : this.realValue + (this.unit || '')}</span>
}
},
getRules() {
if (!this.isEdit) {
return undefined
}
let rules
if (this.required) {
if (this.tagType === 'text' || this.tagType === 'textarea') {
rules = {
message: '請輸入' + this.label,
required: true,
trigger: 'blur',
}
} else {
rules = {
message: '請選擇' + this.label,
required: true,
trigger: ['change'],
}
}
}
if (this.rules) {
if (rules) {
if (Array.isArray(this.rules)) {
rules = [rules, ...this.rules]
} else {
rules = [rules, this.rules]
}
} else {
rules = this.rules
}
}
return rules
},
renderFormItem() {
const rules = this.getRules()
return (
<el-form-item label={this.label} prop={this.prop} rules={rules} labelWidth={this.labelWidth}>
{this.$slots.default ? this.$slots.default : this.renderFormInner()}
</el-form-item>
)
},
},
render(h) {
const formItem = this.renderFormItem()
if (typeof this.layout === 'object') {
return h('el-col', { props: this.layout }, [formItem])
} else if (this.layout) {
return <el-col span={this.layout}>{formItem}</el-col>
}
return formItem
},
}
工具庫代碼 @/utils/prop
/**
* 根據欄位 key 獲取值或設定值,參考element-ui原始碼,小部分調整
*/
import Vue from 'vue'
export function getValueByPath(object, prop) {
prop = prop || ''
const paths = prop.split('.')
let current = object
let result = null
for (let i = 0, j = paths.length; i < j; i++) {
const path = paths[i]
if (!current) break
if (i === j - 1) {
result = current[path]
break
}
current = current[path]
}
return result
}
export function setValueByPath(object, prop, val, isVueSet) {
prop = prop || ''
const paths = prop.split('.')
let current = object
for (let i = 0, j = paths.length; i < j; i++) {
const path = paths[i]
if (!current) break
if (i === j - 1) {
if (isVueSet) {
Vue.set(current, path, val)
} else {
current[path] = val
}
break
}
current = current[path]
}
}
時間格式化庫 @/utils/date
/**
* 日期時間格式庫
* @author zhoufei
*/
function zeroize(n) {
return Number(n) >= 10 ? n : "0" + n
}
/**
* new Date
* @param date
*/
function newDate(date = new Date()) {
if (Object.prototype.toString.call(date) === "[object Date]") {
return date
} else if (Number(date)) {
return new Date(Number(date))
} else if (typeof date === "string") {
// 在ios上必須要用 YYYY/MM/DD 的格式
date = date.replace(new RegExp(/-/gm), "/")
// 在ie瀏覽器中還必須補零 new Date('2020-01')可以, new Date('2020/1')不可以
return new Date(date)
} else {
return new Date(date)
}
}
function isEmpty(v) {
return v === "" || v === undefined || v === null
}
/**
* 按所給的時間格式輸出指定的時間
* @param {Date|Number|String} data
* @param {string} format 格式化字串
* @return {string} 格式化的時間
* @example formatDate(new Date(1409894060000), 'yyyy-MM-dd HH:mm:ss 星期w') ==> "2014-09-05 13:14:20 星期五"
* 格式說明
對于 2014.09.05 13:14:20
yyyy: 年份,2014
yy: 年份,14
MM: 月份,補滿兩位,09
M: 月份, 9
dd: 日期,補滿兩位,05
d: 日期, 5
HH: 24制小時,補滿兩位,13
H: 24制小時,13
hh: 12制小時,補滿兩位,01
h: 12制小時,1
mm: 分鐘,補滿兩位,14
m: 分鐘,14
ss: 秒,補滿兩位,20
s: 秒,20
*/
function formatDate(date, format = "yyyy/MM/dd HH:mm") {
if (isEmpty(date)) return "-"
if (date) var tmpDate = newDate(date)
if (String(tmpDate) === "Invalid Date") return date
date = tmpDate
var y = date.getFullYear()
var obj = {
M: date.getMonth() + 1, // 0 ~ 11
d: date.getDate(), // 1 ~ 31
H: date.getHours(), // 0 ~ 23
h: date.getHours() % 12,
m: date.getMinutes(), // 0 ~ 59
s: date.getSeconds(), // 0 ~ 59
w: ["日", "一", "二", "三", "四", "五", "六"][date.getDay()], // 0 ~ 6
}
format = format.replace(/yy(yy)?/, function(_, v) {
return v ? y + "" : (y + "").slice(-2)
})
for (var key in obj) {
// format = format.replace(new RegExp(`${key}(${key})?`), (_, v) => v ? zeroize(obj[key]) : obj[key])
var reg = new RegExp(key + "(" + key + ")?")
format = format.replace(reg, function(_, v) {
return v ? zeroize(obj[key]) : obj[key]
})
}
return format
}
export { formatDate }
校驗
方便傳入各種校驗規則 rulesConf.mobile 校驗手機號,rulesConf.xxx這樣來校驗
<ZFormItem label="手機號碼" prop="responsibilityMobile" :rules="rulesConf.mobile" maxlength="11" show-word-limit></ZFormItem><ZFormItem label="官方網址" prop="website" :rules="rulesConf.url" maxlength="40" show-word-limit></ZFormItem>
@/utils/validate
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* URL地址
* @param {*} s
*/
export function isURL(s) {
return /^http[s]?:\/\/.*/.test(s)
}
/** 正整數 */
export const intergerRE = /^\d*$/
/** 小數、包含正整數、開頭只能為一個零 */
export const decimalRE = /^(0|[1-9]\d*)(\.\d*)?$/
/** 手機號碼 */
export const mobileRE = /^1[3456789]\d{9}$/
/** 座機 */
export const landlineRE = /^(0[0-9]{2,3}\-)([2-9][0-9]{6,7})+(\-[0-9]{1,4})?$/
/** 郵編 */
export const postcodeRE = /^[1-9]\d{5}$/
/** 傳真 */
export const faxRE = /^(\d{3,4}-)?\d{7,8}$/
/** 電子郵箱 */
export const emailRE = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
/** 信用代碼 */
export const creditCodeRE = /^[A-Z0-9]{18}$/
/** 英文名稱 */
export const englishName = /^[a-zA-Z&.,\'\/\\\-\_\(\)\s]+$/g
/**
* 正整數
* @param {*} val
*/
export function isInterger(val) {
return intergerRE.test(val)
}
/**
* 小數、包含正數、開頭只能為一個零
* @param {*} val
*/
export function isDecimal(val) {
return decimalRE.test(val)
}
/**
* 手機號碼
* @param {*} val
*/
export function isMoblie(val) {
return mobileRE.test(val)
}
/**
* 座機號碼
* @param {*} val
*/
export function isLandline(val) {
return landlineRE.test(val)
}
/**
* 郵編
* @param {*} val
*/
export function isPostcode(val) {
return postcodeRE.test(val)
}
export function isFax(val) {
return faxRE.test(val)
}
/**
* 電子郵箱
* @param {*} val
*/
export function isEmail(val) {
return emailRE.test(val)
}
/**
* 信用代碼
* @param {*} val
*/
export function isCreditCode(val) {
return creditCodeRE.test(val)
}
/**
* 英文名稱
* @param {*} val
*/
export function isEnglishName(val) {
return englishName.test(val)
}
/** 身份證號碼規則、但不能校驗是否正確 */
export const IDCardNORE = /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/
/**
* 是否是身份證號碼
* @param {string} idcard 注意身份證號碼必須是字串,數字格式javascript放不了這么多位
*/
export function isIDCardNO(idcard) {
const result = parseIDCardNO(idcard)
if (typeof result === 'object') return true
return false
}
/**
* 決議身份證號碼
* @param {string} ID
*/
function parseIDCardNO(ID) {
if (ID === '' || ID === undefined || ID === null) return false
if (typeof ID !== 'string') return '非法字串'
let city = { 11: '北京', 12: '天津', 13: '河北', 14: '山西', 15: '內蒙古', 21: '遼寧', 22: '吉林', 23: '黑龍江 ', 31: '上海', 32: '江蘇', 33: '浙江', 34: '安徽', 35: '福建', 36: '江西', 37: '山東', 41: '河南', 42: '湖北 ', 43: '湖南', 44: '廣東', 45: '廣西', 46: '海南', 50: '重慶', 51: '四川', 52: '貴州', 53: '云南', 54: '西藏 ', 61: '陜西', 62: '甘肅', 63: '青海', 64: '寧夏', 65: '新疆', 71: '臺灣', 81: '香港', 82: '澳門', 91: '國外' }
let birthday = ID.substr(6, 4) + '/' + Number(ID.substr(10, 2)) + '/' + Number(ID.substr(12, 2))
let d = new Date(birthday)
let newBirthday = d.getFullYear() + '/' + Number(d.getMonth() + 1) + '/' + Number(d.getDate())
let currentTime = new Date().getTime()
let time = d.getTime()
let arrInt = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
let arrCh = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
let sum = 0
let i
let residue
if (!/^\d{17}(\d|x)$/i.test(ID)) return '非法身份證'
if (city[ID.substr(0, 2)] === undefined) return '非法地區'
if (time >= currentTime || birthday !== newBirthday) return '非法生日'
for (i = 0; i < 17; i++) {
sum += ID.substr(i, 1) * arrInt[i]
}
residue = arrCh[sum % 11]
if (residue !== ID.substr(17, 1)) return '非法身份證哦'
return {
result: city[ID.substr(0, 2)] + ',' + birthday + ',' + (ID.substr(16, 1) % 2 ? ' 男' : '女')
}
}
const elREValidator = function(rule, value, callback) {
// 不在這里驗證必填
if (value === '' || value === undefined || value == null) {
return callback()
}
if (!rule.regexp.test(value)) {
return callback(new Error(rule.message))
}
return callback()
}
/**
* 校驗規則
*/
export const rulesConf = Object.freeze({
generateRequired(label, trigger = 'blur') {
return {
required: true,
message: (trigger === 'blur' ? '請輸入' : '請選擇') + label,
trigger
}
},
/** 必填 */
required: {
required: true,
message: '這是必填項,請輸入',
trigger: 'blur'
},
// 有可能資料要校驗,但不是必填的
// 所以以下校驗都沒有加必填標識
mobile: {
message: '手機號碼格式不正確',
regexp: mobileRE,
validator: elREValidator,
trigger: ['blur']
},
phone: {
message: '電話號碼格式不正確',
// regexp: phoneRE,
validator: function(rule, value, callback) {
// 不在這里驗證必填
if (value === '' || value === undefined || value == null) {
return callback()
}
if (mobileRE.test(value) || landlineRE.test(value)) {
return callback()
}
return callback(new Error(rule.message))
},
trigger: ['blur']
},
/** 郵政編碼 */
postcode: {
message: '郵政編碼格式不正確',
pattern: postcodeRE,
trigger: ['blur']
},
/** 信用代碼 */
creditCode: {
message: '信用代碼格式不正確',
regexp: creditCodeRE,
validator: elREValidator,
trigger: ['blur']
},
/** 郵箱 */
email: {
message: '郵箱格式不正確,請重新輸入',
regexp: emailRE,
validator: elREValidator,
trigger: ['blur']
},
/** 身份證號碼 */
idCard: {
message: '身份證號碼格式不正確,請重新輸入',
regexp: IDCardNORE,
validator: elREValidator,
trigger: ['blur']
},
url: {
regexp: /^http[s]?:\/\/.*/,
message: 'url地址輸入不正確,請重新輸入',
validator: elREValidator,
trigger: ['blur']
}
})
表格、串列分頁資料
未完,待更新
這個表格的封裝方法有很多,要看具體的專案,需要適當修改代碼
先看看我再專案中怎么用的吧、這個 useList 之后就有表格的所有資訊,頁碼,加載,資料,重置,搜索等
<template>
<div class="padding-24">
<div class="flex-justify-center mb-24">
<SearchInput v-model="table.params.name" @search="table.search"></SearchInput>
<el-button type="text" @click="table.reset">重置</el-button>
<Pagination v-bind="table.pagination"></Pagination>
</div>
<el-table :data="table.list" v-loading="table.loading">
<el-table-column label="問題名稱" prop="name"></el-table-column>
<el-table-column label="所屬分類">
<template v-slot="{ row }">
{{ row.requstionRsp.map((item) => item.name).join("、") }}
</template>
</el-table-column>
<el-table-column label="操作" prop="id" width="120">
<template v-slot="{ row }">
<router-link :to="'/issue/' + row.id"><button class="text-button">詳情</button></router-link>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { useList } from "@/utils"
export default {
data() {
return {
table: useList({
params: {
name: "", //問題名稱
id: "", // 型別id
},
request: (http, params) => {
// 一定要return
return http.post("/api-backstand/foundation/selelctQuesttionDetailList", params)
},
}),
}
},
}
</script>
未完待更新…
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/404161.html
標籤:其他
