371 lines
11 KiB
Vue
Raw Normal View History

2025-12-05 19:05:35 +08:00
<template>
<div class="dynamic-input-container">
<!-- 1. 开关组件(替代原单选框 type=0:是/否 → 开启/关闭) -->
<el-switch
active-text="开"
inactive-text="关"
class="custom-switch"
v-if="type === '0'"
v-model="innerValue"
active-color="#13ce66"
inactive-color="#ff4949"
active-value="1"
inactive-value="0"
@change="handleSwitchChange"
/>
<!-- 2. 下拉框(1):直接绑定value -->
<el-select
v-else-if="type === '1'"
v-model="innerValue"
size="small"
clearable
@change="handleValueChange"
>
<el-option
v-for="item in dictOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 3. 字典单选(2):绑定zddh(value),显示zdmc(label) -->
<el-select
v-else-if="type === '2'"
v-model="innerValue"
size="small"
clearable
@change="handleValueChange"
v-loading="dictLoading"
>
<el-option
v-for="item in dictOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 4. 字典多选(3):数组↔逗号分隔字符串 -->
<el-select
v-else-if="type === '3'"
v-model="multipleValue"
size="small"
multiple
@change="handleMultipleChange"
v-loading="dictLoading"
>
<el-option
v-for="item in dictOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 5. 特殊(4) -->
<el-input
v-else-if="type === '4'"
v-model="innerValue"
size="small"
disabled
@change="handleValueChange"
/>
<!-- 6. 文本框(5) -->
<el-input
v-else-if="type === '5'"
v-model="innerValue"
size="small"
:placeholder="placeholder"
@change="handleValueChange"
/>
<!-- 7. 数字框(6) -->
<el-input-number
v-else-if="type === '6'"
v-model="innerValue"
size="small"
:min="minNum"
:precision="precisionNum"
controls-position="right"
@change="handleValueChange"
/>
<!-- 8. 日期框(7):适配yyyy/mm/dd -->
<el-date-picker
v-else-if="type === '7'"
v-model="innerValue"
size="small"
type="date"
format="YYYY/MM/DD"
value-format="YYYY-MM-DD"
:placeholder="placeholder"
@change="handleValueChange"
/>
<!-- 9. 时间框(8):适配hh:mm(修复标签闭合问题) -->
<el-time-picker
v-else-if="type === '8'"
v-model="innerValue"
size="small"
format="HH:mm"
value-format="HH:mm:ss"
:placeholder="placeholder"
@change="handleValueChange"
:picker-options="{ selectableRange: '00:00:00 - 23:59:59' }"
/>
<!-- 兜底:默认文本框 -->
<el-input
v-else
v-model="innerValue"
size="small"
:placeholder="placeholder"
@change="handleValueChange"
/>
</div>
</template>
<script setup name="DynamicInput">
import { ref, watch, defineProps, defineEmits, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { getoptionselect } from "@/api/liswork/xtwh/localconfigApi.ts";
// 字典缓存:key=字典类型,value=映射后的{label,value}列表
const dictCache = ref({});
// Props定义
const props = defineProps({
modelValue: { // Vue3 规范:v-model 绑定 modelValue
type: [String, Number, Boolean],
required: true,
default: "" // 开关默认值:0(关闭)
},
type: { type: String, required: true, default: "5" },
dictData: { type: [String, Object, Array], default: "" },
placeholder: { type: String, default: "请输入值" },
minNum: { type: Number, default: 0 },
precisionNum: { type: Number, default: 2 }
});
console.log("子组件接收的props:", {
type: props.type,
value: props.modelValue,
dictData: props.dictData,
valueType: typeof props.modelValue
});
// Emits定义(统一触发 update:modelValue)
const emit = defineEmits(["update:modelValue", "change"]);
// 内部状态
const innerValue = ref(""); // 单值组件绑定(开关/下拉/文本等)
const multipleValue = ref([]); // 多选值(仅type=3用)
const dictOptions = ref([]); // 下拉/字典选项列表
const dictLoading = ref(false); // 字典加载状态
// ========== 核心:值初始化 + 格式适配 ==========
watch(() => props.modelValue, (newVal) => {
const val = newVal ?? ""; // 开关兜底默认值:0
switch (props.type) {
// 开关组件(替代原单选框):后端1/0 → 前端"是"/"否"
case "0":
innerValue.value = (val === 1 || val === "1" || val === true) ? "1" : "0";
break;
// 下拉框/字典单选:直接绑定value(zddh)
case "1":
case "2":
innerValue.value = String(val); // 强制字符串,匹配选项value类型
break;
// 字典多选:后端逗号字符串→前端数组
case "3":
multipleValue.value = typeof val === "string" && val
? val.split(",").map(item => String(item))
: [];
innerValue.value = val;
break;
// 数字框:确保是数字类型
case "6":
innerValue.value = !isNaN(Number(val)) ? Number(val) : 0;
break;
// 日期框:后端 yyyy/mm/dd → 前端 yyyy-MM-dd
case "7":
innerValue.value = typeof val === "string" && val.includes("/")
? val.replace(/\//g, "-")
: val;
break;
// 时间框:后端 hh:mm → 前端 hh:mm:ss
case "8":
innerValue.value = typeof val === "string" && val.includes(":") && val.split(":").length === 2
? `${val}:00`
: val;
break;
// 其他类型:直接赋值
default:
innerValue.value = val;
break;
}
}, { immediate: true, deep: true });
// ========== 同步子组件值到父组件 ==========
watch(innerValue, (newVal) => {
let finalVal = newVal;
// 数字框:确保数字类型
if (props.type === "6") {
finalVal = !isNaN(Number(newVal)) ? Number(newVal) : 0;
}
// 日期框:前端 yyyy-MM-dd → 后端 yyyy/mm/dd
else if (props.type === "7" && typeof newVal === "string" && newVal.includes("-")) {
finalVal = newVal.replace(/-/g, "/");
}
// 时间框:前端 hh:mm:ss → 后端 hh:mm
else if (props.type === "8" && typeof newVal === "string" && newVal.includes(":")) {
finalVal = newVal.split(":").slice(0, 2).join(":");
}
emit("update:modelValue", finalVal);
emit("change", finalVal);
}, { deep: true });
// ========== 字典加载逻辑 ==========
watch([() => props.type, () => props.dictData], async () => {
dictOptions.value = [];
if (props.type === "1") {
await parseJsonDict();
} else if (["2", "3"].includes(props.type)) {
await loadApiDict();
}
}, { immediate: true, deep: true });
// 解析下拉框JSON数组(type=1)
async function parseJsonDict() {
if (!props.dictData) return;
try {
let jsonStr = typeof props.dictData === "string" ? props.dictData : JSON.stringify(props.dictData);
jsonStr = jsonStr.replace(/\s+/g, " ").replace(/[\n\t]/g, "");
let jsonData = JSON.parse(jsonStr);
if (!Array.isArray(jsonData)) jsonData = [jsonData];
dictOptions.value = jsonData.map(item => ({
label: item.label || item.zdmc || item.name || "",
value: item.value || item.zddh || item.code || ""
})).filter(item => item.label && item.value);
} catch (error) {
ElMessage.error(`下拉框数据解析失败:${error.message}`);
console.error("JSON解析失败:", { raw: props.dictData, error });
}
}
// 加载字典API(type=2/3):zddh→value,zdmc→label
async function loadApiDict() {
const dictType = props.dictData;
if (!dictType) return;
if (dictCache.value[dictType]) {
dictOptions.value = dictCache.value[dictType];
return;
}
try {
dictLoading.value = true;
const res = await getoptionselect({ zdlb: dictType });
if (res.code === 200 && Array.isArray(res.data)) {
dictOptions.value = res.data.map(item => ({
label: item.zdmc || item.label || "",
value: item.zddh || item.value || ""
})).filter(item => item.label && item.value);
dictCache.value[dictType] = dictOptions.value;
} else {
ElMessage.warning(`字典${dictType}无有效数据`);
}
} catch (error) {
ElMessage.error("字典请求失败");
console.error("字典API异常:", { dictType, error });
} finally {
dictLoading.value = false;
}
}
// ========== 开关组件专属变更处理(直接返回数字1/0) ==========
function handleSwitchChange(val) {
// val是字符串1/0,转为数字提交给后端
const finalVal = Number(val);
emit("update:modelValue", finalVal);
emit("change", finalVal);
}
// ========== 普通值变更处理(除开关/多选外) ==========
function handleValueChange(val) {
let finalVal = val;
emit("update:modelValue", finalVal);
emit("change", finalVal);
}
// ========== 多选值变更处理 ==========
function handleMultipleChange(val) {
const strVal = val?.join(",") || "";
innerValue.value = strVal;
multipleValue.value = val;
emit("update:modelValue", strVal);
emit("change", strVal);
}
// ========== 多选值同步监听 ==========
watch(multipleValue, (newVal) => {
if (props.type === "3") {
const strVal = newVal?.join(",") || "";
innerValue.value = strVal;
emit("update:modelValue", strVal);
}
}, { deep: true });
// ========== 初始化兜底 ==========
onMounted(() => {
// 开关组件初始值兜底
if (props.type === "0" && !innerValue.value) {
innerValue.value = "否"; // 默认关闭
emit("update:modelValue", 0); // 后端默认值:0
}
// 多选值初始化
if (props.type === "3" && !multipleValue.value.length && innerValue.value) {
multipleValue.value = innerValue.value.split(",").map(item => String(item));
}
});
</script>
<style scoped>
.dynamic-input-container {
width: 100%;
display: flex;
align-items: center; /* 开关组件垂直居中 */
}
/* 开关组件样式优化 */
:deep(.el-switch) {
margin: 0 auto; /* 开关水平居中 */
}
/* 深度选择器强制覆盖激活颜色 */
:deep(.custom-switch .el-switch__core) {
/* 开启状态背景色 */
--el-switch-on-color: #13ce66 !important;
/* 关闭状态背景色(可选) */
--el-switch-off-color: #ff4949 !important;
}
/* 兼容旧版Element Plus(可选) */
:deep(.custom-switch .el-switch__core.is-checked) {
background-color: #13ce66 !important;
border-color: #13ce66 !important;
}
:deep(.el-radio-group) {
display: flex;
gap: 10px;
align-items: center;
}
:deep(.el-select--small),
:deep(.el-input--small),
:deep(.el-input-number--small),
:deep(.el-date-picker--small),
:deep(.el-time-picker--small) {
width: 100%;
}
</style>