淘宝授权推送

This commit is contained in:
yankang 2026-06-02 14:59:50 +08:00
parent acd448b6dd
commit d1c0419d76
20 changed files with 1495 additions and 702 deletions

17
App.vue
View File

@ -3,6 +3,23 @@
export default { export default {
globalData: { globalData: {
/**
* urlParams - 当前页面 URL 参数集合
*
* 来源
* 1. H5 环境下从 window.locationsearch + 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) {

View File

@ -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) {

View File

@ -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
} }

View File

@ -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) {

View File

@ -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) {
// 3uid
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) {

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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() {

View File

@ -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;

View File

@ -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);
}
}); });
} }
} }

View File

@ -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();
}
}); });
} }
} }

257
request/request.js Normal file
View File

@ -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.dataCMS 标准格式
* - 不存在则取 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 };

257
scratch/test_request.mjs Normal file
View File

@ -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.dataCMS 标准格式
* - 不存在则取 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 };

View File

@ -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)
} }
} }
}) })

150
test.vue Normal file
View File

@ -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>

View File

@ -1,8 +1,8 @@
{ {
"hash": "0694fca5", "hash": "aee3647b",
"configHash": "6057985e", "configHash": "43aa957d",
"lockfileHash": "e3b0c442", "lockfileHash": "32773baf",
"browserHash": "9518c2ac", "browserHash": "732cb9f3",
"optimized": {}, "optimized": {},
"chunks": {} "chunks": {}
} }

5
utils/index.js Normal file
View File

@ -0,0 +1,5 @@
// 预估消费券函数
export function estimateCoupon(tkmoney = 0, percentage = 0.3) {
let result = (tkmoney * percentage).toFixed(2);
return result;
}