Browse Source

1、w-grid修改双击默认不进行任何操作。

2、增加w-db-table-select组件,可以选择数据库表。
main
likunming 2 months ago
parent
commit
c661ac56d1
  1. 3
      io.sc.platform.core.frontend/src/platform/components/form/FormField.ts
  2. 14
      io.sc.platform.core.frontend/src/platform/components/form/FormGroup.vue
  3. 2
      io.sc.platform.core.frontend/src/platform/components/grid/ts/Init.ts
  4. 3
      io.sc.platform.core.frontend/src/platform/components/index.ts
  5. 507
      io.sc.platform.core.frontend/src/platform/components/select/WDbTableSelect.vue
  6. 4430
      io.sc.platform.core.frontend/src/platform/components/select/WDbTableSelectMock.ts
  7. 1
      io.sc.platform.core.frontend/src/platform/index.ts

3
io.sc.platform.core.frontend/src/platform/components/form/FormField.ts

@ -179,7 +179,8 @@ export const getDefaultValue = (field) => {
field.type === 'w-user-select' ||
field.type === 'w-org-select' ||
field.type === 'w-grid-select' ||
field.type === 'w-role-select') &&
field.type === 'w-role-select' ||
field.type === 'w-db-table-select') &&
field.multiple
) {
return [];

14
io.sc.platform.core.frontend/src/platform/components/form/FormGroup.vue

@ -229,6 +229,20 @@ const formElementDivStyle = (field: any) => {
if (props.layout === Constant.FORM_GROUP_LAYOUT.FORM) {
styleStr += jsonStyle2String(form.getFieldStyle(field));
}
if (!getShow(field)) {
styleStr += ';display: none;';
}
return styleStr;
};
const getShow = (field: any) => {
if (!Tools.isEmpty(field.showIf)) {
if (typeof field.showIf === 'boolean') {
return field.showIf;
} else if (typeof field.showIf === 'function') {
return field.showIf({ value: form.data[field.name], form: form.instance }) || false;
}
}
return true;
};
</script>

2
io.sc.platform.core.frontend/src/platform/components/grid/ts/Init.ts

@ -46,7 +46,7 @@ export class Init {
primaryKey: { type: String, default: 'id' }, // 数据主键(常规表格模式时,该字段可用作内置的编辑删除等功能对应的后端API入参,如:继承RestCrudController的update所需的入参。树形表格模式时,该字段为构建树数据的主键)
foreignKey: { type: String, default: 'parent' }, // 数据外键(常规表格模式时,该字段暂时无用,将来可用作多个表格的数据关系字段。树形表格模式时,该字段为构建树数据的关系字段)
refreshData: { type: Boolean, default: false }, // 新增、删除、修改成功后是否刷新数据列表,默认不刷新但是新增修改后台必须返回对应的行数据对象,删除则必须返回删除的记录集primaryKey集合。
dbClickOperation: { type: String, default: 'view' }, // 默认的双击操作:可填写内置或自定义按钮name,执行的操作为按钮对应的click,固定值提供:expand(展开双击的行)、none(双击不执行任何动作)
dbClickOperation: { type: String, default: 'none' }, // 默认的双击操作:可填写内置或自定义按钮name,执行的操作为按钮对应的click,固定值提供:expand(展开双击的行)、none(双击不执行任何动作)
separator: { type: String, default: 'cell' }, // 表格分割线,支持:horizontal、vertical、cell、none
hideHeader: { type: Boolean, default: false }, // 隐藏表头
hideBottom: { type: Boolean, default: false }, // 隐藏底部

3
io.sc.platform.core.frontend/src/platform/components/index.ts

@ -23,6 +23,7 @@ import WGridSelect from './select/WGridSelect.vue';
import WUserSelect from './select/WUserSelect.vue';
import WOrgSelect from './select/WOrgSelect.vue';
import WRoleSelect from './select/WRoleSelect.vue';
import WDbTableSelect from './select/WDbTableSelect.vue';
import WDate from './date/WDate.vue';
import WDateRange from './date/WDateRange.vue';
import WCheckbox from './checkbox/WCheckbox.vue';
@ -92,6 +93,7 @@ export default {
app.component('WUserSelect', WUserSelect);
app.component('WOrgSelect', WOrgSelect);
app.component('WRoleSelect', WRoleSelect);
app.component('WDbTableSelect', WDbTableSelect);
app.component('WDate', WDate);
app.component('WDateRange', WDateRange);
app.component('WCheckbox', WCheckbox);
@ -157,6 +159,7 @@ export {
WUserSelect,
WOrgSelect,
WRoleSelect,
WDbTableSelect,
WLabel,
WRadio,
WExtRadio,

507
io.sc.platform.core.frontend/src/platform/components/select/WDbTableSelect.vue

@ -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>

4430
io.sc.platform.core.frontend/src/platform/components/select/WDbTableSelectMock.ts

File diff suppressed because it is too large

1
io.sc.platform.core.frontend/src/platform/index.ts

@ -151,6 +151,7 @@ export {
WOrgSelect,
WUserSelect,
WRoleSelect,
WDbTableSelect,
WLabel,
WRadio,
WExtRadio,

Loading…
Cancel
Save