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