297 lines
9.1 KiB
Vue
297 lines
9.1 KiB
Vue
<template>
|
||
<view :style="themeColor">
|
||
<!-- 返回主页按钮:左上角固定,始终可见 -->
|
||
<view class="home-btn" @click="goHome">
|
||
<text class="home-btn-text">{{ $lang('form.formdata.backHome') }}</text>
|
||
</view>
|
||
<block v-if="detail">
|
||
<view class="page">
|
||
<view class="system-form-wrap">
|
||
<!-- 复用 diy-form:banner / tip / 表单字段 / 提交按钮 全部由其内部渲染,样式(bannerPaddingX/tipColor/submitBgColor 等)由 form_config 控制。
|
||
:onSubmit 接管提交逻辑(带 uniacid + showModal);跨 H5 / MP 一致,不依赖 $listeners。
|
||
注意:create 重度依赖本组件 this(id/uniacid/memberInfo/$api 等),必须在 created 阶段 bind this,
|
||
否则 diy-form 通过 this.onSubmit(formData) 调用时 this 会漂移到子组件实例 -->
|
||
<diy-form ref="form" :value="diyValue" dataMode="raw" :onSubmit="handleSubmit" />
|
||
</view>
|
||
</view>
|
||
</block>
|
||
<block v-else>
|
||
<ns-empty :text="complete ? $lang('form.formdata.submitSuccess') : $lang('form.formdata.noFormInfo')"
|
||
:isIndex="false"></ns-empty>
|
||
</block>
|
||
<loading-cover ref="loadingCover"></loading-cover>
|
||
<ns-login ref="login"></ns-login>
|
||
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
|
||
import diyForm from '@/components-diy/diy-form.vue';
|
||
import scroll from '@/common/js/scroll-view.js';
|
||
|
||
export default {
|
||
components: {
|
||
diyForm
|
||
},
|
||
mixins: [scroll],
|
||
data() {
|
||
return {
|
||
id: 0,
|
||
detail: null,
|
||
isRepeat: false,
|
||
complete: false,
|
||
scroll: true,
|
||
uniacid: 0,
|
||
isFromMpWebView: false, // 是否是从微信小程序的WebView跳转过来的
|
||
hideNavbar: false, // 查询参数中支持隐藏Navbar
|
||
urlParams: {}, // 通用 URL 透传参数收集
|
||
handleSubmit: null, // create 的 this 绑定版本,作为 :onSubmit 传给 diy-form(见 created)
|
||
|
||
// #ifdef H5
|
||
formTitle: '', // 查询参数传入的Title
|
||
enableSetTitle: false, // 查询参数传入的是否可以强制设置Title
|
||
// #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: {
|
||
// 构造 diy-form raw 模式期望的 value 形态:
|
||
// - jsonData:透传给 ns-newform(已是 ns-newform 期望的 [{ controller, value, val }])
|
||
// - formConfig:后端返回 camelCase(如 formPaddingX / formBgColor / labelFontSize),
|
||
// diy-form 内部 mergedFormConfig 同时兼容 snake_case 后再交给 ns-newform
|
||
// - show_banner / form_banner:交给 diy-form 渲染 banner,样式走 form_config.bannerPaddingX;form_banner 给保底图
|
||
// - show_tip / form_tip:交给 diy-form 渲染 tip,样式走 form_config;form_tip 给保底文案
|
||
// 注:本 computed 只在 <block v-if="detail"> 内被消费,无需 null 守卫
|
||
diyValue() {
|
||
return {
|
||
jsonData: this.detail.json_data,
|
||
formConfig: this.detail.form_config,
|
||
show_banner: this.detail.show_banner,
|
||
form_banner: this.detail.form_banner || 'public/uniapp/form/banner.png',
|
||
show_tip: this.detail.show_tip,
|
||
form_tip: this.detail.form_tip || this.$lang('form.formdata.formTipPlaceholder')
|
||
}
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
console.log("=====表单页面====", options)
|
||
if (!options.uniacid) {
|
||
uni.showModal({
|
||
title: this.$lang('form.formdata.tip'),
|
||
content: this.$lang('form.formdata.paramError'),
|
||
showCancel: false,
|
||
success: () => { }
|
||
})
|
||
return false
|
||
}
|
||
|
||
uni.setStorageSync('skipPrivacyCheck', true);
|
||
|
||
// #ifdef H5
|
||
this.formTitle = options?.title ?? '';
|
||
this.enableSetTitle = options?.enable_set_title == 1;
|
||
// #endif
|
||
|
||
this.uniacid = options.uniacid
|
||
this.id = options.id || 0
|
||
this.isFromMpWebView = options.from === 'mp-webview';
|
||
this.hideNavbar = Boolean(options?.hide_navbar);
|
||
|
||
// 通用 URL 参数收集:除业务保留字段外,其余全部透传给后端
|
||
// 后端会做卫生过滤并广播事件,供 recommend 等插件消费
|
||
const reservedKeys = ['uniacid', 'id', 'scene', 'from', 'hide_navbar', 'title', 'enable_set_title'];
|
||
this.urlParams = {};
|
||
for (let key in options) {
|
||
if (reservedKeys.indexOf(key) !== -1) continue;
|
||
this.urlParams[key] = options[key];
|
||
}
|
||
|
||
if (options.scene) {
|
||
const scene = decodeURIComponent(options.scene)
|
||
const params = scene.split('&')
|
||
params.length && params.forEach(item => {
|
||
if (item.indexOf('id') != -1) {
|
||
this.id = item.split('-')[1]
|
||
}
|
||
// scene 中也可能携带其他参数
|
||
let pair = item.split('=');
|
||
if (pair.length === 2 && pair[0] !== 'id') {
|
||
this.urlParams[pair[0]] = pair[1];
|
||
}
|
||
})
|
||
}
|
||
|
||
// #ifdef H5
|
||
if (this.isFromMpWebView || this.hideNavbar) {
|
||
this.$nextTick(() => {
|
||
const navbar = document.querySelector('uni-page-head')
|
||
if (navbar) navbar.style.display = 'none'
|
||
})
|
||
}
|
||
// #endif
|
||
|
||
this.getData()
|
||
},
|
||
|
||
watch: {
|
||
storeToken(newVal, oldVal) {
|
||
newVal && this.getData()
|
||
}
|
||
},
|
||
onShow() {
|
||
this.updateNavbarTitle();
|
||
},
|
||
methods: {
|
||
getData() {
|
||
this.$api.sendRequest({
|
||
url: '/form/api/form/info',
|
||
data: {
|
||
form_id: this.id,
|
||
uniacid: this.uniacid
|
||
},
|
||
success: res => {
|
||
if (res.code == 0 && res.data) {
|
||
this.detail = res.data
|
||
this.updateNavbarTitle();
|
||
}
|
||
this.$refs.loadingCover && this.$refs.loadingCover.hide()
|
||
},
|
||
fail: () => {
|
||
this.$refs.loadingCover && this.$refs.loadingCover.hide()
|
||
}
|
||
})
|
||
},
|
||
updateNavbarTitle() {
|
||
if (!this.detail?.form_name) return;
|
||
let formName = this.detail.form_name;
|
||
// #ifdef H5
|
||
if (this.enableSetTitle && this.formTitle) {
|
||
formName = this.formTitle;
|
||
}
|
||
// #endif
|
||
uni.setNavigationBarTitle({ title: formName });
|
||
},
|
||
// 返回主页(用 reLaunch 避免返回后还能 navigateBack 回到已提交的表单页)
|
||
goHome() {
|
||
this.$util.redirectTo(this.$util.INDEX_PAGE_URL, {}, 'reLaunch');
|
||
},
|
||
// 清空表单数据:通过 diy-form 的 ref 拿到 ns-newform 实例,调用 setFormData 重置
|
||
resetForm() {
|
||
try {
|
||
if (this.$refs.form && this.$refs.form.$refs.nsNewform && this.$refs.form.$refs.nsNewform.setFormData) {
|
||
this.$refs.form.$refs.nsNewform.setFormData();
|
||
}
|
||
} catch(e) {}
|
||
},
|
||
// 由 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) {
|
||
// 防重复提交:console 在拦截之前,避免日志噪声;拦截后立即置位
|
||
if (this.isRepeat) 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
|
||
},
|
||
success: res => {
|
||
if (res.code == 0) {
|
||
this.complete = true
|
||
// 提交成功后清空表单
|
||
this.resetForm()
|
||
this.isRepeat = false
|
||
uni.showModal({
|
||
title: this.$lang('form.formdata.tip'),
|
||
content: this.$lang('form.formdata.submitSuccess'),
|
||
showCancel: false,
|
||
success: () => { }
|
||
})
|
||
} else {
|
||
this.isRepeat = false
|
||
this.$util.showToast({
|
||
title: res.message
|
||
})
|
||
}
|
||
},
|
||
fail: () => {
|
||
// 网络失败时复位防重复标记,允许用户重试
|
||
this.isRepeat = false
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
page {
|
||
background: #fff;
|
||
}
|
||
|
||
.system-form-wrap {
|
||
overflow: hidden;
|
||
margin: 0 0 60rpx 0;
|
||
}
|
||
|
||
// 穿透 diy-form 包装层:消除其自带的 10rpx 圆角与白色背景,
|
||
// 让 form 内容区域保持与原始 ns-newform 接入时一致的视觉
|
||
.system-form-wrap ::v-deep .diy-from {
|
||
background: transparent;
|
||
border-radius: 0;
|
||
}
|
||
|
||
// 左上角返回主页按钮
|
||
.home-btn {
|
||
position: fixed;
|
||
left: 30rpx;
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-LARK
|
||
// 小程序端需避开右上角胶囊按钮,下移到状态栏 + 胶囊高度之下
|
||
top: calc(var(--status-bar-height, 20px) + 10rpx);
|
||
// #endif
|
||
// #ifdef H5
|
||
top: 20rpx;
|
||
// #endif
|
||
z-index: 999;
|
||
padding: 12rpx 28rpx;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
border-radius: 40rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.home-btn-text {
|
||
color: #ffffff;
|
||
font-size: 26rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
::v-deep .mescroll-totop {
|
||
/* #ifdef H5 */
|
||
right: 27rpx !important;
|
||
bottom: 120rpx !important;
|
||
/* #endif */
|
||
/* #ifdef MP */
|
||
right: 27rpx !important;
|
||
bottom: 180rpx !important;
|
||
/* #endif */
|
||
}
|
||
</style>
|