init
This commit is contained in:
407
src/iview/form/IViewFormSelect.vue
Normal file
407
src/iview/form/IViewFormSelect.vue
Normal file
@@ -0,0 +1,407 @@
|
||||
<template>
|
||||
<template v-if="supportedRemoteSearch">
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
:value="currentValue"
|
||||
:mode="multiple ? 'multiple' : '-'"
|
||||
:id="'form_item_' + formatFieldName(field.name)"
|
||||
:disabled="readOnly"
|
||||
:allowClear="field.clearable"
|
||||
:placeholder="placeholder"
|
||||
:filter-option="supportedFilterOption"
|
||||
:show-search="supportedRemoteSearch"
|
||||
@search="handleRemoteSearchValue"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template v-if="remoteSearchLoading || remoteOptionsLoading" #notFoundContent>
|
||||
<a-spin size="small" />
|
||||
</template>
|
||||
<template
|
||||
v-else-if="supportedRemoteSearch || supportedRemoteOptions"
|
||||
#notFoundContent
|
||||
>
|
||||
{{
|
||||
remoteSearchErrorMsg
|
||||
? remoteSearchErrorMsg
|
||||
: remoteOptionsErrorMsg
|
||||
? remoteOptionsErrorMsg
|
||||
: notFoundContent
|
||||
}}
|
||||
<a-button
|
||||
v-if="remoteSearchErrorMsg || remoteOptionsErrorMsg"
|
||||
size="small"
|
||||
type="default"
|
||||
@click="handleRefresh"
|
||||
>
|
||||
刷新
|
||||
</a-button>
|
||||
</template>
|
||||
<a-select-option
|
||||
v-for="option in parseOptions(field.options)"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ readLabel(option.label) }}
|
||||
<template v-if="showValueTag">
|
||||
<span style="color: #999; font-size: 12px"> {{ option.value }} </span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="!!readTag(option.label)" style="color: #999; font-size: 12px">
|
||||
{{ readTag(option.label) }}
|
||||
</span>
|
||||
</template>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
:value="currentValue"
|
||||
:mode="multiple ? 'multiple' : '-'"
|
||||
:id="'form_item_' + formatFieldName(field.name)"
|
||||
:disabled="readOnly"
|
||||
:allowClear="field.clearable"
|
||||
:placeholder="placeholder"
|
||||
:filter-option="supportedFilterOption"
|
||||
:show-search="false"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template v-if="remoteSearchLoading || remoteOptionsLoading" #notFoundContent>
|
||||
<a-spin size="small" />
|
||||
</template>
|
||||
<template
|
||||
v-else-if="supportedRemoteSearch || supportedRemoteOptions"
|
||||
#notFoundContent
|
||||
>
|
||||
{{
|
||||
remoteSearchErrorMsg
|
||||
? remoteSearchErrorMsg
|
||||
: remoteOptionsErrorMsg
|
||||
? remoteOptionsErrorMsg
|
||||
: notFoundContent
|
||||
}}
|
||||
<a-button
|
||||
v-if="remoteSearchErrorMsg || remoteOptionsErrorMsg"
|
||||
size="small"
|
||||
type="default"
|
||||
@click="handleRefresh"
|
||||
>
|
||||
刷新
|
||||
</a-button>
|
||||
</template>
|
||||
<a-select-option
|
||||
v-for="option in parseOptions(field.options)"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ readLabel(option.label) }}
|
||||
<template v-if="showValueTag">
|
||||
<span style="color: #999; font-size: 12px"> {{ option.value }} </span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="!!readTag(option.label)" style="color: #999; font-size: 12px">
|
||||
{{ readTag(option.label) }}
|
||||
</span>
|
||||
</template>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import { useVModel } from "@/iview/utils/useModel";
|
||||
import { delayCall } from "@/iview/utils/delayCall";
|
||||
import { callFunc } from "@/iview/utils/func";
|
||||
import { toAny } from "@/iview/utils/toany";
|
||||
|
||||
const props = defineProps({
|
||||
field: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
fieldPrefix: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择",
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rawFormData: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const vModel = useVModel(props, "modelValue", emit);
|
||||
const initValueFlag = ref(false);
|
||||
const remoteOptionsData = ref([]);
|
||||
const remoteOptionsLoading = ref(false);
|
||||
const remoteOptionsErrorMsg = ref(null);
|
||||
|
||||
const remoteSearchValue = ref("");
|
||||
const remoteSearchData = ref([]);
|
||||
const remoteSearchLoading = ref(false);
|
||||
const remoteSearchErrorMsg = ref(null);
|
||||
const remoteSearchId = ref(0);
|
||||
|
||||
const handleChange = (value) => {
|
||||
if (multiple.value && maxSelectCount.value > 0 && value.length > maxSelectCount.value) {
|
||||
return;
|
||||
}
|
||||
vModel.value[formatFieldName(props.field.name)] = value;
|
||||
};
|
||||
|
||||
const maxSelectCount = computed(() => {
|
||||
return props.field.maxSelectCount || 0;
|
||||
});
|
||||
|
||||
const showValueTag = computed(() => {
|
||||
return !!props.field.showValueTag;
|
||||
});
|
||||
|
||||
const notFoundContent = computed(() => {
|
||||
if (!supportedRemoteSearch.value) {
|
||||
return "暂无可选项";
|
||||
}
|
||||
return props.field.notFoundContent || !!remoteSearchValue.value
|
||||
? "暂无可选项"
|
||||
: "输入关键字搜索";
|
||||
});
|
||||
|
||||
const defaultValueList = computed(() => {
|
||||
return props.field.defaultValueList || [];
|
||||
});
|
||||
|
||||
const forceInOptions = computed(() => {
|
||||
return props.field.forceInOptions === true;
|
||||
});
|
||||
|
||||
const multiple = computed(() => {
|
||||
return props.field.multiple || false;
|
||||
});
|
||||
|
||||
// 解析选项字符串或数组
|
||||
const parseOptions = (options) => {
|
||||
if (remoteSearchData.value && remoteSearchData.value.length > 0) {
|
||||
return remoteSearchData.value;
|
||||
}
|
||||
if (remoteOptionsData.value && remoteOptionsData.value.length > 0) {
|
||||
return remoteOptionsData.value;
|
||||
}
|
||||
if (supportedRemoteSearch.value && currentValue.value) {
|
||||
return [
|
||||
{
|
||||
label: currentValue.value,
|
||||
value: currentValue.value,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!options) return [];
|
||||
// 如果已经是数组,直接返回
|
||||
if (Array.isArray(options)) {
|
||||
return options;
|
||||
}
|
||||
// 如果是字符串,按换行符分割,再按|分割键值对
|
||||
if (typeof options === "string") {
|
||||
return options
|
||||
.split("\n")
|
||||
.filter((line) => line.trim() !== "")
|
||||
.map((line) => {
|
||||
const parts = line.split("|");
|
||||
return {
|
||||
label: parts[0]?.trim() || "",
|
||||
value: parts[1]?.trim() || parts[0]?.trim() || "",
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const inOption = (value) => {
|
||||
let optionValues = parseOptions(props.field.options).map((item) => {
|
||||
return item.value;
|
||||
});
|
||||
return optionValues.includes(value);
|
||||
};
|
||||
|
||||
const formatFieldName = (name) => {
|
||||
if (!props.fieldPrefix) {
|
||||
return name;
|
||||
}
|
||||
return props.fieldPrefix + "-" + name;
|
||||
};
|
||||
|
||||
const readLabel = (value) => {
|
||||
return `${value}`.split("#")[0];
|
||||
};
|
||||
|
||||
const readTag = (value) => {
|
||||
return `${value}`.split("#")[1];
|
||||
};
|
||||
|
||||
const currentValue = computed(() => {
|
||||
let value = vModel.value[formatFieldName(props.field.name)];
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (!!value || inOption(value)) {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const supportedRemoteSearch = computed(() => {
|
||||
return (
|
||||
!!props.field.remoteSearchMethod &&
|
||||
typeof props.field.remoteSearchMethod === "function"
|
||||
);
|
||||
});
|
||||
|
||||
const supportedRemoteOptions = computed(() => {
|
||||
return (
|
||||
!!props.field.remoteOptionsMethod &&
|
||||
typeof props.field.remoteOptionsMethod === "function"
|
||||
);
|
||||
});
|
||||
|
||||
const supportedFilterOption = computed(() => {
|
||||
return !!props.field.supportedFilterOption && !supportedRemoteSearch.value;
|
||||
});
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (!supportedRemoteSearch.value && !supportedRemoteOptions.value) {
|
||||
return;
|
||||
}
|
||||
if (supportedRemoteSearch.value && !!remoteSearchValue.value) {
|
||||
handleRemoteSearchValue(remoteSearchValue.value);
|
||||
}
|
||||
if (supportedRemoteOptions.value) {
|
||||
handleRemoteOptions();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoteOptions = () => {
|
||||
if (!supportedRemoteOptions.value) {
|
||||
return;
|
||||
}
|
||||
remoteOptionsLoading.value = true;
|
||||
remoteOptionsErrorMsg.value = null;
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
let remoteOptionsMethod = props.field.remoteOptionsMethod;
|
||||
return callFunc([], [], remoteOptionsMethod, []);
|
||||
})
|
||||
.then((data) => {
|
||||
remoteOptionsData.value = data || [];
|
||||
remoteOptionsLoading.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
remoteOptionsErrorMsg.value =
|
||||
(err && err.msg) || (err && err.message) || "连接服务器失败,请检查网络";
|
||||
remoteOptionsLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoteSearchValue = delayCall((value) => {
|
||||
remoteSearchId.value += 1;
|
||||
remoteSearchValue.value = value;
|
||||
const currentRemoteSearchId = remoteSearchId.value;
|
||||
remoteSearchLoading.value = true;
|
||||
remoteSearchErrorMsg.value = null;
|
||||
remoteSearchData.value = [];
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
let remoteSearchMethod = props.field.remoteSearchMethod;
|
||||
return callFunc(["value"], [value], remoteSearchMethod, []);
|
||||
})
|
||||
.then((data) => {
|
||||
if (currentRemoteSearchId !== remoteSearchId.value) {
|
||||
return;
|
||||
}
|
||||
remoteSearchData.value = data || [];
|
||||
remoteSearchLoading.value = false;
|
||||
remoteSearchValue.value = null;
|
||||
})
|
||||
.catch((err) => {
|
||||
remoteSearchErrorMsg.value =
|
||||
(err && err.msg) || (err && err.message) || "连接服务器失败,请检查网络";
|
||||
remoteSearchLoading.value = false;
|
||||
});
|
||||
}, 300);
|
||||
|
||||
const readInitValue = () => {
|
||||
if (initValueFlag.value) {
|
||||
initValueFlag.value = false;
|
||||
return;
|
||||
}
|
||||
initValueFlag.value = true;
|
||||
let curValue = [];
|
||||
if (!!currentValue.value) {
|
||||
if (currentValue.value instanceof Array) {
|
||||
curValue = currentValue.value.filter((val) => !!val);
|
||||
} else if (typeof currentValue.value === "string") {
|
||||
curValue = [currentValue.value];
|
||||
}
|
||||
}
|
||||
let newValue = [];
|
||||
if (forceInOptions.value && !supportedRemoteSearch.value) {
|
||||
let inOptions = [];
|
||||
let options = parseOptions(props.field.options);
|
||||
for (let option of options) {
|
||||
curValue.forEach((val) => {
|
||||
if (option.value === val) {
|
||||
inOptions.push(option.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
newValue = inOptions;
|
||||
} else {
|
||||
newValue = curValue;
|
||||
}
|
||||
if (newValue.length === 0) {
|
||||
newValue = defaultValueList.value || [];
|
||||
}
|
||||
if (newValue !== undefined && newValue !== null) {
|
||||
return multiple.value ? newValue : newValue[0];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const checkValue = () => {
|
||||
let newValue = readInitValue();
|
||||
let curValue = currentValue.value;
|
||||
if (!!newValue && toAny(newValue, "") !== toAny(curValue, "")) {
|
||||
vModel.value[formatFieldName(props.field.name)] = newValue;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkValue();
|
||||
handleRemoteOptions();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => forceInOptions.value,
|
||||
() => {
|
||||
checkValue();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => defaultValueList.value,
|
||||
() => {
|
||||
checkValue();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
Reference in New Issue
Block a user