48 changed files with 315 additions and 3810 deletions
@ -1,528 +0,0 @@ |
|||||
<template> |
|
||||
<q-form ref="formRef" v-bind="extractFormProps(formProps)"> |
|
||||
<div class="grid gap-2" :class="formLayoutComputed"> |
|
||||
<template v-for="(field, index) in formFields as any" :key="String(index)"> |
|
||||
<q-input |
|
||||
v-if="field.type === 'dateRange' && index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
v-model="formData[field.fmtModelName]" |
|
||||
:label="field.required ? '* ' + field.label : field.label" |
|
||||
:rules="fieldRulesFun(field.required, field)" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
> |
|
||||
<template #append> |
|
||||
<q-icon :name="PlatformIconEnum.日期范围" class="cursor-pointer"> |
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> |
|
||||
<q-date |
|
||||
v-model="formData[field.modelName]" |
|
||||
range |
|
||||
:mask="field.mask ? field.mask : 'YYYY-MM-DD'" |
|
||||
:readonly="formStatus === 'view' || field.readonly ? true : false" |
|
||||
> |
|
||||
<div class="row items-center justify-end"> |
|
||||
<q-btn v-close-popup label="关闭" color="primary" flat /> |
|
||||
</div> |
|
||||
</q-date> |
|
||||
</q-popup-proxy> |
|
||||
</q-icon> |
|
||||
</template> |
|
||||
</q-input> |
|
||||
<q-input |
|
||||
v-else-if="field.type === 'date' && index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
v-model="formData[field.modelName]" |
|
||||
:label="field.required ? '* ' + field.label : field.label" |
|
||||
:rules="fieldRulesFun(field.required, field)" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
> |
|
||||
<template #append> |
|
||||
<q-icon :name="PlatformIconEnum.日期" class="cursor-pointer"> |
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> |
|
||||
<q-date |
|
||||
v-model="formData[field.modelName]" |
|
||||
today-btn |
|
||||
:mask="field.mask ? field.mask : 'YYYY-MM-DD'" |
|
||||
:readonly="formStatus === 'view' || field.readonly ? true : false" |
|
||||
> |
|
||||
<div class="row items-center justify-end"> |
|
||||
<q-btn v-close-popup label="关闭" color="primary" flat /> |
|
||||
</div> |
|
||||
</q-date> |
|
||||
</q-popup-proxy> |
|
||||
</q-icon> |
|
||||
</template> |
|
||||
</q-input> |
|
||||
<q-select |
|
||||
v-else-if="field.type === 'select' && index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
v-model="formData[field.modelName]" |
|
||||
:label="field.required ? '* ' + field.label : field.label" |
|
||||
:rules="fieldRulesFun(field.required, field)" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
@filter="field.filterFun" |
|
||||
@focus="field.focusFun" |
|
||||
> |
|
||||
<template v-if="field.afterButton" #after> |
|
||||
<q-btn |
|
||||
:round="field.afterButton.round" |
|
||||
:dense="field.afterButton.dense" |
|
||||
:flat="field.afterButton.flat" |
|
||||
:unelevated="field.afterButton.unelevated" |
|
||||
:outline="field.afterButton.outline" |
|
||||
:color="field.afterButton.color" |
|
||||
:icon="field.afterButton.icon" |
|
||||
:label="field.afterButton.label" |
|
||||
:disable="formStatus === PageStatusEnum.查看 || field.afterButton.disable ? true : false" |
|
||||
@click="field.afterButton.click" |
|
||||
/> |
|
||||
</template> |
|
||||
</q-select> |
|
||||
<q-checkbox |
|
||||
v-else-if="field.type === 'checkbox' && index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
v-model="formData[field.modelName]" |
|
||||
:label="field.required ? '* ' + field.label : field.label" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
/> |
|
||||
<div |
|
||||
v-else-if="field.type === 'optionGroup' && index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
class="border-solid border" |
|
||||
> |
|
||||
<span class="p-2.5">{{ field.label }}</span> |
|
||||
<q-option-group |
|
||||
v-model="formData[field.modelName]" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
/> |
|
||||
</div> |
|
||||
<q-input |
|
||||
v-else-if="index < queryFormShowFieldNumber" |
|
||||
v-show="!field.hasOwnProperty('hide') || !field.hide" |
|
||||
v-model="formData[field.modelName]" |
|
||||
:label="field.required ? '* ' + field.label : field.label" |
|
||||
:rules="fieldRulesFun(field.required, field)" |
|
||||
v-bind="extractFormItemComponentProps(field.type, field)" |
|
||||
:readonly="formStatus === PageStatusEnum.查看 || field.readonly ? true : false" |
|
||||
@update:model-value="field.changeFun" |
|
||||
> |
|
||||
<template v-if="field.afterButton" #after> |
|
||||
<q-btn |
|
||||
round |
|
||||
dense |
|
||||
flat |
|
||||
:disable="formStatus === PageStatusEnum.查看 || field.afterButton.disable ? true : false" |
|
||||
:icon="field.afterButton.icon" |
|
||||
@click="field.afterButton.click" |
|
||||
/> |
|
||||
</template> |
|
||||
</q-input> |
|
||||
</template> |
|
||||
</div> |
|
||||
<slot></slot> |
|
||||
</q-form> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { ref, reactive, watch, computed, toRaw, defineProps } from 'vue'; |
|
||||
import { |
|
||||
extractFormProps, |
|
||||
extractFormItemComponentProps, |
|
||||
arrayToMap, |
|
||||
PlatformIconEnum, |
|
||||
FormComponentValidateEnum, |
|
||||
PageStatusEnum, |
|
||||
isEmpty, |
|
||||
} from '@/platform/components/utils'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
formProps: { |
|
||||
type: Object, |
|
||||
default: () => { |
|
||||
return { autofocus: false, greedy: true }; |
|
||||
}, |
|
||||
}, |
|
||||
formColsNumber: { type: Number, default: 3 }, |
|
||||
formColsAuto: { type: Boolean, default: true }, |
|
||||
formFields: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
queryFormShowFieldNumber: { type: Number, default: 999 }, |
|
||||
}); |
|
||||
|
|
||||
const formRef = ref(); |
|
||||
const formStatus = ref('add'); |
|
||||
const formFieldsMap = arrayToMap('modelName', props.formFields); |
|
||||
|
|
||||
// 由于采用字符串拼接 class 名称,tailwind 无法生效,模板文件中必须出现完整的类名,最终构建的css文件中才会包含进去。 |
|
||||
const formLayoutComputed = computed(() => { |
|
||||
let className = ''; |
|
||||
switch (props.formColsNumber) { |
|
||||
case 1: |
|
||||
className = !props.formColsAuto ? 'grid-cols-1' : 'xs:grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'; |
|
||||
break; |
|
||||
case 2: |
|
||||
className = !props.formColsAuto ? 'grid-cols-2' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'; |
|
||||
break; |
|
||||
case 4: |
|
||||
className = !props.formColsAuto ? 'grid-cols-4' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-6'; |
|
||||
break; |
|
||||
case 5: |
|
||||
className = !props.formColsAuto ? 'grid-cols-5' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6'; |
|
||||
break; |
|
||||
case 6: |
|
||||
className = !props.formColsAuto ? 'grid-cols-6' : 'xs:grid-cols-1 sm:grid-cols-3 md:grid-cols-6 lg:grid-cols-6 xl:grid-cols-6'; |
|
||||
break; |
|
||||
case 7: |
|
||||
className = !props.formColsAuto ? 'grid-cols-7' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-7 lg:grid-cols-7 xl:grid-cols-7'; |
|
||||
break; |
|
||||
case 8: |
|
||||
className = !props.formColsAuto ? 'grid-cols-8' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-8 lg:grid-cols-8 xl:grid-cols-8'; |
|
||||
break; |
|
||||
case 9: |
|
||||
className = !props.formColsAuto ? 'grid-cols-9' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-9 lg:grid-cols-9 xl:grid-cols-9'; |
|
||||
break; |
|
||||
case 10: |
|
||||
className = !props.formColsAuto ? 'grid-cols-10' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-10 lg:grid-cols-10 xl:grid-cols-10'; |
|
||||
break; |
|
||||
case 11: |
|
||||
className = !props.formColsAuto ? 'grid-cols-11' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-11 lg:grid-cols-11 xl:grid-cols-11'; |
|
||||
break; |
|
||||
case 12: |
|
||||
className = !props.formColsAuto ? 'grid-cols-12' : 'xs:grid-cols-1 sm:grid-cols-4 md:grid-cols-12 lg:grid-cols-12 xl:grid-cols-12'; |
|
||||
break; |
|
||||
default: |
|
||||
className = !props.formColsAuto ? 'grid-cols-3' : 'xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'; |
|
||||
} |
|
||||
return className; |
|
||||
}); |
|
||||
|
|
||||
const selectDefaultValue = (field) => { |
|
||||
if (field.hasOwnProperty('defaultValue') && field.defaultValue !== null) { |
|
||||
if (typeof field.defaultValue === 'string' || typeof field.defaultValue === 'boolean' || typeof field.defaultValue === 'number') { |
|
||||
const dftValue = field.options.filter((item) => { |
|
||||
return item.value === field.defaultValue; |
|
||||
}); |
|
||||
if (dftValue && dftValue.length > 0) { |
|
||||
return field.multiple ? [dftValue[0]] : dftValue[0]; |
|
||||
} else { |
|
||||
return field.multiple ? [] : ''; |
|
||||
} |
|
||||
} else if (Array.isArray(field.defaultValue)) { |
|
||||
const dftValue = field.options.filter((item) => { |
|
||||
return field.defaultValue.filter((val) => { |
|
||||
return val === item.value; |
|
||||
}); |
|
||||
}); |
|
||||
return field.multiple ? dftValue : dftValue[0]; |
|
||||
} |
|
||||
} else { |
|
||||
return field.multiple ? [] : ''; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const numberDefaultValue = (field) => { |
|
||||
if (field.hasOwnProperty('defaultValue') && typeof field.defaultValue === 'number') { |
|
||||
return field.defaultValue; |
|
||||
} else { |
|
||||
return ''; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const checkboxDefaultValue = (field) => { |
|
||||
if (field.hasOwnProperty('defaultValue')) { |
|
||||
return field.defaultValue; |
|
||||
} else { |
|
||||
return false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const optionGroupDefaultValue = (field) => { |
|
||||
if (field.hasOwnProperty('defaultValue')) { |
|
||||
return field.defaultValue; |
|
||||
} else if (field.optionGroupType && field.optionGroupType === 'checkbox') { |
|
||||
return []; |
|
||||
} else { |
|
||||
return ''; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const textDefaultValue = (field) => { |
|
||||
if (field.hasOwnProperty('defaultValue')) { |
|
||||
return field.defaultValue; |
|
||||
} else { |
|
||||
return ''; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const formModel: any = {}; |
|
||||
for (const field of props.formFields as any) { |
|
||||
if (field.type === 'dateRange') { |
|
||||
formModel[field.fmtModelName] = ''; |
|
||||
} else if (field.type === 'select') { |
|
||||
formModel[field.modelName] = selectDefaultValue(field); |
|
||||
} else if (field.type === 'number') { |
|
||||
formModel[field.modelName] = numberDefaultValue(field); |
|
||||
} else if (field.type === 'checkbox') { |
|
||||
formModel[field.modelName] = checkboxDefaultValue(field); |
|
||||
} else if (field.type === 'optionGroup') { |
|
||||
formModel[field.modelName] = optionGroupDefaultValue(field); |
|
||||
} else { |
|
||||
formModel[field.modelName] = textDefaultValue(field); |
|
||||
} |
|
||||
} |
|
||||
const formData = reactive(formModel); |
|
||||
for (const field of props.formFields as any) { |
|
||||
if (field.type === 'dateRange') { |
|
||||
watch( |
|
||||
() => formData[field.modelName], |
|
||||
(newVal, oldVal) => { |
|
||||
if (!newVal || newVal.from === '') { |
|
||||
formData[field.fmtModelName] = ''; |
|
||||
} else { |
|
||||
formData[field.fmtModelName] = newVal.from + ' 至 ' + newVal.to; |
|
||||
} |
|
||||
}, |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const isNumber = (num) => { |
|
||||
return !isNaN(parseFloat(num)) && isFinite(num); |
|
||||
}; |
|
||||
|
|
||||
const fieldRulesFun = (required, field) => { |
|
||||
let resultRules = <any>[]; |
|
||||
if (field.type === 'select' && field.multiple && required) { |
|
||||
resultRules.push((val) => { |
|
||||
if (val !== null && val.length > 0) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '该字段为必填项'; |
|
||||
} |
|
||||
}); |
|
||||
} else if (required) { |
|
||||
resultRules.push((val) => (val !== null && val !== '') || '该字段为必填项'); |
|
||||
} |
|
||||
if (field.rules && field.rules.length > 0) { |
|
||||
field.rules.forEach((rule) => { |
|
||||
if (typeof rule === 'string' && rule === FormComponentValidateEnum.字符串不能包含空格校验) { |
|
||||
// 字符串不能包含空格校验 |
|
||||
resultRules.push((val) => { |
|
||||
if (isEmpty(val) || val.indexOf(' ') === -1) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '不能包含空格'; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'string' && rule === FormComponentValidateEnum.默认日期格式校验) { |
|
||||
resultRules.push((val) => { |
|
||||
if (isEmpty(val) || /^(\d{4})-(\d{2})-(\d{2})$/.test(val)) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '日期格式校验未通过'; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'string' && rule === FormComponentValidateEnum.必须为整数校验) { |
|
||||
// 必须为整数校验 |
|
||||
resultRules.push((val) => { |
|
||||
const tmp = String(val); |
|
||||
if (val === null || (tmp.indexOf('.') === -1 && Number.isInteger(parseInt(tmp)))) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '只能输入整数'; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.字符串最大长度校验) { |
|
||||
// 字符串最大长度校验 |
|
||||
resultRules.push((val) => { |
|
||||
const tmp = String(val); |
|
||||
if (val === null || tmp.length <= rule.value) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '最大允许输入的长度为:' + rule.value; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.最大小数位数校验) { |
|
||||
// 最大小数位数校验 |
|
||||
resultRules.push((val) => { |
|
||||
const tmp = String(val); |
|
||||
if (val === null || tmp.indexOf('.') === -1 || tmp.substring(tmp.indexOf('.') + 1).length <= rule.value) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '最大允许输入的小数位数为:' + rule.value; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.数字最小值校验) { |
|
||||
// 数字最小值校验 |
|
||||
resultRules.push((val) => { |
|
||||
const tmp = String(val); |
|
||||
if (val === null || parseFloat(tmp) >= rule.value) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '最小允许输入的值为:' + rule.value; |
|
||||
} |
|
||||
}); |
|
||||
} else if (typeof rule === 'object' && rule.name === FormComponentValidateEnum.数字最大值校验) { |
|
||||
// 数字最大值校验 |
|
||||
resultRules.push((val) => { |
|
||||
const tmp = String(val); |
|
||||
if (val === null || parseFloat(tmp) <= rule.value) { |
|
||||
return true; |
|
||||
} else { |
|
||||
return '最大允许输入的值为:' + rule.value; |
|
||||
} |
|
||||
}); |
|
||||
} else { |
|
||||
resultRules.push(rule); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
return resultRules; |
|
||||
}; |
|
||||
|
|
||||
const formValidateFun = async () => { |
|
||||
const v = await formValidate(); |
|
||||
return v; |
|
||||
}; |
|
||||
const formValidate = async () => { |
|
||||
let validate = false; |
|
||||
await formRef.value.validate().then((success) => { |
|
||||
if (success) { |
|
||||
validate = true; |
|
||||
} |
|
||||
}); |
|
||||
return validate; |
|
||||
}; |
|
||||
|
|
||||
const getFormDataFun = () => { |
|
||||
const data = { ...toRaw(formData) }; |
|
||||
const selectFields = props.formFields.filter((item: any) => { |
|
||||
return item.type === 'select'; |
|
||||
}); |
|
||||
selectFields.forEach((item: any) => { |
|
||||
let selectValues = ''; |
|
||||
if (item.multiple && data[item.modelName] && data[item.modelName].length > 0) { |
|
||||
data[item.modelName].forEach((val) => { |
|
||||
selectValues = selectValues + ',' + val.value; |
|
||||
}); |
|
||||
selectValues = selectValues.substring(1, selectValues.length); |
|
||||
} else if (data[item.modelName]) { |
|
||||
selectValues = data[item.modelName].value ?? data[item.modelName]; |
|
||||
} |
|
||||
data[item.modelName] = selectValues; |
|
||||
}); |
|
||||
return data; |
|
||||
}; |
|
||||
|
|
||||
const setFormDataFun = (record) => { |
|
||||
for (const field of props.formFields as any) { |
|
||||
if (field.type === 'select') { |
|
||||
if (field.multiple && record[field.modelName].indexOf(',') > -1) { |
|
||||
const recordSelectValues = record[field.modelName].split(','); |
|
||||
const selectValues = <any>[]; |
|
||||
recordSelectValues.forEach((item) => { |
|
||||
selectValues.push(setSelectValue(field.options, item)); |
|
||||
}); |
|
||||
formData[field.modelName] = selectValues; |
|
||||
} else { |
|
||||
formData[field.modelName] = setSelectValue(field.options, record[field.modelName]); |
|
||||
} |
|
||||
} else if (field.type === 'optionGroup') { |
|
||||
if (record[field.modelName]) { |
|
||||
formData[field.modelName] = record[field.modelName]; |
|
||||
} else { |
|
||||
formData[field.modelName] = []; |
|
||||
} |
|
||||
} else { |
|
||||
formData[field.modelName] = record[field.modelName]; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const setSelectValue = (options, val) => { |
|
||||
const opt = options.filter((option) => { |
|
||||
return option.value === val; |
|
||||
}); |
|
||||
if (opt && opt.length > 0) { |
|
||||
return opt[0]; |
|
||||
} else { |
|
||||
return val; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const setFieldValueFun = (fieldName, value) => { |
|
||||
const field = formFieldsMap.get(fieldName); |
|
||||
if (field.type === 'select') { |
|
||||
if (field.multiple && Array.isArray(value)) { |
|
||||
const selectValues = <any>[]; |
|
||||
value.forEach((item) => { |
|
||||
selectValues.push(setSelectValue(field.options, item)); |
|
||||
}); |
|
||||
formData[field.modelName] = selectValues; |
|
||||
} else { |
|
||||
formData[field.modelName] = setSelectValue(field.options, value); |
|
||||
} |
|
||||
} else { |
|
||||
formData[field.modelName] = value; |
|
||||
} |
|
||||
}; |
|
||||
const getFieldValueFun = (fieldName) => { |
|
||||
return formData[fieldName]; |
|
||||
}; |
|
||||
|
|
||||
const resetFormDataFun = () => { |
|
||||
Object.keys(formData).forEach((key) => { |
|
||||
switch (typeof formData[key]) { |
|
||||
case 'string': |
|
||||
formData[key] = ''; |
|
||||
break; |
|
||||
case 'boolean': |
|
||||
formData[key] = false; |
|
||||
break; |
|
||||
case 'number': |
|
||||
formData[key] = ''; |
|
||||
break; |
|
||||
default: |
|
||||
formData[key] = undefined; |
|
||||
break; |
|
||||
} |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
const setFormStatusFun = (status) => { |
|
||||
formStatus.value = status; |
|
||||
}; |
|
||||
|
|
||||
const getFormStatusFun = () => { |
|
||||
return toRaw(formStatus.value); |
|
||||
}; |
|
||||
|
|
||||
const getFormFieldMapFun = () => { |
|
||||
return formFieldsMap; |
|
||||
}; |
|
||||
|
|
||||
defineExpose({ |
|
||||
formValidateFun, |
|
||||
getFormDataFun, |
|
||||
setFormDataFun, |
|
||||
getFormStatusFun, |
|
||||
setFormStatusFun, |
|
||||
resetFormDataFun, |
|
||||
setFieldValueFun, |
|
||||
getFieldValueFun, |
|
||||
getFormFieldMapFun, |
|
||||
}); |
|
||||
</script> |
|
@ -1,15 +0,0 @@ |
|||||
<template> |
|
||||
<div> |
|
||||
<q-icon v-if="value" :name="IconEnum.是状态" color="green" size="sm"> </q-icon> |
|
||||
<q-icon v-else-if="!value && showNoEnable" :name="IconEnum.否状态" color="red" size="sm"> </q-icon> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup> |
|
||||
import { IconEnum } from '@/platform/enums'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
value: { type: Boolean, default: false }, // 值 |
|
||||
showNoEnable: { type: Boolean, default: false }, // 不可用是否显示 |
|
||||
}); |
|
||||
</script> |
|
File diff suppressed because it is too large
@ -1,30 +0,0 @@ |
|||||
<template> |
|
||||
<div :ref="(node) => drag(drop(node as any))" :class="borderClass"> |
|
||||
<q-icon v-if="typeof tdValue[0] === 'boolean' && tdValue[0]" :name="PlatformIconEnum.是状态" color="green" size="sm"> </q-icon> |
|
||||
<template v-else-if="typeof tdValue[0] === 'boolean' && !tdValue[0]"> </template> |
|
||||
<template v-else> |
|
||||
{{ tdValue[0] }} |
|
||||
</template> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup> |
|
||||
import { computed, unref } from 'vue'; |
|
||||
import { toRefs } from '@vueuse/core'; |
|
||||
import { PlatformIconEnum } from '@/platform/components/utils'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
tdValue: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
rowIndex: { type: Number, default: 0 }, |
|
||||
}); |
|
||||
const emit = defineEmits(['tableSortFun']); |
|
||||
|
|
||||
export interface DropResult { |
|
||||
name: string; |
|
||||
} |
|
||||
</script> |
|
@ -1,123 +0,0 @@ |
|||||
<template> |
|
||||
<div class="flex flex-nowrap py-2"> |
|
||||
<div ref="titleContainerRef" class="flex items-end text-subtitle2 text-no-wrap">{{ title }}</div> |
|
||||
<q-space /> |
|
||||
<div ref="actionContainerRef" class="flex flex-nowrap"> |
|
||||
<!-- baseActions --> |
|
||||
<template v-for="(action, index) in baseActions" :key="'baseAction_' + index"> |
|
||||
<q-separator |
|
||||
v-if="action.separator" |
|
||||
vertical |
|
||||
class="class-action-item" |
|
||||
:style="{ |
|
||||
'margin-left': '5px', |
|
||||
'margin-right': '5px', |
|
||||
}" |
|
||||
/> |
|
||||
<q-btn |
|
||||
v-else |
|
||||
v-bind="action" |
|
||||
:id="action.name" |
|
||||
:disable="action.enableIf ? !action.enableIf() : false" |
|
||||
no-wrap |
|
||||
class="class-action-item" |
|
||||
:style="{ |
|
||||
'margin-left': '5px', |
|
||||
'margin-right': '5px', |
|
||||
}" |
|
||||
@click="action.click" |
|
||||
/> |
|
||||
</template> |
|
||||
|
|
||||
<!-- moreActions --> |
|
||||
<q-btn-dropdown v-if="moreActions && moreActions.length > 0" :label="$t('more')" class="class-action-item" style="margin-left: 5px"> |
|
||||
<q-list> |
|
||||
<template v-for="(action, index) in moreActions" :key="'moreAction_' + index"> |
|
||||
<q-separator v-if="action.separator" /> |
|
||||
<q-item v-else v-close-popup clickable @click="action.click"> |
|
||||
<q-item-section avatar style="min-width: 28px; padding-right: 0px"> |
|
||||
<q-icon :name="action.icon" size="20px" /> |
|
||||
</q-item-section> |
|
||||
<q-item-section> |
|
||||
<q-item-label :v-bind="action">{{ action.label }}</q-item-label> |
|
||||
</q-item-section> |
|
||||
</q-item> |
|
||||
</template> |
|
||||
</q-list> |
|
||||
</q-btn-dropdown> |
|
||||
</div> |
|
||||
<q-resize-observer @resize="onResize" /> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
import { ref } from 'vue'; |
|
||||
import { Tools } from '@/platform/utils'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
title: { type: String, default: '' }, |
|
||||
noActionIcon: { type: Boolean, default: false }, |
|
||||
actions: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
const titleContainerRef = ref(); |
|
||||
const actionContainerRef = ref(); |
|
||||
|
|
||||
const actions = props.actions; |
|
||||
if (actions && actions.length > 0 && props.noActionIcon) { |
|
||||
for (const action of actions) { |
|
||||
action.icon = undefined; |
|
||||
} |
|
||||
} |
|
||||
const baseActions = ref(actions); |
|
||||
const moreActions = ref([]); |
|
||||
const isActionWidthInitializedRef = ref(false); |
|
||||
const moreActionWidth = 100; |
|
||||
|
|
||||
const onResize = (size) => { |
|
||||
if (Tools.isUndefinedOrNull(titleContainerRef.value) || Tools.isUndefinedOrNull(actionContainerRef.value)) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (!isActionWidthInitializedRef.value) { |
|
||||
const nodes = actionContainerRef.value.getElementsByClassName('class-action-item'); |
|
||||
for (let i = 0; i < actions.length; i++) { |
|
||||
actions[i].width = nodes[i].clientWidth + 10; |
|
||||
} |
|
||||
isActionWidthInitializedRef.value = true; |
|
||||
} |
|
||||
|
|
||||
const _baseActions = []; |
|
||||
const _moreActions = []; |
|
||||
const length = actions.length; |
|
||||
let availableWidth = size.width - titleContainerRef.value.clientWidth; |
|
||||
let width = 0; |
|
||||
let index = 0; |
|
||||
|
|
||||
for (; index < length; index++) { |
|
||||
if (width + actions[index].width > availableWidth) { |
|
||||
availableWidth -= moreActionWidth; |
|
||||
while (width > availableWidth) { |
|
||||
index--; |
|
||||
width -= actions[index].width; |
|
||||
_baseActions.pop(); |
|
||||
} |
|
||||
break; |
|
||||
} else { |
|
||||
_baseActions.push(actions[index]); |
|
||||
width += actions[index].width; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
for (; index < length; index++) { |
|
||||
_moreActions.push(actions[index]); |
|
||||
} |
|
||||
|
|
||||
baseActions.value = _baseActions; |
|
||||
moreActions.value = _moreActions; |
|
||||
}; |
|
||||
</script> |
|
@ -1,53 +0,0 @@ |
|||||
<template> |
|
||||
<q-tr> |
|
||||
<q-td v-for="(col, index) in cols" :key="col.name" :style="{ 'padding-left': index == 0 ? '2px' : '7px' }"> |
|
||||
<div class="flex flex-nowrap items-center"> |
|
||||
<template v-if="index == 0"> |
|
||||
<!--层级占位符--> |
|
||||
<span :style="`width:${27 * props.level}px;`"></span> |
|
||||
<!--展开按钮--> |
|
||||
<q-btn |
|
||||
v-if="data.children && data.children.length > 0" |
|
||||
flat |
|
||||
size="16px" |
|
||||
dense |
|
||||
padding="0px 0px" |
|
||||
:icon="data.expand ? 'bi-dash' : 'bi-plus'" |
|
||||
@click="data.expand = !data.expand" |
|
||||
/> |
|
||||
<!--展开按钮占位符--> |
|
||||
<span v-else style="width: 27px"></span> |
|
||||
<!--选择框--> |
|
||||
<q-checkbox v-model="data.selected" flat size="40px" dense /> |
|
||||
<!--文件夹图标--> |
|
||||
<q-icon v-if="data.children && data.children.length > 0" name="sym_o_folder_open" size="20px" class="px-1"></q-icon> |
|
||||
<!--文件图标--> |
|
||||
<q-icon v-else name="bi-file-earmark" size="16px" class="px-1"></q-icon> |
|
||||
</template> |
|
||||
<div v-dompurify-html="col.format ? col.format(data[col.name], data) : col.value"></div> |
|
||||
</div> |
|
||||
</q-td> |
|
||||
</q-tr> |
|
||||
<template v-for="child in data.children" :key="child.id"> |
|
||||
<TableRow v-if="data.expand" :cols="cols" :data="child" :level="props.level + 1"></TableRow> |
|
||||
</template> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
const props = defineProps({ |
|
||||
level: { type: Number, default: 0 }, |
|
||||
cols: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
data: { |
|
||||
type: Object, |
|
||||
default: () => { |
|
||||
return {}; |
|
||||
}, |
|
||||
}, |
|
||||
}); |
|
||||
const cols = props.cols; |
|
||||
const data = props.data; |
|
||||
</script> |
|
@ -1,306 +0,0 @@ |
|||||
<template> |
|
||||
<div> |
|
||||
<div class="row q-gutter-x-md q-gutter-y-sm"> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense :options="['ksdjlfsd', 'sdkjfklsd']" /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
<q-input label="loginName" outlined dense /> |
|
||||
</div> |
|
||||
<TableAction title="User List" :no-action-icon="props.noActionIcon" :actions="tableActions"></TableAction> |
|
||||
|
|
||||
<q-table flat bordered separator="cell" :rows="data" :columns="columns" row-key="name"> |
|
||||
<template #header="headerProps"> |
|
||||
<q-tr :props="headerProps"> |
|
||||
<q-th v-for="col in headerProps.cols" :key="col.name" :props="headerProps"> |
|
||||
{{ col.label }} |
|
||||
</q-th> |
|
||||
</q-tr> |
|
||||
</template> |
|
||||
|
|
||||
<template #body="bodyProps"> |
|
||||
<TableRow :cols="bodyProps.cols" :data="bodyProps.row"></TableRow> |
|
||||
</template> |
|
||||
</q-table> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { ref, computed, onMounted } from 'vue'; |
|
||||
import { useI18n } from 'vue-i18n'; |
|
||||
import { axios, Tools, TreeBuilder } from '@/platform'; |
|
||||
import TableAction from './TableAction.vue'; |
|
||||
import TableRow from './TableRow.vue'; |
|
||||
|
|
||||
const props = defineProps({ |
|
||||
tree: { type: Boolean, default: false }, |
|
||||
title: { type: String, default: '' }, |
|
||||
noActionIcon: { type: Boolean, default: false }, |
|
||||
targetObjectName: { type: String, default: '' }, |
|
||||
actions: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
columns: { |
|
||||
type: Array, |
|
||||
default: () => { |
|
||||
return []; |
|
||||
}, |
|
||||
}, |
|
||||
autoFetchData: { type: Boolean, default: false }, |
|
||||
dataUrl: { type: String, default: undefined }, |
|
||||
fetchDataUrl: { type: String, default: undefined }, |
|
||||
addDataUrl: { type: String, default: undefined }, |
|
||||
removeDataUrl: { type: String, default: undefined }, |
|
||||
updateDataUrl: { type: String, default: undefined }, |
|
||||
}); |
|
||||
|
|
||||
const { t } = useI18n(); |
|
||||
|
|
||||
const defaultTableActions = { |
|
||||
separator: { |
|
||||
separator: true, |
|
||||
}, |
|
||||
query: { |
|
||||
name: 'query', |
|
||||
label: t('query'), |
|
||||
icon: 'bi-search', |
|
||||
click: () => { |
|
||||
console.log('query'); |
|
||||
}, |
|
||||
}, |
|
||||
refresh: { |
|
||||
name: 'refresh', |
|
||||
label: t('refresh'), |
|
||||
icon: 'bi-arrow-clockwise', |
|
||||
click: () => { |
|
||||
console.log('refresh'); |
|
||||
}, |
|
||||
}, |
|
||||
add: { |
|
||||
name: 'add', |
|
||||
label: t('addNew'), |
|
||||
icon: 'bi-plus-lg', |
|
||||
click: () => { |
|
||||
console.log('add'); |
|
||||
}, |
|
||||
}, |
|
||||
clone: { |
|
||||
name: 'clone', |
|
||||
label: t('clone'), |
|
||||
icon: 'content_copy', |
|
||||
click: () => { |
|
||||
console.log('clone'); |
|
||||
}, |
|
||||
}, |
|
||||
edit: { |
|
||||
name: 'edit', |
|
||||
label: t('edit'), |
|
||||
icon: 'bi-pencil-square', |
|
||||
click: () => { |
|
||||
console.log('edit'); |
|
||||
}, |
|
||||
}, |
|
||||
remove: { |
|
||||
name: 'remove', |
|
||||
label: t('delete'), |
|
||||
icon: 'bi-x-lg', |
|
||||
click: () => { |
|
||||
console.log('remove'); |
|
||||
}, |
|
||||
}, |
|
||||
removeAll: { |
|
||||
name: 'removeAll', |
|
||||
label: t('deleteAll'), |
|
||||
click: () => { |
|
||||
console.log('removeAll'); |
|
||||
}, |
|
||||
}, |
|
||||
detail: { |
|
||||
name: 'detail', |
|
||||
label: t('detail'), |
|
||||
icon: 'bi-info-circle', |
|
||||
click: () => { |
|
||||
console.log('detail'); |
|
||||
}, |
|
||||
}, |
|
||||
expandAll: { |
|
||||
name: 'expandAll', |
|
||||
label: t('expandAll'), |
|
||||
icon: 'bi-plus-square', |
|
||||
click: () => { |
|
||||
console.log('expandAll'); |
|
||||
}, |
|
||||
}, |
|
||||
selectAll: { |
|
||||
name: 'selectAll', |
|
||||
label: t('selectAll'), |
|
||||
icon: 'bi-check-square', |
|
||||
click: () => { |
|
||||
console.log('selectAll'); |
|
||||
}, |
|
||||
}, |
|
||||
addTop: { |
|
||||
name: 'addTop', |
|
||||
label: t('addTop', { object: props.targetObjectName }), |
|
||||
icon: 'addTop', |
|
||||
click: () => { |
|
||||
console.log('addTop'); |
|
||||
}, |
|
||||
}, |
|
||||
addChild: { |
|
||||
name: 'addChild', |
|
||||
label: t('addChild', { object: props.targetObjectName }), |
|
||||
icon: 'addChild', |
|
||||
click: () => { |
|
||||
console.log('addChild'); |
|
||||
}, |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
const tableActions = []; |
|
||||
if (props.actions && props.actions.length > 0) { |
|
||||
for (const action of props.actions) { |
|
||||
if (Tools.isString(action)) { |
|
||||
tableActions.push(defaultTableActions[action]); |
|
||||
} else if (Tools.isObject(action)) { |
|
||||
tableActions.push(action); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const data = ref([]); |
|
||||
if (props.autoFetchData) { |
|
||||
axios.get(props.fetchDataUrl ? props.fetchDataUrl : props.dataUrl).then((response) => { |
|
||||
if (props.tree) { |
|
||||
data.value = TreeBuilder.build(response.data); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
const selected = ref([]); |
|
||||
const val2 = ref(true); |
|
||||
|
|
||||
const rows = ref([ |
|
||||
{ |
|
||||
name: 'Frozen Yogurt', |
|
||||
calories: 159, |
|
||||
fat: 6.0, |
|
||||
carbs: 24, |
|
||||
protein: 4.0, |
|
||||
sodium: 87, |
|
||||
calcium: '14%', |
|
||||
iron: '1%', |
|
||||
selected: true, |
|
||||
children: [ |
|
||||
{ |
|
||||
name: 'Ice cream sandwich3', |
|
||||
calories: 237, |
|
||||
fat: 9.0, |
|
||||
carbs: 37, |
|
||||
protein: 4.3, |
|
||||
sodium: 129, |
|
||||
calcium: '8%', |
|
||||
iron: '1%', |
|
||||
}, |
|
||||
], |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Ice cream sandwich', |
|
||||
calories: 237, |
|
||||
fat: 9.0, |
|
||||
carbs: 37, |
|
||||
protein: 4.3, |
|
||||
sodium: 129, |
|
||||
calcium: '8%', |
|
||||
iron: '1%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Eclair', |
|
||||
calories: 262, |
|
||||
fat: 16.0, |
|
||||
carbs: 23, |
|
||||
protein: 6.0, |
|
||||
sodium: 337, |
|
||||
calcium: '6%', |
|
||||
iron: '7%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Cupcake', |
|
||||
calories: 305, |
|
||||
fat: 3.7, |
|
||||
carbs: 67, |
|
||||
protein: 4.3, |
|
||||
sodium: 413, |
|
||||
calcium: '3%', |
|
||||
iron: '8%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Gingerbread', |
|
||||
calories: 356, |
|
||||
fat: 16.0, |
|
||||
carbs: 49, |
|
||||
protein: 3.9, |
|
||||
sodium: 327, |
|
||||
calcium: '7%', |
|
||||
iron: '16%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Jelly bean', |
|
||||
calories: 375, |
|
||||
fat: 0.0, |
|
||||
carbs: 94, |
|
||||
protein: 0.0, |
|
||||
sodium: 50, |
|
||||
calcium: '0%', |
|
||||
iron: '0%', |
|
||||
}, |
|
||||
{ |
|
||||
name: '<span style="color: red">This should be red.</span>', |
|
||||
calories: 392, |
|
||||
fat: 0.2, |
|
||||
carbs: 98, |
|
||||
protein: 0, |
|
||||
sodium: 38, |
|
||||
calcium: '0%', |
|
||||
iron: '2%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Honeycomb', |
|
||||
calories: 408, |
|
||||
fat: 3.2, |
|
||||
carbs: 87, |
|
||||
protein: 6.5, |
|
||||
sodium: 562, |
|
||||
calcium: '0%', |
|
||||
iron: '45%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'Donut', |
|
||||
calories: 452, |
|
||||
fat: 25.0, |
|
||||
carbs: 51, |
|
||||
protein: 4.9, |
|
||||
sodium: 326, |
|
||||
calcium: '2%', |
|
||||
iron: '22%', |
|
||||
}, |
|
||||
{ |
|
||||
name: 'KitKat', |
|
||||
calories: 518, |
|
||||
fat: 26.0, |
|
||||
carbs: 65, |
|
||||
protein: 7, |
|
||||
sodium: 54, |
|
||||
calcium: '12%', |
|
||||
iron: '6%', |
|
||||
}, |
|
||||
]); |
|
||||
</script> |
|
@ -0,0 +1,20 @@ |
|||||
|
import { Tools } from '.'; |
||||
|
|
||||
|
class VueTools { |
||||
|
/** |
||||
|
* 将 vue 组件导出的方法和属性直接挂接到 vue 组件的实例上, 便于使用 |
||||
|
* @param instance vue 组件实例对象 |
||||
|
*/ |
||||
|
public static expose2Instance(instance: any): void { |
||||
|
const exposeds = instance.exposed || {}; |
||||
|
for (const exposed in exposeds) { |
||||
|
if (Tools.isUndefinedOrNull(instance[exposed])) { |
||||
|
instance[exposed] = exposeds[exposed]; |
||||
|
} else { |
||||
|
console.warn(exposed + ' already exists in component insatance!'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export { VueTools }; |
@ -0,0 +1,34 @@ |
|||||
|
<template> |
||||
|
<q-btn label="OK" @click="click"></q-btn> |
||||
|
</template> |
||||
|
<script setup lang="ts"> |
||||
|
import { getCurrentInstance } from 'vue'; |
||||
|
import { VueTools } from 'platform-core'; |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
( |
||||
|
e: 'wsp', // 行点击事件 |
||||
|
evt: Event, // 第一个参数,JS 事件对象 |
||||
|
row: any, // 第二个参数, 点击的行对象 |
||||
|
index: number, // 第三个参数, 当前页中行的索引 |
||||
|
): void; |
||||
|
}>(); |
||||
|
|
||||
|
const click = () => { |
||||
|
emit('wsp', instance, 'kdsjlfj'); |
||||
|
}; |
||||
|
|
||||
|
const ok = () => { |
||||
|
return 'ok'; |
||||
|
}; |
||||
|
|
||||
|
const hello = 'hello'; |
||||
|
|
||||
|
defineExpose({ |
||||
|
ok, |
||||
|
hello, |
||||
|
}); |
||||
|
|
||||
|
const instance = getCurrentInstance(); |
||||
|
VueTools.expose2Instance(instance); |
||||
|
</script> |
@ -0,0 +1,17 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<q-chip v-if="enable && !accountExpired && !accountLocked && !credentialsExpired" color="green" text-color="white" :label="$t('normal')" dense></q-chip> |
||||
|
<q-chip v-if="!enable" color="red" text-color="white" :label="$t('disable')" dense></q-chip> |
||||
|
<q-chip v-if="accountExpired" color="red" text-color="white" :label="$t('accountExpired')" dense></q-chip> |
||||
|
<q-chip v-if="accountLocked" color="red" text-color="white" :label="$t('accountLocked')" dense></q-chip> |
||||
|
<q-chip v-if="credentialsExpired" color="red" text-color="white" :label="$t('credentialsExpired')" dense></q-chip> |
||||
|
</div> |
||||
|
</template> |
||||
|
<script setup lang="ts"> |
||||
|
const props = defineProps({ |
||||
|
enable: { type: Boolean, default: true }, |
||||
|
accountExpired: { type: Boolean, default: false }, |
||||
|
accountLocked: { type: Boolean, default: false }, |
||||
|
credentialsExpired: { type: Boolean, default: false }, |
||||
|
}); |
||||
|
</script> |
Loading…
Reference in new issue