<template>
<aui-dialog-box
:visible="visible"
:title="title"
width="500px"
append-to-body
draggable
:close-on-click-modal="false"
@close="onClose"
>
<aui-form
ref="form"
label-position="top"
validate-position="bottom"
:model="formData"
:rules="formRules"
:append-to-body="false"
>
<aui-form-item :label="mainOptionsLabel" prop="selectMainKey">
<aui-checkbox-group v-if="isMultiMain" v-model="formData.selectMainKey" :options="mainOptions">
</aui-checkbox-group>
<aui-radio-group v-else v-model="formData.selectMainKey" :options="mainOptions"> </aui-radio-group>
</aui-form-item>
<div v-for="rule in ruleList" :key="rule.id">
<!-- <aui-form-item :prop="rule.id" :required="rule.fieldIsRequired === 'yes'"> -->
<!-- <span>{{ rule.id }}</span> -->
<aui-form-item :prop="rule.id">
<template #label>
<div class="flex-wrap">
{{ rule[nameKey] }}
<aui-tooltip v-if="rule[commentKey]" effect="light" placement="top" popper-class="commentTippoper">
<template #content>
{{ rule[commentKey] }}
</template>
<IconHelpCircle class="iconHelp" />
</aui-tooltip>
</div>
</template>
<!-- <span>{{ ruleValueList[rule.id] }}</span> -->
<aui-select
:ref="handleRef(rule)"
v-model="ruleValueList[rule.id]"
v-show-all-checked-tag="getShowAllCheckedTagBindingValue(rule, selectOptionLabels, ruleValueList)"
:multiple="isMultiple(rule)"
:query-debounce="300"
:collapse-tags="isMultiple(rule)"
:remote-method="searchText => remoteMethod(rule, searchText)"
:remote-config="{ autoSearch: true, clearData: true, showIcon: true }"
remote
filterable
clearable
reserve-keyword
:loading="loadingList[rule.id]"
loading-text="Loading..."
@change="selected => onSelectChange(rule, selected)"
@clear="onClearSelection(rule)"
is-drop-inherit-width
>
<template #dropdown>
<li
v-if="isMultiple(rule) && hasOptions(rule)"
class="aui-select-dropdown__item all-checked-option"
data-tag="aui-select-dropdown-item"
:class="[{ selected: isAllChecked(rule) }]"
@mouseenter="onAllCheckedOptionMouseOver"
>
<aui-checkbox
:indeterminate="isIndeterminate(rule)"
:checked="isAllChecked(rule)"
@change="allChecked => onAllCheckedChange(rule, allChecked)"
>
{{ $t('semantic.material.MySelect.label.allChecked') }}
</aui-checkbox>
</li>
</template>
<template v-for="item in selectOptionLabels[rule.id]">
<aui-option
v-if="item"
:key="item.value || item"
:label="item.label || item"
:value="item.value || item"
/>
</template>
</aui-select>
</aui-form-item>
</div>
</aui-form>
<template #footer>
<aui-button @click="onCancel"> {{ $t('semantic.material.common.button.cancel') }} </aui-button>
<aui-button type="primary" @click="onConfirm"> {{ $t('semantic.material.common.button.confirm') }} </aui-button>
</template>
</aui-dialog-box>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import {
DialogBox,
Radio,
Form,
FormItem,
Select,
Option,
Button,
CheckboxGroup,
RadioGroup,
Tooltip,
Checkbox,
} from '@aurora/vue';
import { IconHelpCircle } from '@aurora/vue-icon';
import { getQueryParams, getFieldDataFromContent, showSelectLabel } from '../../query-select/QueryDataHelper';
import { queryDetailData } from '../../../service/detailDataService';
import { isEmptyStringArray, isLocaleZh } from '../../../utils/PageContextUtils';
import * as Export from '../common/export';
import { createDirective as createShowAllCheckedTagDirective } from '../../../directives/show-all-checked-tag';
type FormState = {
selectMainKey: string[] | string;
};
let promise: Promise<FormState> | null;
let resolve: ((v: FormState) => void) | null;
let reject: ((err: Error) => void) | null;
export default defineComponent({
components: {
AuiDialogBox: DialogBox,
AuiForm: Form,
AuiFormItem: FormItem,
AuiRadio: Radio,
AuiOption: Option,
AuiSelect: Select,
AuiButton: Button,
AuiCheckboxGroup: CheckboxGroup,
AuiRadioGroup: RadioGroup,
AuiTooltip: Tooltip,
AuiCheckbox: Checkbox,
IconHelpCircle: IconHelpCircle(),
},
directives: {
showAllCheckedTag: createShowAllCheckedTagDirective(),
},
setup() {},
data() {
return {
formData: {
selectMainKey: [],
},
mainOptions: [],
configData: [],
visible: false,
ruleList: [],
ruleValueList: {},
loadingList: {},
selectOptionLabels: {},
searchValue: '',
remoteSearched: {},
isMultiMain: false,
mainOptionsLabel: '',
cascadeRuleMap: new Map(),
title: '',
defaultAllChecked: {},
};
},
computed: {
nameKey() {
return isLocaleZh() ? 'nameCn' : 'nameEn';
},
commentKey() {
return isLocaleZh() ? 'commentCn' : 'commentEn';
},
configDataMapById() {
return new Map(this.configData.map(item => [item.id, item]));
},
formRules() {
const rules: any = {};
// 为 selectMainKey 添加必填规则
rules['selectMainKey'] = [
{
required: true,
trigger: 'change',
},
];
// 动态生成其他规则
this.ruleList.forEach(rule => {
if (rule.fieldIsRequired === 'yes') {
rules[rule.id] = [
{
required: true,
trigger: 'change',
},
];
}
});
console.log(rules, '添加必填规则');
return rules;
},
},
watch: {
'formData.selectMainKey': {
handler(newVal) {
this.changeRuleList(newVal);
},
},
},
methods: {
/** 全选选项的切换选中状态回调 */
onAllCheckedChange(rule, allChecked: string[]) {
const options = this.selectOptionLabels[rule.id];
const oldChecked = this.ruleValueList[rule.id];
let newChecked;
if (allChecked) {
// 追加
newChecked = [...new Set([...oldChecked, ...options])];
} else {
// 剔除列表中选中的项
newChecked = oldChecked.filter(checked => !options.includes(checked));
}
this.$set(this.ruleValueList, rule.id, newChecked);
this.updateDefaultAllCheckedOnSelect(rule, allChecked);
// 触发级联查询 下级字段
if (this.cascadeRuleMap.get(rule.id)?.length) {
// 下级字段
this.setSelectChangeCascaded(rule);
}
},
/** 移除兄弟节点的 hover 样式 */
onAllCheckedOptionMouseOver(e) {
[...e.target.parentNode.children].forEach(ele => {
if (ele !== e.target) {
ele.classList.remove('hover');
}
});
},
/** 是否有下拉选项 */
hasOptions(rule) {
return this.selectOptionLabels?.[rule.id]?.length > 0;
},
isDefaultAllCheckedRule(rule) {
return this.defaultAllChecked[rule.id] === true;
},
/** 是否有选中项 */
hasSelected(rule) {
const value = this.ruleValueList?.[rule.id];
if (typeof value === 'number' && !isNaN(value)) {
return true;
}
return value?.length > 0;
},
/**
* 远程搜索扩展的半选逻辑。
*/
isIndeterminate(rule) {
if (!this.hasSelected(rule) || !this.hasOptions(rule)) {
return false;
}
// 只要某个选项未选中(即选中列表未包含所有选项),就是半选
return !this.isSelectedContainsAllOptions(rule);
},
isAllChecked(rule) {
if (!this.hasSelected(rule) || !this.hasOptions(rule)) {
return false;
}
// 选中列表未包含所有选项就是全选
return this.isSelectedContainsAllOptions(rule);
},
/** 更新全选标记 */
updateDefaultAllChecked(rule: typeof this.ruleList[number], allChecked) {
this.$set(this.defaultAllChecked, rule.id, allChecked);
},
isRemoteSearchedRule(rule) {
return this.remoteSearched[rule.id] === true;
},
isSelectedContainsAllOptions(rule, checkedList = this.ruleValueList[rule.id]) {
const allSelect = this.selectOptionLabels[rule.id]?.every(ruleId => checkedList?.includes(ruleId));
// 模糊搜索后,列表项会发生变化,如果所有列表项被选中,全部也要选中
// 避免在存在重复value的场景时,全选后,无法直接取消用于重复value项的勾选
const lengthValid = this.selectOptionLabels[rule.id]?.length <= checkedList.length;
return allSelect && lengthValid;
},
updateDefaultAllCheckedOnSelect(rule, allCheckedOrCheckList) {
if (!this.isMultiple(rule)) {
return;
}
const forAllCheck = typeof allCheckedOrCheckList === 'boolean';
let ret = false;
// 只要触发了远程搜索,就不会打上 全选 标记
if (this.isRemoteSearchedRule(rule)) {
ret = false;
} else if (forAllCheck) {
// 如果是全选选项触发的选中,全选标记的值与 全选选项的状态一致
ret = allCheckedOrCheckList;
} else {
// 如果是非全选选项触发的选中,则判断所有选项都被选中,即 选中列表包含所有选项
// 为什么要用 判断选中列表是否包含了所有列表,是因为有需求:
// 模糊搜索后,列表项会发生变化,如果所有列表项被选中,全部也要选中
ret = this.isSelectedContainsAllOptions(rule, allCheckedOrCheckList);
}
this.updateDefaultAllChecked(rule, ret);
},
isAllCheckedTagShow(rule) {
return this.isMultiple(rule) && !this.isRemoteSearchedRule(rule) && this.isDefaultAllCheckedRule(rule);
},
getShowAllCheckedTagBindingValue(rule, selectOptionLabels, ruleValueList) {
return {
isAllChecked: this.isAllCheckedTagShow(rule),
options: selectOptionLabels[rule.id],
checked: ruleValueList[rule.id],
label: this.$t('semantic.material.MySelect.label.allChecked'),
updateAllCheckedStatus: allChecked => {
this.updateDefaultAllChecked(rule, allChecked);
},
check: options => {
this.$set(this.ruleValueList, rule.id, options);
},
showLog: false,
};
},
setPromise() {
return (promise = new Promise((rawResolve, rawReject) => {
resolve = rawResolve;
reject = rawReject;
}));
},
clearPromise() {
promise = null;
resolve = null;
reject = null;
},
changeRuleList(newVal) {
const checked = newVal
? [newVal].flat()
: [];
// 使用 this.$set 确保 ruleList 的更新是响应式的
this.$set(this, 'ruleList', checked.flatMap(id => {
return this.configDataMapById.get(id).customOptions ?? [];
}));
},
isMultiple(rule) {
return rule.functionalOperator?.includes('IN');
},
// 因为select是循环渲染出来的,所以绑定的ref也是动态的
handleRef(rule) {
if (this.isMultiple(rule)) {
return `multiSelect${rule.id}`;
} else {
return `select${rule.id}`;
}
},
remoteMethod(rule, selectValue) {
this.processData(rule, selectValue, false);
const select = this.$refs[this.handleRef(rule)][0];
select.state.cachedOptions.forEach(item => {
item.state.visible = !selectValue || showSelectLabel(rule, selectValue, item.label);
});
},
normalizeQueryParams(rule, selectValue) {
const { modelId, tableId, tableName } = rule;
let params = getQueryParams(modelId, tableId, tableName, rule, selectValue);
params.resultFilter.having = ''; // 分组筛选器,这里传空
return this.normalizeCascadeRuleQueryParams(rule, params);
},
normalizeProcessDataFetcher(rule, selectValue, useCache = false) {
if (rule.cascadeRuleList.length) {
const params = this.normalizeQueryParams(rule, selectValue);
return queryDetailData({ ...params, useCache });
} else {
return this.getData(rule, selectValue, useCache);
}
},
setFieldValue(rule, content) {
const obj = getFieldDataFromContent(rule, content);
this.$set(this.selectOptionLabels, rule.id, obj.valueFieldData);
},
getParamsOptions(rule) {
const list = rule.field.valueList.map(item => ({ value: item.value, label: item[this.nameKey] }));
this.$set(this.selectOptionLabels, rule.id, list);
},
// 数据处理
async processData(rule, selectValue, useCache = false) {
this.searchValue = selectValue;
if (rule.tableId === 'params') {
this.getParamsOptions(rule);
return;
}
this.$set(this.loadingList, rule.id, true);
return this.normalizeProcessDataFetcher(rule, selectValue, useCache)
.then(
data => {
if (selectValue === this.searchValue) {
this.setFieldValue(rule, data);
this.$set(this.remoteSearched, rule.id, Boolean(selectValue));
}
},
e => {
this.$set(this.remoteSearched, rule.id, false);
return Promise.reject(e);
},
)
.finally(() => {
this.$set(this.loadingList, rule.id, false);
});
},
getData(rule, searchText, useCache) {
const { modelId, tableId, tableName } = rule;
const params = getQueryParams(modelId, tableId, tableName, rule, searchText);
return queryDetailData({ ...params, useCache });
},
normalizeCondition(rule, ruleValue) {
const field = { ...rule.field };
return {
functionalOperator: rule.functionalOperator,
field: {
id: field.id,
},
args: {
type: field.customDataType || field.dataType,
values: [...ruleValue].flat(),
},
tableId: rule.tableId,
};
},
/** 已拼查询条件中的 conditions */
conditionsOfParams(params) {
try {
return JSON.parse(params.resultFilter.where)?.conditions ?? [];
} catch (e) {
return [];
}
},
/** 拼级联条件 */
normalizeCascadeRuleQueryParams(rule, params) {
if (!rule.cascadeRuleList.length) {
return params;
}
const cascadeRuleList = rule.cascadeRuleList;
// 条件操作符,带入已拼的搜索关键字及其他
const conditions = this.conditionsOfParams(params);
// 多个上级
this.ruleList.forEach(item => {
if (cascadeRuleList.includes(item.id)) {
// 上级已选值
const value = this.ruleValueList[item.id];
if (!isEmptyStringArray(value)) {
conditions.push(this.normalizeCondition(item, value));
}
}
});
// 如果父级有部分是非全选的,conditions 一定是非空数组
if (conditions.length) {
params.resultFilter.where = JSON.stringify({
logicalOperator: 'and', // 只有一层默认传and
conditions,
});
}
return params;
},
/** 非全选选项切换选中状态回调 */
onSelectChange(rule, checkedList) {
this.$set(this.ruleValueList, rule.id, checkedList);
console.log(this.ruleValueList, rule.id, this.ruleValueList[rule.id],'选中的值');
this.updateDefaultAllCheckedOnSelect(rule, checkedList);
// 触发级联查询
if (this.cascadeRuleMap.get(rule.id)?.length) {
// 下级字段
this.setSelectChangeCascaded(rule);
}
},
// 级联字段清空
setSelectChangeCascaded(rule) {
this.ruleList.forEach(item => {
if (this.cascadeRuleMap.get(rule.id)?.includes(item.id)) {
// 清空已选
this.$set(this.ruleValueList, item.id, '');
// 清空下拉框值
this.$set(this.selectOptionLabels, item.id, []);
// 查询下拉框值
this.processData(item, '', false);
}
});
},
/** 清除选中项时执行的回调 */
onClearSelection(rule) {
this.remoteMethod(rule, '');
},
initDefaultAllChecked() {
this.ruleListData?.forEach(rule => {
this.defaultAllChecked[rule.id] = false;
});
},
getChildRuleId(rule, ruleList) {
const childRuleId = ruleList.filter(item => item.cascadeRuleList.includes(rule.id)).map(item => item.id);
childRuleId.length && this.cascadeRuleMap.set(rule.id, childRuleId);
},
async show(buttonConfig) {
this.title = buttonConfig[this.nameKey];
const serviceConfig = buttonConfig.service;
this.cascadeRuleMap.clear();
this.initDefaultAllChecked();
this.configData = serviceConfig.list.map(item => ({
...item,
customOptions:
item.customOptions?.map(rule => {
this.getChildRuleId(rule, item.customOptions || []);
return {
...rule,
parentId: item.id,
};
}) || [],
}));
this.isMultiMain = serviceConfig.isMultiple ?? false;
this.mainOptionsLabel = serviceConfig[this.nameKey];
this.mainOptions = this.configData.map(item => ({ text: item?.servicePath?.[this.nameKey], label: item.id }));
if (this.isMultiMain) {
this.formData.selectMainKey = [];
} else {
this.formData.selectMainKey = '';
}
this.ruleValueList = {};
this.selectOptionLabels = {};
this.visible = true;
await this.$nextTick();
this.$refs.form?.clearValidate?.();
// 返回 promise,将 show 方法设计成异步方法,点确定的时候,兑现
return this.setPromise();
},
reset() {
this.remoteSearched = {};
this.defaultAllChecked = {};
this.ruleValueList = {};
this.selectOptionLabels = {};
},
hide(success = true) {
if (reject) {
this.visible = false;
this.reset();
if (!success) {
reject(new Error(Export.ErrorMsg.CANCEL));
}
this.clearPromise();
}
},
// 获取所有的选中值
getOptions(id) {
const selectRule = this.ruleList.filter(item => item.parentId === id);
const options: unknown[] = [];
selectRule.forEach(rule => {
let selectValues = [];
if ((this.isMultiple(rule) && this.ruleValueList[rule.id].length) || this.ruleValueList[rule.id]) {
selectValues.push(this.ruleValueList[rule.id]);
}
selectValues = selectValues.flat();
if (selectValues.length) {
options.push({
key: rule.exportType,
value: selectValues,
});
}
});
return options;
},
// 数据拼装
handleParams() {
const selectServiceList = this.configData.filter(item => {
if (this.isMultiMain) {
return this.formData.selectMainKey.includes(item.id);
}
return this.formData.selectMainKey === item.id;
});
const params = selectServiceList.map(item => ({
config: item,
options: this.getOptions(item.id),
}));
return params;
},
async onConfirm() {
await this.$refs.form?.validate?.();
// resolve 一定不能是 null 或 undefined,
// 如果是 null 或 undefined,一定是 setPromise 的调用时机不对
// 需要纠正 setPromise 调用时机,而不是对 resolve 进行空值判断
resolve!(this.handleParams());
this.hide();
},
onClose() {
this.hide(false);
},
onCancel() {
this.hide(false);
},
},
});
</script>
<style lang="less" scoped>
.iconHelp {
margin-left: 5px;
font-size: 1em;
fill: #666;
&:hover {
fill: #0067d1ed;
}
}
.flex-wrap {
display: flex;
align-items: center;
display: inline-block
}
</style>
aui-form 标签上 :model 绑定要用来验证的数据对象,:rules 绑定用来验证表单的规则。
aui-form-item的 prop 属性值需要和 rules 中校验字段对应,prop 属性值和表单组件绑定的变量名一定要相同。
由于<aui-form-item :prop="rule.id"> 和v-model="ruleValueList[rule.id]"对不上,所以导致校验有问题,该怎么修复
最新发布