7 changed files with 4958 additions and 2 deletions
@ -0,0 +1,507 @@ |
|||||
|
<template> |
||||
|
<div v-show="fieldMethodsClass.getShow(props, modelValue)"> |
||||
|
<q-input |
||||
|
ref="textSelectRef" |
||||
|
v-model="displayValueComputed" |
||||
|
:hide-bottom-space="true" |
||||
|
:hide-hint="true" |
||||
|
:outlined="true" |
||||
|
:dense="true" |
||||
|
:autogrow="false" |
||||
|
v-bind="attrs" |
||||
|
:bottom-slots="counter" |
||||
|
type="text" |
||||
|
:rules="fieldMethodsClass.getRules(props, { value: modelValue, displayValue: displayValueComputed }, textSelectRef, undefined)" |
||||
|
:readonly="fieldMethodsClass.getReadOnly(props, { value: modelValue, displayValue: displayValueComputed })" |
||||
|
:disable="fieldMethodsClass.getDisable(props, { value: modelValue, displayValue: displayValueComputed })" |
||||
|
:clearable="false" |
||||
|
> |
||||
|
<template #label><w-label :required="fieldMethodsClass.getRequired(props, modelValue)" :label="attrs.label"></w-label></template> |
||||
|
<template v-if="!props['form'] || (props['form'] && props['form'].getStatus() !== 'view')" #append> |
||||
|
<q-btn v-if="!Tools.isEmpty(displayValueComputed)" flat square unelevated dense icon="cancel" @click="fieldMethodsClass.clearValue"></q-btn> |
||||
|
<q-btn flat square unelevated dense icon="saved_search"> |
||||
|
<q-popup-proxy v-model:model-value="isShow" anchor="bottom right" self="top right" :offset="[0, 10]"> |
||||
|
<div class="p-1 grid grid-cols-2 gap-x-4"> |
||||
|
<w-select |
||||
|
v-model="dataSourceModelValue" |
||||
|
:label="$t('developer.backend.export.liquibase.datasource')" |
||||
|
:options="datasourceOptionsRef" |
||||
|
@update-value=" |
||||
|
(args: any) => { |
||||
|
datasourceChanged(args.value); |
||||
|
} |
||||
|
" |
||||
|
></w-select> |
||||
|
<w-select |
||||
|
v-model="schemaModelValue" |
||||
|
:label="$t('developer.backend.export.liquibase.schema')" |
||||
|
:options="schemaOptionsRef" |
||||
|
@update-value=" |
||||
|
(args: any) => { |
||||
|
showGrid = true; |
||||
|
nextTick(() => { |
||||
|
schemaChanged(dataSourceModelValue, schemaModelValue); |
||||
|
}); |
||||
|
} |
||||
|
" |
||||
|
></w-select> |
||||
|
</div> |
||||
|
<q-linear-progress v-if="loading" size="18px" color="positive" indeterminate> |
||||
|
<div class="absolute-full flex flex-center"> |
||||
|
<q-badge color="white" text-color="dark" label="正在加载数据表,请稍等...(大约需要30秒-2分钟)" /> |
||||
|
</div> |
||||
|
</q-linear-progress> |
||||
|
<q-separator v-else></q-separator> |
||||
|
<div v-if="!showGrid" style="width: 800px; height: 400px; padding: 3px; padding-top: 10px"> |
||||
|
<div class="grid grid-cols-3 gap-x-4"> |
||||
|
<div><q-skeleton type="QRange" /></div> |
||||
|
<div><q-skeleton type="QRange" /></div> |
||||
|
<div><q-skeleton type="QRange" /></div> |
||||
|
</div> |
||||
|
<div class="pt-2"> |
||||
|
<q-skeleton height="320px" square /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<template v-else> |
||||
|
<w-form |
||||
|
ref="queryFormRef" |
||||
|
:cols-num="3" |
||||
|
:fields="[ |
||||
|
{ name: 'groupName', label: '前缀', type: 'w-select', options: prefixs }, |
||||
|
{ name: 'name', label: '表名', type: 'w-text' }, |
||||
|
{ name: 'remarks', label: '表中文名', type: 'w-text' }, |
||||
|
]" |
||||
|
class="px-1 pt-1" |
||||
|
> |
||||
|
</w-form> |
||||
|
<div v-if="props.showField" style="width: 800px; height: 500px; padding: 3px; padding-top: 10px"> |
||||
|
<w-splitter horizontal :model-value="70"> |
||||
|
<template #before> |
||||
|
<w-grid |
||||
|
ref="tableGridRef" |
||||
|
:checkbox-selection="props.multiple || false" |
||||
|
:dense="true" |
||||
|
:pageable="false" |
||||
|
:auto-fetch-data="false" |
||||
|
group-mode="alone" |
||||
|
group-by-field="groupName" |
||||
|
:config-button="false" |
||||
|
:query-criteria="queryCriteriaRef" |
||||
|
:toolbar-actions="[ |
||||
|
{ |
||||
|
extend: 'query', |
||||
|
label: '过滤', |
||||
|
click: (args) => { |
||||
|
tableGridFilter(args); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
extend: 'reset', |
||||
|
click: (args) => { |
||||
|
queryFormRef.reset(); |
||||
|
}, |
||||
|
}, |
||||
|
'separator', |
||||
|
'expand', |
||||
|
]" |
||||
|
:columns="[ |
||||
|
{ name: 'groupName', label: '分组名', showIf: false, sortable: false }, |
||||
|
{ name: 'name', label: '表名', sortable: false }, |
||||
|
{ name: 'remarks', label: '表中文名', sortable: false }, |
||||
|
]" |
||||
|
:ticked-record="{ |
||||
|
columnName: valueUseColumnName, |
||||
|
data: typeof modelValue === 'string' ? [modelValue] : modelValue, |
||||
|
}" |
||||
|
@row-click="rowClick" |
||||
|
@update-ticked="updateTicked" |
||||
|
@update-tickeds="updateTickeds" |
||||
|
> |
||||
|
</w-grid> |
||||
|
</template> |
||||
|
<template #after> |
||||
|
<w-grid |
||||
|
ref="fieldGridRef" |
||||
|
:checkbox-selection="false" |
||||
|
:dense="true" |
||||
|
:pageable="false" |
||||
|
:sort-no="true" |
||||
|
:auto-fetch-data="false" |
||||
|
:config-button="false" |
||||
|
:columns="[ |
||||
|
{ name: 'name', label: '字段名', sortable: false }, |
||||
|
{ name: 'remarks', label: '说明', sortable: false }, |
||||
|
{ name: 'sqlType', label: '类型', sortable: false }, |
||||
|
{ name: 'width', label: '长度', sortable: false }, |
||||
|
]" |
||||
|
> |
||||
|
</w-grid> |
||||
|
</template> |
||||
|
</w-splitter> |
||||
|
</div> |
||||
|
<div v-else style="width: 800px; height: 400px; padding: 3px; padding-top: 10px"> |
||||
|
<w-grid |
||||
|
ref="tableGridRef" |
||||
|
:checkbox-selection="props.multiple || false" |
||||
|
:dense="true" |
||||
|
:pageable="false" |
||||
|
:auto-fetch-data="false" |
||||
|
group-mode="alone" |
||||
|
group-by-field="groupName" |
||||
|
:config-button="false" |
||||
|
:query-criteria="queryCriteriaRef" |
||||
|
:toolbar-actions="[ |
||||
|
{ |
||||
|
extend: 'query', |
||||
|
label: '过滤', |
||||
|
click: (args) => { |
||||
|
tableGridFilter(args); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
extend: 'reset', |
||||
|
click: (args) => { |
||||
|
queryFormRef.reset(); |
||||
|
}, |
||||
|
}, |
||||
|
'separator', |
||||
|
'expand', |
||||
|
]" |
||||
|
:columns="[ |
||||
|
{ name: 'groupName', label: '分组名', showIf: false, sortable: false }, |
||||
|
{ name: 'name', label: '表名', sortable: false }, |
||||
|
{ name: 'remarks', label: '表中文名', sortable: false }, |
||||
|
]" |
||||
|
:ticked-record="{ |
||||
|
columnName: valueUseColumnName, |
||||
|
data: typeof modelValue === 'string' ? [modelValue] : modelValue, |
||||
|
}" |
||||
|
@row-click="rowClick" |
||||
|
@update-ticked="updateTicked" |
||||
|
@update-tickeds="updateTickeds" |
||||
|
> |
||||
|
</w-grid> |
||||
|
</div> |
||||
|
</template> |
||||
|
</q-popup-proxy> |
||||
|
</q-btn> |
||||
|
</template> |
||||
|
<template v-if="counter" #counter> |
||||
|
<div>{{ modelValue?.length }}</div> |
||||
|
</template> |
||||
|
<template v-for="slotName in fieldMethodsClass.slotNames" :key="slotName" #[slotName]> |
||||
|
<slot v-if="fieldMethodsClass.isTemplateSlot" :name="slotName"></slot> |
||||
|
<FormElementSlot v-else :slot-name="slotName" :slot-content="props['slot'][slotName]"></FormElementSlot> |
||||
|
</template> |
||||
|
</q-input> |
||||
|
{{ modelValue }} |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, computed, useAttrs, toRaw, watch, onMounted, onBeforeMount, useSlots, nextTick } from 'vue'; |
||||
|
import { Tools, axios, Environment, Formater, $t } from '@/platform'; |
||||
|
import { FormFieldProps } from '@/platform/components/form/FormField.ts'; |
||||
|
import { FormFieldMethods } from '../form/FormField'; |
||||
|
import FormElementSlot from '../form/FormElementSlot.vue'; |
||||
|
import { dbTables } from './WDbTableSelectMock'; |
||||
|
|
||||
|
const textSelectRef = ref(); |
||||
|
const attrs = useAttrs(); |
||||
|
const slots = useSlots(); |
||||
|
const modelValue = defineModel<string | Array<string>>(); |
||||
|
const modelObjectValue = ref(<any>[]); // 模型值包含实际值与显示值的对象集合。 |
||||
|
const tableGridRef = ref(); |
||||
|
const fieldGridRef = ref(); |
||||
|
const datasourceOptionsRef = ref(<any>[]); |
||||
|
const schemaOptionsRef = ref(<any>[]); |
||||
|
const tablesOptionsRef = ref(<any>[]); |
||||
|
const loading = ref(false); |
||||
|
const prefixs = ref(<any>[]); |
||||
|
const showGrid = ref(false); |
||||
|
const queryFormRef = ref(); |
||||
|
const isShow = ref(false); |
||||
|
const dataSourceModelValue = ref(null); |
||||
|
const schemaModelValue = ref(''); |
||||
|
|
||||
|
interface FieldProps extends FormFieldProps { |
||||
|
multiple?: boolean; |
||||
|
counter?: boolean; |
||||
|
showField?: boolean; |
||||
|
} |
||||
|
const props = withDefaults(defineProps<FieldProps>(), { |
||||
|
showIf: true, |
||||
|
multiple: false, // 是否允许多选,多选模式下模型绑定值必须为数组 |
||||
|
counter: false, // 显示右下角计数器 |
||||
|
showField: false, // 点击表时显示该表字段信息 |
||||
|
}); |
||||
|
class FieldMethods extends FormFieldMethods { |
||||
|
isTemplateSlot = this.getSlotType(slots); |
||||
|
slotNames = this.getSlotNames(slots, props); |
||||
|
updateValue = (value_) => { |
||||
|
if (props['onUpdateValue']) { |
||||
|
props['onUpdateValue']({ |
||||
|
value: value_, |
||||
|
displayValue: displayValueComputed.value, |
||||
|
form: props['form'], |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
validate = () => { |
||||
|
return textSelectRef.value.validate(); |
||||
|
}; |
||||
|
|
||||
|
setValue = (value) => { |
||||
|
if (props.multiple && Array.isArray(value) && Array.isArray(modelValue.value)) { |
||||
|
fieldMethodsClass.clearValue(); |
||||
|
modelValue.value.push(...value); |
||||
|
setObjectValueByValue(value); |
||||
|
} else if (!props.multiple && !Array.isArray(value)) { |
||||
|
modelValue.value = value; |
||||
|
setObjectValueByValue(value); |
||||
|
} else { |
||||
|
console.info('error========模型值不匹配'); |
||||
|
} |
||||
|
}; |
||||
|
getValue = () => { |
||||
|
return modelValue.value; |
||||
|
}; |
||||
|
getObjectValue = () => { |
||||
|
return modelObjectValue.value; |
||||
|
}; |
||||
|
// 组件清空值 |
||||
|
clearValue = () => { |
||||
|
if (props.multiple && Array.isArray(modelValue.value)) { |
||||
|
modelValue.value.splice(0, modelValue.value.length); |
||||
|
} else { |
||||
|
modelValue.value = undefined; |
||||
|
} |
||||
|
fieldMethodsClass.clearObjectValue(); |
||||
|
}; |
||||
|
// 清空显示值 |
||||
|
clearObjectValue = () => { |
||||
|
modelObjectValue.value.splice(0, modelObjectValue.value.length); |
||||
|
}; |
||||
|
} |
||||
|
const fieldMethodsClass = new FieldMethods(); |
||||
|
const valueUseColumnName = 'name'; |
||||
|
const queryCriteriaRef = ref({}); |
||||
|
|
||||
|
const displayValueComputed = computed(() => { |
||||
|
let result = ''; |
||||
|
if (modelObjectValue.value.length > 0) { |
||||
|
modelObjectValue.value.forEach((item) => { |
||||
|
result = result + ',' + item['displayValue']; |
||||
|
}); |
||||
|
result = result.substring(1, result.length); |
||||
|
} |
||||
|
return result; |
||||
|
}); |
||||
|
|
||||
|
const updateTicked = (args) => { |
||||
|
if (Array.isArray(modelValue.value)) { |
||||
|
if (args.evt && !modelValue.value.includes(args.row[valueUseColumnName])) { |
||||
|
modelValue.value.push(args.row[valueUseColumnName]); |
||||
|
modelObjectValue.value.push({ value: args.row[valueUseColumnName], displayValue: args.row[valueUseColumnName] }); |
||||
|
} else if (!args.evt && modelValue.value.includes(args.row[valueUseColumnName])) { |
||||
|
modelValue.value.splice( |
||||
|
modelValue.value.findIndex((item) => item === args.row[valueUseColumnName]), |
||||
|
1, |
||||
|
); |
||||
|
modelObjectValue.value.splice( |
||||
|
modelObjectValue.value.findIndex((item) => item['value'] === args.row[valueUseColumnName]), |
||||
|
1, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
const updateTickeds = (args) => { |
||||
|
const rows = args.grid.getTickedRows(); |
||||
|
if (rows && args.value) { |
||||
|
rows.forEach((row) => { |
||||
|
if (!modelValue.value?.includes(row[valueUseColumnName])) { |
||||
|
modelValue.value.push(row[valueUseColumnName]); |
||||
|
modelObjectValue.value.push({ value: row[valueUseColumnName], displayValue: row[valueUseColumnName] }); |
||||
|
} |
||||
|
}); |
||||
|
} else if (!args.value) { |
||||
|
fieldMethodsClass.clearValue(); |
||||
|
fieldMethodsClass.clearObjectValue(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const rowClick = (args) => { |
||||
|
const modelValue_ = args.row[valueUseColumnName]; |
||||
|
if (props.multiple && Array.isArray(modelValue.value)) { |
||||
|
if (!args.evt.ctrlKey) { |
||||
|
fieldMethodsClass.clearValue(); |
||||
|
modelValue.value.push(modelValue_); |
||||
|
modelObjectValue.value.push({ value: modelValue_, displayValue: args.row[valueUseColumnName] }); |
||||
|
} else if (!modelValue.value.includes(args.row[valueUseColumnName])) { |
||||
|
modelValue.value.push(modelValue_); |
||||
|
modelObjectValue.value.push({ value: modelValue_, displayValue: args.row[valueUseColumnName] }); |
||||
|
} |
||||
|
} else if (!props.multiple) { |
||||
|
fieldMethodsClass.clearValue(); |
||||
|
modelValue.value = modelValue_; |
||||
|
modelObjectValue.value.push({ value: modelValue_, displayValue: args.row[valueUseColumnName] }); |
||||
|
} else { |
||||
|
console.info('error========模型值不匹配'); |
||||
|
} |
||||
|
if (props.showField) { |
||||
|
fieldGridRef.value.setLocalData(args.row['columns']); |
||||
|
} else if (!props.showField && !props.multiple) { |
||||
|
isShow.value = false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
watch( |
||||
|
() => modelValue.value, |
||||
|
(newVal, oldVal) => { |
||||
|
if (newVal !== oldVal) { |
||||
|
fieldMethodsClass.updateValue(newVal); |
||||
|
} |
||||
|
if (Tools.isEmpty(newVal) || (Array.isArray(modelValue.value) && modelValue.value.length === 0)) { |
||||
|
fieldMethodsClass.clearObjectValue(); |
||||
|
} else if (newVal !== oldVal) { |
||||
|
if (modelObjectValue.value.length > 0) { |
||||
|
const tempValue = modelObjectValue.value.find((item) => item.value === newVal); |
||||
|
if (!tempValue) { |
||||
|
setObjectValueByValue(newVal); |
||||
|
} |
||||
|
} else { |
||||
|
setObjectValueByValue(newVal); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
watch( |
||||
|
() => isShow.value, |
||||
|
(newVal, oldVal) => { |
||||
|
if (newVal && tablesOptionsRef.value.length > 0) { |
||||
|
nextTick(() => { |
||||
|
tablesOptionsRef.value.forEach((item: any) => { |
||||
|
if (props.multiple && !modelValue.value?.includes(item[valueUseColumnName])) { |
||||
|
item['selected'] = false; |
||||
|
item['ticked'] = false; |
||||
|
} else if (!props.multiple && modelValue.value !== item[valueUseColumnName]) { |
||||
|
item['selected'] = false; |
||||
|
} |
||||
|
}); |
||||
|
tableGridRef.value.setLocalData(tablesOptionsRef.value); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
const loadDatasource = () => { |
||||
|
axios.get(Environment.apiContextPath('/api/system/datasource?pageable=false&sortBy=name')).then((response) => { |
||||
|
const data = response?.data.content; |
||||
|
const datasourceOptions = [{ label: $t('default'), value: '' }]; |
||||
|
if (data && data.length > 0) { |
||||
|
for (let item of data) { |
||||
|
datasourceOptions.push({ label: item.name, value: item.name }); |
||||
|
} |
||||
|
} |
||||
|
datasourceOptionsRef.value = datasourceOptions; |
||||
|
}); |
||||
|
}; |
||||
|
const datasourceChanged = (datasource: string) => { |
||||
|
datasource = datasource || ''; |
||||
|
axios.get(Environment.apiContextPath('/api/jdbc/metadata/getSchemas?datasource=' + datasource)).then((response) => { |
||||
|
const data = response?.data; |
||||
|
const schemaOptions = <any>[]; |
||||
|
if (data && data.length > 0) { |
||||
|
for (let item of data) { |
||||
|
schemaOptions.push({ label: item.name, value: item.name }); |
||||
|
} |
||||
|
} |
||||
|
schemaOptionsRef.value = schemaOptions; |
||||
|
tablesOptionsRef.value = []; |
||||
|
}); |
||||
|
}; |
||||
|
const tableGridFilter = (args) => { |
||||
|
if (tablesOptionsRef.value.length > 0) { |
||||
|
const formData = queryFormRef.value.getData(); |
||||
|
const notNullKeys = Object.keys(formData).filter((item) => !Tools.isEmpty(formData[item])); |
||||
|
if (notNullKeys.length > 0) { |
||||
|
const filterResult = tablesOptionsRef.value.filter((row: any) => { |
||||
|
let result = true; |
||||
|
notNullKeys.forEach((item) => { |
||||
|
if (row[item].toUpperCase().indexOf(formData[item].toUpperCase()) < 0) { |
||||
|
result = false; |
||||
|
} |
||||
|
}); |
||||
|
return result; |
||||
|
}); |
||||
|
args.grid.setLocalData(filterResult); |
||||
|
} else { |
||||
|
args.grid.setLocalData(tablesOptionsRef.value); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
const schemaChanged = (datasource: string, schema: string) => { |
||||
|
// loading.value = true; |
||||
|
// datasource = datasource || ''; |
||||
|
// schema = schema || ''; |
||||
|
// axios |
||||
|
// .get(Environment.apiContextPath('/api/jdbc/metadata/getTables?datasource=' + datasource + '&schema=' + schema)) |
||||
|
// .then((response) => { |
||||
|
// const data = response?.data; |
||||
|
// const tablesOptions = <any>[]; |
||||
|
// if (data && data.length > 0) { |
||||
|
// for (let item of data) { |
||||
|
// tablesOptions.push(item); |
||||
|
// } |
||||
|
// } |
||||
|
// tablesOptionsRef.value = tablesOptions; |
||||
|
// tableGridRef.value.setLocalData(tablesOptions); |
||||
|
// }) |
||||
|
// .finally(() => { |
||||
|
// loading.value = false; |
||||
|
// }); |
||||
|
const group = {}; |
||||
|
dbTables.forEach((table: any) => { |
||||
|
const index = table.name.indexOf('_'); |
||||
|
if (index > 0) { |
||||
|
const groupName = table.name.substring(0, index); |
||||
|
table['groupName'] = groupName; |
||||
|
group[groupName] = groupName; |
||||
|
} |
||||
|
}); |
||||
|
tablesOptionsRef.value = dbTables; |
||||
|
tableGridRef.value.setLocalData(dbTables); |
||||
|
prefixs.value = []; |
||||
|
Object.keys(group).forEach((item) => { |
||||
|
prefixs.value.push(item); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// 根据实际值设置显示值 |
||||
|
const setObjectValueByValue = async (value) => { |
||||
|
fieldMethodsClass.clearObjectValue(); |
||||
|
if (Array.isArray(value) && value.length > 0) { |
||||
|
value.forEach((item) => { |
||||
|
modelObjectValue.value.push({ value: item, displayValue: item }); |
||||
|
}); |
||||
|
} else if (typeof value === 'string' && !Tools.isEmpty(value)) { |
||||
|
modelObjectValue.value.push({ value: value, displayValue: value }); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
onBeforeMount(() => { |
||||
|
loadDatasource(); |
||||
|
}); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
setObjectValueByValue(modelValue.value); |
||||
|
}); |
||||
|
|
||||
|
defineExpose({ |
||||
|
validate: fieldMethodsClass.validate, |
||||
|
setValue: fieldMethodsClass.setValue, |
||||
|
getValue: fieldMethodsClass.getValue, |
||||
|
getObjectValue: fieldMethodsClass.getObjectValue, |
||||
|
clearValue: fieldMethodsClass.clearValue, |
||||
|
}); |
||||
|
</script> |
File diff suppressed because it is too large
Loading…
Reference in new issue