基本表单功能、通用设置界面

This commit is contained in:
Gary Fu
2023-12-30 18:21:15 +08:00
parent d74c876d70
commit a65a1db306
9 changed files with 322 additions and 77 deletions

View File

@@ -0,0 +1,43 @@
<script setup>
import { computed } from 'vue'
import { $i18nBundle } from '@/messages'
/**
* @type {{option:CommonFormOption}}
*/
const props = defineProps({
/**
* @type {CommonFormOption}
*/
option: {
type: Object,
required: true
}
})
const inputType = computed(() => {
return `el-${props.option.type}`
})
const label = computed(() => {
const option = props.option
if (option.labelKey) {
return $i18nBundle(option.labelKey)
}
return option.label
})
</script>
<template>
<component
:is="inputType"
:value="option.value"
:label="label"
v-bind="option.attrs"
/>
</template>
<style scoped>
</style>

View File

@@ -1,76 +1,102 @@
<script setup>
import { computed } from 'vue'
import { $i18nBundle } from '@/messages'
import ControlChild from '@/components/common-form-control/control-child.vue'
/**
* 定义一些注释属性,方便代码提示
* @typedef {Object} CommonFormOption
* @property {'input'|'input-number'|'cascader'|'radio'
* |'radio-group'|'checkbox'|'checkbox-group'|'date-picker'
* |'time-picker'|'switch'|'select'|'option'|'slider'|'transfer'|'upload'} type 类型
* @property {any} value
* @property {any} config
* @property {string} prop
* @property {string|[string]} prop
* @property {string} label
* @property {string} labelKey 用于国际化的label
* @property {boolean} required
* @property {string} placeholder
* @property {{clearable:boolean,disabled:boolean}} attrs
* @property {{clearable:boolean,disabled:boolean,showPassword:boolean}} attrs
* @property {[CommonFormOption]} children 子节点
* @property {Array<RuleItem>} rules 子节点
*/
/**
* @type {CommonFormOption}
* @type {{option:CommonFormOption}}
*/
const props = defineProps({
type: {
type: String,
default: 'input'
},
value: {
/**
* @type {CommonFormOption}
*/
option: {
type: Object,
default: null
required: true
},
prop: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
children: {
type: Array,
default: () => []
},
rules: { type: Array, default: () => [] },
placeholder: {
type: String,
default: ''
},
attrs: {
type: Object,
default: null
model: {
type: Object
}
})
const inputType = computed(() => {
return `el-${props.type}`
return `el-${props.option.type || 'input'}`
})
const modelAttrs = computed(() => {
if (['input', 'select', 'autocomplete', 'cascader'].includes(inputType.value)) {
return Object.assign({ clearable: true }, props.option.attrs || {})
}
return props.option.attrs
})
const label = computed(() => {
const option = props.option
if (option.labelKey) {
return $i18nBundle(option.labelKey)
}
return option.label
})
const controlModel = computed(() => props.option.model || props.model)
const modelValue = computed({
get () {
console.info('=================', controlModel.value)
if (controlModel.value && props.option.prop) {
return controlModel.value[props.option.prop]
}
return null
},
set (val) {
console.info('set===============', controlModel.value)
if (controlModel.value && props.option.prop) {
controlModel.value[props.option.prop] = val
}
}
})
</script>
<template>
<component
:is="inputType"
:prop="prop"
v-bind="attrs"
:placeholder="placeholder"
<el-form-item
:label="label"
:prop="option.prop"
>
<template v-if="children&&children.length">
<common-form-control
v-for="(childItem, index) in children"
:key="index"
:type="childItem.type"
/>
</template>
</component>
<component
:is="inputType"
v-model="modelValue"
v-bind="modelAttrs"
:placeholder="option.placeholder"
@change="option.change"
>
<template v-if="option.children&&option.children.length">
<control-child
v-for="(childItem, index) in option.children"
:key="index"
:option="childItem"
/>
</template>
</component>
</el-form-item>
</template>
<style scoped>

View File

@@ -0,0 +1,53 @@
<script setup>
import { computed } from 'vue'
import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({
/**
* @type [CommonFormOption]
*/
options: {
type: Array,
required: true
},
labelWidth: {
type: String,
default: '100px'
},
model: {
type: Object
}
})
const rules = computed(() => {
const ruleResult = {}
props.options.forEach(option => {
if (option.prop && option.rules) {
ruleResult[option.prop] = cloneDeep(option.rules)
}
})
console.info(ruleResult)
return ruleResult
})
</script>
<template>
<el-form
:model="model"
:rules="rules"
:label-width="labelWidth"
v-bind="$attrs"
>
<common-form-control
v-for="(option,index) in options"
:key="index"
:model="model"
:option="option"
/>
</el-form>
</template>
<style scoped>
</style>

View File

@@ -1,4 +1,5 @@
import CommonIcon from '@/components/common-icon/index.vue'
import CommonForm from '@/components/common-form/index.vue'
import CommonFormControl from '@/components/common-form-control/index.vue'
import CommonMenu from '@/components/common-menu/index.vue'
import CommonMenuItem from '@/components/common-menu-item/index.vue'
@@ -13,6 +14,7 @@ export default {
*/
install (Vue) {
Vue.component('CommonIcon', CommonIcon)
Vue.component('CommonForm', CommonForm)
Vue.component('CommonFormControl', CommonFormControl)
Vue.component('CommonMenu', CommonMenu)
Vue.component('CommonMenuItem', CommonMenuItem)

View File

@@ -15,9 +15,7 @@ export const MENU_INFO_LIST = ref({})
export const useMenuInfo = item => {
const path = item.path
if (path !== '/') {
const menuInfo = MENU_INFO_LIST.value[path]
console.info('config menu:', menuInfo)
return menuInfo
return MENU_INFO_LIST.value[path]
}
}

View File

@@ -40,22 +40,6 @@ export const useBaseTopMenus = () => {
icon: 'Setting',
click: () => globalConfigStore.changeShowSettings(true)
},
{
icon: 'AutoAwesomeMosaicFilled',
isDropdown: true,
children: [
{
iconIf: () => globalConfigStore.layoutMode === GlobalLayoutMode.LEFT ? 'check' : '',
labelKey: 'common.label.layoutLeft',
click: () => globalConfigStore.changeLayout(GlobalLayoutMode.LEFT)
},
{
iconIf: () => globalConfigStore.layoutMode === GlobalLayoutMode.TOP ? 'check' : '',
labelKey: 'common.label.layoutTop',
click: () => globalConfigStore.changeLayout(GlobalLayoutMode.TOP)
}
]
},
{
icon: 'user',
isDropdown: true,

View File

@@ -94,21 +94,18 @@ export const useTabsViewStore = defineStore('tabsView', () => {
isShowTabIcon,
historyTabs,
cachedTabs,
changeTabMode () {
isTabMode.value = !isTabMode.value
changeTabMode (val) {
isTabMode.value = val
if (!isTabMode.value) {
clearHistoryTabs()
}
},
changeCachedTabMode () {
isCachedTabMode.value = !isCachedTabMode.value
changeCachedTabMode (val) {
isCachedTabMode.value = val
if (!isCachedTabMode.value) {
cachedTabs.value = []
}
},
changeShowTabIcon () {
isShowTabIcon.value = !isShowTabIcon.value
},
removeHistoryTab,
removeOtherHistoryTabs,
clearHistoryTabs,

View File

@@ -1,22 +1,109 @@
<script setup>
import {useGlobalConfigStore} from '@/stores/GlobalConfigStore'
import { useGlobalConfigStore } from '@/stores/GlobalConfigStore'
import { useTabsViewStore } from '@/stores/TabsViewStore'
import { GlobalLayoutMode, GlobalLocales } from '@/consts/GlobalConstants'
const globalConfigStore = useGlobalConfigStore()
const tabsViewStore = useTabsViewStore()
/**
* @type {[CommonFormOption]}
*/
const options = [
{
labelKey: 'common.label.theme',
prop: 'isDarkTheme',
type: 'switch',
model: globalConfigStore,
attrs: {
activeActionIcon: 'icon-moon',
inactiveActionIcon: 'icon-sunny'
}
},
{
labelKey: 'common.label.language',
type: 'select',
prop: 'currentLocale',
model: globalConfigStore,
change (val) {
globalConfigStore.changeLocale(val)
},
children: [{
labelKey: 'common.label.langCn',
value: GlobalLocales.CN,
type: 'option'
}, {
labelKey: 'common.label.langEn',
value: GlobalLocales.EN,
type: 'option'
}]
},
{
labelKey: 'common.label.layout',
type: 'select',
prop: 'layoutMode',
model: globalConfigStore,
change (val) {
globalConfigStore.changeLayout(val)
},
children: [{
labelKey: 'common.label.layoutLeft',
value: GlobalLayoutMode.LEFT,
type: 'option'
}, {
labelKey: 'common.label.layoutTop',
value: GlobalLayoutMode.TOP,
type: 'option'
}]
},
{
label: '多标签模式',
prop: 'isTabMode',
type: 'switch',
model: tabsViewStore,
change (val) {
tabsViewStore.changeTabMode(val)
}
},
{
label: '缓存标签',
prop: 'isCachedTabMode',
type: 'switch',
model: tabsViewStore,
change (val) {
tabsViewStore.changeCachedTabMode(val)
}
},
{
label: '标签图标',
prop: 'isShowTabIcon',
type: 'switch',
model: tabsViewStore
}
]
</script>
<template>
<el-drawer v-model="globalConfigStore.isShowSettings" direction="rtl" :size="350">
<el-drawer
v-model="globalConfigStore.isShowSettings"
direction="rtl"
:size="350"
>
<template #header>
<strong>{{ $t('common.label.settings') }}</strong>
</template>
<template #default>
<div>
</div>
<common-form
:options="options"
label-position="left"
/>
</template>
<template #footer>
<div style="flex: auto">
<el-button type="primary" @click="globalConfigStore.changeShowSettings(false)">{{$t('common.label.close')}}</el-button>
<el-button
type="primary"
@click="globalConfigStore.changeShowSettings(false)"
>
{{ $t('common.label.close') }}
</el-button>
</div>
</template>
</el-drawer>

View File

@@ -1,10 +1,65 @@
<script setup>
import { ref } from 'vue'
/**
* @type {[CommonFormOption]}
*/
const formOptions = [{
label: '用户名',
prop: 'userName',
value: '',
placeholder: '请输入用户名',
rules: [
{
required: true,
message: '用户名不能为空',
trigger: 'blur'
},
{
min: 2,
max: 6,
message: '用户名在2-6位之间',
trigger: 'blur'
}
]
}, {
label: '密码',
prop: 'userPassword',
value: '',
placeholder: '请输入密码',
rules: [
{
required: true,
message: '密码不能为空',
trigger: 'blur'
},
{
min: 2,
max: 6,
message: '密码在2-6位之间',
trigger: 'blur'
}
],
attrs: {
showPassword: true
}
}]
const userDto = ref({
userName: '',
userPassword: ''
})
</script>
<template>
<div>
<strong>表单测试</strong>
<common-form
:model="userDto"
:options="formOptions"
label-width="120px"
/>
<div>
{{ userDto }}
</div>
</div>
</template>