淘宝授权推送
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,17 +27,14 @@
|
||||||
},
|
},
|
||||||
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 = ['首页', '榜单', '分类'];
|
const allowedTitles = ['首页', '榜单', '分类'];
|
||||||
this.bottomBarList = res.data.data.bottom_bar
|
this.bottomBarList = res.data.bottom_bar
|
||||||
.filter(item => allowedTitles.includes(item.title))
|
.filter(item => allowedTitles.includes(item.title))
|
||||||
.sort((a, b) => b.sort - a.sort);
|
.sort((a, b) => b.sort - a.sort);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleTabClick(tab, index) {
|
handleTabClick(tab, index) {
|
||||||
|
|
|
||||||
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,12 +157,10 @@
|
||||||
...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) {
|
|
||||||
const block = res.data.data ? (res.data.data.block || []) : (res.data.block || []);
|
|
||||||
this.category.list = block.map((item, index) => ({
|
this.category.list = block.map((item, index) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: String(index)
|
value: String(index)
|
||||||
|
|
@ -170,21 +169,16 @@
|
||||||
const list = this.category.goodsLists[0] || [];
|
const list = this.category.goodsLists[0] || [];
|
||||||
this.listData.list = list;
|
this.listData.list = list;
|
||||||
this.listData.finished = true;
|
this.listData.finished = true;
|
||||||
} else {
|
})
|
||||||
|
.catch(() => {
|
||||||
this.listData.list = [];
|
this.listData.list = [];
|
||||||
this.listData.finished = true;
|
this.listData.finished = true;
|
||||||
}
|
})
|
||||||
},
|
.finally(() => {
|
||||||
fail: () => {
|
|
||||||
this.listData.list = [];
|
|
||||||
this.listData.finished = true;
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.listData.loading = false;
|
this.listData.loading = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -247,16 +241,11 @@
|
||||||
|
|
||||||
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'
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200 && res.data) {
|
|
||||||
const data = res.data.data || res.data;
|
|
||||||
const jumpUrl = data.coupon_click_url || data.item_url;
|
const jumpUrl = data.coupon_click_url || data.item_url;
|
||||||
if (jumpUrl) {
|
if (jumpUrl) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
|
|
@ -286,32 +275,16 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}).catch(() => {
|
||||||
uni.showToast({
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
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'
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200 && res.data) {
|
|
||||||
const data = res.data.data || res.data;
|
|
||||||
const taoword = data.taoword;
|
const taoword = data.taoword;
|
||||||
if (taoword) {
|
if (taoword) {
|
||||||
const taocode = '0' + taoword + '/';
|
const taocode = '0' + taoword + '/';
|
||||||
|
|
@ -326,24 +299,10 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '转链失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
uni.showToast({ title: '转链失败', icon: 'none' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@
|
||||||
|
|
||||||
<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() {
|
||||||
|
|
@ -69,11 +71,12 @@
|
||||||
onLoad() {
|
onLoad() {
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
const params = app.globalData.urlParams || {};
|
const params = app.globalData.urlParams || {};
|
||||||
|
|
||||||
this.Token = params.token || '';
|
this.Token = params.token || '';
|
||||||
this.code = params.code || '';
|
this.code = params.code || '';
|
||||||
this.queryState = params.state || '';
|
this.queryState = params.state || '';
|
||||||
this.$store.dispatch('setCurrentUid', (params.state || '').replace(/^uid/, '') || '')
|
this.exuid = params.exuid || '';
|
||||||
|
if(this.queryState) this.$store.dispatch('setCurrentUid', (params.state || '').replace(/^uid/, '') || '');
|
||||||
|
|
||||||
|
|
||||||
this.handleAuth();
|
this.handleAuth();
|
||||||
},
|
},
|
||||||
|
|
@ -85,13 +88,14 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getBaseUrl() {
|
getBaseUrl() {
|
||||||
|
// return 'https://point.agrimedia.cn';
|
||||||
return window.location.origin;
|
return window.location.origin;
|
||||||
// // #ifdef H5
|
// // #ifdef H5
|
||||||
// return window.location.origin;
|
// return window.location.origin;
|
||||||
// // #endif
|
// // #endif
|
||||||
// // #ifndef H5
|
// // #ifndef H5
|
||||||
// // 非 H5 平台(小程序/App)需配置为已备案的 H5 域名
|
// // 非 H5 平台(小程序/App)需配置为已备案的 H5 域名
|
||||||
// return 'https://tpoint.agrimedia.cn';
|
return 'https://tpoint.agrimedia.cn';
|
||||||
// // #endif
|
// // #endif
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -99,22 +103,49 @@
|
||||||
const baseUrl = this.getBaseUrl();
|
const baseUrl = this.getBaseUrl();
|
||||||
if (this.Token) {
|
if (this.Token) {
|
||||||
// 场景1:授权检测,通过 token 获取用户信息
|
// 场景1:授权检测,通过 token 获取用户信息
|
||||||
uni.request({
|
http.get(`${baseUrl}/api/user`, null, {
|
||||||
url: baseUrl + '/api/user',
|
header: { 'authori-zation': `Bearer ${this.Token}` }
|
||||||
header: {
|
}).then(this.handleUserInfo).catch(this.handleUserError);
|
||||||
'authori-zation': `Bearer ${this.Token}`
|
} 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('授权链接异常,请重新授权!');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200 && res.data) {
|
handleUserInfo(res) {
|
||||||
const data = res.data.data || {};
|
const data = res.data || {};
|
||||||
const relationId = data.relation_id;
|
const relationId = Number(data.relation_id);
|
||||||
const uid = data.uid;
|
const uid = data.uid;
|
||||||
|
const pid = data.tbk_pid;
|
||||||
if (relationId) {
|
if (relationId) {
|
||||||
// 已授权
|
// 其实无论是否授权过,都请求都会返回有值的 pid,但是只有授权过才能正常使用
|
||||||
this.$store.dispatch('setRelationId', relationId);
|
this.setAuthData(relationId, pid);
|
||||||
|
|
||||||
|
// 此处为处理跳转指定页面逻辑
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
const params = app.globalData.urlParams || {};
|
const params = app.globalData.urlParams || {};
|
||||||
if(params.page_uri) {
|
if (params?.page_uri && this.isValidPagePath(params.page_uri)) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: params.page_uri
|
url: params.page_uri
|
||||||
});
|
});
|
||||||
|
|
@ -128,49 +159,27 @@
|
||||||
} else {
|
} else {
|
||||||
this.showFail('获取用户信息失败!');
|
this.showFail('获取用户信息失败!');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.showFail(res.data?.msg || res.data?.message || '获取用户信息失败');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
|
||||||
|
handleUserError(err) {
|
||||||
console.error('获取用户信息失败:', err);
|
console.error('获取用户信息失败:', err);
|
||||||
this.showFail('获取用户信息失败,请检查网络');
|
const msg = err.raw?.data?.msg || err.raw?.data?.message || err.message || '获取用户信息失败,请检查网络';
|
||||||
}
|
this.showFail(msg);
|
||||||
});
|
|
||||||
} 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) {
|
setAuthData(relationId, pid) {
|
||||||
const result = res.data;
|
this.$store.dispatch('setRelationId', relationId);
|
||||||
// 兼容 status / code 两种业务状态码
|
this.$store.dispatch('setPid', pid || 'mm_284380119_1881450385_111415850448');
|
||||||
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);
|
isValidPagePath(pageUri) {
|
||||||
this.showFail('授权处理失败,请重新授权');
|
if (!pageUri) return false;
|
||||||
}
|
let path = pageUri.startsWith('/') ? pageUri.slice(1) : pageUri;
|
||||||
});
|
const queryIndex = path.indexOf('?');
|
||||||
} else {
|
if (queryIndex !== -1) {
|
||||||
this.showFail('授权链接异常,请重新授权!');
|
path = path.slice(0, queryIndex);
|
||||||
// this.$store.dispatch('setRelationId', '123456789');
|
|
||||||
// this.showSuccess('授权已完成,跳转中...');
|
|
||||||
}
|
}
|
||||||
|
return pagesConfig.pages.some(page => page.path === path);
|
||||||
},
|
},
|
||||||
|
|
||||||
goHome() {
|
goHome() {
|
||||||
|
|
@ -204,7 +213,8 @@
|
||||||
this.status = 'success';
|
this.status = 'success';
|
||||||
this.title = '授权成功';
|
this.title = '授权成功';
|
||||||
this.desc = message;
|
this.desc = message;
|
||||||
this._t = setTimeout(() => this.goHome(), 500);
|
// this._t = setTimeout(() => this.goHome(), 500);
|
||||||
|
this.goHome()
|
||||||
},
|
},
|
||||||
|
|
||||||
CopyStatus(message) {
|
CopyStatus(message) {
|
||||||
|
|
|
||||||
|
|
@ -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,12 +161,10 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
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) => {
|
|
||||||
if (res.data && res.data.code === 200) {
|
|
||||||
// 映射为与首页一致的 navList 结构
|
// 映射为与首页一致的 navList 结构
|
||||||
const categories = res.data.data.map(item => ({
|
const categories = res.data.map(item => ({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
cat_id: item.cat_id,
|
cat_id: item.cat_id,
|
||||||
second: item.second || []
|
second: item.second || []
|
||||||
|
|
@ -171,7 +176,6 @@
|
||||||
this.subCategories = currentCat.second;
|
this.subCategories = currentCat.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getProducts(refresh = false) {
|
getProducts(refresh = false) {
|
||||||
|
|
@ -191,16 +195,15 @@
|
||||||
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) {
|
|
||||||
const list = res.data.data.item_info.map(item => ({
|
|
||||||
id: item.id,
|
id: item.id,
|
||||||
image: item.itempic,
|
image: item.itempic,
|
||||||
title: item.itemshorttitle && item.itemshorttitle.length > 17 ? item.itemshorttitle.substring(0, 17) + '...' : item.itemshorttitle,
|
title: item.itemshorttitle && item.itemshorttitle.length > 17 ? item.itemshorttitle.substring(0, 17) + '...' : item.itemshorttitle,
|
||||||
finalPrice: item.itemendprice,
|
finalPrice: item.itemendprice,
|
||||||
couponValue: item.couponmoney || 0,
|
couponValue: item.couponmoney || 0,
|
||||||
|
tkmoney: item.tkmoney || 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,
|
||||||
|
|
@ -221,10 +224,8 @@
|
||||||
} else {
|
} else {
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
}
|
}
|
||||||
},
|
}).finally(() => {
|
||||||
complete: () => {
|
|
||||||
this.loading = false;
|
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;
|
||||||
|
|
|
||||||
|
|
@ -33,26 +33,33 @@
|
||||||
|
|
||||||
<!-- 高级筛选标签栏 -->
|
<!-- 高级筛选标签栏 -->
|
||||||
<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">
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -129,6 +151,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import http from '@/request/request.js';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -195,17 +218,20 @@
|
||||||
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
|
||||||
|
.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 || []
|
labels: item.label || []
|
||||||
|
|
@ -229,10 +255,8 @@
|
||||||
this.finished = true;
|
this.finished = true;
|
||||||
if (refresh) this.getRecommendList();
|
if (refresh) this.getRecommendList();
|
||||||
}
|
}
|
||||||
},
|
}).finally(() => {
|
||||||
complete: () => {
|
|
||||||
this.loading = false;
|
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 => {
|
||||||
|
console.log('推荐商品:', item.id || item.itemid)
|
||||||
return {
|
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;
|
||||||
}
|
}
|
||||||
},
|
}).finally(() => {
|
||||||
complete: () => {
|
|
||||||
this.loadingRecommend = false;
|
this.loadingRecommend = false;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeSort(type) {
|
changeSort(type) {
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,29 +76,23 @@
|
||||||
},
|
},
|
||||||
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) => {
|
}).then(res => {
|
||||||
if (res.data && res.data.code === 200) {
|
this.categoryData = res.data;
|
||||||
this.categoryData = res.data.data;
|
|
||||||
// 获取数据后尝试匹配底部导航高亮状态
|
// 获取数据后尝试匹配底部导航高亮状态
|
||||||
this.matchNavActive();
|
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) => {
|
|
||||||
if (res.data && res.data.code === 200 && res.data.data.bottom_bar) {
|
|
||||||
const allowedTitles = ['首页', '榜单', '分类'];
|
const allowedTitles = ['首页', '榜单', '分类'];
|
||||||
const list = res.data.data.bottom_bar
|
const list = res.data.bottom_bar
|
||||||
.filter(item => allowedTitles.includes(item.title))
|
.filter(item => allowedTitles.includes(item.title))
|
||||||
.sort((a, b) => b.sort - a.sort);
|
.sort((a, b) => b.sort - a.sort);
|
||||||
const idx = list.findIndex(item => item.title === '分类');
|
const idx = list.findIndex(item => item.title === '分类');
|
||||||
|
|
@ -105,7 +100,6 @@
|
||||||
this.navActiveIndex = idx;
|
this.navActiveIndex = idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
switchMainCategory(index) {
|
switchMainCategory(index) {
|
||||||
|
|
|
||||||
|
|
@ -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,19 +423,17 @@
|
||||||
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',
|
|
||||||
success: (res) => {
|
|
||||||
if (res.data && res.data.code === 200) {
|
|
||||||
const formatData = (list) => {
|
const formatData = (list) => {
|
||||||
return list.map(item => ({
|
return list.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|
@ -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 || '大牌特卖',
|
||||||
|
|
@ -483,7 +476,8 @@
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}).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,10 +234,9 @@
|
||||||
});
|
});
|
||||||
this.goodsList = [...this.goodsList, ...list];
|
this.goodsList = [...this.goodsList, ...list];
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
complete: () => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadMore() {
|
loadMore() {
|
||||||
|
|
|
||||||
|
|
@ -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,22 +257,18 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -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,13 +361,13 @@
|
||||||
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' });
|
||||||
},
|
this.loading = false;
|
||||||
complete: () => {
|
})
|
||||||
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onScrollBottom() {
|
onScrollBottom() {
|
||||||
|
|
@ -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,10 +635,9 @@
|
||||||
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,15 +328,14 @@
|
||||||
} 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