chore:表单提交正常
This commit is contained in:
@@ -10,7 +10,13 @@
|
||||
<!-- 行为设置:提交验证码 -->
|
||||
<view class="captcha-wrap" v-if="behavior.captcha == 1">
|
||||
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
|
||||
<image class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #ifdef H5 -->
|
||||
<img v-if="captcha.img" class="captcha-img" :src="captcha.img" @click="getCaptcha()">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<image v-if="captcha.img" class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #endif -->
|
||||
<view class="captcha-btn" @click="getCaptcha()">{{ captcha.img ? '换一张' : '获取验证码' }}</view>
|
||||
</view>
|
||||
<button type="primary" size="mini" class="button mini" @click="create()">{{ $lang('common.submit') }}</button>
|
||||
</view>
|
||||
@@ -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 {
|
||||
|
||||
@@ -40,7 +40,13 @@
|
||||
<view v-if="showCaptcha" class="form-extra-wrap">
|
||||
<view class="captcha-wrap">
|
||||
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
|
||||
<image class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #ifdef H5 -->
|
||||
<img v-if="captcha.img" class="captcha-img" :src="captcha.img" @click="getCaptcha()">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<image v-if="captcha.img" class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #endif -->
|
||||
<view class="captcha-btn" @click="getCaptcha()">{{ captcha.img ? '换一张' : '获取验证码' }}</view>
|
||||
</view>
|
||||
<view class="custom-submit" @click="submitFromPage">{{ submitBtnText }}</view>
|
||||
</view>
|
||||
@@ -60,7 +66,13 @@
|
||||
<view v-if="showCaptcha" class="form-extra-wrap">
|
||||
<view class="captcha-wrap">
|
||||
<input class="captcha-input" type="text" v-model="captcha.code" :placeholder="$lang('form.form.captchaPlaceholder')" />
|
||||
<image class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #ifdef H5 -->
|
||||
<img v-if="captcha.img" class="captcha-img" :src="captcha.img" @click="getCaptcha()">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<image v-if="captcha.img" class="captcha-img" :src="captcha.img" mode="aspectFill" @click="getCaptcha()"></image>
|
||||
<!-- #endif -->
|
||||
<view class="captcha-btn" @click="getCaptcha()">{{ captcha.img ? '换一张' : '获取验证码' }}</view>
|
||||
</view>
|
||||
<view class="custom-submit" @click="submitFromPage">{{ submitBtnText }}</view>
|
||||
</view>
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user