|
|
@ -1,23 +1,34 @@ |
|
|
|
<template> |
|
|
|
<div> |
|
|
|
<q-card flat bordered> |
|
|
|
<q-item dense style="padding: 0px"> |
|
|
|
<q-item-section> |
|
|
|
<q-item-label header :style="headerStyleComputed"> |
|
|
|
<q-icon v-if="props.icon" :name="props.icon" size="sm" v-bind="props.iconAttrs" /> |
|
|
|
<span style="margin-left: 5px">{{ label }}</span> |
|
|
|
</q-item-label> |
|
|
|
</q-item-section> |
|
|
|
</q-item> |
|
|
|
<q-separator /> |
|
|
|
<q-card-section style="padding: 8px"> |
|
|
|
<div class="grid" :style="layoutStyleComputed"> |
|
|
|
<template v-for="(field, index) in fields as any" :key="String(index)"> |
|
|
|
<FormElement :field="field"></FormElement> |
|
|
|
</template> |
|
|
|
<div :style="componentStyle"> |
|
|
|
<div v-if="cardModeComputed"> |
|
|
|
<q-card flat bordered> |
|
|
|
<q-item dense style="padding: 0px"> |
|
|
|
<q-item-section> |
|
|
|
<q-item-label header :style="cardModeHeaderStyleComputed"> |
|
|
|
<q-icon v-if="props.icon" :name="props.icon" size="sm" v-bind="props.iconAttrs" /> |
|
|
|
<span style="margin-left: 5px">{{ label }}</span> |
|
|
|
</q-item-label> |
|
|
|
</q-item-section> |
|
|
|
</q-item> |
|
|
|
<q-separator /> |
|
|
|
<q-card-section style="padding: 8px"> |
|
|
|
<div :class="props.class" :style="contentStyleComputed"> |
|
|
|
<template v-for="(field, index) in fields as any" :key="String(index)"> |
|
|
|
<div :class="field.class" :style="formElementDivStyle(field)"> |
|
|
|
<FormElement :field="field"></FormElement> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</div> |
|
|
|
</q-card-section> |
|
|
|
</q-card> |
|
|
|
</div> |
|
|
|
<div v-else :class="props.class" :style="contentStyleComputed"> |
|
|
|
<template v-for="(field, index) in fields as any" :key="String(index)"> |
|
|
|
<div :class="field.class" :style="formElementDivStyle(field)"> |
|
|
|
<FormElement :field="field"></FormElement> |
|
|
|
</div> |
|
|
|
</q-card-section> |
|
|
|
</q-card> |
|
|
|
</template> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
@ -26,6 +37,7 @@ import { inject, computed } from 'vue'; |
|
|
|
import { useQuasar } from 'quasar'; |
|
|
|
import { Tools } from '@/platform'; |
|
|
|
import { Form } from './ts/Form'; |
|
|
|
import { Constant } from './ts/Constant'; |
|
|
|
import FormElement from './FormElement.vue'; |
|
|
|
|
|
|
|
const $q = useQuasar(); |
|
|
@ -53,16 +65,35 @@ const colors = [red, orange, yellow, green, teal, blue, purple, grey]; |
|
|
|
const colorJson = { red, orange, yellow, green, teal, blue, purple, grey }; |
|
|
|
|
|
|
|
const props = defineProps({ |
|
|
|
// 分组标签名 |
|
|
|
// 分组模式 |
|
|
|
mode: { type: String, default: Constant.FORM_GROUP_MODE.CONTAINER }, |
|
|
|
// card模式-分组标签名 |
|
|
|
label: { type: String, default: '' }, |
|
|
|
// 分组头背景色 |
|
|
|
color: { type: String, default: undefined }, |
|
|
|
// 分组图标 |
|
|
|
// card模式-分组头背景色 |
|
|
|
headerBgColor: { type: String, default: undefined }, |
|
|
|
// card模式-分组图标 |
|
|
|
icon: { type: String, default: undefined }, |
|
|
|
// 分组图标其余属性 |
|
|
|
// card模式-分组图标其余属性 |
|
|
|
iconAttrs: { type: Object, default: undefined }, |
|
|
|
// 分组内一行放几个字段,与 form 保持一致,可配置具体值,也可配置屏幕断点显示值 |
|
|
|
// 分组div`class`,card模式下作用在内容块div上,container模式下作用在整个分组div上 |
|
|
|
class: { type: String, default: undefined }, |
|
|
|
// 分组div样式,card模式下作用在内容块div上,container模式下作用在整个分组div上 |
|
|
|
style: { type: String, default: undefined }, |
|
|
|
// 内置分组div快速布局配置,支持left、right、center、between、form; |
|
|
|
// 解释: |
|
|
|
// left = display: flex; justify-content: flex-start; |
|
|
|
// right = display: flex; justify-content: flex-end; |
|
|
|
// center = display: flex; justify-content: center; |
|
|
|
// between = display: flex; justify-content: space-between; |
|
|
|
// form = 与 form 相同的布局方式(每行放置固定列数); |
|
|
|
// 注意:当class、style、align都配置的情况下,首先在div上配置class与style,align对应的样式会追加到style中 |
|
|
|
align: { type: String, default: undefined }, |
|
|
|
// 分组内一行放几个字段,仅 `align` 为 `form` 时有效,与 form 保持一致,可配置具体值,也可配置屏幕断点显示值 |
|
|
|
colsNum: { type: [Number, Object], default: 0 }, |
|
|
|
// 使用align配置布局时的x轴间隙(像素点) |
|
|
|
xGap: { type: Number, default: 8 }, |
|
|
|
// 使用align配置布局时的y轴间隙(像素点) |
|
|
|
yGap: { type: Number, default: 4 }, |
|
|
|
// 字段集合 |
|
|
|
fields: { |
|
|
|
type: Array, |
|
|
@ -70,9 +101,75 @@ const props = defineProps({ |
|
|
|
return []; |
|
|
|
}, |
|
|
|
}, |
|
|
|
// 组件样式(组件嵌套最外层的样式属性,不提供给用户配置) |
|
|
|
componentStyle: { |
|
|
|
type: Object, |
|
|
|
default: undefined, |
|
|
|
}, |
|
|
|
}); |
|
|
|
/** |
|
|
|
* 当前屏幕断点一列应该显示的元素个数 |
|
|
|
*/ |
|
|
|
const screenColsNumComputed = computed(() => { |
|
|
|
if (typeof props.colsNum === 'number' && props.colsNum > 0) { |
|
|
|
return props.colsNum; |
|
|
|
} else if (typeof props.colsNum === 'object') { |
|
|
|
const screen = { ...form.cm.screenCols, ...props.colsNum }; |
|
|
|
return screen[$q.screen.name]; |
|
|
|
} |
|
|
|
return form.cm.screenCols[$q.screen.name]; |
|
|
|
}); |
|
|
|
/** |
|
|
|
* 使用form布局时的样式 |
|
|
|
*/ |
|
|
|
const useFormLayoutStyleComputed = computed(() => { |
|
|
|
const style = { display: 'grid' }; |
|
|
|
if (typeof props.colsNum === 'number' && props.colsNum > 0) { |
|
|
|
style['grid-template-columns'] = 'repeat(' + props.colsNum + ', minmax(0, 1fr))'; |
|
|
|
} else { |
|
|
|
style['grid-template-columns'] = 'repeat(' + screenColsNumComputed.value + ', minmax(0, 1fr))'; |
|
|
|
} |
|
|
|
style['column-gap'] = form.props.xGap + 'px'; |
|
|
|
style['row-gap'] = form.props.yGap + 'px'; |
|
|
|
return style; |
|
|
|
}); |
|
|
|
|
|
|
|
const headerStyleComputed = computed(() => { |
|
|
|
const jsonStyle2String = (json: any) => { |
|
|
|
return Object.entries(json) |
|
|
|
.map(([k, v]) => `${k}:${v}`) |
|
|
|
.join(';'); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* 快速布局对应的样式 |
|
|
|
*/ |
|
|
|
const alignJson = { |
|
|
|
left: { |
|
|
|
value: 'left', |
|
|
|
style: 'display:flex; justify-content: flex-start;' + 'column-gap: ' + props.xGap + 'px;' + 'row-gap: ' + props.yGap + 'px;', |
|
|
|
}, |
|
|
|
right: { |
|
|
|
value: 'right', |
|
|
|
style: 'display:flex; justify-content: flex-end;' + 'column-gap: ' + props.xGap + 'px;' + 'row-gap: ' + props.yGap + 'px;', |
|
|
|
}, |
|
|
|
center: { |
|
|
|
value: 'center', |
|
|
|
style: 'display:flex; justify-content: center;' + 'column-gap: ' + props.xGap + 'px;' + 'row-gap: ' + props.yGap + 'px;', |
|
|
|
}, |
|
|
|
between: { |
|
|
|
value: 'between', |
|
|
|
style: 'display:flex; justify-content: space-between;' + 'column-gap: ' + props.xGap + 'px;' + 'row-gap: ' + props.yGap + 'px;', |
|
|
|
}, |
|
|
|
form: { |
|
|
|
value: 'form', |
|
|
|
style: jsonStyle2String(useFormLayoutStyleComputed.value), |
|
|
|
}, |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* card模式标题块样式 |
|
|
|
*/ |
|
|
|
const cardModeHeaderStyleComputed = computed(() => { |
|
|
|
const style = { |
|
|
|
// 垂直居中 |
|
|
|
display: 'flex', |
|
|
@ -81,16 +178,16 @@ const headerStyleComputed = computed(() => { |
|
|
|
color: '#000', |
|
|
|
padding: '5px', |
|
|
|
}; |
|
|
|
if (props.color) { |
|
|
|
if (props.headerBgColor) { |
|
|
|
// 背景颜色 |
|
|
|
if (props.color === 'auto') { |
|
|
|
if (props.headerBgColor === 'auto') { |
|
|
|
const index = form.fieldArray.value.findIndex((item) => item.label === props.label); |
|
|
|
let colorIndex = index > -1 && index < colors.length ? index : getRandomInt(); |
|
|
|
style['background-color'] = colors[colorIndex]; |
|
|
|
} else if (Tools.hasOwnProperty(colorJson, props.color)) { |
|
|
|
style['background-color'] = colorJson[props.color]; |
|
|
|
} else if (Tools.hasOwnProperty(colorJson, props.headerBgColor)) { |
|
|
|
style['background-color'] = colorJson[props.headerBgColor]; |
|
|
|
} else { |
|
|
|
style['background-color'] = props.color; |
|
|
|
style['background-color'] = props.headerBgColor; |
|
|
|
} |
|
|
|
} |
|
|
|
return style; |
|
|
@ -103,25 +200,36 @@ const getRandomInt = () => { |
|
|
|
return Math.floor(Math.random() * (max - min + 1) + min); |
|
|
|
}; |
|
|
|
|
|
|
|
const screenColsNumComputed = computed(() => { |
|
|
|
if (typeof props.colsNum === 'number' && props.colsNum > 0) { |
|
|
|
return props.colsNum; |
|
|
|
} else if (typeof props.colsNum === 'object') { |
|
|
|
const screen = { ...form.cm.screenCols, ...props.colsNum }; |
|
|
|
return screen[$q.screen.name]; |
|
|
|
} |
|
|
|
return form.cm.screenCols[$q.screen.name]; |
|
|
|
/** |
|
|
|
* 当前为card模式 |
|
|
|
*/ |
|
|
|
const cardModeComputed = computed(() => { |
|
|
|
return props.mode === Constant.FORM_GROUP_MODE.CARD; |
|
|
|
}); |
|
|
|
|
|
|
|
const layoutStyleComputed = computed(() => { |
|
|
|
const style = {}; |
|
|
|
if (typeof props.colsNum === 'number' && props.colsNum > 0) { |
|
|
|
style['grid-template-columns'] = 'repeat(' + props.colsNum + ', minmax(0, 1fr))'; |
|
|
|
} else { |
|
|
|
style['grid-template-columns'] = 'repeat(' + screenColsNumComputed.value + ', minmax(0, 1fr))'; |
|
|
|
/** |
|
|
|
* 分组下内容块使用的样式 |
|
|
|
*/ |
|
|
|
const contentStyleComputed = computed(() => { |
|
|
|
let styleStr = ''; |
|
|
|
if (props.style) { |
|
|
|
// 初始化用户配置的样式,如果没有以分号结尾,增加分号 |
|
|
|
styleStr = props.style + (props.style.trim().endsWith(';') ? '' : ';'); |
|
|
|
} |
|
|
|
style['column-gap'] = form.props.xGap + 'px'; |
|
|
|
style['row-gap'] = form.props.yGap + 'px'; |
|
|
|
return style; |
|
|
|
if (props.align && Tools.hasOwnProperty(alignJson, props.align)) { |
|
|
|
styleStr += alignJson[props.align]['style']; |
|
|
|
} |
|
|
|
return styleStr; |
|
|
|
}); |
|
|
|
|
|
|
|
const formElementDivStyle = (field: any) => { |
|
|
|
let styleStr = ''; |
|
|
|
if (field.style) { |
|
|
|
styleStr = field.style + (field.style.trim().endsWith(';') ? '' : ';'); |
|
|
|
} |
|
|
|
if (props.align && props.align === alignJson.form.value) { |
|
|
|
styleStr += jsonStyle2String(form.getFieldStyle(field)); |
|
|
|
} |
|
|
|
return styleStr; |
|
|
|
}; |
|
|
|
</script> |
|
|
|