From 50ad810ab4abab01ded96cf1317c605658cf85e4 Mon Sep 17 00:00:00 2001 From: 王彪总 Date: Sat, 10 Oct 2026 14:25:39 +0800 Subject: [PATCH] 业务线 小程序不用写死,从后端接口获取 --- common/config/global.js | 3 +++ common/utils/request.js | 4 ++++ components/grouped-picker/grouped-picker.vue | 517 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pages-sub/daily/maintain-manage/add-record.vue | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 605 insertions(+), 3 deletions(-) create mode 100644 components/grouped-picker/grouped-picker.vue diff --git a/common/config/global.js b/common/config/global.js index 5729efd..b8937d4 100644 --- a/common/config/global.js +++ b/common/config/global.js @@ -9,6 +9,9 @@ export default { timeout: 10000, tokenKey: 'jcss_token' }, + // 多租户:后端开启 urbanops.tenant.enable=true 后,未登录请求必须携带 tenant-id 请求头 + // 取值 = system_tenant.id(集团租户号,与 sql/mysql/多租户-03-租户套餐与账号迁移.sql 的 @tenant_id 保持一致) + tenantId: '1', router: { loginPath: '/pages/login/index', tabBarList: [ diff --git a/common/utils/request.js b/common/utils/request.js index 8e95448..6918e8f 100644 --- a/common/utils/request.js +++ b/common/utils/request.js @@ -22,6 +22,10 @@ const request = (options) => { if (token) { opts.header['Authorization'] = `Bearer ${token}`; + } else if (globalConfig.tenantId) { + // 多租户:未登录时后端无法从 token 推导租户,必须显式携带 tenant-id(登录、刷新令牌等接口) + // 已登录请求由后端按 token 取租户,故不硬编码此头,避免将来多租户下跨租户 403 + opts.header['tenant-id'] = String(globalConfig.tenantId); } opts.url = globalConfig.api.baseUrl + opts.url; diff --git a/components/grouped-picker/grouped-picker.vue b/components/grouped-picker/grouped-picker.vue new file mode 100644 index 0000000..5bd5d63 --- /dev/null +++ b/components/grouped-picker/grouped-picker.vue @@ -0,0 +1,517 @@ + + + + + diff --git a/pages-sub/daily/maintain-manage/add-record.vue b/pages-sub/daily/maintain-manage/add-record.vue index aa11786..56fade7 100644 --- a/pages-sub/daily/maintain-manage/add-record.vue +++ b/pages-sub/daily/maintain-manage/add-record.vue @@ -17,6 +17,14 @@ + + + + @@ -120,6 +128,12 @@ + + @@ -178,6 +192,20 @@ export default { fieldCode: '', // 当前操作的字段 options: [] as any[], fieldItem: null // 保存当前点击的field对象,用于后面查找label + }, + // 分层选项选择器配置(选项带 path 的 3/6 字段用,其余字段仍走上面的 actionSheet / 内联复选框) + groupPicker: { + show: false, + fieldCode: '', + fieldItem: null as any, + title: '', + options: [] as any[], + multiple: false, + maxCount: Infinity, + needQuantity: false, + value: '' as any, + quantities: {} as any, + unit: '' } } }, @@ -224,10 +252,14 @@ export default { }, /** * 点击表单项时按类型决定弹出哪个选择器: - * 3 枚举(单选) / 9 关联记录 → 底部 actionSheet;11 地理位置 → 微信地图选点 + * 分层选项(带 path 的 3/6) → 分组弹层;3 枚举(单选) / 9 关联记录 → 底部 actionSheet;11 地理位置 → 微信地图选点 * 其余类型(多选、图片、附件、可输入的链接)不在这里处理,由控件自身响应 */ openPickerIfNeeded(item: any) { + // 分层选项优先:它的只读入口是靠本方法打开弹层的(模板里 3/6 的原有分支已被 isGroupedField 挡在前面) + if (this.isGroupedField(item)) { + return this.openGroupedPicker(item) + } // 条件与模板互补:模板用 multiple === 1 渲染复选框组,这里就必须用 !== 1 才打开弹窗, // 否则 multiple 为 NULL/0 的存量单选枚举会既不渲染复选框、又点不开弹窗 if (item.fieldType === 3 && item.multiple !== 1) { @@ -241,6 +273,51 @@ export default { } }, /** + * 是否为「分层选项」字段:3枚举/6多选,且选项带了后端下发的 path。 + * path 是纯增量字段,老字段(静态/字典/物料/数据表/接口/无 gN_ 别名的 SQL)没有它 → 走原路径,行为不变 + */ + isGroupedField(item: any) { + if (!item || (item.fieldType !== 3 && item.fieldType !== 6)) { + return false + } + return (item.selectOptions || []).some((opt: any) => Array.isArray(opt.path) && opt.path.length > 0) + }, + /** + * 打开分层选项弹层。单选/多选判定与模板同一口径: + * 6 → 多选;3 且 multiple===1 → 多选(提交按逗号拼接);3 其余 → 单选 + */ + openGroupedPicker(item: any) { + const code = item.fieldCode + const multiple = item.fieldType === 6 || item.multiple === 1 + this.groupPicker.fieldCode = code + this.groupPicker.fieldItem = item + this.groupPicker.title = item.fieldName + this.groupPicker.options = item.selectOptions || [] + this.groupPicker.multiple = multiple + this.groupPicker.maxCount = this.maxCountOf(item, Infinity) + // 只有 6 多选需要每项数量;3 枚举没有这层语义 + this.groupPicker.needQuantity = item.fieldType === 6 && this.needQty(item) + this.groupPicker.value = multiple ? (this.inspectForm[code] || []).slice() : (this.inspectForm[code] || '') + this.groupPicker.quantities = Object.assign({}, this.qtyMap[code] || {}) + this.groupPicker.unit = item.unit || '' + this.groupPicker.show = true + }, + /** + * 分层弹层确定回调。写入位置与原有两条路径完全一致(inspectForm/labelMap/qtyMap), + * 因此 buildDynamicFields() 一行都不用改 + */ + onGroupedConfirm(payload: any) { + const code = this.groupPicker.fieldCode + this.inspectForm[code] = payload.values + this.labelMap[code] = payload.labelText + if (this.groupPicker.needQuantity) { + // 合并而不是替换:保留 loadDynamicFields 预建的选项键,避免非分层路径读到 undefined + this.qtyMap[code] = Object.assign({}, this.qtyMap[code] || {}, payload.quantities || {}) + } + this.groupPicker.show = false + this.$refs.inspectFormRef.validateField(code) + }, + /** * 打开底部选择弹窗 actionSheet */ openActionSheet(item: any) { @@ -343,7 +420,7 @@ export default { // 由后端按 optionSource 分发。类型 3/6/9 需要选项,其余类型不需要 if (field.fieldType === 3 || field.fieldType === 6 || field.fieldType === 9) { if (field.optionSource === 1 && Array.isArray(field.options)) { - field.selectOptions = field.options.map(opt => ({ label: opt.label, value: String(opt.value) })) + field.selectOptions = field.options.map(opt => ({ label: opt.label, value: String(opt.value), path: opt.path })) } else if (field.optionSource === 3) { // 物料来源:调 materials(比 options 多返回 currentNum 库存余量/unit),供选择时显示库存 const matRes = await getMaterialsByCode({ @@ -365,7 +442,8 @@ export default { workTypeId, fieldCode: field.fieldCode }) - field.selectOptions = (optRes || []).map(opt => ({ label: opt.label, value: String(opt.value) })) + // path 后端仅在 SQL 来源按 gN_key/gN_label 别名产出;透传后由 isGroupedField 判定是否走分层弹层 + field.selectOptions = (optRes || []).map(opt => ({ label: opt.label, value: String(opt.value), path: opt.path })) } } // 数量收集初始化(必须在 selectOptions 装载之后): -- libgit2 0.21.4