refactor(form): 重构提交逻辑以支持跨端统一接管

diy-form 组件将提交事件机制改为函数 prop (onSubmit) 接管,避免 $listeners 在 MP 端不可用的问题。
formdata 页面使用 bind 预绑定 this,防止子组件调用时上下文漂移。
新增防重复提交标记复位逻辑,优化网络失败时的重试体验。
This commit is contained in:
2026-08-07 10:31:57 +08:00
parent 619ca3a236
commit c26280a4d3
3 changed files with 76 additions and 31 deletions

View File

@@ -22,13 +22,18 @@
<!-- 提交按钮(外层 = form-bg + form-padding-x;按钮 = submit-bg + submit-color + input-height + input-radius)。 <!-- 提交按钮(外层 = form-bg + form-padding-x;按钮 = submit-bg + submit-color + input-height + input-radius)。
showSubmit=false 时由父级页面渲染自己的提交按钮并通过 ref 调用本组件的 verify()/getFormData()。 --> showSubmit=false 时由父级页面渲染自己的提交按钮并通过 ref 调用本组件的 verify()/getFormData()。 -->
<view v-if="showSubmit" class="form-submit-wrap" :style="submitWrapStyle"> <view v-if="showSubmit" class="form-submit-wrap" :style="submitWrapStyle">
<view @click.stop="submitForm" class="form-submit" :style="submitStyle">{{ submitText }}</view> <view @click.stop="handleSubmit" class="form-submit" :style="submitStyle">{{ submitText }}</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
// 自定义表单 // 自定义表单
// 提交接管三档(不依赖 $listeners / 事件机制,跨 H5 / MP / 鸿蒙一致):
// 1) 默认 → showSubmit=true + 不传 onSubmit → 内置按钮 + 内置 defaultSubmit
// 2) 复用按钮、自定义逻辑 → showSubmit=true + :onSubmit="fn" → 内置按钮 + 父级 fn
// 3) 完全自定义 UI → showSubmit=false + :onSubmit="fn" 或 ref.verify()/getFormData() → 父级全控
// 注:type: Function prop 在 uni-app MP 端走 Vue runtime 注入,机制与 ns-navbar.customBack 一致
import DiyMinx from './minx.js' import DiyMinx from './minx.js'
import NsNewform from '@/components/ns-newform/ns-newform.vue' import NsNewform from '@/components/ns-newform/ns-newform.vue'
@@ -52,6 +57,12 @@ export default {
showSubmit: { showSubmit: {
type: Boolean, type: Boolean,
default: true default: true
},
// 父级可选地接管提交:传入函数 prop 时,verify 通过后由它处理 formData;
// 不传时走组件内置 defaultSubmit(兜底)。default 必须是 null,不能写成函数返回 null
onSubmit: {
type: Function,
default: null
} }
}, },
mixins: [DiyMinx], mixins: [DiyMinx],
@@ -148,6 +159,10 @@ export default {
submitText() { submitText() {
return this.mergedFormConfig.submitText || '提交'; return this.mergedFormConfig.submitText || '提交';
}, },
// 父级是否接管提交(集中判断,便于以后替换机制;避免多个调用点各自 typeof 校验)
hasCustomSubmit() {
return typeof this.onSubmit === 'function';
},
// 后端把表单样式配置统一存放在 data.formConfig 子对象中(与后端 design.js getDefaultFormData().formConfig 字段名一致)。 // 后端把表单样式配置统一存放在 data.formConfig 子对象中(与后端 design.js getDefaultFormData().formConfig 字段名一致)。
// 这里做一次归一化:优先取 formConfig;如果历史数据是扁平结构(直接挂在 value 上),也做兼容。 // 这里做一次归一化:优先取 formConfig;如果历史数据是扁平结构(直接挂在 value 上),也做兼容。
formCfg() { formCfg() {
@@ -286,24 +301,26 @@ export default {
return true; return true;
}, },
// 提交表单 // 统一提交入口:内置按钮 / 父级 ref 调用都走这里
submitForm() { // 不依赖 $listeners / 事件机制(MP 端 $listeners 为 undefined),跨端一致
var self = this; handleSubmit() {
// 校验(同时返回 formData 数组;校验失败时 ns-newform 内部 toast 提示) // 校验(同时返回 formData 数组;校验失败时 ns-newform 内部 toast 提示)
if (!this.verify()) { if (!this.verify()) {
return; return;
} }
const formData = this.getFormData();
// 若父级监听了 @submit,则把提交控制权交给父级(用于父级有自定义提交字段/反馈逻辑的页面,如 formdata.vue 传 uniacid) // 父级显式接管:function prop 在 H5 / MP / 鸿蒙一致可用
if (this.$listeners && this.$listeners.submit) { if (this.hasCustomSubmit) {
this.$emit('submit', this.getFormData()); return this.onSubmit(formData);
return;
} }
// 兜底:内置 sendRequest 流程
this.defaultSubmit(formData);
},
// 获得保单数据 // 公共兜底实现:默认的 /form/api/form/create 流程;
const submitFormData = this.getFormData(); // 父级若需"先自定义、失败回退",可经 ref.defaultSubmit(formData) 调用
defaultSubmit(submitFormData) {
// 提交到后端 var self = this;
this.$api.sendRequest({ this.$api.sendRequest({
url: '/form/api/form/create', url: '/form/api/form/create',
data: { data: {

View File

@@ -978,6 +978,7 @@ export default {
}, },
onShareClick() { onShareClick() {
console.log('[推荐码分享]点击分享按钮,完整推广链接:', this.formatShareLink,'当前推荐码:',this.code);
const vm = this; const vm = this;
const v = this.nc; const v = this.nc;
const shareUrl = this.formatShareLink; // 改用后台拼接链接 const shareUrl = this.formatShareLink; // 改用后台拼接链接
@@ -1013,6 +1014,9 @@ onShareClick() {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
// open-type="share" 会自动触发 onShareAppMessage 弹出微信原生分享面板 // open-type="share" 会自动触发 onShareAppMessage 弹出微信原生分享面板
// 存储分享数据供页面级 onShareAppMessage 读取(组件级钩子会被页面级覆盖) // 存储分享数据供页面级 onShareAppMessage 读取(组件级钩子会被页面级覆盖)
console.log('分享链接准备,原始配置', v);
console.log('生成的分享完整路径:', this.extractMiniProgramPath());
try { try {
var shareData = { var shareData = {
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台', title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',

View File

@@ -2,16 +2,18 @@
<view :style="themeColor"> <view :style="themeColor">
<block v-if="detail"> <block v-if="detail">
<view class="page"> <view class="page">
<view class="system-form-wrap"> <view class="system-form-wrap">
<!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。 <!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。
@submit 接管提交逻辑(带 uniacid + showModal) --> :onSubmit 接管提交逻辑(带 uniacid + showModal);跨 H5 / MP 一致,不依赖 $listeners。
<diy-form 注意:create 重度依赖本组件 this(id/uniacid/memberInfo/$api 等),必须在 created 阶段 bind this,
ref="form" 否则 diy-form 通过 this.onSubmit(formData) 调用时 this 会漂移到子组件实例 -->
:value="diyValue" <diy-form
dataMode="raw" ref="form"
@submit="create" :value="diyValue"
/> dataMode="raw"
</view> :onSubmit="handleSubmit"
/>
</view>
</view> </view>
</block> </block>
<block v-else> <block v-else>
@@ -44,6 +46,7 @@ export default {
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的 isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
hideNavbar: false, // 查询参数中支持隐藏Navbar hideNavbar: false, // 查询参数中支持隐藏Navbar
urlParams: {}, // 通用 URL 透传参数收集 urlParams: {}, // 通用 URL 透传参数收集
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
// #ifdef H5 // #ifdef H5
formTitle: '', // 查询参数传入的Title formTitle: '', // 查询参数传入的Title
@@ -51,6 +54,13 @@ export default {
// #endif // #endif
} }
}, },
created() {
// 预绑定 this:create 重度依赖本组件 this(id/uniacid/memberInfo/$api/$lang/$util 等)。
// 若直接传 :onSubmit="create",diy-form 通过 this.onSubmit(formData) 调用时,
// 普通函数调用的 this 会指向调用者(子组件实例),导致字段全部读不到。
// bind 后无论子组件如何调用,this 始终是 formdata 实例。
this.handleSubmit = this.create.bind(this);
},
computed: { computed: {
// 构造 diy-form raw 模式期望的 value 形态: // 构造 diy-form raw 模式期望的 value 形态:
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }]) // - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
@@ -71,6 +81,7 @@ export default {
} }
}, },
onLoad(options) { onLoad(options) {
console.log("=====表单页面====",options)
if (!options.uniacid) { if (!options.uniacid) {
uni.showModal({ uni.showModal({
title: this.$lang('form.formdata.tip'), title: this.$lang('form.formdata.tip'),
@@ -167,22 +178,31 @@ export default {
// #endif // #endif
uni.setNavigationBarTitle({ title: formName }); uni.setNavigationBarTitle({ title: formName });
}, },
// 由 diy-form 内置提交按钮触发:diy-form 已先做 verify(),仅在通过时才 emit('submit', formData) // 由 diy-form 内置提交按钮触发:diy-form handleSubmit 已先做 verify(),
// 仅在通过时才通过 :onSubmit 函数 prop 调用本方法(跨 H5 / MP 一致,不走 $emit / $listeners)。
// 注意:本方法通过 created 中 this.handleSubmit = this.create.bind(this) 预绑定 this,
// 因此内部所有 this.xxx 都指向 formdata 实例,不会漂移到子组件。
create(formData) { create(formData) {
// 防重复提交:console 在拦截之前,避免日志噪声;拦截后立即置位
if (this.isRepeat) return if (this.isRepeat) return
const 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
}
console.log(`=====表单提交====`, data);
this.isRepeat = true this.isRepeat = true
this.$api.sendRequest({ this.$api.sendRequest({
url: '/form/api/form/create', url: '/form/api/form/create',
data: { 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
},
success: res => { success: res => {
if (res.code == 0) { if (res.code == 0) {
this.complete = true
uni.showModal({ uni.showModal({
title: this.$lang('form.formdata.tip'), title: this.$lang('form.formdata.tip'),
content: this.$lang('form.formdata.submitSuccess'), content: this.$lang('form.formdata.submitSuccess'),
@@ -195,6 +215,10 @@ export default {
title: res.message title: res.message
}) })
} }
},
fail: () => {
// 网络失败时复位防重复标记,允许用户重试
this.isRepeat = false
} }
}) })
} }