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 @@
@@ -60,7 +66,13 @@
@@ -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 {