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