淘宝授权推送
This commit is contained in:
parent
acd448b6dd
commit
d1c0419d76
17
App.vue
17
App.vue
|
|
@ -3,6 +3,23 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
globalData: {
|
globalData: {
|
||||||
|
/**
|
||||||
|
* urlParams - 当前页面 URL 参数集合
|
||||||
|
*
|
||||||
|
* 来源:
|
||||||
|
* 1. H5 环境下从 window.location(search + hash)解析
|
||||||
|
* 2. 小程序/App 环境下从页面栈 currentPage.options 获取
|
||||||
|
* 3. 分享/扫码进入时从 App.onLaunch/onShow 的 options.query 合并
|
||||||
|
*
|
||||||
|
* 目前业务中实际使用的参数:
|
||||||
|
* - token {string} 用户登录凭证
|
||||||
|
* - code {string} 淘宝后返回授权码
|
||||||
|
* - state {string} 状态值,格式通常为 "uidxxx",用于提取用户 ID(淘宝授权返回)
|
||||||
|
* - exuid {string} 推广人/邀请人用户 ID(三方进入用户标识)
|
||||||
|
* - page_uri {string} 授权成功后需要跳转的目标页面路径(必须授权过的用户该参数才有效)
|
||||||
|
*
|
||||||
|
* 注:UrlQuery 支持获取任意 URL 参数,以上仅为业务代码中显式消费的字段
|
||||||
|
*/
|
||||||
urlParams: {}
|
urlParams: {}
|
||||||
},
|
},
|
||||||
onLaunch: function(options) {
|
onLaunch: function(options) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
activeTab: {
|
activeTab: {
|
||||||
|
|
@ -25,16 +27,13 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getBottomBar() {
|
getBottomBar() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/bottomBar/lists?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/bottomBar/lists?cid=YsWZ21tx',
|
if (res.data && res.data.bottom_bar) {
|
||||||
success: (res) => {
|
// 过滤并排序:剔除“发现”,仅保留首页、榜单、分类
|
||||||
if (res.data && res.data.code === 200 && res.data.data.bottom_bar) {
|
const allowedTitles = ['首页', '榜单', '分类'];
|
||||||
// 过滤并排序:剔除“发现”,仅保留首页、榜单、分类
|
this.bottomBarList = res.data.bottom_bar
|
||||||
const allowedTitles = ['首页', '榜单', '分类'];
|
.filter(item => allowedTitles.includes(item.title))
|
||||||
this.bottomBarList = res.data.data.bottom_bar
|
.sort((a, b) => b.sort - a.sort);
|
||||||
.filter(item => allowedTitles.includes(item.title))
|
|
||||||
.sort((a, b) => b.sort - a.sort);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
4
main.js
4
main.js
|
|
@ -1,9 +1,10 @@
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import { estimateCoupon } from './utils/index.js'
|
||||||
// #ifndef VUE3
|
// #ifndef VUE3
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import './uni.promisify.adaptor'
|
import './uni.promisify.adaptor'
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
Vue.prototype.$estimateCoupon = estimateCoupon;
|
||||||
App.mpType = 'app'
|
App.mpType = 'app'
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
...App
|
...App
|
||||||
|
|
@ -17,6 +18,7 @@ import store from './store'
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
app.config.globalProperties.$estimateCoupon = estimateCoupon;
|
||||||
return {
|
return {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
const API_BASE = 'https://v2.api.haodanku.com';
|
const API_BASE = 'https://v2.api.haodanku.com';
|
||||||
const CUSTOM_PARAMS = {
|
const CUSTOM_PARAMS = {
|
||||||
apikey: '5417B681C5EA'
|
apikey: '5417B681C5EA'
|
||||||
|
|
@ -156,36 +157,29 @@
|
||||||
...this.getListDataParams()
|
...this.getListDataParams()
|
||||||
};
|
};
|
||||||
|
|
||||||
uni.request({
|
http.get(`${API_BASE}/get_index_activity_items`, params)
|
||||||
url: `${API_BASE}/get_index_activity_items`,
|
.then(res => {
|
||||||
data: params,
|
const body = res.body || {};
|
||||||
success: (res) => {
|
const block = body.data ? (body.data.block || []) : (body.block || []);
|
||||||
if (res.statusCode === 200 && res.data) {
|
this.category.list = block.map((item, index) => ({
|
||||||
const block = res.data.data ? (res.data.data.block || []) : (res.data.block || []);
|
label: item.name,
|
||||||
this.category.list = block.map((item, index) => ({
|
value: String(index)
|
||||||
label: item.name,
|
}));
|
||||||
value: String(index)
|
this.category.goodsLists = block.map(item => item.item || []);
|
||||||
}));
|
const list = this.category.goodsLists[0] || [];
|
||||||
this.category.goodsLists = block.map(item => item.item || []);
|
this.listData.list = list;
|
||||||
const list = this.category.goodsLists[0] || [];
|
this.listData.finished = true;
|
||||||
this.listData.list = list;
|
})
|
||||||
this.listData.finished = true;
|
.catch(() => {
|
||||||
} else {
|
|
||||||
this.listData.list = [];
|
|
||||||
this.listData.finished = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
this.listData.list = [];
|
this.listData.list = [];
|
||||||
this.listData.finished = true;
|
this.listData.finished = true;
|
||||||
},
|
})
|
||||||
complete: () => {
|
.finally(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.listData.loading = false;
|
this.listData.loading = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reGetListData() {
|
reGetListData() {
|
||||||
|
|
@ -247,103 +241,68 @@
|
||||||
|
|
||||||
if (!isWechatEnv) {
|
if (!isWechatEnv) {
|
||||||
// 非微信环境:弹窗确认后跳转
|
// 非微信环境:弹窗确认后跳转
|
||||||
uni.request({
|
http.post(`${API_BASE}/ratesurl`, param, {
|
||||||
url: `${API_BASE}/ratesurl`,
|
header: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
method: 'POST',
|
}).then(res => {
|
||||||
data: param,
|
const body = res.body || {};
|
||||||
header: {
|
const data = body.data || body;
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
const jumpUrl = data.coupon_click_url || data.item_url;
|
||||||
},
|
if (jumpUrl) {
|
||||||
success: (res) => {
|
uni.showModal({
|
||||||
if (res.statusCode === 200 && res.data) {
|
title: '提示',
|
||||||
const data = res.data.data || res.data;
|
content: '是否跳转到下单页面?',
|
||||||
const jumpUrl = data.coupon_click_url || data.item_url;
|
success: (modalRes) => {
|
||||||
if (jumpUrl) {
|
if (modalRes.confirm) {
|
||||||
uni.showModal({
|
// #ifdef H5
|
||||||
title: '提示',
|
window.location.href = jumpUrl;
|
||||||
content: '是否跳转到下单页面?',
|
// #endif
|
||||||
success: (modalRes) => {
|
// #ifdef APP-PLUS
|
||||||
if (modalRes.confirm) {
|
plus.runtime.openURL(jumpUrl);
|
||||||
// #ifdef H5
|
// #endif
|
||||||
window.location.href = jumpUrl;
|
// #ifndef H5 || APP-PLUS
|
||||||
// #endif
|
uni.setClipboardData({
|
||||||
// #ifdef APP-PLUS
|
data: jumpUrl,
|
||||||
plus.runtime.openURL(jumpUrl);
|
success: () => {
|
||||||
// #endif
|
uni.showToast({
|
||||||
// #ifndef H5 || APP-PLUS
|
title: '链接已复制,请在浏览器中打开',
|
||||||
uni.setClipboardData({
|
icon: 'none',
|
||||||
data: jumpUrl,
|
duration: 3000
|
||||||
success: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '链接已复制,请在浏览器中打开',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// #endif
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
// #endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 微信环境:获取淘口令并复制
|
// 微信环境:获取淘口令并复制
|
||||||
uni.request({
|
http.post(`${API_BASE}/ratesurl`, param, {
|
||||||
url: `${API_BASE}/ratesurl`,
|
header: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
method: 'POST',
|
}).then(res => {
|
||||||
data: param,
|
const body = res.body || {};
|
||||||
header: {
|
const data = body.data || body;
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
const taoword = data.taoword;
|
||||||
},
|
if (taoword) {
|
||||||
success: (res) => {
|
const taocode = '0' + taoword + '/';
|
||||||
if (res.statusCode === 200 && res.data) {
|
uni.setClipboardData({
|
||||||
const data = res.data.data || res.data;
|
data: taocode,
|
||||||
const taoword = data.taoword;
|
success: () => {
|
||||||
if (taoword) {
|
|
||||||
const taocode = '0' + taoword + '/';
|
|
||||||
uni.setClipboardData({
|
|
||||||
data: taocode,
|
|
||||||
success: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '复制口令成功,请打开淘宝领取',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '转链失败',
|
title: '复制口令成功,请打开淘宝领取',
|
||||||
icon: 'none'
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -45,325 +45,335 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getEnvironment } from '@/utils/env.js';
|
import { getEnvironment } from '@/utils/env.js';
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
import pagesConfig from '@/pages.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
client_id: '30004725',
|
client_id: '30004725',
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
title: '正在完成授权...',
|
title: '正在完成授权...',
|
||||||
desc: '正在处理授权逻辑...',
|
desc: '正在处理授权逻辑...',
|
||||||
CopyLink: false,
|
CopyLink: false,
|
||||||
Token: '',
|
Token: '',
|
||||||
code: '',
|
code: '',
|
||||||
queryState: '',
|
queryState: '',
|
||||||
_t: null
|
_t: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentUid() {
|
||||||
|
return this.$store.getters.currentUid
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const app = getApp();
|
||||||
|
const params = app.globalData.urlParams || {};
|
||||||
|
this.Token = params.token || '';
|
||||||
|
this.code = params.code || '';
|
||||||
|
this.queryState = params.state || '';
|
||||||
|
this.exuid = params.exuid || '';
|
||||||
|
if(this.queryState) this.$store.dispatch('setCurrentUid', (params.state || '').replace(/^uid/, '') || '');
|
||||||
|
|
||||||
|
|
||||||
|
this.handleAuth();
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
if (this._t) {
|
||||||
|
clearTimeout(this._t);
|
||||||
|
this._t = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBaseUrl() {
|
||||||
|
// return 'https://point.agrimedia.cn';
|
||||||
|
return window.location.origin;
|
||||||
|
// // #ifdef H5
|
||||||
|
// return window.location.origin;
|
||||||
|
// // #endif
|
||||||
|
// // #ifndef H5
|
||||||
|
// // 非 H5 平台(小程序/App)需配置为已备案的 H5 域名
|
||||||
|
return 'https://tpoint.agrimedia.cn';
|
||||||
|
// // #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
handleAuth() {
|
||||||
|
const baseUrl = this.getBaseUrl();
|
||||||
|
if (this.Token) {
|
||||||
|
// 场景1:授权检测,通过 token 获取用户信息
|
||||||
|
http.get(`${baseUrl}/api/user`, null, {
|
||||||
|
header: { 'authori-zation': `Bearer ${this.Token}` }
|
||||||
|
}).then(this.handleUserInfo).catch(this.handleUserError);
|
||||||
|
} else if (this.code && this.queryState) {
|
||||||
|
// 场景2:淘宝授权回调(带 code + state)
|
||||||
|
http.get(`${baseUrl}/api/taobao/execute`, {
|
||||||
|
code: this.code,
|
||||||
|
state: this.queryState
|
||||||
|
}).then(res => {
|
||||||
|
const data = res.data || {};
|
||||||
|
this.setAuthData(data.relation_id, data.tbk_pid);
|
||||||
|
this.showSuccess('授权已完成,跳转中...');
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('授权失败:', err);
|
||||||
|
const result = err.raw?.data;
|
||||||
|
const msg = result?.msg || result?.message || err.message || '授权处理失败,请重新授权';
|
||||||
|
this.showFail(msg);
|
||||||
|
});
|
||||||
|
} else if (this.exuid) {
|
||||||
|
// 场景3:三方uid授权
|
||||||
|
this.$store.dispatch('setIsThirdParty', true);
|
||||||
|
|
||||||
|
http.get(`${baseUrl}/api/taobao/third/relation_id`, {
|
||||||
|
uid: this.exuid
|
||||||
|
}).then(this.handleUserInfo).catch(this.handleUserError);
|
||||||
|
}else{
|
||||||
|
this.showFail('授权链接异常,请重新授权!');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
currentUid() {
|
handleUserInfo(res) {
|
||||||
return this.$store.getters.currentUid
|
const data = res.data || {};
|
||||||
|
const relationId = Number(data.relation_id);
|
||||||
|
const uid = data.uid;
|
||||||
|
const pid = data.tbk_pid;
|
||||||
|
if (relationId) {
|
||||||
|
// 其实无论是否授权过,都请求都会返回有值的 pid,但是只有授权过才能正常使用
|
||||||
|
this.setAuthData(relationId, pid);
|
||||||
|
|
||||||
|
// 此处为处理跳转指定页面逻辑
|
||||||
|
const app = getApp();
|
||||||
|
const params = app.globalData.urlParams || {};
|
||||||
|
if (params?.page_uri && this.isValidPagePath(params.page_uri)) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: params.page_uri
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.goHome();
|
||||||
|
}
|
||||||
|
} else if (uid) {
|
||||||
|
// 未授权
|
||||||
|
this.$store.dispatch('setCurrentUid', uid);
|
||||||
|
this.Environment();
|
||||||
|
} else {
|
||||||
|
this.showFail('获取用户信息失败!');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
|
||||||
const app = getApp();
|
|
||||||
const params = app.globalData.urlParams || {};
|
|
||||||
|
|
||||||
this.Token = params.token || '';
|
handleUserError(err) {
|
||||||
this.code = params.code || '';
|
console.error('获取用户信息失败:', err);
|
||||||
this.queryState = params.state || '';
|
const msg = err.raw?.data?.msg || err.raw?.data?.message || err.message || '获取用户信息失败,请检查网络';
|
||||||
this.$store.dispatch('setCurrentUid', (params.state || '').replace(/^uid/, '') || '')
|
this.showFail(msg);
|
||||||
|
|
||||||
this.handleAuth();
|
|
||||||
},
|
},
|
||||||
onUnload() {
|
|
||||||
|
setAuthData(relationId, pid) {
|
||||||
|
this.$store.dispatch('setRelationId', relationId);
|
||||||
|
this.$store.dispatch('setPid', pid || 'mm_284380119_1881450385_111415850448');
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidPagePath(pageUri) {
|
||||||
|
if (!pageUri) return false;
|
||||||
|
let path = pageUri.startsWith('/') ? pageUri.slice(1) : pageUri;
|
||||||
|
const queryIndex = path.indexOf('?');
|
||||||
|
if (queryIndex !== -1) {
|
||||||
|
path = path.slice(0, queryIndex);
|
||||||
|
}
|
||||||
|
return pagesConfig.pages.some(page => page.path === path);
|
||||||
|
},
|
||||||
|
|
||||||
|
goHome() {
|
||||||
if (this._t) {
|
if (this._t) {
|
||||||
clearTimeout(this._t);
|
clearTimeout(this._t);
|
||||||
this._t = null;
|
this._t = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
});
|
||||||
|
// const url = '/affiliate-activity/pages/index/index';
|
||||||
|
|
||||||
|
// // #ifdef H5
|
||||||
|
// window.location.href = url;
|
||||||
|
// return;
|
||||||
|
// // #endif
|
||||||
|
|
||||||
|
// uni.reLaunch({
|
||||||
|
// url: url
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
|
showFail(message) {
|
||||||
|
this.status = 'fail';
|
||||||
|
this.title = '授权失败';
|
||||||
|
this.desc = message || '请重新授权或返回首页';
|
||||||
|
},
|
||||||
|
|
||||||
|
showSuccess(message) {
|
||||||
|
this.status = 'success';
|
||||||
|
this.title = '授权成功';
|
||||||
|
this.desc = message;
|
||||||
|
// this._t = setTimeout(() => this.goHome(), 500);
|
||||||
|
this.goHome()
|
||||||
|
},
|
||||||
|
|
||||||
|
CopyStatus(message) {
|
||||||
|
this.status = 'CopyUrl';
|
||||||
|
this.title = '';
|
||||||
|
this.desc = message || '请点击下方按钮复制授权地址';
|
||||||
|
},
|
||||||
|
|
||||||
|
Environment() {
|
||||||
|
const env = getEnvironment();
|
||||||
|
const uid = `uid${this.currentUid}`;
|
||||||
|
const redirectUri = this.getRedirectUri();
|
||||||
|
const authUrl = `https://oauth.taobao.com/authorize?response_type=code&client_id=${this.client_id}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${uid}&view=wap`;
|
||||||
|
|
||||||
|
if (env.isBrowser || env.isAppWebview) {
|
||||||
|
// 浏览器/APP WebView:直接跳转授权
|
||||||
|
// #ifdef H5
|
||||||
|
window.location.href = authUrl;
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
plus.runtime.openURL(authUrl);
|
||||||
|
// #endif
|
||||||
|
} else if (env.isWechatWebview || env.isWechatH5 || env.isWechatMiniProgram) {
|
||||||
|
// 微信/小程序环境:显示复制按钮
|
||||||
|
this.CopyStatus('请复制授权地址到浏览器打开');
|
||||||
|
this.CopyLink = true;
|
||||||
|
} else {
|
||||||
|
// 其他平台默认显示复制按钮
|
||||||
|
this.CopyStatus('请复制授权地址到浏览器打开');
|
||||||
|
this.CopyLink = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
getBaseUrl() {
|
|
||||||
return window.location.origin;
|
|
||||||
// // #ifdef H5
|
|
||||||
// return window.location.origin;
|
|
||||||
// // #endif
|
|
||||||
// // #ifndef H5
|
|
||||||
// // 非 H5 平台(小程序/App)需配置为已备案的 H5 域名
|
|
||||||
// return 'https://tpoint.agrimedia.cn';
|
|
||||||
// // #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
handleAuth() {
|
copyText() {
|
||||||
const baseUrl = this.getBaseUrl();
|
const uid = `uid${this.currentUid}`;
|
||||||
if (this.Token) {
|
const redirectUri = this.getRedirectUri();
|
||||||
// 场景1:授权检测,通过 token 获取用户信息
|
const text = `https://oauth.taobao.com/authorize?response_type=code&client_id=${this.client_id}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${uid}&view=wap`;
|
||||||
uni.request({
|
|
||||||
url: baseUrl + '/api/user',
|
uni.setClipboardData({
|
||||||
header: {
|
data: text,
|
||||||
'authori-zation': `Bearer ${this.Token}`
|
success: () => {
|
||||||
},
|
uni.showToast({
|
||||||
success: (res) => {
|
title: '授权链接已复制',
|
||||||
if (res.statusCode === 200 && res.data) {
|
icon: 'success'
|
||||||
const data = res.data.data || {};
|
});
|
||||||
const relationId = data.relation_id;
|
},
|
||||||
const uid = data.uid;
|
fail: () => {
|
||||||
if (relationId) {
|
uni.showToast({
|
||||||
// 已授权
|
title: '复制失败,请手动复制',
|
||||||
this.$store.dispatch('setRelationId', relationId);
|
icon: 'none'
|
||||||
const app = getApp();
|
|
||||||
const params = app.globalData.urlParams || {};
|
|
||||||
if(params.page_uri) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: params.page_uri
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
this.goHome();
|
|
||||||
}
|
|
||||||
} else if (uid) {
|
|
||||||
// 未授权
|
|
||||||
this.$store.dispatch('setCurrentUid', uid);
|
|
||||||
this.Environment();
|
|
||||||
} else {
|
|
||||||
this.showFail('获取用户信息失败!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showFail(res.data?.msg || res.data?.message || '获取用户信息失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('获取用户信息失败:', err);
|
|
||||||
this.showFail('获取用户信息失败,请检查网络');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else if (this.code && this.queryState) {
|
|
||||||
// 场景2:淘宝授权回调(带 code + state)
|
|
||||||
uni.request({
|
|
||||||
url: baseUrl + '/api/taobao/execute',
|
|
||||||
data: {
|
|
||||||
code: this.code,
|
|
||||||
state: this.queryState
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200 && res.data) {
|
|
||||||
const result = res.data;
|
|
||||||
// 兼容 status / code 两种业务状态码
|
|
||||||
const status = Number(result.status != null ? result.status : result.code);
|
|
||||||
if (status === 200 || status === 1) {
|
|
||||||
const data = result.data || {};
|
|
||||||
this.$store.dispatch('setRelationId', data.relation_id);
|
|
||||||
this.showSuccess('授权已完成,跳转中...');
|
|
||||||
} else {
|
|
||||||
this.showFail(result.msg || result.message || '授权处理失败,请重新授权');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showFail('授权处理失败,请重新授权');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('授权失败:', err);
|
|
||||||
this.showFail('授权处理失败,请重新授权');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.showFail('授权链接异常,请重新授权!');
|
|
||||||
// this.$store.dispatch('setRelationId', '123456789');
|
|
||||||
// this.showSuccess('授权已完成,跳转中...');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goHome() {
|
|
||||||
if (this._t) {
|
|
||||||
clearTimeout(this._t);
|
|
||||||
this._t = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/index/index'
|
|
||||||
});
|
|
||||||
// const url = '/affiliate-activity/pages/index/index';
|
|
||||||
|
|
||||||
// // #ifdef H5
|
|
||||||
// window.location.href = url;
|
|
||||||
// return;
|
|
||||||
// // #endif
|
|
||||||
|
|
||||||
// uni.reLaunch({
|
|
||||||
// url: url
|
|
||||||
// });
|
|
||||||
},
|
|
||||||
|
|
||||||
showFail(message) {
|
|
||||||
this.status = 'fail';
|
|
||||||
this.title = '授权失败';
|
|
||||||
this.desc = message || '请重新授权或返回首页';
|
|
||||||
},
|
|
||||||
|
|
||||||
showSuccess(message) {
|
|
||||||
this.status = 'success';
|
|
||||||
this.title = '授权成功';
|
|
||||||
this.desc = message;
|
|
||||||
this._t = setTimeout(() => this.goHome(), 500);
|
|
||||||
},
|
|
||||||
|
|
||||||
CopyStatus(message) {
|
|
||||||
this.status = 'CopyUrl';
|
|
||||||
this.title = '';
|
|
||||||
this.desc = message || '请点击下方按钮复制授权地址';
|
|
||||||
},
|
|
||||||
|
|
||||||
Environment() {
|
|
||||||
const env = getEnvironment();
|
|
||||||
const uid = `uid${this.currentUid}`;
|
|
||||||
const redirectUri = this.getRedirectUri();
|
|
||||||
const authUrl = `https://oauth.taobao.com/authorize?response_type=code&client_id=${this.client_id}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${uid}&view=wap`;
|
|
||||||
|
|
||||||
if (env.isBrowser || env.isAppWebview) {
|
|
||||||
// 浏览器/APP WebView:直接跳转授权
|
|
||||||
// #ifdef H5
|
|
||||||
window.location.href = authUrl;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
plus.runtime.openURL(authUrl);
|
|
||||||
// #endif
|
|
||||||
} else if (env.isWechatWebview || env.isWechatH5 || env.isWechatMiniProgram) {
|
|
||||||
// 微信/小程序环境:显示复制按钮
|
|
||||||
this.CopyStatus('请复制授权地址到浏览器打开');
|
|
||||||
this.CopyLink = true;
|
|
||||||
} else {
|
|
||||||
// 其他平台默认显示复制按钮
|
|
||||||
this.CopyStatus('请复制授权地址到浏览器打开');
|
|
||||||
this.CopyLink = true;
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
},
|
||||||
|
|
||||||
copyText() {
|
getRedirectUri() {
|
||||||
const uid = `uid${this.currentUid}`;
|
const baseUrl = this.getBaseUrl();
|
||||||
const redirectUri = this.getRedirectUri();
|
return baseUrl + '/affiliate-activity/pages/auth/auth';
|
||||||
const text = `https://oauth.taobao.com/authorize?response_type=code&client_id=${this.client_id}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${uid}&view=wap`;
|
},
|
||||||
|
|
||||||
uni.setClipboardData({
|
|
||||||
data: text,
|
|
||||||
success: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '授权链接已复制',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '复制失败,请手动复制',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getRedirectUri() {
|
|
||||||
const baseUrl = this.getBaseUrl();
|
|
||||||
return baseUrl + '/affiliate-activity/pages/auth/auth';
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-page {
|
.auth-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(180deg, #f0f2f5 0%, #f7f8fa 100%);
|
background: linear-gradient(180deg, #f0f2f5 0%, #f7f8fa 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 60rpx;
|
padding: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-box {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 100rpx 56rpx 80rpx;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640rpx;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-icon {
|
||||||
|
margin: 0 auto 40rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin-animation {
|
||||||
|
animation: spin 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-box {
|
100% {
|
||||||
background: #ffffff;
|
transform: rotate(360deg);
|
||||||
border-radius: 24rpx;
|
|
||||||
padding: 100rpx 56rpx 80rpx;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 640rpx;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.auth-icon {
|
.auth-title {
|
||||||
margin: 0 auto 40rpx;
|
font-size: 44rpx;
|
||||||
display: flex;
|
color: #1a1a1a;
|
||||||
align-items: center;
|
margin: 24rpx 0 20rpx;
|
||||||
justify-content: center;
|
font-weight: 600;
|
||||||
height: 100rpx;
|
letter-spacing: 2rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spin-animation {
|
.auth-desc {
|
||||||
animation: spin 1.2s linear infinite;
|
font-size: 28rpx;
|
||||||
}
|
color: #666666;
|
||||||
|
margin-bottom: 56rpx;
|
||||||
|
line-height: 1.8;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
.auth-btn {
|
||||||
0% {
|
margin-top: 32rpx;
|
||||||
transform: rotate(0deg);
|
height: 96rpx;
|
||||||
}
|
line-height: 96rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #333333;
|
||||||
|
border: 2rpx solid #e0e0e0;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
padding: 0 48rpx;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
100% {
|
.auth-btn:active {
|
||||||
transform: rotate(360deg);
|
transform: scale(0.98);
|
||||||
}
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-title {
|
.auth-btn::after {
|
||||||
font-size: 44rpx;
|
border: none;
|
||||||
color: #1a1a1a;
|
}
|
||||||
margin: 24rpx 0 20rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 2rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-desc {
|
.auth-btn--primary {
|
||||||
font-size: 28rpx;
|
background: linear-gradient(135deg, #ff715a, #ff416c);
|
||||||
color: #666666;
|
color: #ffffff;
|
||||||
margin-bottom: 56rpx;
|
border: none;
|
||||||
line-height: 1.8;
|
box-shadow: 0 8rpx 24rpx rgba(255, 65, 108, 0.25);
|
||||||
padding: 0 20rpx;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.auth-btn {
|
.btn-text {
|
||||||
margin-top: 32rpx;
|
color: inherit;
|
||||||
height: 96rpx;
|
font-size: 30rpx;
|
||||||
line-height: 96rpx;
|
font-weight: 500;
|
||||||
background: #ffffff;
|
}
|
||||||
color: #333333;
|
|
||||||
border: 2rpx solid #e0e0e0;
|
|
||||||
border-radius: 48rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
padding: 0 48rpx;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12rpx;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-btn:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-btn::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-btn--primary {
|
|
||||||
background: linear-gradient(135deg, #ff715a, #ff416c);
|
|
||||||
color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(255, 65, 108, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,12 @@
|
||||||
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="price-coupon" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(goods.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="goods-bottom-info">
|
<view class="goods-bottom-info">
|
||||||
|
|
@ -103,6 +109,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -154,22 +161,19 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getCategoryTabs() {
|
getCategoryTabs() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/superCategory?is_get_second=1&cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/index/superCategory?is_get_second=1&cid=YsWZ21tx',
|
if (res.data) {
|
||||||
success: (res) => {
|
// 映射为与首页一致的 navList 结构
|
||||||
if (res.data && res.data.code === 200) {
|
const categories = res.data.map(item => ({
|
||||||
// 映射为与首页一致的 navList 结构
|
name: item.name,
|
||||||
const categories = res.data.data.map(item => ({
|
cat_id: item.cat_id,
|
||||||
name: item.name,
|
second: item.second || []
|
||||||
cat_id: item.cat_id,
|
}));
|
||||||
second: item.second || []
|
this.navList = [{ name: '首页', cat_id: 0 }, { name: '推荐', cat_id: -1 }, ...categories];
|
||||||
}));
|
|
||||||
this.navList = [{ name: '首页', cat_id: 0 }, { name: '推荐', cat_id: -1 }, ...categories];
|
const currentCat = this.navList.find(c => c.cat_id == this.currentCatId);
|
||||||
|
if (currentCat && currentCat.second) {
|
||||||
const currentCat = this.navList.find(c => c.cat_id == this.currentCatId);
|
this.subCategories = currentCat.second;
|
||||||
if (currentCat && currentCat.second) {
|
|
||||||
this.subCategories = currentCat.second;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -191,40 +195,37 @@
|
||||||
sortParam = this.priceOrder === 'asc' ? 8 : 9;
|
sortParam = this.priceOrder === 'asc' ? 8 : 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.request({
|
http.get(`https://api.cmspro.haodanku.com/find/allItemList?category_id=${this.currentCatId}&page=${this.page}&sort=${sortParam}&page_size=20&cid=YsWZ21tx`).then(res => {
|
||||||
url: `https://api.cmspro.haodanku.com/find/allItemList?category_id=${this.currentCatId}&page=${this.page}&sort=${sortParam}&page_size=20&cid=YsWZ21tx`,
|
if (res.data && res.data.item_info) {
|
||||||
success: (res) => {
|
const list = res.data.item_info.map(item => ({
|
||||||
if (res.data && res.data.code === 200 && res.data.data && res.data.data.item_info) {
|
id: item.id,
|
||||||
const list = res.data.data.item_info.map(item => ({
|
image: item.itempic,
|
||||||
id: item.id,
|
title: item.itemshorttitle && item.itemshorttitle.length > 17 ? item.itemshorttitle.substring(0, 17) + '...' : item.itemshorttitle,
|
||||||
image: item.itempic,
|
finalPrice: item.itemendprice,
|
||||||
title: item.itemshorttitle && item.itemshorttitle.length > 17 ? item.itemshorttitle.substring(0, 17) + '...' : item.itemshorttitle,
|
couponValue: item.couponmoney || 0,
|
||||||
finalPrice: item.itemendprice,
|
tkmoney: item.tkmoney || 0,
|
||||||
couponValue: item.couponmoney || 0,
|
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' : item.itemsale,
|
||||||
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' : item.itemsale,
|
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
||||||
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
shopName: item.shopname,
|
||||||
shopName: item.shopname,
|
labels: item.label || []
|
||||||
labels: item.label || []
|
}));
|
||||||
}));
|
|
||||||
|
if (list.length === 0) {
|
||||||
if (list.length === 0) {
|
|
||||||
this.finished = true;
|
|
||||||
} else {
|
|
||||||
// 翻页添加数据
|
|
||||||
this.goodsList = [...this.goodsList, ...list];
|
|
||||||
this.page++;
|
|
||||||
// 如果返回的数量小于请求的数量,说明已到最后一页
|
|
||||||
if (list.length < 20) {
|
|
||||||
this.finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
|
} else {
|
||||||
|
// 翻页添加数据
|
||||||
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
|
this.page++;
|
||||||
|
// 如果返回的数量小于请求的数量,说明已到最后一页
|
||||||
|
if (list.length < 20) {
|
||||||
|
this.finished = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
complete: () => {
|
this.finished = true;
|
||||||
this.loading = false;
|
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
switchCategory(item) {
|
switchCategory(item) {
|
||||||
|
|
@ -625,6 +626,23 @@
|
||||||
padding: 0 8rpx;
|
padding: 0 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-coupon {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-val {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-bottom-info {
|
.goods-bottom-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<scroll-view scroll-y class="scroll-content" @scrolltolower="loadMore">
|
<scroll-view scroll-y class="scroll-content" @scrolltolower="loadMore">
|
||||||
<!-- 占位适配固定头部 -->
|
<!-- 占位适配固定头部 -->
|
||||||
<view class="header-placeholder" :style="{ height: '44px' }"></view>
|
<view class="header-placeholder" :style="{ height: '44px' }"></view>
|
||||||
|
|
||||||
<!-- 排序筛选栏 -->
|
<!-- 排序筛选栏 -->
|
||||||
<view class="filter-bar">
|
<view class="filter-bar">
|
||||||
<view class="filter-item" :class="{ 'active': sortType === 1 }" @click="changeSort(1)">综合</view>
|
<view class="filter-item" :class="{ 'active': sortType === 1 }" @click="changeSort(1)">综合</view>
|
||||||
|
|
@ -33,28 +33,35 @@
|
||||||
|
|
||||||
<!-- 高级筛选标签栏 -->
|
<!-- 高级筛选标签栏 -->
|
||||||
<view class="sub-filter-row">
|
<view class="sub-filter-row">
|
||||||
<view class="sub-filter-item" :class="{ 'active': filterHasCoupon }" @click="toggleFilter('coupon')">有券</view>
|
<view class="sub-filter-item" :class="{ 'active': filterHasCoupon }" @click="toggleFilter('coupon')">有券
|
||||||
<view class="sub-filter-item" :class="{ 'active': filterIsFlagship }" @click="toggleFilter('flagship')">旗舰店</view>
|
</view>
|
||||||
<view class="sub-filter-item" :class="{ 'active': filterIsTmall }" @click="toggleFilter('tmall')">天猫</view>
|
<view class="sub-filter-item" :class="{ 'active': filterIsFlagship }" @click="toggleFilter('flagship')">
|
||||||
<view class="sub-filter-item" :class="{ 'active': filterIsBrand }" @click="toggleFilter('brand')">品牌</view>
|
旗舰店</view>
|
||||||
|
<view class="sub-filter-item" :class="{ 'active': filterIsTmall }" @click="toggleFilter('tmall')">天猫
|
||||||
|
</view>
|
||||||
|
<view class="sub-filter-item" :class="{ 'active': filterIsBrand }" @click="toggleFilter('brand')">品牌
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 商品列表 (横向单列风格) -->
|
<!-- 商品列表 (横向单列风格) -->
|
||||||
<view class="goods-list">
|
<view class="goods-list">
|
||||||
<view class="goods-item" v-for="(goods, idx) in goodsList" :key="idx" @click="goToDetail('id', goods.id)">
|
<view class="goods-item" v-for="(goods, idx) in goodsList" :key="idx"
|
||||||
|
@click="goToDetail('id', goods.id)">
|
||||||
<view class="g-img-left">
|
<view class="g-img-left">
|
||||||
<image class="goods-img" :src="goods.image" mode="aspectFill"></image>
|
<image class="goods-img" :src="goods.image" mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="goods-info">
|
<view class="goods-info">
|
||||||
<view class="goods-title-row">
|
<view class="goods-title-row">
|
||||||
<image src="https://img-haodanku-com.cdn.fudaiapp.com/FlyOSTvjC3LjrkUoJ0NPxx1qnGz4" class="platform-icon"></image>
|
<image src="https://img-haodanku-com.cdn.fudaiapp.com/FlyOSTvjC3LjrkUoJ0NPxx1qnGz4"
|
||||||
|
class="platform-icon"></image>
|
||||||
<text class="title-text">{{ goods.title }}</text>
|
<text class="title-text">{{ goods.title }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 标签组 -->
|
<!-- 标签组 -->
|
||||||
<view class="labels-row" v-if="goods.labels && goods.labels.length > 0">
|
<view class="labels-row" v-if="goods.labels && goods.labels.length > 0">
|
||||||
<text class="label-tag" v-for="(label, lIdx) in goods.labels.slice(0, 2)" :key="lIdx">{{ label }}</text>
|
<text class="label-tag" v-for="(label, lIdx) in goods.labels.slice(0, 2)"
|
||||||
|
:key="lIdx">{{ label }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="goods-price-section">
|
<view class="goods-price-section">
|
||||||
<view class="price-main">
|
<view class="price-main">
|
||||||
<text class="price-tip">{{ goods.couponValue > 0 ? '券后' : '抢购价' }}</text>
|
<text class="price-tip">{{ goods.couponValue > 0 ? '券后' : '抢购价' }}</text>
|
||||||
|
|
@ -65,6 +72,12 @@
|
||||||
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="price-coupon" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(goods.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="goods-bottom-info">
|
<view class="goods-bottom-info">
|
||||||
|
|
@ -85,17 +98,20 @@
|
||||||
|
|
||||||
<!-- 推荐商品列表 -->
|
<!-- 推荐商品列表 -->
|
||||||
<view class="goods-list recommend-list" v-if="recommendList.length > 0">
|
<view class="goods-list recommend-list" v-if="recommendList.length > 0">
|
||||||
<view class="goods-item" v-for="(goods, idx) in recommendList" :key="idx" @click="goToDetail('keywordid', goods.id)">
|
<view class="goods-item" v-for="(goods, idx) in recommendList" :key="idx"
|
||||||
|
@click="goToDetail('keywordid', goods.id)">
|
||||||
<view class="g-img-left">
|
<view class="g-img-left">
|
||||||
<image class="goods-img" :src="goods.image" mode="aspectFill"></image>
|
<image class="goods-img" :src="goods.image" mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="goods-info">
|
<view class="goods-info">
|
||||||
<view class="goods-title-row">
|
<view class="goods-title-row">
|
||||||
<image src="https://img-haodanku-com.cdn.fudaiapp.com/FlyOSTvjC3LjrkUoJ0NPxx1qnGz4" class="platform-icon"></image>
|
<image src="https://img-haodanku-com.cdn.fudaiapp.com/FlyOSTvjC3LjrkUoJ0NPxx1qnGz4"
|
||||||
|
class="platform-icon"></image>
|
||||||
<text class="title-text">{{ goods.title }}</text>
|
<text class="title-text">{{ goods.title }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="labels-row" v-if="goods.labels && goods.labels.length > 0">
|
<view class="labels-row" v-if="goods.labels && goods.labels.length > 0">
|
||||||
<text class="label-tag" v-for="(label, lIdx) in goods.labels.slice(0, 2)" :key="lIdx">{{ label }}</text>
|
<text class="label-tag" v-for="(label, lIdx) in goods.labels.slice(0, 2)"
|
||||||
|
:key="lIdx">{{ label }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="goods-price-section">
|
<view class="goods-price-section">
|
||||||
<view class="price-main">
|
<view class="price-main">
|
||||||
|
|
@ -107,6 +123,12 @@
|
||||||
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="price-coupon" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(goods.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="goods-bottom-info">
|
<view class="goods-bottom-info">
|
||||||
<text class="sales">已售{{ goods.sales }}件</text>
|
<text class="sales">已售{{ goods.sales }}件</text>
|
||||||
|
|
@ -122,13 +144,14 @@
|
||||||
<view v-else-if="goodsList.length === 0" class="empty-text">暂无相关商品</view>
|
<view v-else-if="goodsList.length === 0" class="empty-text">暂无相关商品</view>
|
||||||
<view v-else-if="finished" class="finished-text">-- 已经到底啦 --</view>
|
<view v-else-if="finished" class="finished-text">-- 已经到底啦 --</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="footer-placeholder"></view>
|
<view class="footer-placeholder"></view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -154,11 +177,11 @@
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
const sysInfo = uni.getSystemInfoSync();
|
const sysInfo = uni.getSystemInfoSync();
|
||||||
this.statusBarHeight = sysInfo.statusBarHeight || 44;
|
this.statusBarHeight = sysInfo.statusBarHeight || 44;
|
||||||
|
|
||||||
if (options.cate_name) this.cateName = decodeURIComponent(options.cate_name);
|
if (options.cate_name) this.cateName = decodeURIComponent(options.cate_name);
|
||||||
if (options.id) this.mainCatId = options.id;
|
if (options.id) this.mainCatId = options.id;
|
||||||
if (options.second_category) this.secondCategory = options.second_category;
|
if (options.second_category) this.secondCategory = options.second_category;
|
||||||
|
|
||||||
this.getProducts(true);
|
this.getProducts(true);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -172,7 +195,7 @@
|
||||||
this.goodsList = [];
|
this.goodsList = [];
|
||||||
}
|
}
|
||||||
if (this.loading || this.finished) return;
|
if (this.loading || this.finished) return;
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
// 构建排序参数 (0:综合, 3:销量, 8:券后价低到高, 9:券后价高到低)
|
// 构建排序参数 (0:综合, 3:销量, 8:券后价低到高, 9:券后价高到低)
|
||||||
let sortParam = 0;
|
let sortParam = 0;
|
||||||
|
|
@ -186,7 +209,7 @@
|
||||||
let filterParams = '';
|
let filterParams = '';
|
||||||
if (this.filterHasCoupon) filterParams += '&filtrate_type=16';
|
if (this.filterHasCoupon) filterParams += '&filtrate_type=16';
|
||||||
if (this.filterIsBrand) filterParams += '&is_brand=1';
|
if (this.filterIsBrand) filterParams += '&is_brand=1';
|
||||||
|
|
||||||
// 动态构建 shoptype: 旗舰店为1,天猫为2,两者都选为1,2
|
// 动态构建 shoptype: 旗舰店为1,天猫为2,两者都选为1,2
|
||||||
let shopTypes = [];
|
let shopTypes = [];
|
||||||
if (this.filterIsFlagship) shopTypes.push(1);
|
if (this.filterIsFlagship) shopTypes.push(1);
|
||||||
|
|
@ -195,44 +218,45 @@
|
||||||
filterParams += `&shoptype=${shopTypes.join(',')}`;
|
filterParams += `&shoptype=${shopTypes.join(',')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.request({
|
http.get(
|
||||||
url: `https://api.cmspro.haodanku.com/find/allItemList?category_id=${this.mainCatId}&son_category=${encodeURIComponent(this.secondCategory)}&page=${this.page}&sort=${sortParam}&page_size=20&cid=YsWZ21tx${filterParams}`,
|
`https://api.cmspro.haodanku.com/find/allItemList?category_id=${this.mainCatId}&son_category=${encodeURIComponent(this.secondCategory)}&page=${this.page}&sort=${sortParam}&page_size=20&cid=YsWZ21tx${filterParams}`
|
||||||
success: (res) => {
|
).then(res => {
|
||||||
if (res.data && res.data.code === 200 && res.data.data && res.data.data.item_info) {
|
if (res.data && res.data.item_info) {
|
||||||
const list = res.data.data.item_info.map(item => ({
|
const list = res.data.item_info.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
image: item.itempic,
|
image: item.itempic,
|
||||||
title: item.itemshorttitle && item.itemshorttitle.length > 18 ? item.itemshorttitle.substring(0, 18) + '...' : item.itemshorttitle,
|
title: item.itemshorttitle && item.itemshorttitle.length > 18 ? item
|
||||||
finalPrice: item.itemendprice,
|
.itemshorttitle.substring(0, 18) + '...' : item.itemshorttitle,
|
||||||
couponValue: item.couponmoney || 0,
|
finalPrice: item.itemendprice,
|
||||||
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' : item.itemsale,
|
couponValue: item.couponmoney || 0,
|
||||||
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
tkmoney: item.tkmoney || 0,
|
||||||
shopName: item.shopname,
|
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' :
|
||||||
labels: item.label || []
|
item.itemsale,
|
||||||
}));
|
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
||||||
|
shopName: item.shopname,
|
||||||
if (list.length === 0) {
|
labels: item.label || []
|
||||||
this.finished = true;
|
}));
|
||||||
} else {
|
|
||||||
this.goodsList = [...this.goodsList, ...list];
|
if (list.length === 0) {
|
||||||
this.page++;
|
|
||||||
if (list.length < 20) {
|
|
||||||
this.finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兜底逻辑:如果主列表数据少于 10 条且是首屏加载,则请求推荐列表
|
|
||||||
if (refresh && this.goodsList.length < 10) {
|
|
||||||
this.getRecommendList();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
if (refresh) this.getRecommendList();
|
} else {
|
||||||
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
|
this.page++;
|
||||||
|
if (list.length < 20) {
|
||||||
|
this.finished = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
complete: () => {
|
// 兜底逻辑:如果主列表数据少于 10 条且是首屏加载,则请求推荐列表
|
||||||
this.loading = false;
|
if (refresh && this.goodsList.length < 10) {
|
||||||
|
this.getRecommendList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.finished = true;
|
||||||
|
if (refresh) this.getRecommendList();
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getRecommendList() {
|
getRecommendList() {
|
||||||
|
|
@ -244,30 +268,33 @@
|
||||||
if (this.filterIsFlagship) recommendParams += '&is_tmall=1';
|
if (this.filterIsFlagship) recommendParams += '&is_tmall=1';
|
||||||
if (this.filterIsTmall) recommendParams += '&min_id=1';
|
if (this.filterIsTmall) recommendParams += '&min_id=1';
|
||||||
|
|
||||||
uni.request({
|
http.get(
|
||||||
url: `https://api.cmspro.haodanku.com/superSearch/getList?sort=0&page_size=20&category_id=${this.mainCatId}&son_category=${encodeURIComponent(this.secondCategory)}&cid=YsWZ21tx${recommendParams}`,
|
`https://api.cmspro.haodanku.com/superSearch/getList?sort=0&page_size=20&category_id=${this.mainCatId}&son_category=${encodeURIComponent(this.secondCategory)}&cid=YsWZ21tx${recommendParams}`
|
||||||
success: (res) => {
|
).then(res => {
|
||||||
if (res.data && res.data.code === 200 && res.data.data) {
|
console.log('res', res)
|
||||||
const list = res.data.data.map(item => {
|
if (res.data) {
|
||||||
console.log(item)
|
const list = res.data.map(item => {
|
||||||
return {
|
console.log('推荐商品:', item.id || item.itemid)
|
||||||
|
return {
|
||||||
id: item.id || item.itemid,
|
id: item.id || item.itemid,
|
||||||
image: item.itempic,
|
image: item.itempic,
|
||||||
title: item.itemshorttitle && item.itemshorttitle.length > 18 ? item.itemshorttitle.substring(0, 18) + '...' : item.itemshorttitle,
|
title: item.itemshorttitle && item.itemshorttitle.length > 18 ? item
|
||||||
|
.itemshorttitle.substring(0, 18) + '...' : item.itemshorttitle,
|
||||||
finalPrice: item.itemendprice,
|
finalPrice: item.itemendprice,
|
||||||
couponValue: item.couponmoney || 0,
|
couponValue: item.couponmoney || 0,
|
||||||
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' : item.itemsale,
|
tkmoney: item.tkmoney || 0,
|
||||||
|
sales: item.itemsale >= 10000 ? (item.itemsale / 10000).toFixed(1) + '万' :
|
||||||
|
item.itemsale,
|
||||||
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
||||||
shopName: item.shopname,
|
shopName: item.shopname,
|
||||||
labels: item.label || (item.couponmoney > 0 ? [`${item.couponmoney}元券`] : [])
|
labels: item.label || (item.couponmoney > 0 ? [`${item.couponmoney}元券`] :
|
||||||
|
[])
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.recommendList = list;
|
this.recommendList = list;
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
this.loadingRecommend = false;
|
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.loadingRecommend = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeSort(type) {
|
changeSort(type) {
|
||||||
|
|
@ -284,13 +311,13 @@
|
||||||
if (type === 'flagship') this.filterIsFlagship = !this.filterIsFlagship;
|
if (type === 'flagship') this.filterIsFlagship = !this.filterIsFlagship;
|
||||||
if (type === 'tmall') this.filterIsTmall = !this.filterIsTmall;
|
if (type === 'tmall') this.filterIsTmall = !this.filterIsTmall;
|
||||||
if (type === 'brand') this.filterIsBrand = !this.filterIsBrand;
|
if (type === 'brand') this.filterIsBrand = !this.filterIsBrand;
|
||||||
|
|
||||||
this.getProducts(true);
|
this.getProducts(true);
|
||||||
},
|
},
|
||||||
loadMore() {
|
loadMore() {
|
||||||
this.getProducts();
|
this.getProducts();
|
||||||
},
|
},
|
||||||
goToDetail(key,id) {
|
goToDetail(key, id) {
|
||||||
// console.log(`/pages/detail/detail?${key}=${id}`)
|
// console.log(`/pages/detail/detail?${key}=${id}`)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/detail?${key}=${id}`
|
url: `/pages/detail/detail?${key}=${id}`
|
||||||
|
|
@ -548,7 +575,7 @@
|
||||||
.coupon-icon {
|
.coupon-icon {
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background: rgba(255,255,255,0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
padding: 0 6rpx;
|
padding: 0 6rpx;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -561,6 +588,23 @@
|
||||||
padding: 0 8rpx;
|
padding: 0 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-coupon {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-val {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-bottom-info {
|
.goods-bottom-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -609,4 +653,4 @@
|
||||||
.footer-placeholder {
|
.footer-placeholder {
|
||||||
height: 40rpx;
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -75,35 +76,28 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getSuperCategory() {
|
getSuperCategory() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/superCategory?cid=YsWZ21tx', undefined, {
|
||||||
url: 'https://api.cmspro.haodanku.com/index/superCategory?cid=YsWZ21tx',
|
|
||||||
header: {
|
header: {
|
||||||
'accept': 'application/json, text/plain, */*',
|
'accept': 'application/json, text/plain, */*',
|
||||||
'accept-language': 'zh-CN,zh;q=0.9'
|
'accept-language': 'zh-CN,zh;q=0.9'
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.data && res.data.code === 200) {
|
|
||||||
this.categoryData = res.data.data;
|
|
||||||
// 获取数据后尝试匹配底部导航高亮状态
|
|
||||||
this.matchNavActive();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}).then(res => {
|
||||||
|
this.categoryData = res.data;
|
||||||
|
// 获取数据后尝试匹配底部导航高亮状态
|
||||||
|
this.matchNavActive();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
matchNavActive() {
|
matchNavActive() {
|
||||||
// 获取底部导航数据,查找“分类”对应的索引
|
// 获取底部导航数据,查找“分类”对应的索引
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/bottomBar/lists?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/bottomBar/lists?cid=YsWZ21tx',
|
if (res.data && res.data.bottom_bar) {
|
||||||
success: (res) => {
|
const allowedTitles = ['首页', '榜单', '分类'];
|
||||||
if (res.data && res.data.code === 200 && res.data.data.bottom_bar) {
|
const list = res.data.bottom_bar
|
||||||
const allowedTitles = ['首页', '榜单', '分类'];
|
.filter(item => allowedTitles.includes(item.title))
|
||||||
const list = res.data.data.bottom_bar
|
.sort((a, b) => b.sort - a.sort);
|
||||||
.filter(item => allowedTitles.includes(item.title))
|
const idx = list.findIndex(item => item.title === '分类');
|
||||||
.sort((a, b) => b.sort - a.sort);
|
if (idx !== -1) {
|
||||||
const idx = list.findIndex(item => item.title === '分类');
|
this.navActiveIndex = idx;
|
||||||
if (idx !== -1) {
|
|
||||||
this.navActiveIndex = idx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
<!-- 活动横幅 -->
|
<!-- 活动横幅 -->
|
||||||
<view class="activity-banner" v-if="product.activity && product.activity.app_img">
|
<view class="activity-banner" v-if="product.activity && product.activity.app_img">
|
||||||
<image :src="product.activity.app_img" mode="widthFix" class="banner-img"></image>
|
<image :src="product.activity.app_img" mode="widthFix" class="banner-img"></image>
|
||||||
<view class="banner-time" v-if="product.activityTime">TIME: {{ product.activityTime }}</view>
|
<!-- <view class="banner-time" v-if="product.activityTime">TIME: {{ product.activityTime }}</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 热销榜单提示 -->
|
<!-- 热销榜单提示 -->
|
||||||
|
|
@ -68,6 +68,9 @@
|
||||||
<view class="tags-row">
|
<view class="tags-row">
|
||||||
<text class="tag-capsule" v-for="(tag, index) in product.labels" :key="index">{{ tag }}</text>
|
<text class="tag-capsule" v-for="(tag, index) in product.labels" :key="index">{{ tag }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="estimate-tip" v-if="!$store.state.isThirdParty">
|
||||||
|
<text>消费券:{{ $estimateCoupon(product.commission) }}为预估值,实际以系统发放为准</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 优惠券模块 -->
|
<!-- 优惠券模块 -->
|
||||||
|
|
@ -180,6 +183,12 @@
|
||||||
<text>{{ item.couponmoney }}元</text>
|
<text>{{ item.couponmoney }}元</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex-left coupon-row" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="d-coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(item.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view class="flex-left flex-left-t">
|
<view class="flex-left flex-left-t">
|
||||||
<view class="sales-volume">已售{{ formatSales(item.itemsale) }}件</view>
|
<view class="sales-volume">已售{{ formatSales(item.itemsale) }}件</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -439,7 +448,10 @@
|
||||||
url: `https://api.cmspro.haodanku.com/detail/getRecommendItems?itemid=${itemId}&son_category=${sonCategory}&cid=YsWZ21tx`,
|
url: `https://api.cmspro.haodanku.com/detail/getRecommendItems?itemid=${itemId}&son_category=${sonCategory}&cid=YsWZ21tx`,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.data && res.data.code === 200) {
|
if (res.data && res.data.code === 200) {
|
||||||
this.similarProducts = res.data.data.slice(0, 10);
|
this.similarProducts = res.data.data.slice(0, 10).map(item => ({
|
||||||
|
...item,
|
||||||
|
tkmoney: item.tkmoney || 0
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -903,6 +915,22 @@
|
||||||
padding: 0 8rpx;
|
padding: 0 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.coupon-row {
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-row .coupon-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-row .coupon-val {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.sales-volume {
|
.sales-volume {
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
@ -1097,6 +1125,15 @@
|
||||||
margin-bottom: 10rpx;
|
margin-bottom: 10rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.estimate-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
background-color: #fff8f0;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
/* 优惠券 */
|
/* 优惠券 */
|
||||||
.coupon-section {
|
.coupon-section {
|
||||||
padding: 0 30rpx;
|
padding: 0 30rpx;
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,12 @@
|
||||||
</view>
|
</view>
|
||||||
<view class="g-coupon-tag">券 {{ goods.couponValue }}元</view>
|
<view class="g-coupon-tag">券 {{ goods.couponValue }}元</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="price-coupon" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(goods.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view class="goods-sales">已售 {{ goods.sales }} 件</view>
|
<view class="goods-sales">已售 {{ goods.sales }} 件</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -255,6 +261,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -354,11 +361,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getIndexData() {
|
getIndexData() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/index?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/index/index?cid=YsWZ21tx',
|
const d = res.data;
|
||||||
success: (res) => {
|
|
||||||
if (res.data && res.data.code === 200 && res.data.data) {
|
|
||||||
const d = res.data.data;
|
|
||||||
if (Array.isArray(d.navs)) {
|
if (Array.isArray(d.navs)) {
|
||||||
this.menus = d.navs;
|
this.menus = d.navs;
|
||||||
}
|
}
|
||||||
|
|
@ -370,8 +374,8 @@
|
||||||
if (Array.isArray(d.tile_long) && d.tile_long.length > 0) {
|
if (Array.isArray(d.tile_long) && d.tile_long.length > 0) {
|
||||||
this.adList = d.tile_long.map(t => t.img).filter(Boolean);
|
this.adList = d.tile_long.map(t => t.img).filter(Boolean);
|
||||||
}
|
}
|
||||||
}
|
}).catch(err => {
|
||||||
}
|
console.error('获取首页数据失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
goToDetail(id) {
|
goToDetail(id) {
|
||||||
|
|
@ -383,34 +387,27 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
getCategoryList() {
|
getCategoryList() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/category?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/index/category?cid=YsWZ21tx',
|
if (res.data.category) {
|
||||||
success: (res) => {
|
this.navList = [{ name: '首页', cat_id: 0 }, { name: '推荐', cat_id: -1 }, ...res.data.category];
|
||||||
if (res.data && res.data.code === 200 && res.data.data.category) {
|
|
||||||
this.navList = [{ name: '首页', cat_id: 0 }, { name: '推荐', cat_id: -1 }, ...res.data.data.category];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取分类失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getNoticeList() {
|
getNoticeList() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/msg/getMsgs?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/msg/getMsgs?cid=YsWZ21tx',
|
this.noticeList = res.data.msgs;
|
||||||
success: (res) => {
|
if (res.data.num) {
|
||||||
if (res.data && res.data.code === 200) {
|
this.qiangNum = res.data.num;
|
||||||
this.noticeList = res.data.data.msgs;
|
|
||||||
if (res.data.data.num) {
|
|
||||||
this.qiangNum = res.data.data.num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取通知失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getGoodsList() {
|
getGoodsList() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/recommend/getRecommend?page_size=200&page=1&type=2&cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/recommend/getRecommend?page_size=200&page=1&type=2&cid=YsWZ21tx',
|
const list = res.data.recommends.map(item => {
|
||||||
success: (res) => {
|
|
||||||
if (res.data && res.data.code === 200) {
|
|
||||||
const list = res.data.data.recommends.map(item => {
|
|
||||||
// 格式化销量
|
// 格式化销量
|
||||||
let salesStr = item.itemsale;
|
let salesStr = item.itemsale;
|
||||||
if (item.itemsale >= 10000) {
|
if (item.itemsale >= 10000) {
|
||||||
|
|
@ -426,20 +423,18 @@
|
||||||
sales: salesStr,
|
sales: salesStr,
|
||||||
brandTag: item.brand_name,
|
brandTag: item.brand_name,
|
||||||
lowestTag: item.label && item.label.length > 0 ? item.label[0] : '',
|
lowestTag: item.label && item.label.length > 0 ? item.label[0] : '',
|
||||||
shopType: item.shoptype === 'B' ? '天猫' : '淘宝'
|
shopType: item.shoptype === 'B' ? '天猫' : '淘宝',
|
||||||
|
tkmoney: item.tkmoney
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.goodsList = list;
|
this.goodsList = list;
|
||||||
}
|
}).catch(err => {
|
||||||
}
|
console.error('获取商品列表失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getWorthBuyLists() {
|
getWorthBuyLists() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/deserveLists?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/index/deserveLists?cid=YsWZ21tx',
|
const formatData = (list) => {
|
||||||
success: (res) => {
|
|
||||||
if (res.data && res.data.code === 200) {
|
|
||||||
const formatData = (list) => {
|
|
||||||
return list.map(item => ({
|
return list.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
img: item.itempic,
|
img: item.itempic,
|
||||||
|
|
@ -454,19 +449,17 @@
|
||||||
shopType: item.shoptype === 'B' ? '天猫' : '淘宝'
|
shopType: item.shoptype === 'B' ? '天猫' : '淘宝'
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
this.deserveList = formatData(res.data.data.deserve_lists);
|
this.deserveList = formatData(res.data.deserve_lists);
|
||||||
this.nineList = formatData(res.data.data.nine_lists);
|
this.nineList = formatData(res.data.nine_lists);
|
||||||
this.nineteenList = formatData(res.data.data.nineteen_lists);
|
this.nineteenList = formatData(res.data.nineteen_lists);
|
||||||
}
|
}).catch(err => {
|
||||||
}
|
console.error('获取值得买列表失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getBrandSaleList() {
|
getBrandSaleList() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/brandItem/choiceness?cid=YsWZ21tx').then(res => {
|
||||||
url: 'https://api.cmspro.haodanku.com/brandItem/choiceness?cid=YsWZ21tx',
|
if (res.data.brand_prefecture) {
|
||||||
success: (res) => {
|
this.brandSaleShops = res.data.brand_prefecture.map(shop => ({
|
||||||
if (res.data && res.data.code === 200 && res.data.data.brand_prefecture) {
|
|
||||||
this.brandSaleShops = res.data.data.brand_prefecture.map(shop => ({
|
|
||||||
bg: shop.backimage || 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80',
|
bg: shop.backimage || 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80',
|
||||||
logo: shop.brand_logo || 'https://images.unsplash.com/photo-1560155016-bd4879ae8f21?w=200&q=80',
|
logo: shop.brand_logo || 'https://images.unsplash.com/photo-1560155016-bd4879ae8f21?w=200&q=80',
|
||||||
name: shop.fq_brand_name || '大牌特卖',
|
name: shop.fq_brand_name || '大牌特卖',
|
||||||
|
|
@ -481,9 +474,10 @@
|
||||||
price: goods.itemendprice,
|
price: goods.itemendprice,
|
||||||
coupon: goods.couponmoney
|
coupon: goods.couponmoney
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取品牌特卖失败:', err.message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
switchTab(index) {
|
switchTab(index) {
|
||||||
|
|
@ -1632,6 +1626,23 @@
|
||||||
border-radius: 4rpx;
|
border-radius: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-coupon {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-val {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-sales {
|
.goods-sales {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
import BottomNav from '@/components/bottom-nav/bottom-nav.vue';
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -194,18 +195,16 @@
|
||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/ranking/lists', {
|
||||||
url: 'https://api.cmspro.haodanku.com/ranking/lists',
|
|
||||||
data: {
|
|
||||||
type: this.rankType,
|
type: this.rankType,
|
||||||
category_id: this.currentCateId,
|
category_id: this.currentCateId,
|
||||||
page: this.page,
|
page: this.page,
|
||||||
page_size: 60,
|
page_size: 60,
|
||||||
cid: 'YsWZ21tx'
|
cid: 'YsWZ21tx'
|
||||||
},
|
})
|
||||||
success: (res) => {
|
.then(res => {
|
||||||
if (res.data && res.data.code === 200 && res.data.data && res.data.data.list_item && res.data.data.list_item.list) {
|
if (res.data && res.data.list_item && res.data.list_item.list) {
|
||||||
const list = res.data.data.list_item.list.map(item => {
|
const list = res.data.list_item.list.map(item => {
|
||||||
// 处理标题
|
// 处理标题
|
||||||
let title = item.itemshorttitle || item.itemtitle;
|
let title = item.itemshorttitle || item.itemtitle;
|
||||||
if (title.length > 18) {
|
if (title.length > 18) {
|
||||||
|
|
@ -235,11 +234,10 @@
|
||||||
});
|
});
|
||||||
this.goodsList = [...this.goodsList, ...list];
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
complete: () => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
loadMore() {
|
loadMore() {
|
||||||
// 榜单通常展示前60条
|
// 榜单通常展示前60条
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,12 @@
|
||||||
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
<text class="coupon-txt">{{ goods.couponValue }}元</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="price-coupon" v-if="!$store.state.isThirdParty">
|
||||||
|
<view class="coupon-left">
|
||||||
|
<text class="coupon-tip">预估消费券</text>
|
||||||
|
<text class="coupon-val">{{ $estimateCoupon(goods.tkmoney) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="goods-bottom-info">
|
<view class="goods-bottom-info">
|
||||||
<text class="sales">已售{{ goods.sales }}件</text>
|
<text class="sales">已售{{ goods.sales }}件</text>
|
||||||
|
|
@ -174,6 +180,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -250,24 +257,20 @@
|
||||||
uni.setStorageSync('search_history', JSON.stringify(list));
|
uni.setStorageSync('search_history', JSON.stringify(list));
|
||||||
},
|
},
|
||||||
getRankingList() {
|
getRankingList() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/search/searchRankingList?cid=YsWZ21tx')
|
||||||
url: 'https://api.cmspro.haodanku.com/search/searchRankingList?cid=YsWZ21tx',
|
.then(res => {
|
||||||
success: (res) => {
|
if (res.data && res.data.ranking_list) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data.ranking_list) {
|
this.rankingList = res.data.ranking_list.slice(0, 10);
|
||||||
this.rankingList = res.data.data.ranking_list.slice(0, 10);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
getHotThemes() {
|
getHotThemes() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/index/hotTheme?cid=YsWZ21tx')
|
||||||
url: 'https://api.cmspro.haodanku.com/index/hotTheme?cid=YsWZ21tx',
|
.then(res => {
|
||||||
success: (res) => {
|
if (res.data) {
|
||||||
if (res.data && res.data.code === 200) {
|
this.hotThemes = res.data.slice(0, 10);
|
||||||
this.hotThemes = res.data.data.slice(0, 10);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onInputFocus() {
|
onInputFocus() {
|
||||||
// 聚焦搜索框时切回推荐视图(保留已搜索结果与关键词,下次搜索仍可继续)
|
// 聚焦搜索框时切回推荐视图(保留已搜索结果与关键词,下次搜索仍可继续)
|
||||||
|
|
@ -317,11 +320,9 @@
|
||||||
if (this.filterIsTmall) shopTypes.push(2);
|
if (this.filterIsTmall) shopTypes.push(2);
|
||||||
if (shopTypes.length > 0) data.shoptype = shopTypes.join(',');
|
if (shopTypes.length > 0) data.shoptype = shopTypes.join(',');
|
||||||
|
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/find/allItemList', data)
|
||||||
url: 'https://api.cmspro.haodanku.com/find/allItemList',
|
.then(res => {
|
||||||
data,
|
const body = res.body || {};
|
||||||
success: (res) => {
|
|
||||||
const body = res.data || {};
|
|
||||||
if (body.code === 200 && body.data) {
|
if (body.code === 200 && body.data) {
|
||||||
if (body.data.num_page) this.numPage = body.data.num_page;
|
if (body.data.num_page) this.numPage = body.data.num_page;
|
||||||
const rawList = body.data.item_info || [];
|
const rawList = body.data.item_info || [];
|
||||||
|
|
@ -334,6 +335,7 @@
|
||||||
: (item.itemshorttitle || item.itemtitle),
|
: (item.itemshorttitle || item.itemtitle),
|
||||||
finalPrice: item.itemendprice,
|
finalPrice: item.itemendprice,
|
||||||
couponValue: item.couponmoney || 0,
|
couponValue: item.couponmoney || 0,
|
||||||
|
tkmoney: item.tkmoney || 0,
|
||||||
sales: item.itemsale >= 10000
|
sales: item.itemsale >= 10000
|
||||||
? (item.itemsale / 10000).toFixed(1) + '万'
|
? (item.itemsale / 10000).toFixed(1) + '万'
|
||||||
: item.itemsale,
|
: item.itemsale,
|
||||||
|
|
@ -359,14 +361,14 @@
|
||||||
uni.showToast({ title: body.msg || '搜索失败', icon: 'none' });
|
uni.showToast({ title: body.msg || '搜索失败', icon: 'none' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: () => {
|
.catch(err => {
|
||||||
uni.showToast({ title: '网络错误', icon: 'none' });
|
uni.showToast({ title: err.message || '网络错误', icon: 'none' });
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
})
|
||||||
});
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onScrollBottom() {
|
onScrollBottom() {
|
||||||
if (this.isSearching && !this.finished && !this.loading && this.currentKeyword) {
|
if (this.isSearching && !this.finished && !this.loading && this.currentKeyword) {
|
||||||
|
|
@ -976,6 +978,23 @@
|
||||||
padding: 0 8rpx;
|
padding: 0 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.price-coupon {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-tip {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-val {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff8a00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.goods-bottom-info {
|
.goods-bottom-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -443,11 +445,10 @@
|
||||||
fetchTopBrandsData(isLoadMore = false) {
|
fetchTopBrandsData(isLoadMore = false) {
|
||||||
if (this.topLoading) return;
|
if (this.topLoading) return;
|
||||||
this.topLoading = true;
|
this.topLoading = true;
|
||||||
uni.request({
|
http.get(`https://api.cmspro.haodanku.com/brandItem/getBrands?page=${this.topPage}&page_size=20&cid=YsWZ21tx`)
|
||||||
url: `https://api.cmspro.haodanku.com/brandItem/getBrands?page=${this.topPage}&page_size=20&cid=YsWZ21tx`,
|
.then((res) => {
|
||||||
success: (res) => {
|
if (res.data && Array.isArray(res.data.list)) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data && Array.isArray(res.data.data.list)) {
|
const list = res.data.list.map(item => ({
|
||||||
const list = res.data.data.list.map(item => ({
|
|
||||||
id: item.id,
|
id: item.id,
|
||||||
fq_brand_name: item.fq_brand_name,
|
fq_brand_name: item.fq_brand_name,
|
||||||
brand_logo: item.brand_logo ? item.brand_logo.replace('http://', 'https://') : ''
|
brand_logo: item.brand_logo ? item.brand_logo.replace('http://', 'https://') : ''
|
||||||
|
|
@ -461,7 +462,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagination = res.data.data.pagination;
|
const pagination = res.data.pagination;
|
||||||
if (pagination && this.topPage >= pagination.page_count) {
|
if (pagination && this.topPage >= pagination.page_count) {
|
||||||
this.topFinished = true;
|
this.topFinished = true;
|
||||||
} else if (list.length < 20) {
|
} else if (list.length < 20) {
|
||||||
|
|
@ -472,27 +473,25 @@
|
||||||
this.topFinished = true;
|
this.topFinished = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: (err) => {
|
.catch((err) => {
|
||||||
console.log('拉取上方滚动品牌接口异常', err);
|
console.log('拉取上方滚动品牌接口异常', err);
|
||||||
if (isLoadMore && this.topPage > 1) {
|
if (isLoadMore && this.topPage > 1) {
|
||||||
this.topPage -= 1;
|
this.topPage -= 1;
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
complete: () => {
|
.finally(() => {
|
||||||
this.topLoading = false;
|
this.topLoading = false;
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// 全量通用专场初始化及导航字典挂载
|
// 全量通用专场初始化及导航字典挂载
|
||||||
fetchChoicenessData() {
|
fetchChoicenessData() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/brandItem/choiceness?is_get_category=1&cid=YsWZ21tx')
|
||||||
url: 'https://api.cmspro.haodanku.com/brandItem/choiceness?is_get_category=1&cid=YsWZ21tx',
|
.then((res) => {
|
||||||
success: (res) => {
|
if (res.data) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data) {
|
|
||||||
// 1. 挂载/补充导航字典
|
// 1. 挂载/补充导航字典
|
||||||
if (res.data.data.category && Array.isArray(res.data.data.category)) {
|
if (res.data.category && Array.isArray(res.data.category)) {
|
||||||
const mappedCats = res.data.data.category.map(c => ({
|
const mappedCats = res.data.category.map(c => ({
|
||||||
cat_id: Number(c.cat_id),
|
cat_id: Number(c.cat_id),
|
||||||
cat_name: c.cat_name
|
cat_name: c.cat_name
|
||||||
}));
|
}));
|
||||||
|
|
@ -500,8 +499,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 挂载全景精选海报流
|
// 2. 挂载全景精选海报流
|
||||||
if (res.data.data.brand_prefecture && Array.isArray(res.data.data.brand_prefecture)) {
|
if (res.data.brand_prefecture && Array.isArray(res.data.brand_prefecture)) {
|
||||||
const list = res.data.data.brand_prefecture.map(shop => {
|
const list = res.data.brand_prefecture.map(shop => {
|
||||||
let logoStr = shop.brand_logo ? shop.brand_logo.replace('http://', 'https://') : 'https://cdn-icons-png.flaticon.com/512/882/882730.png';
|
let logoStr = shop.brand_logo ? shop.brand_logo.replace('http://', 'https://') : 'https://cdn-icons-png.flaticon.com/512/882/882730.png';
|
||||||
let bgStr = shop.backimage ? shop.backimage.replace('http://', 'https://') : 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80';
|
let bgStr = shop.backimage ? shop.backimage.replace('http://', 'https://') : 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80';
|
||||||
|
|
||||||
|
|
@ -531,18 +530,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: (err) => {
|
.catch((err) => {
|
||||||
console.log('拉取线上精选品牌专场分类联动数据失败,采用固定底座显示', err);
|
console.log('拉取线上精选品牌专场分类联动数据失败,采用固定底座显示', err);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// 接驳 brandCategory 接口分流,内置光影轮盘自动修复缺失背景。取消突兀 Toast 完美适配内嵌底座
|
// 接驳 brandCategory 接口分流,内置光影轮盘自动修复缺失背景。取消突兀 Toast 完美适配内嵌底座
|
||||||
fetchBrandCategoryData(categoryId) {
|
fetchBrandCategoryData(categoryId) {
|
||||||
uni.request({
|
http.get(`https://api.cmspro.haodanku.com/brandItem/brandCategory?category_id=${categoryId}&cid=YsWZ21tx`)
|
||||||
url: `https://api.cmspro.haodanku.com/brandItem/brandCategory?category_id=${categoryId}&cid=YsWZ21tx`,
|
.then((res) => {
|
||||||
success: (res) => {
|
if (res.data && Array.isArray(res.data.brands)) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data && Array.isArray(res.data.data.brands)) {
|
|
||||||
// 预置唯美光影/极简质感图库底座,智能赋能无海报的品牌
|
// 预置唯美光影/极简质感图库底座,智能赋能无海报的品牌
|
||||||
const defaultBanners = [
|
const defaultBanners = [
|
||||||
'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80',
|
'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800&q=80',
|
||||||
|
|
@ -552,7 +549,7 @@
|
||||||
'https://images.unsplash.com/photo-1621939514649-280e2fc8a00w?w=800&q=80'
|
'https://images.unsplash.com/photo-1621939514649-280e2fc8a00w?w=800&q=80'
|
||||||
];
|
];
|
||||||
|
|
||||||
const list = res.data.data.brands.map((b, bIndex) => {
|
const list = res.data.brands.map((b, bIndex) => {
|
||||||
let logoStr = b.brand_logo ? b.brand_logo.replace('http://', 'https://') : 'https://cdn-icons-png.flaticon.com/512/882/882730.png';
|
let logoStr = b.brand_logo ? b.brand_logo.replace('http://', 'https://') : 'https://cdn-icons-png.flaticon.com/512/882/882730.png';
|
||||||
let bgStr = defaultBanners[bIndex % defaultBanners.length];
|
let bgStr = defaultBanners[bIndex % defaultBanners.length];
|
||||||
|
|
||||||
|
|
@ -592,19 +589,17 @@
|
||||||
} else {
|
} else {
|
||||||
console.log('拉取分类数据异常或格式不对');
|
console.log('拉取分类数据异常或格式不对');
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: (err) => {
|
.catch((err) => {
|
||||||
console.log('调用 brandCategory 接口失败', err);
|
console.log('调用 brandCategory 接口失败', err);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// 底部品牌热销单品聚合数据流
|
// 底部品牌热销单品聚合数据流
|
||||||
fetchBrandSaleData() {
|
fetchBrandSaleData() {
|
||||||
uni.request({
|
http.get('https://api.cmspro.haodanku.com/brandItem/brandSale?cid=YsWZ21tx')
|
||||||
url: 'https://api.cmspro.haodanku.com/brandItem/brandSale?cid=YsWZ21tx',
|
.then((res) => {
|
||||||
success: (res) => {
|
if (res.data && Array.isArray(res.data.items)) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data && Array.isArray(res.data.data.items)) {
|
const list = res.data.items.map(item => {
|
||||||
const list = res.data.data.items.map(item => {
|
|
||||||
let logoUrl = '';
|
let logoUrl = '';
|
||||||
if (item.brand_info && item.brand_info.brand_logo) {
|
if (item.brand_info && item.brand_info.brand_logo) {
|
||||||
logoUrl = item.brand_info.brand_logo.replace('http://', 'https://');
|
logoUrl = item.brand_info.brand_logo.replace('http://', 'https://');
|
||||||
|
|
@ -640,11 +635,10 @@
|
||||||
this.brandSaleList = list;
|
this.brandSaleList = list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: (err) => {
|
.catch((err) => {
|
||||||
console.log('拉取底部品牌热销接口异常,采用极尽精美的静态底座显示', err);
|
console.log('拉取底部品牌热销接口异常,采用极尽精美的静态底座显示', err);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -278,21 +280,20 @@
|
||||||
sortParam = this.priceOrder === 'asc' ? 8 : 9;
|
sortParam = this.priceOrder === 'asc' ? 8 : 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.request({
|
http.get(`https://api.cmspro.haodanku.com/brandItem/detail?page=${this.page}&page_size=20&sort=${sortParam}&brand_id=${this.brandId}&cid=YsWZ21tx`)
|
||||||
url: `https://api.cmspro.haodanku.com/brandItem/detail?page=${this.page}&page_size=20&sort=${sortParam}&brand_id=${this.brandId}&cid=YsWZ21tx`,
|
.then((res) => {
|
||||||
success: (res) => {
|
if (res.data) {
|
||||||
if (res.data && res.data.code === 200 && res.data.data) {
|
|
||||||
// 挂载核心品牌自画像
|
// 挂载核心品牌自画像
|
||||||
if (res.data.data.brand_info) {
|
if (res.data.brand_info) {
|
||||||
const info = res.data.data.brand_info;
|
const info = res.data.brand_info;
|
||||||
if (info.brand_logo) info.brand_logo = info.brand_logo.replace('http://', 'https://');
|
if (info.brand_logo) info.brand_logo = info.brand_logo.replace('http://', 'https://');
|
||||||
if (info.inside_logo) info.inside_logo = info.inside_logo.replace('http://', 'https://');
|
if (info.inside_logo) info.inside_logo = info.inside_logo.replace('http://', 'https://');
|
||||||
this.brandInfo = info;
|
this.brandInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 挂载分页商品方阵
|
// 挂载分页商品方阵
|
||||||
if (res.data.data.items && Array.isArray(res.data.data.items.list)) {
|
if (res.data.items && Array.isArray(res.data.items.list)) {
|
||||||
const list = res.data.data.items.list.map(goods => {
|
const list = res.data.items.list.map(goods => {
|
||||||
let pic = goods.itempic ? goods.itempic.replace('http://', 'https://') : '';
|
let pic = goods.itempic ? goods.itempic.replace('http://', 'https://') : '';
|
||||||
return {
|
return {
|
||||||
id: goods.id,
|
id: goods.id,
|
||||||
|
|
@ -315,7 +316,7 @@
|
||||||
this.itemList = list;
|
this.itemList = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagination = res.data.data.items.pagination;
|
const pagination = res.data.items.pagination;
|
||||||
if (pagination && this.page >= pagination.page_count) {
|
if (pagination && this.page >= pagination.page_count) {
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
} else if (list.length < 20) {
|
} else if (list.length < 20) {
|
||||||
|
|
@ -327,16 +328,15 @@
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '加载专区数据失败', icon: 'none' });
|
uni.showToast({ title: '加载专区数据失败', icon: 'none' });
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
fail: (err) => {
|
.catch((err) => {
|
||||||
console.log('拉取品牌专页接口错误', err);
|
console.log('拉取品牌专页接口错误', err);
|
||||||
if (isLoadMore && this.page > 1) this.page -= 1;
|
if (isLoadMore && this.page > 1) this.page -= 1;
|
||||||
},
|
})
|
||||||
complete: () => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (!isLoadMore) uni.hideLoading();
|
if (!isLoadMore) uni.hideLoading();
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,257 @@
|
||||||
|
/**
|
||||||
|
* uni.request 统一封装
|
||||||
|
*
|
||||||
|
* 设计目标:
|
||||||
|
* 1. 自动兼容项目中所有 API 的成功状态判断(code === 200 / code === 1 / status === 200)
|
||||||
|
* 2. 支持自定义 header、超时、baseURL
|
||||||
|
* 3. Promise 化,调用简单
|
||||||
|
* 4. 支持请求/响应拦截器(便于后续统一注入 token、处理错误等)
|
||||||
|
* 5. 数据提取自动降级:优先取 res.data.data,不存在则取 res.data
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
/**
|
||||||
|
* @param {Object} config - 全局默认配置
|
||||||
|
* @param {string} config.baseURL - 基础 URL
|
||||||
|
* @param {number} config.timeout - 超时时间(ms),默认 30000
|
||||||
|
* @param {Object} config.header - 默认请求头
|
||||||
|
*/
|
||||||
|
constructor(config = {}) {
|
||||||
|
this.config = {
|
||||||
|
baseURL: '',
|
||||||
|
timeout: 30000,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
...config
|
||||||
|
};
|
||||||
|
this.interceptors = {
|
||||||
|
request: [],
|
||||||
|
response: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加请求拦截器
|
||||||
|
* @param {Function} fn - (config) => config
|
||||||
|
*/
|
||||||
|
addRequestInterceptor(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
this.interceptors.request.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加响应拦截器
|
||||||
|
* @param {Function} fn - (response) => response
|
||||||
|
*/
|
||||||
|
addResponseInterceptor(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
this.interceptors.response.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应是否成功
|
||||||
|
*
|
||||||
|
* 兼容项目中实际存在的 4 种响应格式:
|
||||||
|
* 1. CMS API: res.data.code === 200
|
||||||
|
* 2. Auth API: res.statusCode === 200 && (res.data.status === 200 || res.data.code === 1)
|
||||||
|
* 3. Hdk v2 API: res.data.code === 1
|
||||||
|
* 4. 兜底: res.statusCode === 200(无业务状态码时)
|
||||||
|
*
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isSuccess(res) {
|
||||||
|
// HTTP 层已失败(如 404/500)
|
||||||
|
if (res.statusCode >= 400) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
// 无响应体时,以 HTTP 状态码为准
|
||||||
|
return res.statusCode === 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取业务状态码(兼容 code / status 两种字段名)
|
||||||
|
const code = data.code !== undefined ? Number(data.code)
|
||||||
|
: data.status !== undefined ? Number(data.status)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (code !== null && !isNaN(code)) {
|
||||||
|
return code === 200 || code === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无业务状态码时,以 HTTP 状态码为准
|
||||||
|
return res.statusCode === 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应中提取业务数据
|
||||||
|
*
|
||||||
|
* 降级策略:
|
||||||
|
* - 优先取 res.data.data(CMS 标准格式)
|
||||||
|
* - 不存在则取 res.data 本身(直接返回格式)
|
||||||
|
* - 空响应返回 null
|
||||||
|
*
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
extractData(res) {
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return data.data !== undefined ? data.data : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应中提取错误信息
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
extractError(res) {
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
return `请求失败 (HTTP ${res.statusCode})`;
|
||||||
|
}
|
||||||
|
return data.msg || data.message || data.error || `请求失败 (code: ${data.code || data.status})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心请求方法
|
||||||
|
*
|
||||||
|
* @param {Object} options - 请求配置
|
||||||
|
* @param {string} options.url - 请求地址(可相对 baseURL)
|
||||||
|
* @param {string} options.method - 请求方法:GET/POST/PUT/DELETE,默认 GET
|
||||||
|
* @param {Object} options.data - 请求数据
|
||||||
|
* @param {Object} options.header - 自定义请求头(会合并覆盖默认 header,大小写不敏感)
|
||||||
|
* @param {number} options.timeout - 本次请求单独设置的超时时间
|
||||||
|
* @returns {Promise<{success: true, data: any, body: any, raw: Object}>}
|
||||||
|
*/
|
||||||
|
request(options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 1. 合并配置:默认 → 实例配置 → 单次请求配置
|
||||||
|
// header 合并需大小写不敏感去重(如 Content-Type 与 content-type 是同一头)
|
||||||
|
const mergedHeader = {};
|
||||||
|
for (const [k, v] of Object.entries(this.config.header)) {
|
||||||
|
mergedHeader[k.toLowerCase()] = v;
|
||||||
|
}
|
||||||
|
for (const [k, v] of Object.entries(options.header || {})) {
|
||||||
|
mergedHeader[k.toLowerCase()] = v;
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
...this.config,
|
||||||
|
...options,
|
||||||
|
header: mergedHeader
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. 拼接 baseURL(如果 url 不是完整 HTTP 地址)
|
||||||
|
if (config.baseURL && config.url && !config.url.startsWith('http')) {
|
||||||
|
config.url = config.baseURL.replace(/\/$/, '') + '/' + config.url.replace(/^\//, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 执行请求拦截器
|
||||||
|
try {
|
||||||
|
for (const fn of this.interceptors.request) {
|
||||||
|
config = fn(config) || config;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject({ success: false, message: err.message || '请求拦截器异常', code: -2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发起请求
|
||||||
|
uni.request({
|
||||||
|
url: config.url,
|
||||||
|
method: (config.method || 'GET').toUpperCase(),
|
||||||
|
data: config.data,
|
||||||
|
header: config.header,
|
||||||
|
timeout: config.timeout,
|
||||||
|
success: (res) => {
|
||||||
|
// 5. 执行响应拦截器
|
||||||
|
try {
|
||||||
|
for (const fn of this.interceptors.response) {
|
||||||
|
res = fn(res) || res;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject({ success: false, message: err.message || '响应拦截器异常', code: -3, raw: res });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 判断成功/失败
|
||||||
|
if (this.isSuccess(res)) {
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
data: this.extractData(res),
|
||||||
|
body: res.data,
|
||||||
|
raw: res
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: this.extractError(res),
|
||||||
|
code: res.data?.code ?? res.data?.status ?? res.statusCode,
|
||||||
|
raw: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: err.errMsg || '网络请求失败,请检查网络',
|
||||||
|
code: -1,
|
||||||
|
raw: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET 请求快捷方法
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
get(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'GET', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求快捷方法
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
post(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'POST', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT 请求快捷方法
|
||||||
|
*/
|
||||||
|
put(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'PUT', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE 请求快捷方法
|
||||||
|
*/
|
||||||
|
delete(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'DELETE', ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 创建全局默认实例
|
||||||
|
// ============================================================
|
||||||
|
const http = new Request();
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 导出
|
||||||
|
// ============================================================
|
||||||
|
export default http;
|
||||||
|
export { Request };
|
||||||
|
|
@ -0,0 +1,257 @@
|
||||||
|
/**
|
||||||
|
* uni.request 统一封装
|
||||||
|
*
|
||||||
|
* 设计目标:
|
||||||
|
* 1. 自动兼容项目中所有 API 的成功状态判断(code === 200 / code === 1 / status === 200)
|
||||||
|
* 2. 支持自定义 header、超时、baseURL
|
||||||
|
* 3. Promise 化,调用简单
|
||||||
|
* 4. 支持请求/响应拦截器(便于后续统一注入 token、处理错误等)
|
||||||
|
* 5. 数据提取自动降级:优先取 res.data.data,不存在则取 res.data
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
/**
|
||||||
|
* @param {Object} config - 全局默认配置
|
||||||
|
* @param {string} config.baseURL - 基础 URL
|
||||||
|
* @param {number} config.timeout - 超时时间(ms),默认 30000
|
||||||
|
* @param {Object} config.header - 默认请求头
|
||||||
|
*/
|
||||||
|
constructor(config = {}) {
|
||||||
|
this.config = {
|
||||||
|
baseURL: '',
|
||||||
|
timeout: 30000,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
...config
|
||||||
|
};
|
||||||
|
this.interceptors = {
|
||||||
|
request: [],
|
||||||
|
response: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加请求拦截器
|
||||||
|
* @param {Function} fn - (config) => config
|
||||||
|
*/
|
||||||
|
addRequestInterceptor(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
this.interceptors.request.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加响应拦截器
|
||||||
|
* @param {Function} fn - (response) => response
|
||||||
|
*/
|
||||||
|
addResponseInterceptor(fn) {
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
this.interceptors.response.push(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断响应是否成功
|
||||||
|
*
|
||||||
|
* 兼容项目中实际存在的 4 种响应格式:
|
||||||
|
* 1. CMS API: res.data.code === 200
|
||||||
|
* 2. Auth API: res.statusCode === 200 && (res.data.status === 200 || res.data.code === 1)
|
||||||
|
* 3. Hdk v2 API: res.data.code === 1
|
||||||
|
* 4. 兜底: res.statusCode === 200(无业务状态码时)
|
||||||
|
*
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isSuccess(res) {
|
||||||
|
// HTTP 层已失败(如 404/500)
|
||||||
|
if (res.statusCode >= 400) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
// 无响应体时,以 HTTP 状态码为准
|
||||||
|
return res.statusCode === 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取业务状态码(兼容 code / status 两种字段名)
|
||||||
|
const code = data.code !== undefined ? Number(data.code)
|
||||||
|
: data.status !== undefined ? Number(data.status)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (code !== null && !isNaN(code)) {
|
||||||
|
return code === 200 || code === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无业务状态码时,以 HTTP 状态码为准
|
||||||
|
return res.statusCode === 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应中提取业务数据
|
||||||
|
*
|
||||||
|
* 降级策略:
|
||||||
|
* - 优先取 res.data.data(CMS 标准格式)
|
||||||
|
* - 不存在则取 res.data 本身(直接返回格式)
|
||||||
|
* - 空响应返回 null
|
||||||
|
*
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
extractData(res) {
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return data.data !== undefined ? data.data : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应中提取错误信息
|
||||||
|
* @param {Object} res - uni.request 的 success 回调参数
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
extractError(res) {
|
||||||
|
const data = res.data;
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
return `请求失败 (HTTP ${res.statusCode})`;
|
||||||
|
}
|
||||||
|
return data.msg || data.message || data.error || `请求失败 (code: ${data.code || data.status})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心请求方法
|
||||||
|
*
|
||||||
|
* @param {Object} options - 请求配置
|
||||||
|
* @param {string} options.url - 请求地址(可相对 baseURL)
|
||||||
|
* @param {string} options.method - 请求方法:GET/POST/PUT/DELETE,默认 GET
|
||||||
|
* @param {Object} options.data - 请求数据
|
||||||
|
* @param {Object} options.header - 自定义请求头(会合并覆盖默认 header,大小写不敏感)
|
||||||
|
* @param {number} options.timeout - 本次请求单独设置的超时时间
|
||||||
|
* @returns {Promise<{success: true, data: any, body: any, raw: Object}>}
|
||||||
|
*/
|
||||||
|
request(options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 1. 合并配置:默认 → 实例配置 → 单次请求配置
|
||||||
|
// header 合并需大小写不敏感去重(如 Content-Type 与 content-type 是同一头)
|
||||||
|
const mergedHeader = {};
|
||||||
|
for (const [k, v] of Object.entries(this.config.header)) {
|
||||||
|
mergedHeader[k.toLowerCase()] = v;
|
||||||
|
}
|
||||||
|
for (const [k, v] of Object.entries(options.header || {})) {
|
||||||
|
mergedHeader[k.toLowerCase()] = v;
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
...this.config,
|
||||||
|
...options,
|
||||||
|
header: mergedHeader
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. 拼接 baseURL(如果 url 不是完整 HTTP 地址)
|
||||||
|
if (config.baseURL && config.url && !config.url.startsWith('http')) {
|
||||||
|
config.url = config.baseURL.replace(/\/$/, '') + '/' + config.url.replace(/^\//, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 执行请求拦截器
|
||||||
|
try {
|
||||||
|
for (const fn of this.interceptors.request) {
|
||||||
|
config = fn(config) || config;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject({ success: false, message: err.message || '请求拦截器异常', code: -2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发起请求
|
||||||
|
uni.request({
|
||||||
|
url: config.url,
|
||||||
|
method: (config.method || 'GET').toUpperCase(),
|
||||||
|
data: config.data,
|
||||||
|
header: config.header,
|
||||||
|
timeout: config.timeout,
|
||||||
|
success: (res) => {
|
||||||
|
// 5. 执行响应拦截器
|
||||||
|
try {
|
||||||
|
for (const fn of this.interceptors.response) {
|
||||||
|
res = fn(res) || res;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject({ success: false, message: err.message || '响应拦截器异常', code: -3, raw: res });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 判断成功/失败
|
||||||
|
if (this.isSuccess(res)) {
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
data: this.extractData(res),
|
||||||
|
body: res.data,
|
||||||
|
raw: res
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: this.extractError(res),
|
||||||
|
code: res.data?.code ?? res.data?.status ?? res.statusCode,
|
||||||
|
raw: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: err.errMsg || '网络请求失败,请检查网络',
|
||||||
|
code: -1,
|
||||||
|
raw: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET 请求快捷方法
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
get(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'GET', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求快捷方法
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
post(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'POST', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT 请求快捷方法
|
||||||
|
*/
|
||||||
|
put(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'PUT', ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE 请求快捷方法
|
||||||
|
*/
|
||||||
|
delete(url, data, options = {}) {
|
||||||
|
return this.request({ url, data, method: 'DELETE', ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 创建全局默认实例
|
||||||
|
// ============================================================
|
||||||
|
const http = new Request();
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 导出
|
||||||
|
// ============================================================
|
||||||
|
export default http;
|
||||||
|
export { Request };
|
||||||
|
|
@ -3,11 +3,15 @@ import { createStore } from 'vuex'
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
state: {
|
state: {
|
||||||
currentUid: '',
|
currentUid: '',
|
||||||
relationId: ''
|
relationId: '',
|
||||||
|
pid: '',
|
||||||
|
isThirdParty: false
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
currentUid: state => state.currentUid,
|
currentUid: state => state.currentUid,
|
||||||
relationId: state => state.relationId
|
relationId: state => state.relationId,
|
||||||
|
pid: state => state.pid,
|
||||||
|
isThirdParty: state => state.isThirdParty
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_CURRENT_UID(state, uid) {
|
SET_CURRENT_UID(state, uid) {
|
||||||
|
|
@ -21,6 +25,15 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
CLEAR_RELATION_ID(state) {
|
CLEAR_RELATION_ID(state) {
|
||||||
state.relationId = ''
|
state.relationId = ''
|
||||||
|
},
|
||||||
|
SET_PID(state, pid) {
|
||||||
|
state.pid = pid
|
||||||
|
},
|
||||||
|
CLEAR_PID(state) {
|
||||||
|
state.pid = ''
|
||||||
|
},
|
||||||
|
SET_IS_THIRD_PARTY(state, flag) {
|
||||||
|
state.isThirdParty = flag
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|
@ -35,6 +48,15 @@ const store = createStore({
|
||||||
},
|
},
|
||||||
clearRelationId({ commit }) {
|
clearRelationId({ commit }) {
|
||||||
commit('CLEAR_RELATION_ID')
|
commit('CLEAR_RELATION_ID')
|
||||||
|
},
|
||||||
|
setPid({ commit }, pid) {
|
||||||
|
commit('SET_PID', pid)
|
||||||
|
},
|
||||||
|
clearPid({ commit }) {
|
||||||
|
commit('CLEAR_PID')
|
||||||
|
},
|
||||||
|
setIsThirdParty({ commit }, flag) {
|
||||||
|
commit('SET_IS_THIRD_PARTY', flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<template>
|
||||||
|
<view class="test-page">
|
||||||
|
<view class="desc-card">
|
||||||
|
<text class="title">IntersectionObserver 仅首次触发演示</text>
|
||||||
|
<text class="subtitle">向下滚动,观察各模块首次进入视口时的淡入效果</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section-title">场景:仅首次触发 — 视口内淡入动画</view>
|
||||||
|
<view
|
||||||
|
class="fade-box"
|
||||||
|
v-for="(item, index) in fadeList"
|
||||||
|
:key="index"
|
||||||
|
:id="`fade-${index}`"
|
||||||
|
:class="{ 'fade-in': item.visible }"
|
||||||
|
>
|
||||||
|
<text class="box-text">模块 {{ index + 1 }}</text>
|
||||||
|
<text class="box-status">{{ item.triggered ? '✅ 已触发(不再重复)' : '⏳ 等待首次进入视口...' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer-space"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fadeList: Array.from({ length: 10 }, () => ({
|
||||||
|
visible: false,
|
||||||
|
triggered: false
|
||||||
|
})),
|
||||||
|
// ⭐ 关键:用一个数组存放每个元素独立的 observer,避免互相覆盖
|
||||||
|
observers: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
this.initFadeObserver();
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
// 页面卸载时,断开所有 observer
|
||||||
|
this.observers.forEach(obs => obs.disconnect());
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initFadeObserver() {
|
||||||
|
this.fadeList.forEach((_, index) => {
|
||||||
|
// ⭐ 核心修复:每个元素创建独立的 IntersectionObserver 实例
|
||||||
|
// 同一个实例多次调用 observe() 会被覆盖,只有最后一个生效
|
||||||
|
const observer = uni.createIntersectionObserver(this, {
|
||||||
|
thresholds: [0]
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.relativeToViewport();
|
||||||
|
|
||||||
|
observer.observe(`#fade-${index}`, (result) => {
|
||||||
|
const item = this.fadeList[index];
|
||||||
|
|
||||||
|
// 进入视口 且 之前没触发过
|
||||||
|
if (result.intersectionRatio > 0 && !item.triggered) {
|
||||||
|
console.log(`模块 ${index + 1} 首次进入视口`);
|
||||||
|
|
||||||
|
this.$set(this.fadeList, index, {
|
||||||
|
visible: true,
|
||||||
|
triggered: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 触发后断开这个 observer,彻底释放
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存到数组,方便 onUnload 统一清理
|
||||||
|
this.observers.push(observer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.test-page {
|
||||||
|
background-color: #f5f6f8;
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-card {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-size: 26rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 40rpx 0 20rpx;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
border-left: 8rpx solid #ff416c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-box {
|
||||||
|
height: 240rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(60rpx);
|
||||||
|
transition: all 0.6s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-box.fade-in {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-status {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-space {
|
||||||
|
height: 100rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"hash": "0694fca5",
|
"hash": "aee3647b",
|
||||||
"configHash": "6057985e",
|
"configHash": "43aa957d",
|
||||||
"lockfileHash": "e3b0c442",
|
"lockfileHash": "32773baf",
|
||||||
"browserHash": "9518c2ac",
|
"browserHash": "732cb9f3",
|
||||||
"optimized": {},
|
"optimized": {},
|
||||||
"chunks": {}
|
"chunks": {}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
// 预估消费券函数
|
||||||
|
export function estimateCoupon(tkmoney = 0, percentage = 0.3) {
|
||||||
|
let result = (tkmoney * percentage).toFixed(2);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue