Commit e7a6587c authored by 刘子康's avatar 刘子康

init

parents
.DS_Store
node_modules
/dist
.history
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# way-view-form
基于 iview 封装的 JSON To Form 表单组件,支持 Search 和 Form 两种模式
## Props
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :--------- | :---------------- |
| model | 表单模式 | string | 'form' | 'form' / 'search' |
| singleStepErrorTip | 是否启用单步错误提示 该属性用于定义是否启用单步错误提示。如果设置为 true,则只会依次提示第一个未通过的表单项。如果设置为 false,则会依次提示所有未通过的表单项。 | boolean | false | - |
| enableSpin | 是否启用 spin 该属性用于定义是否启用加载状态 UI。如果设置为 true,则会在组件中显示一个加载状态 UI,表示当前正在加载数据。如果设置为 false,则不会显示加载状态 UI。 | boolean | true | - |
| inlineBlock | 该属性用于定义行内换行模式。如果设置为 true,则表单控件会在同一行中显示,并根据内容自动换行。如果设置为 false,则表单控件会分行显示。 | boolean | false | - |
| form | 该属性用于定义表单数据。该属性必须是一个数组,数组的每个元素为一个对象,每个对象都表示一个表单项。 | Array | - | - |
| status | 该属性用于定义表单的状态。可以为 'add' 添加状态,'edit' 编辑状态或 'view' 查看状态。默认为 'add' 添加状态。 | string | 'add' | 'add' / 'edit' |
| formProps | 该属性用于定义 iview form 表单的全量配置 | Object | {} | - |
| formEvents | 该属性用于定义表单的 iview form 表单的全量事件。可以设置表单提交、重置等事件的回调函数。默认为一个空对象。 | Object | {} | - |
| formItemProps | 该属性用于定义表单项的配置。可以设置表单项的布局、宽度、高度、对齐方式、标签等。默认为一个空对象。 | Object | {} | - |
| showFooter | 该属性用于定义是否展示表单底部。如果设置为 true,则会在表单底部显示一个操作按钮区域,用于提交、重置、取消等操作。如果设置为 false,则不会显示表单底部。 | boolean | true | - |
| footerRowProps | 该属性用于定义表单底部的 Row 行配置。可以设置行的对齐方式、宽度、高度等。默认为一个空对象。 | Object | {} | - |
| action | 表单操作按钮配置选项 该属性用于定义表单操作按钮。可以包含 'submit' 提交按钮,'reset' 重置按钮以及其他自定义按钮。默认只包含一个 'submit' 提交按钮。 顺序敏感可调整按钮顺序 | Array | ['submit'] | - |
| actionProps | 该属性用于定义表单操作按钮的配置。可以设置按钮的类型、大小、图标、文字等。默认为一个空对象。 | Object | {} | - |
### form <Array>
| 参数名称 | 类型 | 是否必选 | 说明 |
| :--------------- | :------ | :------- | :----------------------------------------------------- |
| name | string | true | 显示在表单的 label 名称 |
| placeholder | string | | 占位提示符 一般会根据 name 和类型自动生成 |
| type | string | true | 表单的类型,如 'Select'、'Input' 等 |
| key | string | true | 表单提交时的键名,对应表单数据对象的属性名 |
| value | any | | 表单的默认值 |
| rules | array | | 表单的校验规则 |
| required | boolean | | 表单是否必填 rules 的快捷设置 |
| componentsProps | object | | 表单组件的 props 配置,如 'filterable'、'clearable' 等 |
| componentsEvents | object | | 表单组件的事件,如 'on-change'、'on-input' 等 |
| source | object | | 表单项的数据源,包含 API 接口、接口参数、处理函数 |
| showName | object | | 显示在选项中的名称的键名 |
| showValue | object | | 选项值的键名 |
#### rules <Array>
| 参数名称 | 类型 | 是否必选 | 说明 |
| :-------- | :------- | :------- | :----------------------------------------------------------------------------- |
| required | boolean | | 是否必填 |
| message | string | | 校验未通过时的提示信息 |
| trigger | string | | 触发校验的时机,可选值为 'blur'、'change'、'input'、'submit'、'manual' |
| pattern | RegExp | | 正则表达式校验规则 |
| validator | function | | 自定义校验规则,接收两个参数,第一个参数为表单项的值,第二个参数为表单项的配置 |
参考:
```js
[{
message: '装完时间不能大于卸完时间',
trigger: 'change',
validator: this.validateShiftLoadAndDumpTime
},
....,
{
required: true,
message: '请输入正整数',
trigger: ['change'],
pattern: /^[1-9]\d*$/
}]
```
#### source <Object>
| 参数名称 | 类型 | 是否必选 | 说明 |
| :------- | :------- | :------- | :------------------------------------------- |
| api | string | | 数据源的 API 接口 store 中存的 key |
| params | object | | 数据源的 API 接口参数 |
| handle | function | | 数据源的处理函数,第一个参数为接口返回的数据 |
| data | array | | 手动设置的数据源的数据 |
##### data <Array>
| 参数名称 | 类型 | 是否必选 | 说明 |
| :------- | :----- | :------- | :---------------------- |
| label | string | | 显示在表单的 label 名称 |
| value | any | | 对应值 |
参考:
```js
//默认使用label和value也可以通过设置showValue和showName修改keyName
[
({
label: '是',
value: true,
},
{
label: '否',
value: false,
}),
];
```
### actionProps <Object>
| 参数名称 | 类型 | 是否必选 | 说明 |
| :--------------- | :----- | :------- | :-------------------------------------------------- |
| componentsProps | object | | 按钮组件的 props 配置,如 'type'、'size'、'icon' 等 |
| componentsEvents | object | | 按钮组件的事件,如 'on-click' 等 |
| text | string | | 按钮的文字 |
示例:
```js
// 重写默认的搜索重置按钮
actionProps: {
submit: {
componentsProps: {
type: 'primary',
icon: 'md-search',
},
text: '查询'
},
reset: {
componentsProps: {
type: 'default',
icon: 'md-refresh',
},
text: '重置'
},
},
//配合aciton 增加新的按钮
action: ['submit', 'reset', 'add'],
actionProps: {
add: {
componentsProps: {
type: 'primary',
icon: 'md-add',
},
compoentsEvents: {
'on-click': () => {
this.$router.push({
path: '/add',
})
},
},
text: '新增'
},
},
```
### formProps <Object>
iview form 的全量配置
参考链接:https://v4.iviewui.com/components/form
### formItemProps <Object>
iview form-item 的全量配置
参考链接:https://v4.iviewui.com/components/form
### footerRowProps <Object>
iview row 的全量配置
参考链接:https://v4.iviewui.com/components/grid
## 事件
| 事件名称 | 说明 | 回调参数 |
| :------- | :----------- | :------- |
| submit | 表单提交事件 | formData |
| reset | 表单重置事件 | |
### formData 参数
| 参数名称 | 说明 | 类型 |
| :--------- | :----------- | :------ |
| valid | 是否通过校验 | boolean |
| formvalues | 表单对象 | object |
## 方法
| 方法名称 | 说明 | 参数 | 返回值 |
| :------------ | :----------- | :----------- | :----- |
| submit | 提交 | | |
| reset | 重置 | | |
| setFormData | 设置表单数据 | data | |
| setFormValues | 设置表单数据 | (key, value) | |
| updateSources | 更新数据源 | [key] | |
| formRef | 获取表单实例 | | |
## 场景一:联动查询
```js
{
name: '设备类型',
type: 'Select',
key: 'deviceType',
componentsProps: {
clearable: true,
filterable: true,
'label-in-value': true,
},
componentsEvents: {
'on-change': (val) => {
this.getDevices()
}
},
source: {
data: [
{
id: '1',
name: '运输设备'
},
{
id: '2',
name: '挖掘设备'
},
{
id: '3',
name: '卸载设备'
},
{
id: '4',
name: '其它车辆'
},
]
},
showName: 'name',
showValue: 'id',
}, {
name: '设备编号',
type: 'Select',
key: 'equipmentId',
componentsProps: {
filterable: true,
'label-in-value': true,
clearable: true,
},
componentsEvents: {
'on-change': (val) => {
if (val && val.label) {
this.$refs.queryForm.formValues['truckDriverName'] = val.label
}
}
},
source: {
api: 'vehicleList',
params: {
limit: 2000,
type: 1
},
handle: function (res) {
return res.page.list
},
},
showName: 'deviceName',
showValue: 'equipmentId',
},
```
在上面的场景中设备编号的数据源需要通过设备类型来筛选,所以在设备类型的 change 事件中调用 getDevices 方法来更新筛选后的设备编号数据源
```js
// 获取设备
getDevices() {
// 通过ref 获取当前表单的值
const { manufacturerName, deviceType } = this.$refs.queryForm.formValues
// 更新设备编号的数据源的筛选条件
this.originData.find(item => item.key === 'equipmentId').source.params = {
limit: 2000,
manufacturer: manufacturerName,
deviceType: deviceType
}
// 手动更新数据源
this.$refs.queryForm.updateSources('equipmentId')
},
```
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "way-view-form",
"version": "0.1.0",
"private": false,
"author": "flycat",
"main": "lib/way-view-form.umd.min.js",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:lib": "vue-cli-service build --target lib --name way-view-form --dest lib ./src/index.js",
"lint": "vue-cli-service lint"
},
"publishConfig": {
"registry": "http://192.168.9.197:4873/"
},
"registry": "http://192.168.9.197:4873/",
"dependencies": {
"core-js": "^3.8.3",
"less-loader": "^11.1.0",
"view-design": "^4.5.0",
"vue": "^2.6.14"
},
"peerDependencies": {
"view-design": "^4.5.0",
"vue": "^2.6.14"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"keywords": [
"iview",
"view-design",
"form",
"vue"
],
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div class="SearchForm">
<way-view-form model="search" inlineBlock :formProps="formProps" ref="queryForm" :form="originData"
@submit="handleSubmit" :enableSpin="false" :action="['submit', 'reset']" @reset="handleReset"
:actionProps="actionProps" :showFooter="false" />
</div>
</template>
<script>
export default {
data() {
return {
formProps: {
hideLabel: true,
inline: true,
},
originData: [
{
name: '姓名',
required: false,
type: 'Input',
key: 'name',
value: '',
},
{
name: '工种',
type: 'Select',
key: 'type',
required: false,
componentsProps: {
clearable: true
},
source: {
data: [
{
name: '1',
id: '1'
},
{
name: '2',
id: '2'
},
{
name: '3',
id: '3'
},
],
},
showName: 'name',
showValue: 'id',
},
],
actionProps: {
submit: {
componentsProps: {
type: 'primary',
icon: 'md-search',
},
text: '查询'
},
reset: {
componentsProps: {
type: 'default',
icon: 'md-refresh',
},
text: '重置'
},
},
};
},
methods: {
handleSubmit(formData) {
if (!formData.formValues.name && !formData.formValues.type) return this.$Message.warning('请输入搜索项,再搜索!')
this.$emit('handleSearch', formData.formValues)
},
handleReset() {
this.$emit('handleSearch', {})
},
},
watch: {
}
}
</script>
<style lang='less' scoped>
.SearchForm {
padding: 24px 0 24px 0;
}
</style>
<!-- 封装的json to form 组件 支持Select/Input/DatePicker/TimePicker/InputNumber/Radio/RadioGroup/Checkbox/CheckboxGroup/Slider/Rate/Cascader/Switch/AutoComplete/ColorPicker/支持Row/Col布局/支持Title分栏/支持自定生成必填rules和placeholder/支持通过配置source请求数据状态选项 -->
<script>
const requireTypes = {
'Input': 'string',
'InputNumber': 'number',
'Radio': 'boolean',
'RadioGroup': 'string',
'Checkbox': 'boolean',
'CheckboxGroup': 'array',
'Switch': 'boolean',
'Select': 'string', // 默认
'AutoComplete': 'number',
'TimePicker': 'string',
'DatePicker': 'string',
'Cascader': 'array',
'Rate': 'number',
'ColorPicker': 'string',
}
const hasOwnPropertySafely = (obj, key) => {
if (!obj || !key) return false
return Object.prototype.hasOwnProperty.call(obj, key)
}
export default {
name: 'way-view-form',
props: {
requestMethods: {
type: Object,
default: () => {
return {
}
}
},
// 模式 默认form表单模式 search为搜索条模式
model: {
type: String,
default: 'form'
},
// 是否启用单步错误提示 如果有多个未通过的表单项,只会依次提示第一个未通过的表单项
singleStepErrorTip: {
type: Boolean,
default: false
},
// 是否启用加载状态ui
enableSpin: {
type: Boolean,
default: true
},
// 行内换行模式
inlineBlock: {
type: Boolean,
default: false
},
/**
* 表单数据
*/
form: {
type: Array,
require: true
},
/**
* 表单状态
*/
status: {
type: String,
default: 'add'
},
/**
* 表单配置
*/
formProps: {
type: Object,
default: () => {
return {
}
}
},
/**
* 表单事件
*/
formEvents: {
type: Object,
default: () => {
return {}
}
},
/**表单项配置 */
formItemProps: {
type: Object,
default: () => {
return {}
}
},
// 是否展示表单底部
showFooter: {
type: Boolean,
default: true
},
footerRowProps: {
type: Object,
default: () => {
return {}
}
},
// 表单操作按钮
action: {
type: Array,
default: () => {
return ['submit']
}
},
// 表单操作按钮配置
actionProps: {
type: Object,
default: () => {
return {}
}
},
},
data() {
return {
isFirstInit: true, // 是否第一次初始化
formContent: {}, // 表单结构数据
requests: {}, // 请求队列
rules: {},//表单验证规则
sourceData: {},//资源数据
formValues: {}, // 表单值
formInitValues: '', // 表单初始值
};
},
render(h) {
const self = this
if (Object.keys(this.formValues).length == 0 && this.enableSpin) return h('Spin', {
props: {
fix: true,
}
}, [h('Icon', {
class: 'my-form-spin-icon-load',
props: {
type: 'ios-loading',
size: 18,
}
}), h('div', '加载中...')])
let itemNodes = self.generatorForm(h, this.formContent)
return h(
'div',
{
attrs: {
class: `my-custom-form ${this.formProps.hideLabel ? 'my-custom-form-hide-label' : ''} ${this.formProps.class || ''} ${this.model == 'search' ? 'my-custom-search' : ''}`,
},
},
[
h(
'Form',
{
ref: 'formValues',
props: {
model: this.formValues,
rules: this.rules,
...this.formProps
},
on: {
...this.formEvents
}
}, [
itemNodes,
this.model == 'form' ? self.generatorFooter(h) : self.generatorFormButton(h)
])]
)
},
methods: {
formRef() {
return this.$refs.formValues
},
async generatorRluesAndRequests() {
// 如果没有数据直接返回
// 递归处理form数据
let res = this.collectFormContent(this.form)
// 优先 rules生成
this.formContent = res.formContent
this.rules = res.rules
this.requests = res.requests
// 第一次渲染dom时 form表单初始值
if (this.isFirstInit) {
// 请求生成
let requests = Object.keys(res.requests).map(key => {
return res.requests[key]()
})
// 执行请求 完成数据源拉取
if (requests && requests.length > 0) {
await Promise.allSettled(requests).catch(err => {
console.warn(err)
})
}
// form表单生成
this.formValues = res.formData
this.isFirstInit = false
}
// form表单初始值 用于重置表单 每次生成新的初始值避免修改form之后重置表单会将用户主动设置的值清空
this.formInitValues = Object.assign({}, res.formData)
},
/**
* 递归处理form数据
* @param {*} item
*/
collectFormContent(data = []) {
let res = {
formContent: [],
formData: {},
rules: {},
requests: {}
}
let inputTips = ['Input', 'InputNumber']
data.forEach(item => {
// 如果设置了key自动设置不同类型默认值
if (hasOwnPropertySafely(item, 'key')) {
// q: 为什么要这样处理
// a: 因为el-form-item的value默认值是undefined,如果设置了默认值,会导致el-form-item的value和el-form的value不一致,导致校验不通过
if (item.type === 'InputNumber') {
res.formData[item.key] = hasOwnPropertySafely(item, 'value') ? item.value : null
} else {
res.formData[item.key] = hasOwnPropertySafely(item, 'value') ? item.value : undefined
}
}
// 如果设置了rules自动设置rules
if (hasOwnPropertySafely(item, 'rules') && item.rules.length > 0) {
res.rules[item.key] = item.rules
}
// 如果设置了required自动生成校验提示
// rlues对number类型的校验有问题,需要手动设置type
if (item.required) {
let requiredType = hasOwnPropertySafely(item, 'requiredType') ? item.requiredType : (requireTypes[item.type] || 'string') // 默认值
let tips = { required: true, message: `${inputTips.includes(item.type) ? '请填写' : '请选择'}${item.name}`, trigger: 'change', type: requiredType }
if (hasOwnPropertySafely(res.rules, item.key) && res.rules[item.key].length > 0) {
res.rules[item.key] = [tips, ...res.rules[item.key]]
} else {
res.rules[item.key] = [tips]
}
}
console.log(hasOwnPropertySafely(item, 'source'))
console.log(hasOwnPropertySafely(item.source, 'data'))
// 如果设置了source自动生成请求
if (hasOwnPropertySafely(item, 'source') && hasOwnPropertySafely(item, 'api') && hasOwnPropertySafely(item, 'key') && hasOwnPropertySafely(this.requestMethods, 'getMapData') && hasOwnPropertySafely(this.requestMethods, 'getBusinessData')) {
res.requests[item.key] = this.generatorRequestFunc(item, item.source.type)
}
// 如果设置了source自动设置传入静态数据源
else if (hasOwnPropertySafely(item, 'source') && hasOwnPropertySafely(item.source, 'data')) {
this.sourceData[item.key] = item.source.data
} else {
!hasOwnPropertySafely(this.sourceData, item.key) && (this.sourceData[item.key] = [])
}
// 如果没有placeholder自动生成
if (!item.placeholder) {
item.placeholder = (inputTips.includes(item.type) ? '请填写' : '请选择') + item.name
}
if (item.type == 'Row' && hasOwnPropertySafely(item, 'children') && item.children.length > 0) {
let cRes = this.collectFormContent(item.children)
res.rules = Object.assign(res.rules, cRes.rules)
res.formData = Object.assign(res.formData, cRes.formData)
res.requests = Object.assign(res.requests, cRes.requests)
res.formContent = Object.assign(res.formContent, cRes.formContent)
}
// 剪除source属性,初始化之后不再允许通过修改source属性来修改数据源,因为source属性是用来生成请求的且只能生成一次,如需调整指定数据源,可以通过修改source.data属性来实现或者通过内部方法changeSource手动实现
let mItem = Object.assign({}, item)
if (hasOwnPropertySafely(mItem, 'source')) {
delete mItem.source
}
res.formContent.push(mItem)
})
return res
},
generatorFormButton(h, item) {
return h(
'FormItem',
{
class: 'my-custom-form-buttons',
},
[this.generatorFooterButton(h, item)]
)
},
generatorFooterButton(h) {
const btns = {
'reset': h('Button',
{
props: {
type: "default",
},
on: {
click: () => {
this.reset()
}
}
}, '重置'),
'submit': h('Button',
{
props: {
type: "primary"
},
on: {
click: () => {
this.submit()
}
}
}, this.model === 'search' ? '搜索' : (this.status == 'add' ? '保存' : '修改'))
}
return this.action.map((btn) => {
// 如果设置actionProps
if (hasOwnPropertySafely(this.actionProps, btn)) {
return h('Button',
{
props: {
type: "default",
...this.actionProps[btn].componentsProps
},
on: {
click: () => {
if (['submit', 'reset'].includes(btn)) {
this[btn]()
}
},
...this.actionProps[btn].componentsEvents
}
}, this.actionProps[btn].text) // 不应用form中的保存修改逻辑
} else {
return btns[btn]
}
})
},
generatorFooter(h) {
// 如果设置了showFooter为false,不显示footer
if (!this.showFooter) return
return h('Row',
{
class: "my-custom-form-footer",
props: {
justify: "end",
...this.footerRowProps
}
},
[this.generatorFooterButton(h)])
},
generatorRow(h, item) {
let itemNodes = []
if (item.children && item.children.length > 0) {
item.children.forEach((it) => {
if ((hasOwnPropertySafely(it, 'visitable') && it.visitable(this.status, this.formValues)) || !hasOwnPropertySafely(it, 'visitable') && hasOwnPropertySafely(it, 'type')) {
let formit = this.generatorCol(h, it)
itemNodes.push(formit)
}
})
itemNodes
}
return h(
'Row',
{
props: {
...item.props
}
}, itemNodes)
},
generatorCol(h, item) {
const colProps = item.layout && item.layout.col ? item.layout.col : {}
let layout = h(
'Col',
{
key: item.key,
props: {
xs: 12,
sm: 12,
md: 12,
lg: 4,
...colProps
},
},
[this.generatorFormItem(h, item)]
)
return layout
},
generatorTitle(h, item) {
const props = item.props ? item.props : {}
return h(
'Row',
{
props: {
...props
}
}, [h(
'h3',
{
props: {
class: 'my-custom-form-title'
}
},
item.name
)])
},
reBuildFormItemProps(data) {
const res = data.reduce((acc, curr) => {
if (!acc[curr.rowKey]) {
acc[curr.rowKey] = [];
}
acc[curr.rowKey].push(curr);
return acc;
}, {});
return res
},
generatorForm(h, data) {
if (!data) return
let res = []
for (let i = 0; i < data.length; i++) {
let item = data[i]
if (['Row', 'Title'].includes(item.type)) {
switch (item.type) {
case 'Row':
// 目前没有支持深度嵌套的情况,如果要支持,需要在这里处理
// let formItem = this.generatorRow(h, item)
res.push(this.generatorRow(h, item))
break;
case 'Title':
// let title =
res.push(this.generatorTitle(h, item))
break;
default:
break;
}
} else { // 理论不会走到这里,默认传入的表单配置根节点类型必须为Row,如果要兼容不穿Row节点的情况,需要在这里处理,包装一个Row节点
if ((hasOwnPropertySafely(item, 'visitable') && item.visitable(this.status, this.formValues)) || !hasOwnPropertySafely(item, 'visitable')) {
let formItem = this.inlineBlock ? this.generatorInlineBlock(h, item) : this.generatorBlock(h, item)
res.push(formItem)
// this.generatorCol(h, item)
}
}
}
return res
},
generatorInlineBlock(h, item) {
if (!hasOwnPropertySafely(item, 'hidden')) {
return this.generatorFormItem(h, item)
}
},
generatorBlock(h, item) {
if (!hasOwnPropertySafely(item, 'hidden')) {
return h(
'Row',
{
props: {
...item.props
}
}, [this.generatorFormItem(h, item)])
}
},
generatorFormItem(h, item) {
return h(
'FormItem',
{
key: item.key,
props: {
prop: item.key,
label: item.name,
},
},
[this.generatorItem(h, item)]
)
},
generatorItem(h, item) {
switch (item.type) {
// 特殊组件需要在这里处理 例如select的结构需要包含option,类似的还有Radio/CheckBox
case 'Select':
return this.generatorSelect(h, item)
case 'RadioGroup':
return this.generatorRadioGroup(h, item)
case 'CheckboxGroup':
return this.generatorCheckboxGroup(h, item)
default:
return this.generatorComponents(h, item)
}
},
generatorComponents(h, item) {
return h(
item.type,
{
props: {
...this.generatorDefaultProps(item),
...this.generatorSourceData(item),
...item.componentsProps
},
on: {
...this.generatorDefaultEvents(item),
...item.componentsEvents
},
}
)
},
/**
* 生成默认的数据源 例如Casader\AutoComplete\Transfer
*/
generatorSourceData(item) {
let res = {}
const types = ['Cascader', 'AutoComplete', 'Transfer']
if (types.includes(item.type) && hasOwnPropertySafely(this.sourceData, item.key)) {
res['data'] = this.sourceData[item.key]
} else {
res['data'] = []
}
return res
},
generatorRadioGroup(h, item) {
const self = this
return h(
'RadioGroup',
{
props: {
value: self.formValues[item.key],
...item.componentsProps
},
on: {
'input': (val) => {
self.formValues[item.key] = val
},
...item.componentsEvents
},
},
self.generatorRadio(h, item, self.sourceData[item.key])
)
},
generatorRadio(h, propItem, data) {
let radios = data && data.length > 0 ? data : (propItem.options || [])
let res = []
const { optionProps } = propItem
for (let i = 0; i < radios.length; i++) {
let item = radios[i]
let option = h(
'Radio',
{
props: {
label: item[propItem.showValue] || item.value,
...optionProps
},
},
item[propItem.showName || 'label']
)
res.push(option)
}
return res
},
generatorCheckboxGroup(h, item) {
const self = this
return h(
'CheckboxGroup',
{
props: {
value: self.formValues[item.key],
...item.componentsProps
},
on: {
'input': (val) => {
self.formValues[item.key] = val
},
...item.componentsEvents
},
},
self.generatorCheckbox(h, item, self.sourceData[item.key])
)
},
generatorCheckbox(h, propItem, data) {
let checkboxs = data && data.length > 0 ? data : (propItem.options || [])
let res = []
const { optionProps } = propItem
for (let i = 0; i < checkboxs.length; i++) {
let item = checkboxs[i]
let option = h(
'Checkbox',
{
props: {
label: item[propItem.showValue] || item.value,
...optionProps
},
},
item[propItem.showName || 'label']
)
res.push(option)
}
return res
},
generatorSelect(h, item) {
const self = this
return h(
'Select',
{
props: {
value: self.formValues[item.key],
placeholder: item.placeholder,
...item.componentsProps
},
on: {
'input': (val) => {
self.formValues[item.key] = val
},
...item.componentsEvents
},
},
item.custom && item.custom.group ? self.generatorOptionsGroup(h, item, self.sourceData[item.key]) : self.generatorOptions(h, item, self.sourceData[item.key])
)
},
generatorOptionsGroup(h, propItem, data = []) {
let res = []
for (let key in data) {
let optionGroup = h(
'OptionGroup',
{
props: {
label: key,
},
},
this.generatorOptions(h, propItem, data[key])
)
res.push(optionGroup)
}
return res
},
generatorOptions(h, propItem, data = []) {
let res = []
for (let i = 0; i < data.length; i++) {
let item = data[i]
let option = h(
'Option',
{
props: {
value: item[propItem.showValue || 'value'],
},
},
item[propItem.showName || 'label']
)
res.push(option)
}
return res
},
generatorDefaultProps(item) {
return {
value: this.formValues[item.key],
placeholder: item.placeholder,
}
},
generatorDefaultEvents(item) {
// 日期组件与其他组件的事件不同
if (['DatePicker'].includes(item.type)) {
return {
'on-change': (val) => {
this.formValues[item.key] = val
}
}
} else {
return {
'input': (val) => {
this.formValues[item.key] = val
}
}
}
},
generatorRequestFunc(item, funcType = 'business') {
const { getBusinessData, getMapData } = this.requestMethods
const $this = this;
let source = item.source;
// 返回一个函数,这个函数将作为网络请求函数
return async function () {
try {
const { api, params, handle } = source;
const res = await (funcType === 'business' ? getBusinessData($this.$store.getters.ServiceUrls[api], params) : getMapData(api));
// 将请求到的数据赋值给sourceData
$this.sourceData[item.key] = handle ? handle(res) : res.page.list;
return res;
} catch (err) {
// 返回一个错误对象
return { error: err };
}
};
},
// 手动更新某些数据源
async updateSources(keys = []) {
if (!keys) return
let requests = []
if (Object.prototype.toString.call(keys) === '[object Array]') {
for (let i = 0; i < keys.length; i++) {
requests.push(this.requests[keys[i]]())
}
} else {
requests = [this.requests[keys]()]
}
if (requests.length === 0) {
return
}
await Promise.allSettled(requests).catch(err => {
console.warn(err)
})
this.$forceUpdate()
},
submit() {
this.$refs['formValues'].validate((valid) => {
this.$emit('submit', {
valid,
formValues: this.formValues
})
if (!valid && this.model === 'search') {
let errorMsg = []
let errorKeys = []
Object.keys(this.$refs['formValues'].$children).forEach(key => {
let item = this.$refs['formValues'].$children[key]
// 表单校验未通过
if (item.validateState === 'error') {
errorMsg.push(item.validateMessage)
errorKeys.push(item.label)
}
})
// 只依次提示第一个错误
if (this.singleStepErrorTip) {
this.$Message.error(errorMsg[0])
} else {
this.$Message.error(`请完善${errorKeys.length > 0 ? errorKeys.join('、') : '查询条件'}`)
}
}
})
},
/**
* 设置表单值
*/
setFormValue(key, value) {
if (hasOwnPropertySafely(this.formValues, key)) {
this.formValues[key] = value;
} else {
console.error('表单中不存在' + key + '属性')
}
},
/**
* 重置表单
*/
reset() {
this.$refs['formValues'].resetFields();
this.$emit('reset')
this.$nextTick(() => {
this.formValues = Object.assign({}, this.formInitValues)
})
},
/**
*
* @param {*} data
*/
setFormData(data) {
console.log(data)
if (data) {
this.$refs['formValues'].resetFields();
this.$nextTick(() => {
Object.keys(data).forEach(key => {
if (hasOwnPropertySafely(this.formValues, key)) {
this.formValues[key] = data[key];
}
});
console.log(this.formValues)
})
}
},
},
watch: {
form: {
handler() {
this.generatorRluesAndRequests()
},
deep: true,
immediate: true
}
},
}
</script>
<style lang='less'>
.my-custom-form {
position: relative;
.ivu-form {
padding: 0;
}
.ivu-form-inline .ivu-form-item {
width: 200px !important;
}
.ivu-cascader {
line-height: inherit;
}
.ivu-input-number-input-wrap {
height: 25px !important;
}
.ivu-input-number-handler-wrap {
height: 22px !important;
top: 2px;
}
.ivu-input-number {
// background-color: #03102c;
width: 100% !important;
background: none;
border: 0px !important;
border-radius: 0px !important;
height: 26px !important;
line-height: 24px !important;
}
.ivu-input-number-handler {
height: 11px;
}
.ivu-select-input {
background-color: #041538 !important;
height: 30px !important;
color: #fff !important;
}
.ivu-input-word-count {
background: none;
}
.ivu-input-type-textarea {
.ivu-input {
height: auto !important;
}
}
}
.my-custom-form-footer {
.ivu-btn {
margin: 0 5px;
}
}
.my-form-spin-icon-load {
animation: ani-form-spin 1s linear infinite;
}
@keyframes ani-form-spin {
from {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
to {
transform: rotate(360deg);
}
}
.my-custom-form-hide-label {
.ivu-form-item-label {
display: none;
}
}
.my-custom-search {
.ivu-form-item {
margin-bottom: 0px;
}
.ivu-form-item-error .ivu-select-arrow {
color: #808695;
}
.ivu-form-item-error-tip {
display: none;
}
}
.my-custom-form-buttons {
.ivu-btn {
margin-right: 10px;
}
}
</style>
import WayViewForm from "./components/index.vue";
// 导出组件
WayViewForm.install = (Vue) => {
Vue.component(WayViewForm.name, WayViewForm);
};
export default WayViewForm;
import Vue from "vue";
import App from "./App.vue";
import WayViewForm from "./index";
import ViewUI from "view-design";
import "view-design/dist/styles/iview.css";
Vue.use(WayViewForm);
Vue.use(ViewUI);
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
}).$mount("#app");
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment