Files
tape-springboot-sysadmin/src/iview/form/IViewFormRadio.vue
iqudoo eb4e8f1a04 init
2026-06-05 17:22:32 +08:00

271 lines
6.8 KiB
Vue

<template>
<div class="iview-form-radio-container">
<a-input
type="text"
style="display: none"
:id="'form_item_' + formatFieldName(field.name)"
/>
<a-form-item-rest>
<div v-if="supportedRemoteSearch" style="margin-bottom: 8px">
<a-input-search
v-model:value="searchInputValue"
:placeholder="field.searchPlaceholder || '输入关键字搜索'"
:loading="remoteSearchLoading"
allow-clear
@search="handleSearch"
@input="handleSearchInput"
/>
</div>
<div v-if="remoteSearchLoading || remoteOptionsLoading" style="padding: 8px 0">
<a-spin size="small" />
</div>
<div
v-else-if="remoteSearchErrorMsg || remoteOptionsErrorMsg"
style="padding: 8px 0; color: #ff4d4f"
>
{{ remoteSearchErrorMsg || remoteOptionsErrorMsg }}
<a-button
size="small"
type="default"
style="margin-left: 8px"
@click="handleRefresh"
>
刷新
</a-button>
</div>
<a-radio-group
v-else
v-model:value="vModel[formatFieldName(field.name)]"
:disabled="readOnly"
>
<a-radio
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-radio>
</a-radio-group>
</a-form-item-rest>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useVModel } from "@/iview/utils/useModel";
import { delayCall } from "@/iview/utils/delayCall";
import { callFunc } from "@/iview/utils/func";
const props = defineProps({
field: {
type: Object,
required: true,
},
fieldPrefix: {
type: String,
default: "",
},
modelValue: {
type: Object,
required: true,
},
readOnly: {
type: Boolean,
default: false,
},
rawFormData: {
type: Object,
},
});
const emit = defineEmits(["update:modelValue"]);
const vModel = useVModel(props, "modelValue", emit);
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 searchInputValue = ref("");
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 showValueTag = computed(() => {
return !!props.field.showValueTag;
});
const currentValue = computed(() => {
let value = vModel.value[formatFieldName(props.field.name)];
if (value === undefined || value === null) {
return undefined;
}
return value;
});
// 解析选项字符串或数组
const parseOptions = (options) => {
if (remoteSearchData.value && remoteSearchData.value.length > 0) {
return remoteSearchData.value;
}
if (remoteOptionsData.value && remoteOptionsData.value.length > 0) {
return remoteOptionsData.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 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 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 handleSearch = (value) => {
if (supportedRemoteSearch.value && value) {
handleRemoteSearchValue(value);
}
};
const handleSearchInput = (e) => {
const value = e.target.value;
if (supportedRemoteSearch.value) {
if (value) {
handleRemoteSearchValue(value);
} else {
remoteSearchData.value = [];
remoteSearchErrorMsg.value = null;
}
}
};
const handleRefresh = () => {
if (supportedRemoteSearch.value && !!remoteSearchValue.value) {
handleRemoteSearchValue(remoteSearchValue.value);
}
if (supportedRemoteOptions.value) {
handleRemoteOptions();
}
};
onMounted(() => {
handleRemoteOptions();
});
</script>
<style scoped>
:deep(.ant-radio-group) {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
text-align: left;
}
:deep(.ant-radio-wrapper) {
margin-left: 0;
margin-right: 16px;
padding: 4px 0;
}
</style>