表单处理

master
lion 4 months ago
parent 86a77697d4
commit 0629d827a3

@ -2,8 +2,8 @@
ENV = 'development'
# base api
#VUE_APP_BASE_API='http://192.167.20.118:8080/'
VUE_APP_BASE_API='http://czemc.localhost'
VUE_APP_BASE_API='http://192.167.20.118:8080/'
#VUE_APP_BASE_API='http://czemc.localhost'
VUE_APP_UPLOAD_API='https://cz-hjjc-test.115.langye.net/api/upload-file'
VUE_APP_PREVIEW=//view.langye.net/preview/onlinePreview
VUE_APP_MODULE_NAME=oa

@ -21,6 +21,63 @@ function isJSON(str) {
}
}
// radio 统一按 string 做存储与比较,避免 "1" vs 1 导致回显不勾选
function normalizeRadioValue(v) {
if (v === null || v === undefined) return "";
return String(v);
}
function getRadioOptionValue(option) {
const raw = typeof option === "object" ? option?.id : option;
return normalizeRadioValue(raw);
}
// 支持 radio 选项含“下级勾选项”的简单格式:
// 例:分散采购['委托社会中介代理机构','自行采购']
// 或:分散采购{'委托社会中介代理机构','自行采购'}
function parseNestedRadioOption(option) {
if (typeof option !== "string") {
return { label: typeof option === "object" ? option?.name : option, children: [] };
}
const match = option.match(/^(.*?)\s*(?:\[(.*)\]|\{(.*)\})\s*$/);
if (!match) return { label: option, children: [] };
const label = (match[1] || "").trim();
const rawList = match[2] ?? match[3] ?? "";
// 提取引号内内容,兼容单引号/双引号
const children = Array.from(rawList.matchAll(/'([^']*)'|"([^"]*)"/g))
.map((m) => (m[1] ?? m[2] ?? "").trim())
.filter(Boolean);
return { label: label || option, children };
}
// nested radio父+子)存储(方案 cJSON 字符串
// 例:{"parent":"分散采购","children":["委托社会中介代理机构"]}
function parseNestedRadioStoredValue(v) {
const raw = v === null || v === undefined ? "" : v;
if (typeof raw !== "string") {
return { parent: normalizeRadioValue(raw), child: "" };
}
try {
const obj = JSON.parse(raw);
if (obj && typeof obj === "object" && (obj.parent || obj.children || obj.child)) {
const parent = normalizeRadioValue(obj.parent);
const childArr = Array.isArray(obj.children) ? obj.children : [];
const child = normalizeRadioValue(obj.child ?? childArr?.[0] ?? "");
return { parent, child };
}
} catch (e) {
// ignore
}
return { parent: normalizeRadioValue(raw), child: "" };
}
function buildNestedRadioStoredValue(parent, child) {
return JSON.stringify({
parent: normalizeRadioValue(parent),
children: child ? [normalizeRadioValue(child)] : [],
});
}
/**
* @param {String} device 'desktop' | 'mobile'
* @param {Object} info field参数
@ -205,32 +262,128 @@ export default function formBuilder(
);
break;
case "radio":
// 支持父 radio + 子 radio单选子项存储为 JSON方案 c
const { parent: radioParent, child: radioChild } = parseNestedRadioStoredValue(target[info.name]);
const nestedOptions = options.map((option) => {
const parsed = parseNestedRadioOption(option);
const label = typeof option === "object" ? option.name : parsed.label;
const value = normalizeRadioValue(label);
const children = typeof option === "string" ? parsed.children : [];
return { option, label, value, children };
});
const onParentChange = (parentValue) => {
const nextParent = normalizeRadioValue(parentValue);
const hit = nestedOptions.find((i) => i.value === nextParent);
if (hit && hit.children && hit.children.length > 0) {
const keepChild = hit.children.includes(radioChild) ? radioChild : "";
this.$set(target, info.name, buildNestedRadioStoredValue(nextParent, keepChild));
} else {
this.$set(target, info.name, nextParent);
}
};
const onChildChange = (parentValue, childValue) => {
this.$set(
target,
info.name,
buildNestedRadioStoredValue(parentValue, normalizeRadioValue(childValue))
);
};
formItem = h(
"el-radio-group",
{
props: {
value: target[info.name],
value: radioParent,
},
attrs: {
placeholder: info.help_text,
},
on: {
input: (e) => {
this.$set(target, info.name, e);
},
input: onParentChange,
},
},
options.map((option) =>
h(
nestedOptions.map((item) => {
const checked = item.value === radioParent;
const showChildren = checked && item.children && item.children.length > 0;
return h(
"el-radio",
{
key: item.value,
props: {
label: typeof option === "object" ? option.id : option,
label: item.value,
},
},
typeof option === "object" ? option.name : option
)
)
[
h(
"span",
{
style: {
color: "rgb(51, 51, 51)",
},
},
item.label
),
showChildren
? h(
"span",
{
style: {
marginLeft: "6px",
color: "rgb(51, 51, 51)",
},
},
""
)
: null,
showChildren
? h(
"el-radio-group",
{
style: {
display: "inline-flex",
alignItems: "center",
gap: "10px",
marginLeft: "2px",
marginRight: "2px",
},
props: {
value: radioChild,
},
on: {
input: (v) => onChildChange(item.value, v),
},
},
item.children.map((c) =>
h(
"el-radio",
{
key: `${item.value}__${c}`,
props: {
label: normalizeRadioValue(c),
},
},
c
)
)
)
: null,
showChildren
? h(
"span",
{
style: {
color: "rgb(51, 51, 51)",
},
},
""
)
: null,
]
);
})
);
break;
case "budget-source":
@ -938,6 +1091,127 @@ export default function formBuilder(
getDetailSelectValue()
);
break;
case "radio":
// 只读模式:用“方框 + 勾号”展示全部选项,并高亮已选项(不使用禁用 radio避免灰色字体
const { parent: readonlyParent, child: readonlyChild } = parseNestedRadioStoredValue(target[info.name]);
formItem = h(
"div",
{
style: {
display: "flex",
alignItems: "center",
flexWrap: "wrap",
gap: "10px 16px",
color: "rgb(51, 51, 51)",
lineHeight: "20px",
minHeight: "40px",
},
},
options.map((option) => {
const parsed = parseNestedRadioOption(option);
const labelText = typeof option === "object" ? option.name : parsed.label;
const optValue = normalizeRadioValue(labelText);
const checked = optValue === readonlyParent;
const children = typeof option === "string" ? parsed.children : [];
return h(
"div",
{
style: {
display: "inline-flex",
alignItems: "center",
cursor: "default",
userSelect: "none",
color: "rgb(51, 51, 51)",
},
},
[
h(
"span",
{
style: {
width: "14px",
height: "14px",
border: "1px solid rgb(51, 51, 51)",
borderRadius: "2px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
marginRight: "6px",
boxSizing: "border-box",
fontSize: "12px",
lineHeight: "12px",
},
},
checked ? "✓" : ""
),
h(
"span",
{
style: {
color: "rgb(51, 51, 51)",
},
},
labelText
),
checked && children.length > 0
? h(
"span",
{
style: {
marginLeft: "6px",
display: "inline-flex",
alignItems: "center",
gap: "10px",
color: "rgb(51, 51, 51)",
},
},
[
h("span", ""),
...children.map((c) => {
const cVal = normalizeRadioValue(c);
const cChecked = cVal === readonlyChild;
return h(
"span",
{
key: `${optValue}__${cVal}`,
style: {
display: "inline-flex",
alignItems: "center",
gap: "6px",
},
},
[
h(
"span",
{
style: {
width: "14px",
height: "14px",
border: "1px solid rgb(51, 51, 51)",
borderRadius: "2px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box",
fontSize: "12px",
lineHeight: "12px",
},
},
cChecked ? "✓" : ""
),
h("span", c),
]
);
}),
h("span", ""),
]
)
: null,
]
);
})
);
break;
case "relation-flow":
console.log("info",info,target)
if (!this.flows[info.name]) {
@ -1162,32 +1436,45 @@ export default function formBuilder(
h("br"),
info.is_sign
? log.user?.sign_file?.url
? h("el-image", {
? h("div", {
style: {
"max-height": "80px",
"max-width": "120px",
display: "block",
},
props: {
src: log.user.sign_file.url,
fit: "contain",
alt: log.user?.name || "",
"preview-src-list": [log.user.sign_file.url],
lazy: false,
},
attrs: {
src: log.user.sign_file.url,
},
})
display: "flex",
"align-items": "center",
gap: "8px",
}
}, [
h("el-image", {
style: {
"max-height": "80px",
"max-width": "100px",
display: "block",
},
props: {
src: log.user.sign_file.url,
fit: "contain",
alt: log.user?.name || "",
"preview-src-list": [log.user.sign_file.url],
lazy: false,
},
attrs: {
src: log.user.sign_file.url,
},
}),
h(
"span",
{
style: {
"font-size": "16px",
color: "#000",
}
},
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD日")
: ""
),
])
: h("span", log.user?.name || "")
: "",
info.is_sign ? h("br") : "",
h(
"span",
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD日")
: ""
),
])
);
}
@ -1291,12 +1578,15 @@ export default function formBuilder(
return h("div", {
style: {
"margin-top": "8px",
display: "flex",
"align-items": "center",
gap: "8px",
}
}, [
h("el-image", {
style: {
"max-height": "80px",
"max-width": "120px",
"max-width": "100px",
display: "block",
},
props: {
@ -1314,9 +1604,8 @@ export default function formBuilder(
"div",
{
style: {
"font-size": "12px",
color: "#909399",
"margin-top": "4px",
"font-size": "16px",
color: "#000",
}
},
log.updated_at
@ -1371,30 +1660,109 @@ export default function formBuilder(
});
break;
case "radio":
// 支持父 radio + 子 radio单选子项存储为 JSON方案 c
const { parent: mRadioParent, child: mRadioChild } = parseNestedRadioStoredValue(target[info.name]);
const mNestedOptions = options.map((option) => {
const parsed = parseNestedRadioOption(option);
const label = typeof option === "object" ? option.name : parsed.label;
const value = normalizeRadioValue(label);
const children = typeof option === "string" ? parsed.children : [];
return { option, label, value, children };
});
const onMParentChange = (parentValue) => {
const nextParent = normalizeRadioValue(parentValue);
const hit = mNestedOptions.find((i) => i.value === nextParent);
if (hit && hit.children && hit.children.length > 0) {
const keepChild = hit.children.includes(mRadioChild) ? mRadioChild : "";
this.$set(target, info.name, buildNestedRadioStoredValue(nextParent, keepChild));
} else {
this.$set(target, info.name, nextParent);
}
};
const onMChildChange = (parentValue, childValue) => {
this.$set(
target,
info.name,
buildNestedRadioStoredValue(parentValue, normalizeRadioValue(childValue))
);
};
formItem = h(
"van-radio-group",
{
props: {
value: target[info.name],
value: mRadioParent,
},
on: {
input: (e) => {
this.$set(target, info.name, e);
},
input: onMParentChange,
},
},
options.map((option) =>
h(
"van-radio",
mNestedOptions.map((item) => {
const checked = item.value === mRadioParent;
const showChildren = checked && item.children && item.children.length > 0;
return h(
"div",
{
props: {
name: typeof option === "object" ? option.id : option,
"checked-color": "var(--theme-color)",
key: item.value,
style: {
display: "inline-flex",
alignItems: "center",
flexWrap: "wrap",
gap: "6px",
color: "rgb(51, 51, 51)",
marginRight: "12px",
},
},
typeof option === "object" ? option.name : option
)
)
[
h(
"van-radio",
{
props: {
name: item.value,
"checked-color": "var(--theme-color)",
},
},
item.label
),
showChildren ? h("span", { style: { color: "rgb(51, 51, 51)" } }, "") : null,
showChildren
? h(
"van-radio-group",
{
style: {
display: "inline-flex",
alignItems: "center",
gap: "10px",
},
props: {
value: mRadioChild,
},
on: {
input: (v) => onMChildChange(item.value, v),
},
},
item.children.map((c) =>
h(
"van-radio",
{
key: `${item.value}__${c}`,
props: {
name: normalizeRadioValue(c),
"checked-color": "var(--theme-color)",
},
},
c
)
)
)
: null,
showChildren ? h("span", { style: { color: "rgb(51, 51, 51)" } }, "") : null,
]
);
})
);
break;
case "date":
@ -1970,6 +2338,127 @@ export default function formBuilder(
)
);
break;
case "radio":
// 只读模式:用“方框 + 勾号”展示全部选项,并高亮已选项(不使用禁用 radio避免灰色字体
const { parent: readonlyMParent, child: readonlyMChild } = parseNestedRadioStoredValue(target[info.name]);
formItem = h(
"div",
{
style: {
display: "flex",
alignItems: "center",
flexWrap: "wrap",
gap: "10px 16px",
color: "rgb(51, 51, 51)",
lineHeight: "20px",
minHeight: "40px",
},
},
options.map((option) => {
const parsed = parseNestedRadioOption(option);
const labelText = typeof option === "object" ? option.name : parsed.label;
const optValue = normalizeRadioValue(labelText);
const checked = optValue === readonlyMParent;
const children = typeof option === "string" ? parsed.children : [];
return h(
"div",
{
style: {
display: "inline-flex",
alignItems: "center",
cursor: "default",
userSelect: "none",
color: "rgb(51, 51, 51)",
},
},
[
h(
"span",
{
style: {
width: "14px",
height: "14px",
border: "1px solid rgb(51, 51, 51)",
borderRadius: "2px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
marginRight: "6px",
boxSizing: "border-box",
fontSize: "12px",
lineHeight: "12px",
},
},
checked ? "✓" : ""
),
h(
"span",
{
style: {
color: "rgb(51, 51, 51)",
},
},
labelText
),
checked && children.length > 0
? h(
"span",
{
style: {
marginLeft: "6px",
display: "inline-flex",
alignItems: "center",
gap: "10px",
color: "rgb(51, 51, 51)",
},
},
[
h("span", ""),
...children.map((c) => {
const cVal = normalizeRadioValue(c);
const cChecked = cVal === readonlyMChild;
return h(
"span",
{
key: `${optValue}__${cVal}`,
style: {
display: "inline-flex",
alignItems: "center",
gap: "6px",
},
},
[
h(
"span",
{
style: {
width: "14px",
height: "14px",
border: "1px solid rgb(51, 51, 51)",
borderRadius: "2px",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box",
fontSize: "12px",
lineHeight: "12px",
},
},
cChecked ? "✓" : ""
),
h("span", c),
]
);
}),
h("span", ""),
]
)
: null,
]
);
})
);
break;
case "relation":
console.log("this.form[info.name]",this.form[info.name])
formItem = h("div", [
@ -2146,32 +2635,45 @@ export default function formBuilder(
h("br"),
info.is_sign
? log.user?.sign_file?.url
? h("el-image", {
? h("div", {
style: {
"max-height": "40px",
"max-width": "60px",
display: "block",
},
props: {
src: log.user.sign_file.url,
fit: "contain",
alt: log.user?.name || "",
"preview-src-list": [log.user.sign_file.url],
lazy: false,
},
attrs: {
src: log.user.sign_file.url,
},
})
display: "flex",
"align-items": "center",
gap: "8px",
}
}, [
h("el-image", {
style: {
"max-height": "40px",
"max-width": "60px",
display: "block",
},
props: {
src: log.user.sign_file.url,
fit: "contain",
alt: log.user?.name || "",
"preview-src-list": [log.user.sign_file.url],
lazy: false,
},
attrs: {
src: log.user.sign_file.url,
},
}),
h(
"span",
{
style: {
"font-size": "12px",
color: "#909399",
}
},
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD日")
: ""
),
])
: h("span", log.user?.name || "")
: "",
info.is_sign ? h("br") : "",
h(
"span",
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD日")
: ""
),
])
);
}
@ -2266,6 +2768,9 @@ export default function formBuilder(
return h("div", {
style: {
"margin-top": "8px",
display: "flex",
"align-items": "center",
gap: "8px",
}
}, [
h("el-image", {
@ -2291,7 +2796,6 @@ export default function formBuilder(
style: {
"font-size": "12px",
color: "#909399",
"margin-top": "4px",
}
},
log.updated_at

Loading…
Cancel
Save