From dd94e53d81d3a1a63db60d5131074c8288bf9b13 Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Tue, 25 Aug 2026 17:39:03 +0800 Subject: [PATCH] =?UTF-8?q?chore=EF=BC=9A=E8=A1=A8=E5=8D=95=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=AD=A3=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages_tool/form/form.vue | 127 +++++++++++++++++++++++++++-- pages_tool/form/formdata.vue | 154 +++++++++++++++++++++++++++++++---- 2 files changed, 255 insertions(+), 26 deletions(-) diff --git a/pages_tool/form/form.vue b/pages_tool/form/form.vue index 98e5e81..da0246f 100644 --- a/pages_tool/form/form.vue +++ b/pages_tool/form/form.vue @@ -10,7 +10,13 @@ - + + + + + + + {{ captcha.img ? '换一张' : '获取验证码' }} @@ -41,7 +47,11 @@ // 非成功状态的空态文案(表单未开放/需登录/加载失败等) formError: '', // 图形验证码(behavior.captcha=1 时展示) - captcha: { id: '', img: '', code: '' } + captcha: { id: '', img: '', code: '' }, + // 是否已加载过验证码(避免 onShow 重复刷新导致 session 错乱) + captchaLoaded: false, + // 登录拦截时暂存待提交数据:组件内存(MP 不丢态)+ 本地存储(H5 登录整页回跳后从存储恢复) + pendingSubmit: null }; }, computed: { @@ -87,7 +97,11 @@ }, watch: { storeToken: function(nVal, oVal) { - if (nVal) this.getData(); + if (nVal) { + this.getData(); + // 填写需登录:弹窗登录成功后自动续提交 + this.resumeSubmitAfterLogin(); + } } }, methods: { @@ -101,7 +115,9 @@ if (res.code == 0 && res.data) { this.detail = res.data; this.behavior = (res.data.form_config && res.data.form_config.behavior) || {}; - if (this.behavior.captcha == 1) this.getCaptcha(); + if (this.behavior.captcha == 1 && !this.captchaLoaded) this.getCaptcha(); + // 登录回跳后自动续提交(填写需登录场景:H5 整页回跳 / MP 弹窗登录后) + this.resumeSubmitAfterLogin(); } else if (res.message == 'FORM_LOGIN_REQUIRED') { // 打开需登录:弹登录组件,登录成功后 watch storeToken 重新拉取 this.$refs.login ? this.$refs.login.open('/pages_tool/form/form?id=' + this.id) @@ -118,19 +134,94 @@ } }); }, - // 图形验证码(点击图片刷新;提交失败后自动刷新) + // 图形验证码(点击图片/按钮刷新;提交失败后自动刷新) getCaptcha() { + this.captchaLoaded = true; this.$api.sendRequest({ url: '/api/captcha/captcha', + data: { + captcha_id: this.captcha.id + }, success: res => { if (res.code == 0 && res.data) { this.captcha.id = res.data.id; - this.captcha.img = res.data.img; + this.captcha.img = res.data.img ? res.data.img.replace(/\r\n/g, '') : ''; this.captcha.code = ''; } } }); }, + // 登录拦截时保存待提交数据(组件内存 + 本地存储,H5 登录会整页回跳) + savePendingSubmit(formData) { + const data = formData || (this.$refs.form && this.$refs.form.formData) || null; + const payload = { + formId: this.id, + urlParams: this.urlParams, + formData: data, + captchaId: this.captcha.id, + captchaCode: this.captcha.code, + ts: Date.now() + }; + this.pendingSubmit = payload; + try { uni.setStorageSync('form_pending_submit', payload); } catch (e) {} + // H5 整页回跳标记:仅当确由登录拦截触发时才允许自动续提交,避免陈旧数据被误提交 + try { uni.setStorageSync('form_pending_from_login', 1); } catch (e) {} + }, + // 仅在已登录时取回并消费待提交数据(未登录 / 非登录回跳 / 过期不消费,避免误提交);取后清除 + takePendingSubmit() { + let payload = this.pendingSubmit; + const fromStorage = !payload; + if (fromStorage) { + try { payload = uni.getStorageSync('form_pending_submit'); } catch (e) {} + } + if (!payload || !payload.formData) return null; + if (!this.storeToken) return null; + // H5 存储路径:必须是因登录拦截触发的回跳,否则视为陈旧数据,清理后不提交 + if (fromStorage) { + let fromLogin = false; + try { fromLogin = uni.getStorageSync('form_pending_from_login'); } catch (e) {} + if (!fromLogin) { + this.pendingSubmit = null; + try { uni.removeStorageSync('form_pending_submit'); } catch (e) {} + return null; + } + } + // 仅消费 10 分钟内的待提交,避免极旧数据被自动提交 + if (payload.ts && Date.now() - payload.ts > 10 * 60 * 1000) { + this.pendingSubmit = null; + try { uni.removeStorageSync('form_pending_submit'); } catch (e) {} + try { uni.removeStorageSync('form_pending_from_login'); } catch (e) {} + return null; + } + this.pendingSubmit = null; + try { uni.removeStorageSync('form_pending_submit'); } catch (e) {} + try { uni.removeStorageSync('form_pending_from_login'); } catch (e) {} + return payload; + }, + // 登录成功后:回填已填数据并自动续提交 + resumeSubmitAfterLogin() { + const pending = this.takePendingSubmit(); + if (!pending) return; + // 回填表单字段(nextTick 确保 ns-form 已挂载),避免 H5 整页回跳后好友看到空白表单 + this.$nextTick(() => this.restoreFormData(pending.formData)); + // 验证码:MP 登录不丢态可直接提交;H5 回跳后验证码失效,需重新获取并让用户重填 + if (this.behavior.captcha == 1 && !this.captcha.code) { + this.getCaptcha(); + this.$util.showToast({ title: '请重新输入验证码后提交' }); + return; + } + this.create(); + }, + // 将保存的 formData 回填到 ns-form + restoreFormData(formData) { + try { + const form = this.$refs.form; + if (form && Array.isArray(formData)) { + form.formData = JSON.parse(JSON.stringify(formData)); + if (typeof form.processFormData === 'function') form.processFormData(); + } + } catch (e) {} + }, // 填完跳转:消费统一链接选择器的完整对象(wap_url 外链/页面路径、外部小程序、拨打电话) doRedirect(link) { const fallback = () => { this.complete = true; this.detail = null; }; @@ -158,6 +249,8 @@ // 填写需登录 / 限填(后端强制,前端提前拦截优化体验) if ((behavior.require_login_submit == 1 || behavior.submit_limit > 0) && !this.storeToken) { + // 保存已填写数据,登录成功(MP 弹窗 / H5 回跳)后自动续提交,避免数据丢失 + this.savePendingSubmit(); this.$refs.login && this.$refs.login.open('/pages_tool/form/form?id=' + this.id); return; } @@ -168,6 +261,9 @@ } this.isRepeat = true; + const submitCaptchaId = String(this.captcha.id || ''); + const submitCaptchaCode = String(this.captcha.code || '').toUpperCase(); + console.log('[FORM-SUBMIT-CAPTCHA]', { captcha_id: submitCaptchaId, captcha_code: submitCaptchaCode, raw_code: this.captcha.code }); this.$api.sendRequest({ url: '/form/api/form/create', data: { @@ -175,8 +271,8 @@ form_data: typeof this.$refs.form.formData === 'string' ? this.$refs.form.formData : JSON.stringify(this.$refs.form.formData), url_params: JSON.stringify(this.urlParams), member_id: this.memberInfo ? this.memberInfo.id : 0, - captcha_id: this.captcha.id || '', - captcha_code: this.captcha.code || '' + captcha_id: submitCaptchaId, + captcha_code: submitCaptchaCode }, success: res => { if (res.code == 0) { @@ -189,6 +285,8 @@ } else { this.isRepeat = false; if (res.message == 'FORM_LOGIN_REQUIRED') { + // 提交时后端仍要求登录(如登录态过期):暂存数据,重登后自动续提交 + this.savePendingSubmit(); this.$refs.login && this.$refs.login.open('/pages_tool/form/form?id=' + this.id); } else if (res.message == 'CAPTCHA_ERROR' || res.message == 'CAPTCHA_FAILURE') { this.$util.showToast({ title: this.$lang('form.form.captchaError') }); @@ -262,6 +360,19 @@ border-radius: 12rpx; border: 2rpx solid #e5e7eb; } + + .captcha-btn { + margin-left: 20rpx; + height: 72rpx; + line-height: 72rpx; + padding: 0 24rpx; + font-size: 26rpx; + color: #ff4544; + background: #fff; + border: 2rpx solid #ff4544; + border-radius: 12rpx; + white-space: nowrap; + } } .button { diff --git a/pages_tool/form/formdata.vue b/pages_tool/form/formdata.vue index 4af1693..dec76e8 100644 --- a/pages_tool/form/formdata.vue +++ b/pages_tool/form/formdata.vue @@ -40,7 +40,13 @@ - + + + + + + + {{ captcha.img ? '换一张' : '获取验证码' }} {{ submitBtnText }} @@ -60,7 +66,13 @@ - + + + + + + + {{ captcha.img ? '换一张' : '获取验证码' }} {{ submitBtnText }} @@ -102,8 +114,12 @@ export default { formError: '', // 图形验证码(behavior.captcha=1 时展示,由本页渲染而非 diy-form) captcha: { id: '', img: '', code: '' }, + // 是否已加载过验证码(避免 onShow 重复刷新导致 session 错乱) + captchaLoaded: false, // 登录后回跳地址(onLoad 时由原始 options 重建) backUrl: '', + // 登录拦截时暂存待提交数据:组件内存(MP 不丢态)+ 本地存储(H5 登录整页回跳后从存储恢复) + pendingSubmit: null, // #ifndef H5 navbarHeight: 44, // 自绘 navbar 内容区高度(不含状态栏),跨端兜底 44px @@ -268,7 +284,11 @@ export default { watch: { storeToken(newVal, oldVal) { - newVal && this.getData() + if (newVal) { + this.getData(); + // 填写需登录:弹窗登录成功后自动续提交 + this.resumeSubmitAfterLogin(); + } } }, onShow() { @@ -302,7 +322,9 @@ export default { this.updateNavbarTitle(); // 行为设置(后端已合并默认值):验证码开启时拉取图形验证码 this.behavior = (res.data.form_config && res.data.form_config.behavior) || {} - if (this.behavior.captcha == 1) this.getCaptcha() + if (this.behavior.captcha == 1 && !this.captchaLoaded) this.getCaptcha() + // 登录回跳后自动续提交(填写需登录场景:H5 整页回跳 / MP 弹窗登录后) + this.resumeSubmitAfterLogin() } else if (res.message == 'FORM_LOGIN_REQUIRED') { // 打开需登录:弹登录组件,登录成功后 watch storeToken 重新拉取 this.$refs.login ? this.$refs.login.open(this.backUrl) @@ -337,19 +359,94 @@ export default { this.navbarTitle = formName; // #endif }, - // 图形验证码(点击图片刷新;提交失败后自动刷新) + // 图形验证码(点击图片/按钮刷新;提交失败后自动刷新) getCaptcha() { + this.captchaLoaded = true this.$api.sendRequest({ url: '/api/captcha/captcha', + data: { + captcha_id: this.captcha.id + }, success: res => { if (res.code == 0 && res.data) { this.captcha.id = res.data.id - this.captcha.img = res.data.img + this.captcha.img = res.data.img ? res.data.img.replace(/\r\n/g, '') : '' this.captcha.code = '' } } }) }, + // 登录拦截时保存待提交数据(组件内存 + 本地存储,H5 登录会整页回跳) + savePendingSubmit(formData) { + const payload = { + formId: this.id, + uniacid: this.uniacid, + urlParams: this.urlParams, + formData: formData, + captchaId: this.captcha.id, + captchaCode: this.captcha.code, + ts: Date.now() + } + this.pendingSubmit = payload + try { uni.setStorageSync('form_pending_submit', payload) } catch (e) {} + // H5 整页回跳标记:仅当确由登录拦截触发时才允许自动续提交,避免陈旧数据被误提交 + try { uni.setStorageSync('form_pending_from_login', 1) } catch (e) {} + }, + // 仅在已登录时取回并消费待提交数据(未登录 / 非登录回跳 / 过期不消费,避免误提交);取后清除 + takePendingSubmit() { + let payload = this.pendingSubmit + const fromStorage = !payload + if (fromStorage) { + try { payload = uni.getStorageSync('form_pending_submit') } catch (e) {} + } + if (!payload || !payload.formData) return null + if (!this.storeToken) return null + // H5 存储路径:必须是因登录拦截触发的回跳,否则视为陈旧数据,清理后不提交 + if (fromStorage) { + let fromLogin = false + try { fromLogin = uni.getStorageSync('form_pending_from_login') } catch (e) {} + if (!fromLogin) { + this.pendingSubmit = null + try { uni.removeStorageSync('form_pending_submit') } catch (e) {} + return null + } + } + // 仅消费 10 分钟内的待提交,避免极旧数据被自动提交 + if (payload.ts && Date.now() - payload.ts > 10 * 60 * 1000) { + this.pendingSubmit = null + try { uni.removeStorageSync('form_pending_submit') } catch (e) {} + try { uni.removeStorageSync('form_pending_from_login') } catch (e) {} + return null + } + this.pendingSubmit = null + try { uni.removeStorageSync('form_pending_submit') } catch (e) {} + try { uni.removeStorageSync('form_pending_from_login') } catch (e) {} + return payload + }, + // 登录成功后:回填已填数据并自动续提交 + resumeSubmitAfterLogin() { + const pending = this.takePendingSubmit() + if (!pending) return + // 回填表单字段(nextTick 确保 diy-form 内部 ns-newform 已挂载),避免 H5 整页回跳后好友看到空白表单 + this.$nextTick(() => this.restoreFormData(pending.formData)) + // 验证码:MP 登录不丢态可直接提交;H5 回跳后验证码失效,需重新获取并让用户重填 + if (this.behavior.captcha == 1 && !this.captcha.code) { + this.getCaptcha() + this.$util.showToast({ title: '请重新输入验证码后提交' }) + return + } + this.create(pending.formData) + }, + // 将保存的 formData 回填到 diy-form 内部的 ns-newform + restoreFormData(formData) { + try { + const ns = this.$refs.form && this.$refs.form.$refs && this.$refs.form.$refs.nsNewform + if (ns && Array.isArray(formData)) { + ns.formData = JSON.parse(JSON.stringify(formData)) + if (typeof ns.processFormData === 'function') ns.processFormData() + } + } catch (e) {} + }, // 验证码模式下本页自有提交按钮:先走 diy-form 校验与取数,再进 create submitFromPage() { if (this.isRepeat) return; @@ -392,6 +489,8 @@ export default { const behavior = this.behavior || {}; // 填写需登录 / 限填(后端强制,前端提前拦截优化体验) if ((behavior.require_login_submit == 1 || behavior.submit_limit > 0) && !this.storeToken) { + // 保存已填写数据,登录成功(MP 弹窗 / H5 回跳)后自动续提交,避免数据丢失 + this.savePendingSubmit(formData); this.$refs.login && this.$refs.login.open(this.backUrl); return; } @@ -401,18 +500,22 @@ export default { return; } - this.isRepeat = true; - this.$api.sendRequest({ - url: '/form/api/form/create', - data: { - form_id: this.id, - form_data: typeof formData === 'string' ? formData : JSON.stringify(formData), - uniacid: this.uniacid, - url_params: JSON.stringify(this.urlParams), - member_id: this.memberInfo ? this.memberInfo.id : 0, - captcha_id: this.captcha.id || '', - captcha_code: this.captcha.code || '' - }, + this.isRepeat = true; + console.log('[FORMDATA-SUBMIT-CAPTCHA]=========================验证码========================='); + const submitCaptchaId = String(this.captcha.id || ''); + const submitCaptchaCode = String(this.captcha.code || '').toUpperCase(); + console.log('[FORMDATA-SUBMIT-CAPTCHA]', { captcha_id: submitCaptchaId, captcha_code: submitCaptchaCode, raw_code: this.captcha.code }); + this.$api.sendRequest({ + url: '/form/api/form/create', + data: { + form_id: this.id, + form_data: typeof formData === 'string' ? formData : JSON.stringify(formData), + uniacid: this.uniacid, + url_params: JSON.stringify(this.urlParams), + member_id: this.memberInfo ? this.memberInfo.id : 0, + captcha_id: submitCaptchaId, + captcha_code: submitCaptchaCode + }, success: res => { this.isRepeat = false; if (res.code == 0) { @@ -431,6 +534,8 @@ export default { } } else { if (res.message == 'FORM_LOGIN_REQUIRED') { + // 提交时后端仍要求登录(如登录态过期):暂存数据,重登后自动续提交 + this.savePendingSubmit(formData) this.$refs.login && this.$refs.login.open(this.backUrl) } else if (res.message == 'CAPTCHA_ERROR' || res.message == 'CAPTCHA_FAILURE') { this.$util.showToast({ title: this.$lang('form.form.captchaError') }) @@ -651,6 +756,19 @@ page { border-radius: 12rpx; border: 2rpx solid #e5e7eb; } + + .captcha-btn { + margin-left: 20rpx; + height: 72rpx; + line-height: 72rpx; + padding: 0 24rpx; + font-size: 26rpx; + color: #ff4544; + background: #fff; + border: 2rpx solid #ff4544; + border-radius: 12rpx; + white-space: nowrap; + } } // 提交按钮:视觉对齐 diy-form 内置按钮(红底白字圆角,medium 内边距) .custom-submit {