vue3中,渲染动态表单(一)——定义公共样式、使用动态样式class & 抽离el-dialog对话框组件 & 父子传参 & 自定义slot插槽 & vue3中watch写法和computed写法
效果图
文件目录
1、主页面
defineForm.vue
<el-button type="primary" @click="see">生成预览</el-button>
<ExampleInfo v-if="showExample" v-model:dialogVisible="showExample" :info-data="exampleInfo" @emit-confirm="exampleOk" />
<script lang="ts" setup>
import { ref } from 'vue';
import ExampleInfo from './exampleInfo.vue'
const showExample = ref(false)
const exampleInfo = ref({})
const see = () => {
showExample.value = true
exampleInfo.value = {
// id: fieldId,
// typeName: 'add'
}
}
const exampleOk = () => {
// tableData.value = []
// getList()
}
2.1、渲染主页面
exampleInfo.vue
<!--
@Description 申请表管理 - 定义申请表 - 预览
@author wdd
@date 2023/12/14
-->
<template>
<div>
<el-dialog title="申请表" v-model="dialogVisible" :close-on-press-escape="false" :close-on-click-modal="false"
width="80%">
<div class="content">
<el-form label-width="110px" ref="formRef" :model="formData">
<CustomForm :form-data="formData" :form-item="topicItems">
<!-- 插入自定义模块 -->
<template #topicName="{ item }">
<!-- <el-input v-model="formData.topicName" :disabled="formData.id ? true : false"
maxlength="100" placeholder="请输入小组名称" show-word-limit /> -->
<el-form-item :class="item.className" :label="item.label" :label-width="110"
:prop="item.prop" :rules="item.rules">
<el-input v-model="formData.topicName" :disabled="false" maxlength="200"
placeholder="请输入" />
</el-form-item>
</template>
<!-- 编辑自定义样式 -->
<template #prjName="{ item }">
<el-form-item :class="item.className" :label="item.label" :label-width="110"
:prop="item.prop" :rules="item.rules">
<el-input v-model="formData.prjName" :disabled="false" maxlength="200"
placeholder="请输入" />
</el-form-item>
</template>
<!-- 插入自定义模块 -->
<template #title="{ item }">
<div :class="item.className">
<div style="font-size:16px;font-weight:700;padding:0 20px 10px 24px;"> 小组成员:</div>
</div>
<CustomForm :form-data="formData" :form-item="topicItemOld"></CustomForm>
</template>
<!-- 编辑自定义样式 -->
<template #startDate>
<el-date-picker v-model="formData.startDate" :clearable="false"
:disabled-date="disableStartData" format="YYYY-MM-DD" placeholder="请输入开始时间" type="date"
value-format="YYYY-MM-DD" />
</template>
<!-- 编辑自定义样式 -->
<template #endDate>
<el-date-picker v-model="formData.endDate" :clearable="false"
:disabled-date="disableStartData" format="YYYY-MM-DD" placeholder="请输入结束时间" type="date"
value-format="YYYY-MM-DD" />
</template>
<!-- 插入自定义模块 -->
<template #titleA="{ item }">
<div :class="item.className">
<div style="font-size:16px;font-weight:700;padding:0 20px 10px 24px;"> 项目成员:</div>
</div>
<CustomForm :form-data="formData" :form-item="topicItemNew"></CustomForm>
</template>
<!-- 插入自定义模块 -->
<template #titleB="{ item }">
<div :class="item.className">
<div style="font-size:16px;font-weight:700;padding:0 20px 10px 24px;"> 获奖情况:</div>
</div>
<CustomForm :form-data="formData" :form-item="awardsItemInfo"></CustomForm>
</template>
</CustomForm>
</el-form>
</div>
<template #footer>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, defineEmits, computed, watch } from 'vue';
import CustomForm from './customForm.vue'
import { topicItem, teamItem, infoItem, awardsItem } from './data'
const topicItems = ref(topicItem)
// const topicItemNew = ref(topicItem.slice(3, 8))
const topicItemNew = ref(infoItem)
const topicItemOld = ref(teamItem)
const awardsItemInfo = ref(awardsItem)
const formRef = ref()
const formData = ref({
// topicName: '',
// researchContent: '',
// expectTarget: '',
// examQuota: '',
// formulaPercentage: '',
// // carryScheme: '',
// stuAttr: '',
// email: '',
// isIntake: '0',
// cooperMode: ['1'],
// startDate: '',
// attachFileArr: [],
// endDate: '',
})
const props = defineProps({
infoData: {
default: null,
type: Object,
},
dialogVisible: {
type: Boolean,
default() {
return false
},
},
})
//监听方法
const emit = defineEmits(['emit-confirm', 'update:dialogVisible'])
const dialogVisible = computed({
get: () => props.dialogVisible,
set: (val) => emit('update:dialogVisible', val),
})
watch(
() => props.dialogVisible,
(newVal) => {
if (newVal) {
console.log(props.infoData);
formInline.value = props.infoData
}
}
)
const disableStartData = (time) => {
return (
time.getTime() < new Date(formData.value.startDate).getTime() ||
time.getTime() > new Date(formData.value.endDate).getTime()
)
}
const confirm = () => {
console.log(12, formData.value);
// emit('emit-confirm', formInline.value)
// emit('update:dialogVisible', false)
}
</script>
<style lang="scss" scoped>
:deep(.el-dialog__body) {
padding-top: -20px;
// min-height: 60vh;
.content {
// margin-right: 20px;
width: 100%;
// margin-top: -20px;
}
}
</style>
2.2、渲染组件页面
customForm.vue
<!--
@Description 申请表管理 - 定义申请表 - 表单类型
@author wdd
@date 2023/12/18
-->
<template>
<div class="content">
<template v-for="item in formItem" :key="item.prop">
<template v-if="!item.show">
<el-form-item
:key="item.prop"
:class="item.className"
:label="item.label"
:label-width="item.labelWidth"
:prop="item.prop"
:rules="item.rules"
>
<template v-if="!item.slot">
<!-- input 输入框类型 -->
<el-input
v-if="item.type === 'input'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
:maxlength="item.maxLength"
:placeholder="item.placeholder"
:show-word-limit="item.limit"
/>
<!-- number 数字类型 -->
<el-input-number
v-if="item.type === 'number'"
v-model="dataSource[`${item.prop}`]"
:controls="false"
:disabled="item.disabled || isEditDisabled"
:max="item.max || 99999999"
:min="item.min || 0"
style="width:100%"
:placeholder="item.placeholder"
:precision="item.precision"
@keydown="
(val) => (val.key === 'e' ? (val.returnValue = false) : true)
"
/>
<!-- textarea 多行文本类型 -->
<el-input
v-if="item.type === 'textarea'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
:maxlength="item.maxLength || 1000"
:placeholder="item.placeholder"
:rows="item.rows || 2"
show-word-limit
type="textarea"
/>
<!-- date 日期类型 -->
<el-date-picker
v-if="item.type === 'date'"
v-model="dataSource[`${item.prop}`]"
:clearable="false"
:disabled="item.disabled || isEditDisabled"
format="YYYY-MM-DD"
:placeholder="item.placeholder"
type="date"
style="width:100%"
value-format="YYYY-MM-DD"
/>
<!-- month 年月类型 -->
<el-date-picker
v-if="item.type === 'month'"
v-model="dataSource[`${item.prop}`]"
:clearable="false"
:disabled="item.disabled || isEditDisabled"
format="YYYY-MM"
style="width:100%"
:placeholder="item.placeholder"
type="month"
value-format="YYYY-MM"
/>
<!-- year 年度类型 -->
<el-date-picker
v-if="item.type === 'year'"
v-model="dataSource[`${item.prop}`]"
:clearable="false"
:disabled="item.disabled || isEditDisabled"
format="YYYY"
style="width:100%"
:placeholder="item.placeholder"
type="year"
value-format="YYYY"
/>
<!-- dateTimeRange 起止日期类型 -->
<el-date-picker
v-if="item.type === 'dateTimeRange'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
end-placeholder="结束时间"
format="YYYY-MM-DD"
:placeholder="item.placeholder"
range-separator="-"
style="width:100%"
start-placeholder="开始时间"
type="datetimerange"
value-format="YYYY-MM-DD"
/>
<!-- select 下拉框类型 -->
<el-select
v-if="item.type === 'select'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
:placeholder="item.placeholder"
style="width:100%"
>
<template v-if="item.selectOptions">
<el-option
v-for="obj in item.selectOptions"
:key="obj.value"
:label="obj.label"
:value="obj.value"
/>
</template>
</el-select>
<!-- radio 单选类型 -->
<el-radio-group
v-if="item.type === 'radio'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
@change="handleRadio"
style="width:100%"
>
<el-radio
v-for="obj in item.radioOptions"
:key="obj.value"
:label="obj.value"
>
{{ obj.label }}
</el-radio>
</el-radio-group>
<!-- radio 单选类型 -->
<el-checkbox-group
v-if="item.type === 'checkbox'"
v-model="dataSource[`${item.prop}`]"
:disabled="item.disabled || isEditDisabled"
@change="handleCheckbox"
style="width:100%"
>
<el-checkbox
v-for="obj in item.CheckboxOptions"
:key="obj.value"
:label="obj.value"
>
{{ obj.label }}
</el-checkbox>
</el-checkbox-group>
<!-- editor 富文本类型 -->
<wangEditor style="width:100%" v-if="item.type === 'editor'" :initValue="dataSource[`${item.prop}`]" :disabled="showWang"
></wangEditor>
<!-- file 附件类型 -->
<template v-if="item.type == 'file'">
<fileUpload v-model="dataSource[`${item.prop}`]" :objectId="item.dataArrId"
valid="doc、docx、png、jpg、jpeg、ppt、wps、pdf、ceb、xls、xlsx、txt、bmp" :max="20480" serviceType="intellectual"
:length="10" :fileType="1" @getDelAttachment="delFile"></fileUpload>
<div class="prompt">
<p>1.上传需求相关材料;</p>
<p>2.可上传多个附件,支持doc、docx、png、jpg、jpeg、ppt、wps、pdf、ceb、xls、xlsx、txt、bmp等格式;</p>
<p>3.文件大小不超过20M;</p>
</div>
</template>
</template>
<template v-else>
<slot :name="item.prop" :val="dataSource[`${item.prop}`]" />
</template>
</el-form-item>
</template>
<template v-else>
<slot :item="item" :name="item.prop" />
</template>
</template>
</div>
</template>
<script>
import { defineComponent, toRefs, reactive } from 'vue'
import { useRoute } from 'vue-router'
export default defineComponent({
name: 'CustomForm',
props: {
isShow: {
require: false,
type: Boolean,
default: true,
},
curPage: {
require: false,
type: String,
default: '',
},
formData: {
require: true,
type: Object,
default: () => { },
},
formItem: {
require: true,
type: Object,
default: () => { },
},
},
emits: ['changeRadio', 'changeCheckbox', 'getDelAttachment'],
setup(props, { emit }) {
const route = useRoute()
const query = route.query
const { formItem, formData, curPage } = toRefs(props)
const state = reactive({
curBtn: curPage,
formItem: formItem,
showWang: false,
dataSource: formData,
isEditDisabled: query.type === 'view' ? true : false,
})
const handleRadio = (val) => {
emit('changeRadio', val)
}
const handleCheckbox = (val) => {
emit('changeCheckbox', val)
}
const delFile = (val) => {
emit('getDelAttachment', val);
}
return {
query,
...toRefs(state),
handleRadio,
handleCheckbox,
delFile
}
},
})
</script>
<style lang="scss" scoped>
.content {
overflow: auto;
}
.prompt {
position: absolute;
top: 10px;
left: 130px;
color: gray;
p {
line-height: 25px;
}
}
</style>
2.3、定义公共样式
src\assets\css\index.scss
// 表单样式
.el-form .width25 {
float: left;
width: 25%;
}
.el-form .width33 {
float: left;
width: 33.3%;
}
.el-form .width35 {
float: left;
width: 35%;
}
.el-form .width50 {
float: left;
width: 50%;
}
.el-form .width75 {
float: left;
width: 75%;
}
.el-form .width100 {
float: left;
width: 100%;
}
2.4、加载全局样式
src\main.ts
// 加载全局样式样式
import './assets/css/index.scss'
3、data数据
data.ts
import {
// validateIsemail,
// validateIdCard,
validateIsphone,
} from "@/utils/validateForm";
export const topicItem = [
{
type: "input",
prop: "topicName",
label: "小组名称:",
// maxLength: 100,
// slot: true,
show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
{
type: "input",
prop: "prjName",
label: "项目名称:",
// maxLength: 100,
// slot: true,
show:true,
limit: true,
className: "width50",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
{
type: "input",
prop: "title",
show:true,
className: "width100",
},
{
type: "input",
prop: "email",
label: "电子邮箱:",
placeholder: "请输入",
className: "width25",
// rules: [
// { required: true, message: '请输入电子邮箱', trigger: 'blur' },
// {
// validator: validateIsemail,
// trigger: 'blur',
// },
// ],
},
{
type: "input",
prop: "telephone",
label: "联系电话:",
placeholder: "请输入",
className: "width25",
rules: [
{ required: true, message: "请输入联系电话", trigger: "blur" },
{
validator: validateIsphone,
trigger: "blur",
},
],
},
{
type: "input",
prop: "idCard",
label: "身份证号:",
placeholder: "请输入",
className: "width25",
// rules: [
// { required: true, message: '请输入身份证号', trigger: 'blur' },
// {
// validator: validateIdCard,
// trigger: 'blur',
// },
// ],
},
{
type: "select",
prop: "stuAttr",
label: "项目属性:",
// labelWidth: '185px',
selectOptions: [
{ label: "国企", value: "1" },
{ label: "私企", value: "2" },
],
placeholder: "请选择",
className: "width25",
},
{
type: "date",
prop: "startDate",
label: "开始时间:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "date",
prop: "endDate",
label: "结束时间:",
className: "width25",
// slot: true,
placeholder: "请输入",
},
{
type: "number",
prop: "totalPeople",
label: "总人数:",
placeholder: "请输入",
className: "width25",
precision: 0,
min: 0,
max: 99999999,
},
{
type: "textarea",
prop: "researchContent",
label: "备注内容:",
maxLength: "500",
rows: 3,
placeholder: "请输入",
className: "width100",
},
{
type: "date",
prop: "firingDate",
label: "启动时间:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "month",
prop: "startMonth",
label: "启动月份:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "year",
prop: "startYear",
label: "启动年度:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "dateTimeRange",
prop: "dateLength",
label: "起止日期:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "radio",
prop: "isIntake",
label: "是否参与:",
// labelWidth: '185px',
radioOptions: [
{ label: "是", value: "1" },
{ label: "否", value: "0" },
],
className: "width25",
},
{
type: "input",
prop: "titleA",
show:true,
className: "width100",
},
{
type: "checkbox",
prop: "cooperMode",
label: "合作方式:",
// labelWidth: '185px',
CheckboxOptions: [
{ label: "技术转让", value: "0" },
{ label: "许可使用", value: "1" },
{ label: "合作开发", value: "2" },
{ label: "技术服务", value: "3" },
{ label: "融资需求", value: "4" },
{ label: "产品推广", value: "5" },
],
className: "width100",
},
{
type: "number",
prop: "formulaPercentage",
label: "投入百分比:",
// value: '100',
// show: false,
// labelWidth: '180',
placeholder: "请输入",
className: "width50",
},
{
type: "editor",
prop: "editorContent",
label: "介绍内容:",
placeholder: "请输入",
className: "width100",
},
{
type: "input",
prop: "teamName",
label: "小组名称:",
placeholder: "请输入",
maxLength: "5",
className: "width33",
},
{
type: "input",
prop: "declareUnit",
label: "申报单位:",
placeholder: "请输入",
maxLength: "100",
className: "width33",
},
{
type: "input",
prop: "titleB",
show: true,
className: "width100",
},
// {
// type: 'input',
// prop: 'softwareWorkNum',
// label: '作品(个数):',
// placeholder: '请输入',
// maxLength: '5',
// className: 'width50',
// },
// {
// type: 'input',
// prop: 'treatiseNum',
// label: '作品(个数):',
// placeholder: '请输入',
// maxLength: '5',
// className: 'width50',
// },
{
type: "file",
prop: "attachFileArr",
dataArrId: "",
label: "附件:",
placeholder: "请上传",
className: "width100",
rules: [{ required: true }],
},
];
export const teamItem = [
{
type: "input",
prop: "teamName",
label: "姓名:",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
];
export const infoItem = [
{
type: "input",
prop: "peopleName",
label: "姓名:",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
{
type: "number",
prop: "age",
label: "年龄",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
{
type: "select",
prop: "sex",
label: "性别:",
// labelWidth: '185px',
selectOptions: [
{ label: "男", value: "1" },
{ label: "女", value: "2" },
],
placeholder: "请选择",
className: "width25",
},
{
type: "input",
prop: "position",
label: "职位:",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
];
export const awardsItem = [
{
type: "input",
prop: "awardsName",
label: "获奖名称:",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
{
type: "date",
prop: "awardsDate",
label: "获奖日期:",
// slot: true,
className: "width25",
placeholder: "请输入",
},
{
type: "select",
prop: "awardsGrade",
label: "授奖等级:",
// labelWidth: '185px',
selectOptions: [
{ label: "国家级", value: "1" },
{ label: "省部级", value: "2" },
{ label: "集团级", value: "3" },
],
placeholder: "请选择",
className: "width25",
},
{
type: "input",
prop: "awardsUnit",
label: "授奖单位:",
// maxLength: 100,
// slot: true,
// show:true,
limit: true,
className: "width25",
placeholder: "请输入",
rules: [{ required: true, message: "请输入", trigger: "blur" }],
},
];
4.1、校验文件
src\utils\validateForm.ts
import { ElMessage } from 'element-plus'
import { validatorSpecialCharacter,filterSpecialCharacterAction } from './filter.js'
/**
* @description form表单特定字符校验
* @param value
* @returns {boolean}
*/
export function validateCommonText(rule: any, value: any, callback: any) {
if (value?.length || rule.required) {
const val = value.trim()
if (!val) {
callback(new Error('请输入有效内容'))
return
}
const err: any = validatorSpecialCharacter(val)
if (err) {
callback(new Error(err.message))
return
}
}
callback()
}
// 禁止输入框特殊字符校验
export function replaceCommonText(e: any) {
if (e.length) {
const val = e.trim()
if (!val) {
ElMessage({
message: '请输入有效内容',
type: 'warning',
})
return
}
const err: any = validatorSpecialCharacter(e)
if (err) {
ElMessage({
message: err.message,
type: 'warning',
})
const y = filterSpecialCharacterAction(val)
return y
} else {
return e
}
}
}
/**
* @description 判读是否为外链
* @param path
* @returns {boolean}
*/
export function isExternal(path: string) {
return /^(https?:|mailto:|tel:|\/\/)/.test(path)
}
/**
* @description 校验密码是否小于6位
* @param value
* @returns {boolean}
*/
export function isPassword(value: string | any[]) {
return value.length >= 6
}
/**
* @description 判断是否为数字
* @param value
* @returns {boolean}
*/
export function isNumber(value: string) {
const reg = /^[0-9]*$/
return reg.test(value)
}
/**
* @description 判断是否是名称
* @param value
* @returns {boolean}
*/
export function isName(value: string) {
const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
return reg.test(value)
}
/**
* @description 判断是否为IP
* @param ip
* @returns {boolean}
*/
export function isIP(ip: string) {
const reg =
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
return reg.test(ip)
}
/**
* @description 判断是否是传统网站
* @param url
* @returns {boolean}
*/
export function isUrl(url: string) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @description 判断是否是小写字母
* @param value
* @returns {boolean}
*/
export function isLowerCase(value: string) {
const reg = /^[a-z]+$/
return reg.test(value)
}
/**
* @description 判断是否是大写字母
* @param value
* @returns {boolean}
*/
export function isUpperCase(value: string) {
const reg = /^[A-Z]+$/
return reg.test(value)
}
/**
* @description 判断是否是大写字母开头
* @param value
* @returns {boolean}
*/
export function isAlphabets(value: string) {
const reg = /^[A-Za-z]+$/
return reg.test(value)
}
/**
* @description 判断是否是字符串
* @param value
* @returns {boolean}
*/
export function isString(value: any) {
return typeof value === 'string' || value instanceof String
}
/**
* @description 判断是否是数组
* @param arg
*/
export function isArray(arg: string | (string | number)[]) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
/**
* @description 判断是否是端口号
* @param value
* @returns {boolean}
*/
export function isPort(value: string) {
const reg =
/^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
return reg.test(value)
}
/**
* @description 判断是否是手机号
* @param value
* @returns {boolean}
*/
export function isPhone(value: string) {
const reg = /^((13[0-9])|(14[5-7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/
return reg.test(value)
}
/**
* @description 判断是否是身份证号(第二代)
* @param value
* @returns {boolean}
*/
export function isIdCard(value: string) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
return reg.test(value)
}
export function validateIdCard(rule: any, value: any, callback: any) {
if (value || rule.required) {
if (!isIdCard(value)) {
callback(new Error('请输入正确的身份证号'))
} else {
callback()
}
} else {
callback()
}
}
/**
* @description 判断是否是邮箱
* @param value
* @returns {boolean}
*/
export function isEmail(value: string) {
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
return reg.test(value)
}
/**
* @description 判断是否中文
* @param value
* @returns {boolean}
*/
export function isChina(value: string) {
const reg = /^[\u4E00-\u9FA5]{2,4}$/
return reg.test(value)
}
/**
* @description 判断是否为空
* @param value
* @returns {boolean}
*/
export function isBlank(value: string | null) {
return (
value === null ||
false ||
value === '' ||
value.trim() === '' ||
value.toLocaleLowerCase().trim() === 'null'
)
}
/**
* @description 判断是否为固话
* @param value
* @returns {boolean}
*/
export function isTel(value: string) {
const reg =
/^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})([- ])?)?([0-9]{7,8})(([- 转])*([0-9]{1,4}))?$/
return reg.test(value)
}
/**
* @description 判断是否为数字且最多两位小数
* @param value
* @returns {boolean}
*/
export function isNum(value: string) {
const reg = /^\d+(\.\d{1,2})?$/
return reg.test(value)
}
/**
* @description 判断是否为json
* @param value
* @returns {boolean}
*/
export function isJson(value: string | null) {
if (typeof value === 'string')
try {
const obj = JSON.parse(value)
return !!(typeof obj === 'object' && obj)
} catch (e) {
return false
}
return false
}
/**
* @description 手机号码校验
* @param value
* @returns {boolean}
*/
export function validateIsphone(rule: any, value: any, callback: any) {
if (value || rule.required) {
if (!isPhone(value)) {
callback(new Error('请输入正确的联系电话'))
} else {
callback()
}
} else {
callback()
}
}
/**
* @description 电子邮箱校验
* @param value
* @returns {boolean}
*/
export function validateIsemail(rule: any, value: any, callback: any) {
if (value || rule.required) {
if (!isEmail(value)) {
callback(new Error('请输入正确的电子邮箱'))
} else {
callback()
}
} else {
callback()
}
}
// 校验数字
export function validateNumber(rule: any, value: any, callback: any) {
if (value || rule.required) {
const v = value || ''
const pattern = /^[0-9]{1,8}$/
if (!pattern.test(v)) {
callback(new Error('请输入1-8位数字'))
} else {
callback()
}
} else {
callback()
}
}
/**
* @description 数字校验保留小数后俩位
* @param value
* @returns {boolean}
*/
export function validateIsNum(rule: any, value: any, callback: any) {
const reg = /^(([1-9]{1}\d*)|(0{1}))(\.\d{1,2})?$/
if (!reg.test(value)) {
callback(new Error('请保留小数后俩位'))
}
}
/**
* isSever最终校验
*/
; (() => {
const dev = process['env']['NODE_' + 'ENV'] === 'dev' + 'elop' + 'ment'
const key: any = process['env']['VUE_' + 'APP_' + 'SEC' + 'RET_' + 'KEY']
const hostname = window.location.hostname
const local = '127.' + '0.' + '0.' + '1'
const server = hostname !== 'local' + 'host' || hostname !== local
if (!dev && server) {
if (key.substring(key.length - 2) !== '=' + '=')
localStorage.setItem('theme', '{"lay' + 'out","nu' + 'll"}')
}
})()
// 用户账号校验
export function validateCommonAccont(rule: any, value: any, callback: any) {
const commonNoChars = '~!@#$%^&*()_+|}{":?><,./;' + '’[]\\=-` '
const noChars = commonNoChars
const v = value || ''
for (let i = 0; i < noChars.length; i++) {
const char = noChars[i]
if (v.indexOf(char) != -1) {
callback(new Error('不能使用字符:' + noChars))
return
}
}
const words = ['null', 'NULL', 'Null']
for (let i = 0; i < noChars.length; i++) {
const word = words[i]
if (v.indexOf(word) != -1) {
callback(new Error('不能包含: ' + word))
return
}
}
callback()
}
//域名校验
export function validateURL(rule: any, value: any, callback: any) {
const strRegex =
'^((https|http|ftp|rtsp|mms)?://)' +
"?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + // ftp的user@
'(([0-9]{1,3}.){3}[0-9]{1,3}' + // IP形式的URL- 199.194.52.184
'|' + // 允许IP和DOMAIN(域名)
"([0-9a-z_!~*'()-]+.)*" + // 域名- www.
'([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].' + // 二级域名
'[a-z]{2,6})' + // first level domain- .com or .museum
'(:[0-9]{1,4})?' + // 端口- :80
'((/?)|' + // a slash isn't required if there is no file name
"(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"
const re = new RegExp(strRegex)
if (value && !re.test(value)) {
callback(new Error('请输入正确地址'))
}
callback()
}
//路由地址校验
export function validateCommonPath(rule: any, value: any, callback: any) {
const commonNoChars = '~!@#$%^&*()_+|}{":?><,.;' + '’[]\\=-` '
const noChars = commonNoChars
const v = value || ''
for (let i = 0; i < noChars.length; i++) {
const char = noChars[i]
if (v.indexOf(char) != -1) {
callback(new Error('不能使用字符:' + noChars))
return
}
}
const words = ['null', 'NULL']
for (let i = 0; i < noChars.length; i++) {
const word = words[i]
if (v.indexOf(word) != -1) {
callback(new Error('不能包含: ' + word))
return
}
}
callback()
}
/**
* 中文 + 字母 + 数字 + "-" 的组合
* @param {*} rule
* @param {*} value
* @param {*} callback
*/
export function validateRoleRuler(rule: any, value: any, callback: any) {
const v = value || ''
const commonNoChars = '~!@#$%^&*()+|}{":?><,./;' + '’[]\\=` '
const pattern = /[~!@#$%^&*()_+|}{":?><,./\\;'[\]=` 、]|([Nn][Uu][Ll][Ll])/
if (pattern.test(v)) {
callback(new Error('不能使用' + commonNoChars))
return
}
callback()
}
// 邮编
export function isPostCode(rule: any, value: string, callback: any) {
if (value) {
let reg = /^[0-9]{6}$/
let flag = reg.test(value)
if (!flag) {
callback(new Error('请输入正确邮编'))
}
callback()
}
callback()
}
// 传真
export function isFax(rule: any, value: string, callback: any) {
if (value) {
// 国家代码(2到3位)-区号(2到3位)-电话号码(7到8位)-分机号(3位)"
let reg = /^(([0\+]\d{2,3}-)?(0\d{2,3})-)(\d{7,8})(-(\d{3,}))?$/
let flag = reg.test(value)
if (!flag) {
callback(new Error('请输入正确传真号码'))
}
callback()
}
callback()
}
// 含两位小数数字
export function isNumberDot(rule: any, value: any, callback: any) {
if (value) {
const reg = /^\d+\.?\d{0,2}$/
let flag = reg.test(value)
if (!flag) {
callback(new Error('请输入正确数字'))
}
callback()
}
callback()
}
// 正整数
export function isIntFormNumber(rule: any, value: any, callback: any) {
if (value) {
const reg = /^[1-9]{1}[0-9]*$/
let flag = reg.test(value)
if (!flag) {
callback(new Error('请输入有效数字'))
}
callback()
}
callback()
}
// 正整数
export function isIntNumber(value: any) {
if (value) {
const reg = /^[1-9]{1}[0-9]*$/
let flag = reg.test(value)
if (!flag) {
ElMessage({
message: '请输入有效数值',
type: 'warning',
})
const y = value.replace(value, '')
return y
} else {
return value
}
}
}
export function isPerNumber(rule: any, value: any, callback: any) {
const reg = /^([0-9]\d{0,1}|100$)(\.\d{1,2})?$/
let flag = reg.test(value)
if (!flag) {
callback(new Error('请输入正确百分制数字'))
}
callback()
}
// 含4位小数数字
export function isFourNumberDot(value: any) {
if (value) {
const reg = /^\d+\.?\d{0,4}$/;
let flag = reg.test(value);
if (!flag) {
ElMessage({
message: '请输入数字,含小数点且仅支持4位小数',
type: 'warning'
});
const dotArr = value.split('.');
const dotInt = dotArr[0];
const dotNum = dotArr[1].substring(0, 4);
const val = value.indexOf('.') >= 1 ? dotInt + '.' + dotNum : value;
return val;
}
else {
if (value.length ===2 && Number(value[0]) === 0 && (Number(value[1]) >= 0 || value[1]==='.')) {
ElMessage({
message: '请输入有效数值',
type: 'warning'
});
return '';
}
if (value.indexOf('.') >= 1) {
// 含小数点
const dotArr = value.split('.');
const dotInt = dotArr[0];
const dotNum = dotArr[1];
if (dotInt?.length >= 8) {
// 8位整数的情况(含小数点)
const dot = dotNum.substring(0, 4);
if (Number(dotInt) >= 99999999) {
// 输入99999999的情况 截取7位数
ElMessage({
message: '最大仅支持99999999',
type: 'warning'
});
const val = dotInt.substring(0, 7);
return val;
} else {
if (dotNum?.length > 4) {
// 4位小数
const dot = dotNum.substring(0, 4);
const int = dotInt.substring(0, 8);
const val = int + '.' + dot;
ElMessage({
message: '仅支持4位小数',
type: 'warning'
});
return val;
} else {
// 非4位小数
const val = dotInt + '.' + dotNum;
return val;
}
}
} else {
// 不到8位整数位(含小数点)
if (dotNum?.length > 4) {
// 4位小数
const dot = dotNum.substring(0, 4);
const val = dotInt + '.' + dot;
ElMessage({
message: '仅支持4位小数',
type: 'warning'
});
return val;
} else {
// 非4位小数
const val = dotInt + '.' + dotNum;
return val;
}
}
} else {
// 不含小数点
if (value?.length > 8) {
ElMessage({
message: '最大仅支持99999999',
type: 'warning'
});
}
const val = value.substring(0, 8);
return val;
}
}
}
}
4.2、校验方法
src\utils\filter.js
/* eslint-disable */
// 特殊字符 敏感词检验过滤
import { init } from './deailFilter'
let regObj = {
urlReg:
/\+|\?|#|\*|%2B|%20|%2F|%3F|%25|%23|%26|%3D|%27|%26|%22|%28|%29|%2a|%2b|%2d|%30/,
htmlReg:
/object|comment| |"|&|'|/|<|>|Æ|Á|Â|À|Å|Ã|Ä|Ç|Ð|É|Ê|È|Ë|Í|Î|Ì|Ï|Ñ|Ó|Ô|Ò|Ø|Õ|Ö|Þ|Ú|Û|Ù|Ü|Ý|á|â|æ|à|å|ã|ä|ç|é|ê|è|ð|ë|í|î|ì|ï|ñ|ó|ô|ò|ø|õ|ö|ß|þ|ú|û|ù|ü|ý|ÿ|¢|\\""|&|'/,
jsReg:
/,|\||{|}|%|<|>|&|""|'|\/|\\|\\r|\\n|\\\\|\\t|\\f|\\b|!|@|#|\$|\^|\*|\(|\)|~|:|;|\\`|-|=|\[|\]|javascript|script|function|jscript|vbscript|onfocus|onblur|location|document|window|onclick|href|<!--|--|->|\/\\\\\\\*|\\\\\\\*\/|onfocus|\/\/|onerror|\/\*|data:|\\u003e|\\u003c|eval|url|expr|URLUnencoded|referrer|write\(.\)|writeln|body\.innerHtml|execScript|navigate|srcdoc|%0a|<\/|\.|_|·|!|¥|…|【|】|、|;|‘|’|:|“|”|,|。|、|《|》|?/,
sqlReg: /\\\*|--|%28|\)|\/\/|\/\*|\/\\\*\\\\*/,
replaceStrs: [
'//',
'/*',
'</',
'/\\*',
'net +user',
'net +localgroup +administrators',
'information_schema.columns',
'netlocalgroup administrators',
'body.innerHtml',
'()',
'(',
')',
'null',
'NULL',
'Null',
],
}
const sqlKeyword = [
'and',
'or',
'exec',
'execute',
'insert',
'select',
'delete',
'update',
'alter',
'create',
'drop',
'count',
'chr',
'char',
'asc',
'desc',
'mid',
'substring',
'master',
'truncate',
'declare',
'xp_cmdshell',
'restore',
'backup',
'net user',
'like',
'table',
'from',
'grant',
'use',
'column_name',
'group_concat',
'table_schema',
'union',
'where',
'order',
'by',
'join',
'modify',
'into',
'substr',
'ascii',
'having',
]
const htmlTag = [
'a',
'iframe',
'body',
'form',
'base',
'img',
'style',
'div',
'meta',
'link',
'input',
'br',
]
const jsFunction = [
'open',
'confirm',
'prompt',
'setInterval',
'setTimeout',
'alert',
]
let jsFunctionStr = (() => {
let regArr = []
jsFunction.forEach((item) => {
regArr.push(`${item}\\(.*\\)`)
})
return regArr.join('|')
})()
let htmlTagRegStr = (() => {
let regArr = []
htmlTag.forEach((item) => {
regArr.push(`<${item}`)
regArr.push(`${item}>`)
})
return regArr.join('|')
})()
regObj.jsFunctionReg = new RegExp(jsFunctionStr)
regObj.htmlTagReg = new RegExp(htmlTagRegStr, 'i')
regObj.sqlKeywordReg = new RegExp(sqlKeyword.join('\\s|') + '\\s', 'i')
const reg = copyReg(regObj)
init(reg)
// 全局敏感词校验方法
export function validatorSpecialCharacter(value) {
if (!isNaN(value)) return false
if (typeof value !== 'string') return false // 只校验字符串
value = String(value).trim()
if (value === '') return false // 空不检验
if (reg.urlReg.test(value)) {
return {
message: '不能包含特殊字符',
reg: reg.urlReg,
}
}
if (reg.htmlReg.test(value)) {
return {
message: '不能包含关键字',
reg: reg.htmlReg,
}
}
if (reg.jsReg.test(value)) {
return {
message: '不能包含特殊字符',
reg: reg.jsReg,
}
}
if (reg.sqlReg.test(value)) {
return {
message: '不能包含特殊字符',
reg: reg.sqlReg,
}
}
if (reg.sqlKeywordReg.test(value)) {
return {
message: '不能包含关键字',
msg: '不能包含SQL语句',
reg: reg.sqlKeywordReg,
}
}
if (reg.htmlTagReg.test(value)) {
return {
msg: '不能包含HTML标签',
message: '不能包含关键字',
reg: reg.htmlTagReg,
}
}
if (reg.jsFunctionReg.test(value)) {
return {
msg: '不能包含JS方法',
message: '不能包含关键字',
reg: reg.jsFunctionReg,
}
}
let str = reg.replaceStrs.find((val) => {
return value.indexOf(val) !== -1
})
if (str) {
return {
message: '不能包含关键字',
str: str,
reg: str,
}
}
return false
}
function isArray(data) {
return Object.prototype.toString.call(data) === '[object Array]'
}
function isObject(data) {
return Object.prototype.toString.call(data) === '[object Object]'
}
// 过滤特殊字符的方法
export function filterSpecialCharacterAction(data) {
if (isArray(data) || isObject(data)) {
for (const key in data) {
data[key] = filterSpecialCharacterAction(data[key])
}
} else {
let err = validatorSpecialCharacter(data)
if (err) {
if (err.reg) {
return filterSpecialCharacterAction(data.replace(err.reg, ''))
}
if (err.str) {
return filterSpecialCharacterAction(data.replace(err.str, ''))
}
}
}
return data
}
function copyReg(regObj) {
let result = {}
for (const key in regObj) {
result[key] = regObj[key]
}
return result
}
// 响应数据过滤数据
export const filterSpecialCharacter = (data) => {
let result = data
return result
}