refactor: 移除旧的邀请码来源追踪逻辑
1. 删除App.vue中全局邀请码持久化逻辑与相关工具函数 2. 简化http请求的方法与请求头处理逻辑 3. 移除diy-form组件内的邀请码暂存、提交与展示逻辑 4. 清理不再使用的工具类邀请码辅助函数
This commit is contained in:
67
App.vue
67
App.vue
@@ -90,43 +90,6 @@
|
|||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// ✅ 推荐码 / 邀请码(来源追踪)全局兜底
|
|
||||||
// 规则:只写非空值,不主动清空(diy-form 提交成功后自己清)
|
|
||||||
// ============================================================
|
|
||||||
const FORM_INVITE_KEY = 'form_invite_pending_code'
|
|
||||||
function _persistInviteCodeFromOptions(options) {
|
|
||||||
try {
|
|
||||||
var code = ''
|
|
||||||
// 1) onLaunch/onShow options.query:优先 recommend_code,次选 invite(历史兼容)
|
|
||||||
if (options && options.query) {
|
|
||||||
if (options.query.recommend_code) {
|
|
||||||
code = String(options.query.recommend_code)
|
|
||||||
} else if (options.query.invite) {
|
|
||||||
code = String(options.query.invite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2) H5 兜底:从当前 location.search 取(同样优先 recommend_code)
|
|
||||||
if (!code) {
|
|
||||||
// #ifdef H5
|
|
||||||
try {
|
|
||||||
var sp = (new URL(window.location.href)).searchParams
|
|
||||||
var s1 = sp.get('recommend_code')
|
|
||||||
var s2 = sp.get('invite')
|
|
||||||
if (s1) code = String(s1)
|
|
||||||
else if (s2) code = String(s2)
|
|
||||||
} catch (e) {}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
// 3) 只写非空,不擦除已有暂存
|
|
||||||
if (code) {
|
|
||||||
uni.setStorageSync(FORM_INVITE_KEY, code)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// 极端失败静默,diy-form 仍有 P2/P3 兜底读取
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 主题/语言启动
|
// 主题/语言启动
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -545,36 +508,6 @@
|
|||||||
// #ifdef MP-ALIPAY
|
// #ifdef MP-ALIPAY
|
||||||
if (options.query && options.query.m) uni.setStorageSync('source_member', options.query.m);
|
if (options.query && options.query.m) uni.setStorageSync('source_member', options.query.m);
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// ✅ 邀请码(来源追踪):onLaunch 冷启动兜底解析 URL query → storage
|
|
||||||
_persistInviteCodeFromOptions(options);
|
|
||||||
},
|
|
||||||
onShow: function(options) {
|
|
||||||
// ✅ 邀请码(来源追踪):onShow 热启动/后台切回 兜底解析
|
|
||||||
// 注:onShow 的 options 仅部分平台(MP-WEIXIN, MP-HARMONY, MP-ALIPAY 等)提供 query
|
|
||||||
// 若 options 为空,则 helper 内部自动 fallback 到 H5 location.search 或已有 storage
|
|
||||||
_persistInviteCodeFromOptions(options || {});
|
|
||||||
|
|
||||||
// #ifdef MP
|
|
||||||
// 小程序端:每次回到前台 → 自动授权登录刷新会话 token + memberInfo(与行业模板一致)
|
|
||||||
if (typeof this.getAuthInfo === 'function') {
|
|
||||||
this.getAuthInfo();
|
|
||||||
}
|
|
||||||
if (this.$store && this.$store.state && this.$store.state.token && this.$api) {
|
|
||||||
this.$api.sendRequest({
|
|
||||||
url: '/api/member/info',
|
|
||||||
success: (res) => {
|
|
||||||
if (res && res.code >= 0 && res.data && this.$store && this.$store.commit) {
|
|
||||||
this.$store.commit('setMemberInfo', res.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
if (options && options.query && options.query.m) uni.setStorageSync('source_member', options.query.m);
|
|
||||||
// #endif
|
|
||||||
},
|
},
|
||||||
onHide: function() {},
|
onHide: function() {},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -173,19 +173,8 @@ export default {
|
|||||||
|
|
||||||
_sendRequestNow(params) {
|
_sendRequestNow(params) {
|
||||||
|
|
||||||
// ✅ 关键修复 1:支持调用方指定 params.method(GET/POST/PUT/DELETE);
|
var method = params.data != undefined ? 'POST' : 'GET',
|
||||||
// 没指定时按老规则:有 data → POST,没 data → GET
|
url = (Config.baseUrl + params.url).replace(/(?<!:)\/+/g, '/'),
|
||||||
var method;
|
|
||||||
if (params.method && typeof params.method === 'string') {
|
|
||||||
method = String(params.method).toUpperCase();
|
|
||||||
if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].indexOf(method) === -1) {
|
|
||||||
method = 'GET';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
method = params.data != undefined ? 'POST' : 'GET';
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = (Config.baseUrl + params.url).replace(/(?<!:)\/+/g, '/'),
|
|
||||||
data = {
|
data = {
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
app_type_name: appTypeName
|
app_type_name: appTypeName
|
||||||
@@ -205,22 +194,9 @@ export default {
|
|||||||
|
|
||||||
if (params.data != undefined) Object.assign(data, params.data);
|
if (params.data != undefined) Object.assign(data, params.data);
|
||||||
|
|
||||||
// ✅ 关键修复 2:Content-Type 合法值,两个 MIME 类型不要乱拼接
|
const header = params.header || {
|
||||||
// 规则:
|
'content-type': 'application/x-www-form-urlencoded;application/json'
|
||||||
// - 调用方 params.header 里指定了 content-type → 优先用调用方的(保持原有 override 能力)
|
};
|
||||||
// - GET/HEAD/DELETE:application/x-www-form-urlencoded(URL query 编码)
|
|
||||||
// - POST/PUT/PATCH Object data:application/json(uni.request 会自动 JSON.stringify)
|
|
||||||
// - 如果调用方传 FormData / ArrayBuffer 等二进制:不设 Content-Type,让 uni 自动处理
|
|
||||||
var autoHeader = {};
|
|
||||||
if (method === 'GET' || method === 'HEAD' || method === 'DELETE') {
|
|
||||||
autoHeader['content-type'] = 'application/x-www-form-urlencoded';
|
|
||||||
} else {
|
|
||||||
// POST/PUT/PATCH:默认用 application/json,后端能直接 json_decode(file_get_contents('php://input'))
|
|
||||||
autoHeader['content-type'] = 'application/json';
|
|
||||||
}
|
|
||||||
// 调用方 params.header 合并(调用方指定的覆盖自动设置的)
|
|
||||||
const header = Object.assign({}, autoHeader, (params.header && typeof params.header === 'object') ? params.header : {});
|
|
||||||
|
|
||||||
const dataType = params.dataType || 'json';
|
const dataType = params.dataType || 'json';
|
||||||
const responseType = params.responseType || 'text';
|
const responseType = params.responseType || 'text';
|
||||||
|
|
||||||
@@ -251,9 +227,8 @@ export default {
|
|||||||
uni.request({
|
uni.request({
|
||||||
url: Config.baseUrl + '/api/upload/headimgBase64',
|
url: Config.baseUrl + '/api/upload/headimgBase64',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
// ✅ 修复:application/json,后端能直接 json_decode
|
|
||||||
header: {
|
header: {
|
||||||
'content-type': 'application/json'
|
'content-type': 'application/x-www-form-urlencoded;application/json'
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
@@ -266,7 +241,7 @@ export default {
|
|||||||
console.log('[HTTP][RESPONSE] uploadBase64', 'statusCode=', res.statusCode, JSON.stringify(res.data));
|
console.log('[HTTP][RESPONSE] uploadBase64', 'statusCode=', res.statusCode, JSON.stringify(res.data));
|
||||||
typeof params.success == 'function' && params.success(res.data);
|
typeof params.success == 'function' && params.success(res.data);
|
||||||
},
|
},
|
||||||
fail: (res) => {
|
fail: () => {
|
||||||
console.error('[HTTP][FAIL] uploadBase64', JSON.stringify(res));
|
console.error('[HTTP][FAIL] uploadBase64', JSON.stringify(res));
|
||||||
typeof params.fail == 'function' && params.fail(res);
|
typeof params.fail == 'function' && params.fail(res);
|
||||||
}
|
}
|
||||||
@@ -277,9 +252,8 @@ export default {
|
|||||||
uni.request({
|
uni.request({
|
||||||
url: Config.baseUrl + '/api/upload/headimgPull',
|
url: Config.baseUrl + '/api/upload/headimgPull',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
// ✅ 修复:application/json
|
|
||||||
header: {
|
header: {
|
||||||
'content-type': 'application/json'
|
'content-type': 'application/x-www-form-urlencoded;application/json'
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
@@ -292,7 +266,7 @@ export default {
|
|||||||
console.log('[HTTP][RESPONSE] pullImage', 'statusCode=', res.statusCode, JSON.stringify(res.data));
|
console.log('[HTTP][RESPONSE] pullImage', 'statusCode=', res.statusCode, JSON.stringify(res.data));
|
||||||
typeof params.success == 'function' && params.success(res.data);
|
typeof params.success == 'function' && params.success(res.data);
|
||||||
},
|
},
|
||||||
fail: (res) => {
|
fail: () => {
|
||||||
console.error('[HTTP][FAIL] pullImage', JSON.stringify(res));
|
console.error('[HTTP][FAIL] pullImage', JSON.stringify(res));
|
||||||
typeof params.fail == 'function' && params.fail(res);
|
typeof params.fail == 'function' && params.fail(res);
|
||||||
}
|
}
|
||||||
@@ -300,17 +274,6 @@ export default {
|
|||||||
},
|
},
|
||||||
upload(params) {
|
upload(params) {
|
||||||
console.log('[HTTP][REQUEST] upload', Config.baseUrl + params.url, 'name=', params.name || 'file');
|
console.log('[HTTP][REQUEST] upload', Config.baseUrl + params.url, 'name=', params.name || 'file');
|
||||||
// ✅ 关键修复:uni.uploadFile **绝对不能手动设置 Content-Type**!
|
|
||||||
// 因为 uni.uploadFile 会自动设置成 multipart/form-data; boundary=----xxxxxx
|
|
||||||
// 手动设置会覆盖掉 boundary,后端无法解析分段上传,导致文件/表单字段解析为空
|
|
||||||
var uploadHeader = {};
|
|
||||||
// 只合并调用方传的自定义 header,不默认加 content-type
|
|
||||||
if (params.header && typeof params.header === 'object') {
|
|
||||||
Object.assign(uploadHeader, params.header);
|
|
||||||
// 兜底:如果调用方误传了 content-type,就删掉,让 uni 自动设置 multipart/form-data
|
|
||||||
delete uploadHeader['content-type'];
|
|
||||||
delete uploadHeader['Content-Type'];
|
|
||||||
}
|
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: Config.baseUrl + params.url,
|
url: Config.baseUrl + params.url,
|
||||||
filePath: params.filePath,
|
filePath: params.filePath,
|
||||||
@@ -319,10 +282,10 @@ export default {
|
|||||||
formData: {
|
formData: {
|
||||||
app_type: appType,
|
app_type: appType,
|
||||||
app_type_name: appTypeName,
|
app_type_name: appTypeName,
|
||||||
token: store.state.token || '',
|
|
||||||
uniacid: uni.getStorageSync('uniacid') || Config.uniacid
|
|
||||||
},
|
},
|
||||||
header: uploadHeader,
|
header: {
|
||||||
|
'content-type': 'application/x-www-form-urlencoded;application/json'
|
||||||
|
},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log('[HTTP][RESPONSE] upload', Config.baseUrl + params.url, 'statusCode=', res.statusCode, res.data);
|
console.log('[HTTP][RESPONSE] upload', Config.baseUrl + params.url, 'statusCode=', res.statusCode, res.data);
|
||||||
typeof params.success == 'function' && params.success(JSON.parse(res.data));
|
typeof params.success == 'function' && params.success(JSON.parse(res.data));
|
||||||
|
|||||||
@@ -76,113 +76,6 @@ function _getCurrentUniacid() {
|
|||||||
return _pickCurrentUniacid(storageVal, configVal);
|
return _pickCurrentUniacid(storageVal, configVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从当前登录态读取 memberInfo → 返回后端接口真实生成的推荐码
|
|
||||||
* 读取优先级:storage.memberInfo.recommend_code > Vuex store.state.memberInfo.recommend_code
|
|
||||||
* 没登录 / 未同意授权 / 后端还没返回码 → 返回 ''
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function _getCurrentRecommendCode() {
|
|
||||||
// P1: storage 中 memberInfo.recommend_code(DIY 推荐码组件 _persistRecommendInfo 已写入)
|
|
||||||
try {
|
|
||||||
var info = null;
|
|
||||||
if (typeof uni !== 'undefined' && uni && typeof uni.getStorageSync === 'function') {
|
|
||||||
info = uni.getStorageSync('memberInfo');
|
|
||||||
}
|
|
||||||
if (info && typeof info === 'object') {
|
|
||||||
if (info.recommend_code && String(info.recommend_code).trim()) {
|
|
||||||
return String(info.recommend_code).trim();
|
|
||||||
}
|
|
||||||
// 兼容旧字段 invite_code(商家之前保存的配置)
|
|
||||||
if (info.invite_code && String(info.invite_code).trim()) {
|
|
||||||
return String(info.invite_code).trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
// P2: Vuex store.state.memberInfo(某些平台 storage 还没落地时,内存里可能已经好了)
|
|
||||||
try {
|
|
||||||
if (store && store.state && store.state.memberInfo && typeof store.state.memberInfo === 'object') {
|
|
||||||
var s = store.state.memberInfo;
|
|
||||||
if (s.recommend_code && String(s.recommend_code).trim()) {
|
|
||||||
return String(s.recommend_code).trim();
|
|
||||||
}
|
|
||||||
if (s.invite_code && String(s.invite_code).trim()) {
|
|
||||||
return String(s.invite_code).trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 URL 中移除指定 query 参数(防止 URL 里已存在旧的 recommend_code=xxx 时,append 之后重复)
|
|
||||||
* - 仅移除 key,不影响其他参数
|
|
||||||
* - 同时处理 hash(xxx#yyy) 在 query 后面的情况(保留 hash 原位置)
|
|
||||||
* @param {string} url
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function _removeQueryParam(url, key) {
|
|
||||||
if (!url || !key) return url || '';
|
|
||||||
// 先拆 hash
|
|
||||||
var hashIdx = url.indexOf('#');
|
|
||||||
var hash = '';
|
|
||||||
var main = url;
|
|
||||||
if (hashIdx !== -1) {
|
|
||||||
hash = url.substring(hashIdx);
|
|
||||||
main = url.substring(0, hashIdx);
|
|
||||||
}
|
|
||||||
var qIdx = main.indexOf('?');
|
|
||||||
if (qIdx === -1) return url; // 没 query
|
|
||||||
var path = main.substring(0, qIdx);
|
|
||||||
var qs = main.substring(qIdx + 1);
|
|
||||||
if (!qs) return url;
|
|
||||||
var pairs = qs.split('&').filter(function(p) {
|
|
||||||
if (!p) return false;
|
|
||||||
var eq = p.indexOf('=');
|
|
||||||
var k = (eq === -1) ? p : p.substring(0, eq);
|
|
||||||
// 大小写不敏感(URL 参数通常大小写不敏感)
|
|
||||||
return String(k).toLowerCase() !== String(key).toLowerCase();
|
|
||||||
});
|
|
||||||
var newMain = pairs.length ? (path + '?' + pairs.join('&')) : path;
|
|
||||||
return newMain + hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给 URL 追加当前登录会员的推荐码(方案 B 标准函数,推荐使用)
|
|
||||||
* 规则:
|
|
||||||
* - 当前登录且能拿到后端返回的 recommend_code → 移除 URL 里已存在的旧值
|
|
||||||
* → 同时追加 recommend_code + invite 两个 key(双参数兼容,后端收哪个都能用)
|
|
||||||
* - 未登录 / 推荐码为空 → 原样返回
|
|
||||||
* - 兼容内部相对路径 '/pages/xxx?x=1' 与 外部绝对 URL 'https://host/xx#yyy'
|
|
||||||
* @param {string} url
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function _appendRecommendCodeIfLoggedIn(url) {
|
|
||||||
if (!url) return '';
|
|
||||||
var code = _getCurrentRecommendCode();
|
|
||||||
if (!code) return url; // 没登录或没码:不动
|
|
||||||
// 1) 先清 URL 里已存在的旧参数(两个 key 都清,避免重复追加或旧码覆盖新码)
|
|
||||||
var cleaned = _removeQueryParam(url, 'recommend_code');
|
|
||||||
cleaned = _removeQueryParam(cleaned, 'invite');
|
|
||||||
// 2) 双 key 同时追加:标准名 recommend_code + 历史兼容名 invite
|
|
||||||
// 这样不管后端 parseCode 接口收哪个 key,都能正确命中
|
|
||||||
return _appendQueryParamsBase(cleaned, { recommend_code: code, invite: code });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 旧版函数别名(保持对 _appendInviteCodeIfLoggedIn 的调用兼容)
|
|
||||||
* 之前业务代码调用 _appendInviteCodeIfLoggedIn 的,现在全部委托给新的 _appendRecommendCodeIfLoggedIn
|
|
||||||
* 确保:一次改动,全链路统一使用后端真实码 + 双 key 兼容
|
|
||||||
*/
|
|
||||||
function _appendInviteCodeIfLoggedIn(url) {
|
|
||||||
return _appendRecommendCodeIfLoggedIn(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把 URL 上的 page_params 还原成对象。是 _toParamValueString 的逆操作。
|
* 把 URL 上的 page_params 还原成对象。是 _toParamValueString 的逆操作。
|
||||||
* 容错策略:任何解析失败都安全降级到 defaultValue,不抛异常。
|
* 容错策略:任何解析失败都安全降级到 defaultValue,不抛异常。
|
||||||
@@ -1362,11 +1255,6 @@ export default {
|
|||||||
let url = this.normalizeUrl(decodeEntity(link.wap_url));
|
let url = this.normalizeUrl(decodeEntity(link.wap_url));
|
||||||
url = this.appendQueryParams(url, params);
|
url = this.appendQueryParams(url, params);
|
||||||
|
|
||||||
// ✅ 外链跳转自动拼邀请码:当前登录有邀请码 → 去 URL 旧 invite → 追加成新值
|
|
||||||
// H5: window.location.href 新页面打开时 location.search 能收到
|
|
||||||
// MP: webview src URL 打开目标 H5 页面时能收到
|
|
||||||
url = _appendInviteCodeIfLoggedIn(url);
|
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// console.log(`url ---->`, url, link)
|
// console.log(`url ---->`, url, link)
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
@@ -1479,16 +1367,6 @@ export default {
|
|||||||
// resolveLinkUrl 已把 wap_url 中的 query 合并到 link.page_params
|
// resolveLinkUrl 已把 wap_url 中的 query 合并到 link.page_params
|
||||||
// 这里过滤掉 link 元数据字段, 避免 wap_url/name/parent/title 等被错误附加到目标 URL
|
// 这里过滤掉 link 元数据字段, 避免 wap_url/name/parent/title 等被错误附加到目标 URL
|
||||||
// title/enable_set_title/page_params 仍需透传到目标 URL,page_params 放最后
|
// title/enable_set_title/page_params 仍需透传到目标 URL,page_params 放最后
|
||||||
|
|
||||||
// ✅ 内部页:把推荐码写进 page_params(最终会被拼成 URL query,diy-form onLoad options 能收到)
|
|
||||||
// 仅在当前登录已拿到推荐码时追加,不覆盖上游已有值;双写 recommend_code+ invite(历史兼容)
|
|
||||||
var _curRecommendCode = _getCurrentRecommendCode();
|
|
||||||
if (_curRecommendCode) {
|
|
||||||
if (!link.page_params || typeof link.page_params !== 'object') link.page_params = {};
|
|
||||||
if (!link.page_params.recommend_code) link.page_params.recommend_code = _curRecommendCode;
|
|
||||||
if (!link.page_params.invite) link.page_params.invite = _curRecommendCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { wap_url, name, parent, type, appid, page, mobile, image, ...navParams } = link;
|
const { wap_url, name, parent, type, appid, page, mobile, image, ...navParams } = link;
|
||||||
this.redirectTo(link.wap_url, {
|
this.redirectTo(link.wap_url, {
|
||||||
...navParams,
|
...navParams,
|
||||||
|
|||||||
@@ -19,12 +19,6 @@
|
|||||||
:formConfig="mergedFormConfig"
|
:formConfig="mergedFormConfig"
|
||||||
ref="nsNewform"
|
ref="nsNewform"
|
||||||
></ns-newform>
|
></ns-newform>
|
||||||
|
|
||||||
<!-- ✅ 邀请码归属提示(零感知:只有通过邀请链接进入才显示,纯灰色小字居中,防纠纷核对) -->
|
|
||||||
<view v-if="_inviteCodePending" class="form-invite-attribution">
|
|
||||||
<text class="attribution-text">本次提交推荐邀请码:{{ _inviteCodePending }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 提交按钮(外层 = 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">
|
||||||
@@ -63,15 +57,11 @@ export default {
|
|||||||
mixins: [DiyMinx],
|
mixins: [DiyMinx],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formComponents: [], // 转换后的表单组件
|
formComponents: [] // 转换后的表单组件
|
||||||
// ✅ 表单提交邀请码(来源追踪):3 级读取 → 提交自动注入 → 成功清暂存
|
|
||||||
_inviteCodePending: ''
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initForm();
|
this.initForm();
|
||||||
// ✅ 初始化:从 URL query / storage / H5 location 读取邀请码暂存
|
|
||||||
this._initInviteCode();
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'value': {
|
'value': {
|
||||||
@@ -306,48 +296,22 @@ export default {
|
|||||||
|
|
||||||
// 若父级监听了 @submit,则把提交控制权交给父级(用于父级有自定义提交字段/反馈逻辑的页面,如 formdata.vue 传 uniacid)
|
// 若父级监听了 @submit,则把提交控制权交给父级(用于父级有自定义提交字段/反馈逻辑的页面,如 formdata.vue 传 uniacid)
|
||||||
if (this.$listeners && this.$listeners.submit) {
|
if (this.$listeners && this.$listeners.submit) {
|
||||||
// ✅ 父级自定义流程:把 recommend_code + invite_code 都透传给父级,双保险(formData 数组里 + 第二参数对象里)
|
this.$emit('submit', this.getFormData());
|
||||||
var formDataArr = this.getFormData();
|
|
||||||
try {
|
|
||||||
if (this._inviteCodePending && Array.isArray(formDataArr)) {
|
|
||||||
formDataArr = formDataArr.slice();
|
|
||||||
formDataArr.push({
|
|
||||||
controller: 'hidden',
|
|
||||||
name: '_recommend_code',
|
|
||||||
value: this._inviteCodePending,
|
|
||||||
val: this._inviteCodePending
|
|
||||||
});
|
|
||||||
formDataArr.push({
|
|
||||||
controller: 'hidden',
|
|
||||||
name: '_invite_code',
|
|
||||||
value: this._inviteCodePending,
|
|
||||||
val: this._inviteCodePending
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
this.$emit('submit', formDataArr, {
|
|
||||||
recommend_code: this._inviteCodePending,
|
|
||||||
invite_code: this._inviteCodePending
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得保单数据
|
// 获得保单数据
|
||||||
const submitFormData = this.getFormData();
|
const submitFormData = this.getFormData();
|
||||||
var reqData = {
|
|
||||||
form_id: this.value.form_id || 0,
|
|
||||||
form_data: typeof submitFormData === 'string' ? submitFormData : JSON.stringify(submitFormData),
|
|
||||||
component_id: this.value.id || '',
|
|
||||||
form_name: this.value.form_name || '默认表单'
|
|
||||||
};
|
|
||||||
if (this._inviteCodePending) {
|
|
||||||
reqData.recommend_code = this._inviteCodePending; // 方案 B 标准
|
|
||||||
reqData.invite_code = this._inviteCodePending; // 历史兼容
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 提交到后端
|
||||||
this.$api.sendRequest({
|
this.$api.sendRequest({
|
||||||
url: '/form/api/form/create',
|
url: '/form/api/form/create',
|
||||||
data: reqData,
|
data: {
|
||||||
|
form_id: this.value.form_id || 0,
|
||||||
|
form_data: typeof submitFormData === 'string' ? submitFormData : JSON.stringify(submitFormData),
|
||||||
|
component_id: this.value.id || '',
|
||||||
|
form_name: this.value.form_name || '默认表单'
|
||||||
|
},
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -358,8 +322,6 @@ export default {
|
|||||||
if (self.$refs.nsNewform && self.$refs.nsNewform.setFormData) {
|
if (self.$refs.nsNewform && self.$refs.nsNewform.setFormData) {
|
||||||
self.$refs.nsNewform.setFormData();
|
self.$refs.nsNewform.setFormData();
|
||||||
}
|
}
|
||||||
// ✅ 提交成功后清暂存邀请码(防止后续重复提交归属错人)
|
|
||||||
self._clearInviteCode();
|
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.message || self.$lang('submitFail'),
|
title: res.message || self.$lang('submitFail'),
|
||||||
@@ -368,61 +330,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
✅ 邀请码(来源追踪)辅助方法
|
|
||||||
链路:分享链接 ?invite=XXX → 进入本页 3 级取码 → 暂存 data + storage
|
|
||||||
→ 提交时自动拼入 request → 成功清暂存
|
|
||||||
============================================================ */
|
|
||||||
FORM_INVITE_STORAGE_KEY: 'form_invite_pending_code',
|
|
||||||
_initInviteCode() {
|
|
||||||
var key = this.FORM_INVITE_STORAGE_KEY;
|
|
||||||
var code = '';
|
|
||||||
// P1:当前页面的 onLoad options(通过 getCurrentPages 顶页的 options 拿到)
|
|
||||||
try {
|
|
||||||
var pages = (typeof getCurrentPages === 'function') ? getCurrentPages() : [];
|
|
||||||
if (pages && pages.length) {
|
|
||||||
var top = pages[pages.length - 1];
|
|
||||||
if (top && top.options) {
|
|
||||||
if (top.options.recommend_code) {
|
|
||||||
code = String(top.options.recommend_code);
|
|
||||||
} else if (top.options.invite) {
|
|
||||||
code = String(top.options.invite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
// P2:storage(App.vue onLaunch/onShow 提前写入,全局兜底)
|
|
||||||
if (!code) {
|
|
||||||
try { code = uni.getStorageSync(key) || ''; } catch (e) { code = ''; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// P3:H5 冷启动从 location.search 兜底(同样优先 recommend_code)
|
|
||||||
if (!code) {
|
|
||||||
// #ifdef H5
|
|
||||||
try {
|
|
||||||
var sp = (new URL(window.location.href)).searchParams;
|
|
||||||
var s1 = sp.get('recommend_code');
|
|
||||||
var s2 = sp.get('invite');
|
|
||||||
if (s1) code = String(s1);
|
|
||||||
else if (s2) code = String(s2);
|
|
||||||
} catch (e) {}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写回 data + storage 持久化(跨页面跳转也不丢)
|
|
||||||
if (code) {
|
|
||||||
this._inviteCodePending = code;
|
|
||||||
try { uni.setStorageSync(key, code); } catch (e) {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清邀请码(提交成功后调用,避免串号)
|
|
||||||
_clearInviteCode() {
|
|
||||||
this._inviteCodePending = '';
|
|
||||||
try { uni.removeStorageSync(this.FORM_INVITE_STORAGE_KEY); } catch (e) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -434,20 +341,6 @@ export default {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 10rpx;
|
border-radius: 10rpx;
|
||||||
|
|
||||||
/* ✅ 邀请码归属提示:灰色小字,零感知(只有通过邀请链接才显示) */
|
|
||||||
.form-invite-attribution {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20rpx 30rpx 10rpx;
|
|
||||||
.attribution-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #bbbbbb;
|
|
||||||
line-height: 1.5;
|
|
||||||
letter-spacing: 0.5rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-banner {
|
.form-banner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in New Issue
Block a user