Compare commits
1 Commits
yuanlaide6
...
diy-recomm
| Author | SHA1 | Date | |
|---|---|---|---|
| eae415ecad |
@@ -62,11 +62,7 @@ const localDevConfig = ({
|
||||
'local-11': { // 测试平台
|
||||
uniacid: 11,
|
||||
domain: 'http://localhost:8050/',
|
||||
},
|
||||
'local-18': { // 人工晶体
|
||||
uniacid: 18,
|
||||
domain: 'http://localhost:8050/',
|
||||
},
|
||||
},
|
||||
'100': {
|
||||
uniacid: 100,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
@@ -121,7 +117,7 @@ const localDevConfig = ({
|
||||
},
|
||||
'2482': { // 低空经济
|
||||
uniacid: 2482,
|
||||
domain: 'https://xcx3.aigc-quickapp.com/',
|
||||
domain: 'https://xcx6.aigc-quickapp.com/',
|
||||
},
|
||||
'2485': { // 验证电子表单前后端数据一致性
|
||||
uniacid: 2485,
|
||||
@@ -142,16 +138,12 @@ const localDevConfig = ({
|
||||
'48': { // 测试平台
|
||||
uniacid: 48,
|
||||
domain: 'https://test.aigc-quickapp.com',
|
||||
},
|
||||
'54': { // 测试平台
|
||||
uniacid: 54,
|
||||
domain: 'https://test.aigc-quickapp.com',
|
||||
},
|
||||
// })['5g-237']; // 商品分类,没有立即咨询文字
|
||||
// })['5g-97']; // 大宗商品贸易,支持自定义字体大小、颜色
|
||||
// })['5g-50']; // 燃气安全产品机器人,购物车无反应
|
||||
})['2821']; // 离型纸硅油纸
|
||||
// })['2482']; // 低空经济
|
||||
// })['2821']; // 离型纸硅油纸
|
||||
})['2482']; // 低空经济
|
||||
// })['48']; // 离型纸硅油纸
|
||||
// })['2485']; // 验证电子表单前后端数据一致性
|
||||
// })['5g-11']; // 数码
|
||||
@@ -159,7 +151,6 @@ const localDevConfig = ({
|
||||
// })['2811']; // 选择要使用的环境配置
|
||||
// })['local-11']; // 选择要使用的环境配置
|
||||
// })['local-1']; // 本地
|
||||
// })['local-18']; // 本地-人工晶体-有商品
|
||||
// })['local-2']; // 本地
|
||||
// })['local-11']; // 本地
|
||||
// })['1-test']; // test
|
||||
|
||||
@@ -332,7 +332,7 @@ export default {
|
||||
// 服务端明确返回的业务消息(字符串):直接透传
|
||||
if (info.type === 'business') return info.raw;
|
||||
|
||||
const key = httpErrorKey(info.type);
|
||||
const key = diyErrorKey(info.type);
|
||||
let reason = this.$lang(key);
|
||||
|
||||
// HTTP 状态码类错误:把 statusCode 拼到 reason 后面
|
||||
|
||||
98
common/js/recommend-diy-data.js
Normal file
98
common/js/recommend-diy-data.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 通用 mixin - 推荐系统 diy 组件的内部 ajax 数据获取
|
||||
*
|
||||
* 用法:
|
||||
* import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
* export default { mixins: [recommendData], ... }
|
||||
*
|
||||
* 它会:
|
||||
* 1. 在 mounted 时异步调 /recommend/api/home/homeSummary 一次
|
||||
* 2. 暴露 this.recommendInfo 给组件用
|
||||
* 3. 处理失败(token 失效、未登录)显示占位
|
||||
*
|
||||
* 为什么不用 vuex / pinia:
|
||||
* diy 组件可能在多个页面多次实例化,各自独立拉一次,可避免跨页面污染。
|
||||
*/
|
||||
import http from '@/common/js/http.js';
|
||||
import { $lang } from '@/common/js/lang.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
recommendInfo: null, // 后端原始响应 data
|
||||
recommendLoading: false,
|
||||
recommendError: '',
|
||||
_lastFetchTs: 0, // 防抖:同时多次 mounted 不重复请求
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 余额(可提现 + 冻结 + 累计收益)
|
||||
balance() {
|
||||
return (this.recommendInfo && this.recommendInfo.balance) || {};
|
||||
},
|
||||
// 累计统计
|
||||
statistics() {
|
||||
return (this.recommendInfo && this.recommendInfo.statistics) || {};
|
||||
},
|
||||
// 推荐码
|
||||
code() {
|
||||
return (this.recommendInfo && this.recommendInfo.code) || {};
|
||||
},
|
||||
// 考察期
|
||||
probation() {
|
||||
return (this.recommendInfo && this.recommendInfo.probation) || {};
|
||||
},
|
||||
// 最近推荐
|
||||
recentRecords() {
|
||||
return (this.recommendInfo && this.recommendInfo.recent_records) || [];
|
||||
},
|
||||
// 奖励规则
|
||||
rewardRules() {
|
||||
return (this.recommendInfo && this.recommendInfo.reward_rules) || [];
|
||||
},
|
||||
// 是否登录(可避免显示错误的余额)
|
||||
hasRecommendData() {
|
||||
return !!this.recommendInfo;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchRecommendInfo();
|
||||
},
|
||||
methods: {
|
||||
// 异步拉首页聚合数据(去抖,每 10s 最多一次)
|
||||
fetchRecommendInfo(force = false) {
|
||||
const now = Date.now();
|
||||
if (!force && now - this._lastFetchTs < 10000) {
|
||||
return;
|
||||
}
|
||||
this._lastFetchTs = now;
|
||||
this.recommendLoading = true;
|
||||
this.recommendError = '';
|
||||
|
||||
// 通过 http 通用方法走 token(与全局其他请求一致)
|
||||
http.get('/recommend/api/home/homeSummary').then(res => {
|
||||
this.recommendLoading = false;
|
||||
if (res && res.code === 0 && res.data) {
|
||||
this.recommendInfo = res.data;
|
||||
} else {
|
||||
// 未登录或 token 失败,留 recommendInfo=null 让模板显示占位
|
||||
this.recommendError = (res && res.message) || $lang ? $lang('components-diy.diy-recommend.loadFail') : '加载失败';
|
||||
}
|
||||
}).catch(err => {
|
||||
this.recommendLoading = false;
|
||||
this.recommendError = (err && (err.errMsg || err.message)) || 'network error';
|
||||
});
|
||||
},
|
||||
// 刷新(用户下拉触发)
|
||||
refreshRecommendInfo() {
|
||||
return this.fetchRecommendInfo(true);
|
||||
},
|
||||
// 数字格式化工具
|
||||
formatMoney(n) {
|
||||
if (n == null || n === '') return '0';
|
||||
const num = parseFloat(n);
|
||||
if (isNaN(num)) return '0';
|
||||
return num.toLocaleString('zh-CN', { maximumFractionDigits: 2 });
|
||||
},
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,6 @@
|
||||
<template>
|
||||
<view data-component-name="diy-category" :class="['category-page-wrap', 'category-template-' + value.template]"
|
||||
:style="{ height: 'calc(100vh - ' + tabBarHeight + ')' }">
|
||||
<!-- 模板4: 顶部搜索框(图片样式) -->
|
||||
<view class="search-box search-box-template-4" v-if="value.template == 4 && value.search"
|
||||
@tap.stop="onClickSearch()">
|
||||
<view class="search-content">
|
||||
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')"
|
||||
confirm-type="search" @tap.stop="onClickSearch()" disabled="true" />
|
||||
<text class="iconfont icon-sousuo3"></text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <block v-if="value.template == 4">
|
||||
<view class="search-box" v-if="value.search" @tap.stop="$util.redirectTo('/pages_tool/goods/search')" :style="navbarInnerStyle">
|
||||
@@ -37,7 +28,7 @@
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || MP-HARMONY -->
|
||||
|
||||
<view class="search-box" v-if="value.search && value.template != 4" @tap.stop="onClickSearch()">
|
||||
<view class="search-box" v-if="value.search" @tap.stop="onClickSearch()">
|
||||
<view class="search-content">
|
||||
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')" confirm-type="search"
|
||||
@tap.stop="onClickSearch()" disabled="true" />
|
||||
@@ -104,25 +95,12 @@
|
||||
<block v-if="categoryTree.length">
|
||||
<scroll-view scroll-y="true" class="tree-wrap">
|
||||
<view class="category-item-wrap">
|
||||
<!-- 模板4: 左侧显示一级分类(顶部轮播中的分类) -->
|
||||
<view v-if="value.template == 4">
|
||||
<view class="category-item" v-for="(item, index) in templateFourData" :key="'one-' + index" :class="[
|
||||
{ select: oneCategorySelect == index },
|
||||
{ 'border-bottom': oneCategorySelect + 1 === index },
|
||||
{ 'border-top': oneCategorySelect - 1 === index }
|
||||
]" @tap.stop="templateFourOneFn(index)">
|
||||
<view class="">{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 其他模板: 左侧显示二级分类 -->
|
||||
<view v-else>
|
||||
<view class="category-item" v-for="(item, index) in categoryTree" :key="index" :class="[
|
||||
{ select: select == index },
|
||||
{ 'border-bottom': value.template == 4 && select + 1 === index },
|
||||
{ 'border-top': value.template == 4 && select - 1 === index }
|
||||
]" @tap.stop="switchOneCategory(index)">
|
||||
<view class="">{{ item.category_name }}</view>
|
||||
</view>
|
||||
<view class="category-item" v-for="(item, index) in categoryTree" :key="index" :class="[
|
||||
{ select: select == index },
|
||||
{ 'border-bottom': value.template == 4 && select + 1 === index },
|
||||
{ 'border-top': value.template == 4 && select - 1 === index }
|
||||
]" @tap.stop="switchOneCategory(index)">
|
||||
<view class="">{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -138,36 +116,23 @@
|
||||
:lastSecondCategoryName="lastSecondCategoryName"
|
||||
@tologin="toLogin" @selectsku="selectSku($event, 0)" @addCart="addCartPoint"
|
||||
@saveSecondCategory="saveSecondCategoryName"></diy-category-item>
|
||||
</scroll-view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 模板1: 仿京东/淘宝商品分类 - 按 template 2 的 part-load 逻辑, 切换一级分类时只渲染选中分类的子分类网格 -->
|
||||
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 1"
|
||||
:refresher-enabled="true" refresher-default-style="none"
|
||||
:refresher-triggered="triggered" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
|
||||
<view class="child-category-wrap" v-for="(item, index) in categoryTree" :key="index"
|
||||
:id="'category-' + index" :style="{ display: select == index ? 'block' : 'none' }">
|
||||
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
|
||||
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
|
||||
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
|
||||
@saveSecondCategory="saveSecondCategoryName"></diy-category-item>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 99 || loadType == 'all'"
|
||||
ref="contentWrap" :scroll-into-view="categoryId" :scroll-with-animation="true"
|
||||
@scroll="listenScroll" @touchstart="touchStart" :refresher-enabled="true"
|
||||
refresher-default-style="none" :refresher-triggered="triggered" @refresherrefresh="onRefresh"
|
||||
@refresherrestore="onRestore">
|
||||
<view class="child-category" v-for="(item, index) in categoryTree" :key="index"
|
||||
:id="'category-' + index" :style="{ display: value.template == 99 && select != index ? 'none' : 'block' }">
|
||||
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
|
||||
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
|
||||
:lastSecondCategoryName="value.template == 99 ? lastSecondCategoryName : ''"
|
||||
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
|
||||
@loadfinish="getHeightArea" @saveSecondCategory="saveSecondCategoryName"></diy-category-item>
|
||||
</view>
|
||||
<view class="end-tips" ref="endTips" :style="{ opacity: endTips }">{{ $lang('components-diy.diy-category.reachedBottom') }}</view>
|
||||
</scroll-view>
|
||||
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 1 || value.template == 99 || loadType == 'all'"
|
||||
ref="contentWrap" :scroll-into-view="categoryId" :scroll-with-animation="true"
|
||||
@scroll="listenScroll" @touchstart="touchStart" :refresher-enabled="true"
|
||||
refresher-default-style="none" :refresher-triggered="triggered" @refresherrefresh="onRefresh"
|
||||
@refresherrestore="onRestore">
|
||||
<view class="child-category" v-for="(item, index) in categoryTree" :key="index"
|
||||
:id="'category-' + index" :style="{ display: value.template == 99 && select != index ? 'none' : 'block' }">
|
||||
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
|
||||
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
|
||||
:lastSecondCategoryName="value.template == 99 ? lastSecondCategoryName : ''"
|
||||
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
|
||||
@loadfinish="getHeightArea" @saveSecondCategory="saveSecondCategoryName"></diy-category-item>
|
||||
</view>
|
||||
<view class="end-tips" ref="endTips" :style="{ opacity: endTips }">{{ $lang('components-diy.diy-category.reachedBottom') }}</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="content-wrap"
|
||||
v-if="(value.template == 2 || value.template == 3 || value.template == 4) && loadType == 'part'">
|
||||
@@ -176,7 +141,6 @@
|
||||
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
|
||||
:last="index == categoryTree.length - 1 ? true : false" :select="select"
|
||||
:oneCategorySelect="oneCategorySelect" :isList="isList" @tologin="toLogin"
|
||||
:currentOneCategory="value.template == 4 && templateFourData[oneCategorySelect] ? templateFourData[oneCategorySelect] : null"
|
||||
@selectsku="selectSku($event, index)" @addCart="addCartPoint"
|
||||
@switch="switchOneCategory"></diy-category-item>
|
||||
</view>
|
||||
@@ -189,12 +153,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模板4: 底部购物车栏 -->
|
||||
<view class="cart-box cart-box-template-4"
|
||||
v-if="value.template == 4 && value.quickBuy && storeToken && categoryTree && categoryTree.length">
|
||||
<!-- <view class="cart-box" v-if="(value.template == 2 || value.template == 4) && value.quickBuy && storeToken && categoryTree && categoryTree.length">
|
||||
<view class="left-wrap">
|
||||
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation"
|
||||
@tap.stop="$util.redirectTo('/pages_goods/cart')">
|
||||
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation" @tap.stop="$util.redirectTo('/pages_goods/cart')">
|
||||
<text class="iconfont icon-ziyuan1"></text>
|
||||
<view class="num" v-if="cartNumber">{{ cartNumber < 99 ? cartNumber : '99+' }}</view>
|
||||
</view>
|
||||
@@ -205,10 +166,9 @@
|
||||
<text class="unit font-size-tag price-font">.{{ cartTotalMoney[1] ? cartTotalMoney[1] : '00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right-wrap"><button type="primary" class="settlement-btn"
|
||||
@tap.stop="settlement">去结算</button>
|
||||
<view class="right-wrap"><button type="primary" class="settlement-btn" @tap.stop="settlement">去结算</button>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="cart-point" v-for="(item, index) in carIconList" :key="index"
|
||||
:style="{ left: item.left + 'px', top: item.top + 'px' }"></view>
|
||||
@@ -269,10 +229,6 @@ export default {
|
||||
created() {
|
||||
this.getCategoryTree();
|
||||
this.loadType = this.value.goodsLevel == 1 && this.value.loadType == 'all' ? 'all' : 'part';
|
||||
// 模板4: 默认单列列表布局(图片样式)
|
||||
if (this.value.template == 4) {
|
||||
this.isList = true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
query = uni.createSelectorQuery().in(this);
|
||||
@@ -1060,137 +1016,6 @@ export default {
|
||||
|
||||
// 风格四
|
||||
.category-template-4 {
|
||||
// 模板4 顶部搜索框(图片样式)
|
||||
.search-box-template-4 {
|
||||
padding: 20rpx 30rpx 16rpx;
|
||||
background: #fff;
|
||||
|
||||
.search-content {
|
||||
height: 64rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
|
||||
input {
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
padding: 0 30rpx 0 30rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #89899a;
|
||||
width: 70rpx;
|
||||
text-align: center;
|
||||
right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模板4 底部购物车栏(图片样式)
|
||||
.cart-box-template-4 {
|
||||
height: 110rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.left-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cart-icon {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
position: relative;
|
||||
margin-top: -10rpx;
|
||||
|
||||
.iconfont {
|
||||
color: var(--btn-text-color, #fff);
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: $base-color;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
|
||||
}
|
||||
|
||||
.num {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(20%, 0);
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
padding: 0 8rpx;
|
||||
min-width: 32rpx;
|
||||
border-radius: 18rpx;
|
||||
background-color: var(--price-color, #FF6B35);
|
||||
border: 2rpx solid #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
margin-left: 16rpx;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 1;
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.unit,
|
||||
.money {
|
||||
color: var(--price-color, #FF6B35);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.money {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.right-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settlement-btn {
|
||||
margin: 0;
|
||||
width: 220rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 50rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
background: $base-color;
|
||||
color: var(--btn-text-color, #fff);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
.search-content input {
|
||||
background-color: #f1f1f1;
|
||||
@@ -1418,125 +1243,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 模板1 仿京东/淘宝商品分类样式 =====================
|
||||
.category-template-1 {
|
||||
.search-box {
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
|
||||
.search-content {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
|
||||
input {
|
||||
background: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
padding: 0 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
right: 16rpx;
|
||||
color: #89899a;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-box {
|
||||
background: #fff;
|
||||
|
||||
.tree-wrap {
|
||||
width: 170rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.right-flex-wrap {
|
||||
background: #fff;
|
||||
|
||||
.content-wrap {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 穿透到子组件 diy-category-item: 调整 banner 横向内边距 + 分类网格容器为透明
|
||||
::v-deep .category-adv {
|
||||
padding: 0 20rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
::v-deep .category-list {
|
||||
margin: 0 20rpx;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::v-deep .category-title-with-lines {
|
||||
padding: 30rpx 0 20rpx;
|
||||
}
|
||||
|
||||
// 穿透修复: 图片不需要灰色背景框,与目标图保持一致
|
||||
::v-deep .img-box {
|
||||
background: transparent !important;
|
||||
border-radius: 0 !important;
|
||||
width: 70% !important;
|
||||
padding-bottom: 70% !important;
|
||||
margin-bottom: 16rpx !important;
|
||||
}
|
||||
|
||||
::v-deep .img-box image {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
// 左侧分类栏样式
|
||||
.tree-wrap .category-item {
|
||||
padding: 28rpx 16rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 0;
|
||||
|
||||
view {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&.border-top,
|
||||
&.border-bottom {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&.select {
|
||||
background: #fff;
|
||||
|
||||
view {
|
||||
color: var(--base-color, #FF6B35);
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
width: 6rpx;
|
||||
height: 32rpx;
|
||||
background: var(--base-color, #FF6B35);
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-radius: 0 6rpx 6rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 模板99 商品分类样式 =====================
|
||||
.category-template-99 {
|
||||
.search-box {
|
||||
@@ -1601,7 +1307,7 @@ export default {
|
||||
background: #fff;
|
||||
|
||||
view {
|
||||
color: #333;
|
||||
color: #FF6B35;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
@@ -1610,7 +1316,7 @@ export default {
|
||||
content: ' ';
|
||||
width: 6rpx;
|
||||
height: 36rpx;
|
||||
background: #FF4646;
|
||||
background: #FF6B35;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -1624,14 +1330,6 @@ export default {
|
||||
.content-wrap {
|
||||
background: #f5f5f5;
|
||||
padding: 0;
|
||||
// #ifdef H5
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
.child-category {
|
||||
|
||||
82
components-diy/diy-recommend-action.vue
Normal file
82
components-diy/diy-recommend-action.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-action" class="diy-recommend-action" :style="warpCss">
|
||||
<!-- 主按钮:立即提现 -->
|
||||
<view class="primary-btn" hover-class="btn-hover" @tap="goWithdraw">
|
||||
<text>{{ $lang('components-diy.diy-recommend-action.withdrawNow') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 副按钮:提现记录(横排两列) -->
|
||||
<view class="secondary-row">
|
||||
<view class="secondary-btn" hover-class="btn-hover" @tap="goWithdrawList">
|
||||
<text class="iconfont icon-calendar"></text>
|
||||
<text>{{ $lang('components-diy.diy-recommend-action.withdrawList') }}</text>
|
||||
</view>
|
||||
<view class="secondary-btn" hover-class="btn-hover" @tap="goApply">
|
||||
<text class="iconfont icon-edit"></text>
|
||||
<text>{{ $lang('components-diy.diy-recommend-action.applyEnterprise') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'diy-recommend-action',
|
||||
computed: {
|
||||
warpCss() {
|
||||
return {
|
||||
padding: '24rpx',
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goWithdraw() {
|
||||
uni.navigateTo({ url: '/pages_recommend/withdraw/apply' });
|
||||
},
|
||||
goWithdrawList() {
|
||||
uni.navigateTo({ url: '/pages_recommend/withdraw/list' });
|
||||
},
|
||||
goApply() {
|
||||
uni.navigateTo({ url: '/pages_recommend/apply/apply' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-action {
|
||||
.primary-btn {
|
||||
background: linear-gradient(135deg, #1976d2, #0d47a1);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 50rpx;
|
||||
padding: 24rpx 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: 4rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(25,118,210,0.32);
|
||||
}
|
||||
.secondary-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.secondary-btn {
|
||||
flex: 1;
|
||||
background: #f5f5f5;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 0;
|
||||
font-size: 26rpx;
|
||||
.iconfont {
|
||||
margin-right: 8rpx;
|
||||
color: #1976d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
</style>
|
||||
108
components-diy/diy-recommend-balance.vue
Normal file
108
components-diy/diy-recommend-balance.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-balance" class="diy-recommend-balance" :style="warpCss">
|
||||
<view class="balance-card">
|
||||
<view class="balance-row">
|
||||
<view class="balance-half available">
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-balance.available') }}</text>
|
||||
<text class="big-num price-font">¥{{ formatMoney(balance.available) }}</text>
|
||||
<text class="sub-tip" v-if="balance.available > 0 && probation.total_days">
|
||||
{{ $lang('components-diy.diy-recommend-balance.passedProbation') }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="balance-half probation">
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-balance.frozen') }}</text>
|
||||
<text class="big-num price-font">¥{{ formatMoney(balance.frozen) }}</text>
|
||||
<text class="sub-tip" v-if="probation.remaining_days > 0">
|
||||
{{ $lang('components-diy.diy-recommend-balance.probationDays', { days: probation.remaining_days }) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 累计总收益(底部) -->
|
||||
<view class="total-row">
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-balance.totalEarned') }}</text>
|
||||
<text class="big-num price-font">¥{{ formatMoney(balance.total_earned) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
|
||||
export default {
|
||||
name: 'diy-recommend-balance',
|
||||
mixins: [recommendData],
|
||||
computed: {
|
||||
warpCss() {
|
||||
return {
|
||||
padding: '24rpx',
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-balance {
|
||||
.balance-card {
|
||||
background: linear-gradient(180deg, #d9f2ff, #f0f9ff);
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,102,204,0.08);
|
||||
}
|
||||
.balance-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.balance-half {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255,255,255,0.65);
|
||||
|
||||
&.available {
|
||||
.big-num { color: #1976d2; }
|
||||
}
|
||||
&.probation {
|
||||
background: rgba(255,243,224,0.85);
|
||||
.big-num { color: #e65100; }
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
.big-num {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
.sub-tip {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
.total-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
padding-top: 16rpx;
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
color: #444;
|
||||
}
|
||||
.big-num {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #d81b60;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
167
components-diy/diy-recommend-code.vue
Normal file
167
components-diy/diy-recommend-code.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-code" class="diy-recommend-code" :style="warpCss">
|
||||
<!-- 标题 -->
|
||||
<view class="title-line">
|
||||
<view class="icon iconfont icon-recommend-prize"></view>
|
||||
<text class="title">{{ $lang('components-diy.diy-recommend-code.myCode') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 卡片 -->
|
||||
<view class="code-card" v-if="code.code">
|
||||
<view class="code-text" @tap="copyCode">{{ code.code }}</view>
|
||||
<view class="copy-btn" @tap="copyCode">
|
||||
<text class="iconfont icon-copy"></text>
|
||||
<text>{{ $lang('components-diy.diy-recommend-code.copy') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="code-card placeholder" v-else>
|
||||
<text class="placeholder-text">{{ recommendError || $lang('components-diy.diy-recommend-code.loading') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 复制 toast -->
|
||||
<view v-if="copyToast" class="copy-toast">
|
||||
{{ $lang('components-diy.diy-recommend-code.copySuccess') }}
|
||||
</view>
|
||||
|
||||
<!-- 分享链接区 -->
|
||||
<view class="share-bar" v-if="code.share_link">
|
||||
<text class="iconfont icon-share"></text>
|
||||
<text class="share-tip">{{ $lang('components-diy.diy-recommend-code.shareTip') }}</text>
|
||||
<view class="share-link-card" @tap="copyShareLink">
|
||||
<text class="link-text">{{ code.share_link }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
|
||||
export default {
|
||||
name: 'diy-recommend-code',
|
||||
mixins: [recommendData],
|
||||
data() {
|
||||
return {
|
||||
copyToast: false,
|
||||
_toastTimer: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
warpCss() {
|
||||
return {
|
||||
padding: (this.value && this.value.paddingTop || 16) + 'rpx ' +
|
||||
(this.value && this.value.paddingLeft || 24) + 'rpx',
|
||||
backgroundColor: (this.value && this.value.componentBgColor) || 'transparent',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyCode() {
|
||||
if (!this.code.code) return;
|
||||
uni.setClipboardData({
|
||||
data: this.code.code,
|
||||
success: () => this.showCopyToast(),
|
||||
});
|
||||
},
|
||||
copyShareLink() {
|
||||
if (!this.code.share_link) return;
|
||||
uni.setClipboardData({
|
||||
data: this.code.share_link,
|
||||
success: () => this.showCopyToast(),
|
||||
});
|
||||
},
|
||||
showCopyToast() {
|
||||
this.copyToast = true;
|
||||
if (this._toastTimer) clearTimeout(this._toastTimer);
|
||||
this._toastTimer = setTimeout(() => { this.copyToast = false; }, 1500);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-code {
|
||||
.title-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
.icon {
|
||||
font-size: 32rpx;
|
||||
color: #ff9800;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.code-card {
|
||||
background: linear-gradient(135deg, #fff3e0, #ffe0b2);
|
||||
border: 2rpx dashed #ff9800;
|
||||
border-radius: 12rpx;
|
||||
padding: 36rpx 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.code-text {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: #e65100;
|
||||
letter-spacing: 4rpx;
|
||||
font-family: Menlo, Monaco, monospace;
|
||||
}
|
||||
.copy-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #ff9800;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
.iconfont { font-size: 30rpx; margin-bottom: 4rpx; }
|
||||
}
|
||||
&.placeholder {
|
||||
background: #f5f5f5;
|
||||
border-color: #ccc;
|
||||
justify-content: center;
|
||||
min-height: 100rpx;
|
||||
.placeholder-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.share-bar {
|
||||
margin-top: 16rpx;
|
||||
.icon-share { font-size: 28rpx; color: #ff9800; margin-right: 8rpx; }
|
||||
.share-tip {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
.share-link-card {
|
||||
margin-top: 12rpx;
|
||||
background: #fafafa;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
.link-text {
|
||||
font-size: 24rpx;
|
||||
color: #444;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
.copy-toast {
|
||||
position: fixed;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: #fff;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
z-index: 9999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
147
components-diy/diy-recommend-records.vue
Normal file
147
components-diy/diy-recommend-records.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-records" class="diy-recommend-records" :style="warpCss">
|
||||
<view class="header-row">
|
||||
<text class="title">{{ $lang('components-diy.diy-recommend-records.recent') }}</text>
|
||||
<text class="link" @tap="goAllRecords">{{ $lang('components-diy.diy-recommend-records.viewAll') }} ></text>
|
||||
</view>
|
||||
|
||||
<view v-if="recentRecords.length === 0" class="empty">
|
||||
<text>{{ $lang('components-diy.diy-recommend-records.empty') }}</text>
|
||||
</view>
|
||||
<view v-else class="record-list">
|
||||
<view v-for="(r, i) in recentRecords" :key="r.id || i" class="record-item">
|
||||
<view class="left">
|
||||
<view class="avatar"></view>
|
||||
<view class="info">
|
||||
<text class="name">{{ r.company_name || r.id }}</text>
|
||||
<text class="time">{{ formatTime(r.apply_time) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="amount price-font" v-if="r.amount_text">{{ r.amount_text }}</text>
|
||||
<view class="status-tag" :class="'status-' + r.status">{{ r.status_text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
|
||||
export default {
|
||||
name: 'diy-recommend-records',
|
||||
mixins: [recommendData],
|
||||
computed: {
|
||||
warpCss() {
|
||||
return {
|
||||
padding: '16rpx 24rpx 24rpx',
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goAllRecords() {
|
||||
uni.navigateTo({ url: '/pages_recommend/records/list' });
|
||||
},
|
||||
formatTime(ts) {
|
||||
if (!ts) return '';
|
||||
const d = new Date(parseInt(ts) * 1000);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${dd}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-records {
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.link {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.record-list {
|
||||
.record-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
&:last-child { border-bottom: none; }
|
||||
}
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 12rpx;
|
||||
background: linear-gradient(135deg, #bbdefb, #64b5f6);
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
.time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6rpx;
|
||||
}
|
||||
.amount {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #e65100;
|
||||
}
|
||||
.status-tag {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
&.status-0 { background: #fff3e0; color: #e65100; } // 待审核
|
||||
&.status-1 { background: #e3f2fd; color: #1976d2; } // 审核通过(待缴费)
|
||||
&.status-2 { background: #e8f5e9; color: #4caf50; } // 审核通过(已缴费)
|
||||
&.status-3 { background: #ffebee; color: #c62828; } // 审核不通过
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
components-diy/diy-recommend-rules.vue
Normal file
103
components-diy/diy-recommend-rules.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-rules" class="diy-recommend-rules" :style="warpCss">
|
||||
<view class="header-row">
|
||||
<text class="iconfont icon-book"></text>
|
||||
<text class="title">{{ $lang('components-diy.diy-recommend-rules.title') }}</text>
|
||||
</view>
|
||||
<view class="rules-list">
|
||||
<view v-for="(r, i) in displayRules" :key="i" class="rule-row">
|
||||
<view class="rule-icon" :class="'icon-' + (r.icon || 'money')">
|
||||
<text class="iconfont" :class="iconFontClass(r.icon)"></text>
|
||||
</view>
|
||||
<view class="rule-text">{{ r.title || r.text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
|
||||
export default {
|
||||
name: 'diy-recommend-rules',
|
||||
mixins: [recommendData],
|
||||
computed: {
|
||||
// 可被后台 value.rules 覆盖,默认用 API 返回
|
||||
displayRules() {
|
||||
if (this.value && Array.isArray(this.value.rules) && this.value.rules.length) {
|
||||
return this.value.rules;
|
||||
}
|
||||
return this.rewardRules || [];
|
||||
},
|
||||
warpCss() {
|
||||
return {
|
||||
padding: '24rpx',
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
iconFontClass(icon) {
|
||||
const map = {
|
||||
money: 'icon-money',
|
||||
calendar: 'icon-calendar',
|
||||
wallet: 'icon-wallet',
|
||||
infinite: 'icon-infinite',
|
||||
};
|
||||
return map[icon] || 'icon-money';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-rules {
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #1976d2;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
.rules-list {
|
||||
background: #f3f9ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
}
|
||||
.rule-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1rpx dashed #d6e9f8;
|
||||
&:last-child { border-bottom: none; }
|
||||
}
|
||||
.rule-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background: #1976d2;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
.iconfont { font-size: 24rpx; }
|
||||
}
|
||||
.rule-text {
|
||||
font-size: 24rpx;
|
||||
color: #444;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
67
components-diy/diy-recommend-stat.vue
Normal file
67
components-diy/diy-recommend-stat.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<view data-component-name="diy-recommend-stat" class="diy-recommend-stat" :style="warpCss">
|
||||
<view class="stat-row">
|
||||
<view class="stat-item">
|
||||
<text class="num price-font">{{ statistics.total_recommend || 0 }}</text>
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-stat.totalRecommend') }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="num price-font color-green">{{ statistics.total_entered || 0 }}</text>
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-stat.totalEntered') }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="num price-font color-orange">¥{{ formatMoney(statistics.total_earned) }}</text>
|
||||
<text class="label">{{ $lang('components-diy.diy-recommend-stat.totalEarned') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import recommendData from '@/common/js/recommend-diy-data.js';
|
||||
|
||||
export default {
|
||||
name: 'diy-recommend-stat',
|
||||
mixins: [recommendData],
|
||||
computed: {
|
||||
warpCss() {
|
||||
return {
|
||||
padding: '24rpx',
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diy-recommend-stat {
|
||||
.stat-row {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
&:last-child { border-right: none; }
|
||||
}
|
||||
.num {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
&.color-green { color: #4caf50; }
|
||||
&.color-orange { color: #ff9800; }
|
||||
}
|
||||
.label {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
lang/en-us/components-diy/diy-recommend.js
Normal file
38
lang/en-us/components-diy/diy-recommend.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 推荐系统 diy 组件 6 个组件共用 i18n 文案 (English)
|
||||
* 同 zh-cn 一致,文案翻译为英文
|
||||
*/
|
||||
export const lang = {
|
||||
'diy-recommend-code': {
|
||||
myCode: 'My Referral Code',
|
||||
copy: 'Copy',
|
||||
copySuccess: 'Copied',
|
||||
shareTip: 'Copy the link and share it with enterprises to bind',
|
||||
},
|
||||
'diy-recommend-balance': {
|
||||
available: 'Withdrawable',
|
||||
frozen: 'Frozen',
|
||||
probationDays: '{days} days remaining',
|
||||
passedProbation: 'Probation passed',
|
||||
totalEarned: 'Total earned',
|
||||
},
|
||||
'diy-recommend-stat': {
|
||||
totalRecommend: 'Referred',
|
||||
totalEntered: 'Entered',
|
||||
totalEarned: 'Earned',
|
||||
},
|
||||
'diy-recommend-records': {
|
||||
recent: 'Recent Referrals',
|
||||
viewAll: 'View all',
|
||||
empty: 'No referrals yet',
|
||||
},
|
||||
'diy-recommend-rules': {
|
||||
title: 'Reward Rules',
|
||||
},
|
||||
'diy-recommend-action': {
|
||||
withdrawNow: 'Withdraw Now',
|
||||
withdrawList: 'Records',
|
||||
applyEnterprise: 'Apply',
|
||||
},
|
||||
loadFail: 'Failed to load, please retry',
|
||||
};
|
||||
52
lang/zh-cn/components-diy/diy-recommend.js
Normal file
52
lang/zh-cn/components-diy/diy-recommend.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 推荐系统 diy 组件 6 个组件共用 i18n 文案
|
||||
* 加载路径: pages-tool 中 $lang('components-diy.diy-recommend-code.xxx')
|
||||
* 模块前缀已经写好,这里只导出组件名下的 key
|
||||
*/
|
||||
export const lang = {
|
||||
// ---- diy-recommend-code ----
|
||||
'diy-recommend-code': {
|
||||
myCode: '我的推荐码',
|
||||
copy: '复制',
|
||||
copySuccess: '已复制',
|
||||
shareTip: '复制推荐链接分享给企业,对方填表时填入即可绑定',
|
||||
},
|
||||
|
||||
// ---- diy-recommend-balance ----
|
||||
'diy-recommend-balance': {
|
||||
available: '可提现余额',
|
||||
frozen: '待结算(冻结中)',
|
||||
probationDays: '考察期 {days} 天',
|
||||
passedProbation: '已过考察期',
|
||||
totalEarned: '累计总收益',
|
||||
},
|
||||
|
||||
// ---- diy-recommend-stat ----
|
||||
'diy-recommend-stat': {
|
||||
totalRecommend: '累计推荐',
|
||||
totalEntered: '已入驻',
|
||||
totalEarned: '累计收益',
|
||||
},
|
||||
|
||||
// ---- diy-recommend-records ----
|
||||
'diy-recommend-records': {
|
||||
recent: '最近推荐',
|
||||
viewAll: '查看全部',
|
||||
empty: '暂无推荐记录',
|
||||
},
|
||||
|
||||
// ---- diy-recommend-rules ----
|
||||
'diy-recommend-rules': {
|
||||
title: '奖励规则',
|
||||
},
|
||||
|
||||
// ---- diy-recommend-action ----
|
||||
'diy-recommend-action': {
|
||||
withdrawNow: '立即提现',
|
||||
withdrawList: '提现记录',
|
||||
applyEnterprise: '入驻申请',
|
||||
},
|
||||
|
||||
// ---- 通用 ----
|
||||
loadFail: '加载失败,请稍后重试',
|
||||
};
|
||||
@@ -2422,7 +2422,8 @@
|
||||
"uni-*(\\W.*)": "@/uni_modules/uni$1/components/uni$1/uni$1.vue",
|
||||
"mescroll-uni": "@/components/mescroll/my-list-mescroll.vue",
|
||||
"ns-goods-sku-*(\\W.*)": "@/components/ns-goods-sku/ns-goods-sku$1.vue",
|
||||
"diy-*(\\W.*)": "@/components-diy/diy$1.vue"
|
||||
"diy-*(\\W.*)": "@/components-diy/diy$1.vue",
|
||||
"diy-recommend-*": "@/components-diy/diy-recommend-$1.vue"
|
||||
}
|
||||
},
|
||||
"preloadRule": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
30
scripts/verify_diy_recommend.js
Normal file
30
scripts/verify_diy_recommend.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Quick parse check for the 6 diy-recommend components
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const files = [
|
||||
'diy-recommend-code.vue',
|
||||
'diy-recommend-balance.vue',
|
||||
'diy-recommend-stat.vue',
|
||||
'diy-recommend-records.vue',
|
||||
'diy-recommend-rules.vue',
|
||||
'diy-recommend-action.vue',
|
||||
];
|
||||
|
||||
const base = path.join(__dirname, '..', 'components-diy');
|
||||
let ok = 0;
|
||||
for (const f of files) {
|
||||
const p = path.join(base, f);
|
||||
if (!fs.existsSync(p)) { console.log('✗', f, 'MISSING'); continue; }
|
||||
const content = fs.readFileSync(p, 'utf8');
|
||||
// Check has template/script/style
|
||||
const hasT = /<template>/i.test(content) && /<\/template>/i.test(content);
|
||||
const hasS = /<script>/i.test(content) && /<\/script>/i.test(content);
|
||||
const hasC = /<style /i.test(content) && /<\/style>/i.test(content);
|
||||
const nameMatch = content.match(/name:\s*['"](\S+)['"]/);
|
||||
const name = nameMatch ? nameMatch[1] : '?';
|
||||
console.log(`✓ ${f.padEnd(34)} name="${name}" T:${hasT} S:${hasS} C:${hasC}`);
|
||||
if (hasT && hasS && hasC) ok++;
|
||||
}
|
||||
console.log(`\n${ok}/${files.length} OK`);
|
||||
process.exit(ok === files.length ? 0 : 1);
|
||||
@@ -21,18 +21,12 @@ function _isProductionRelease() {
|
||||
* 生产构建门禁: 同步执行 test-gate.js, 失败立即终止构建 (零产物)。
|
||||
* 与 package.json 的 "prebuild": "npm run test" 互补 —— 那条只覆盖 npm run build,
|
||||
* 本函数覆盖所有会 require vue.config.js 的入口 (HBuilderX / vue-cli-service / uni-cli)。
|
||||
*
|
||||
* 关键: 使用 process.execPath 而非字符串 "node",避免依赖 PATH 查找 node。
|
||||
* 原因: HBuilderX 等 IDE 启动子进程时,PATH 中可能根本没有 node 条目 (本项目
|
||||
* 就曾因 Machine PATH 残留指向 v14.21.3 的 Huawei node 而触发问题)。
|
||||
* process.execPath 始终是"当前正在运行 vue.config.js 的那个 node 二进制",
|
||||
* HBuilderX 自带 node (v22.22.2) ≥ 18,天然满足 test-gate 的版本要求。
|
||||
*/
|
||||
function _enforceTestGate() {
|
||||
if (!_isProductionRelease()) return; // 开发态 / 运行态不拦截
|
||||
console.log(`\n🛡️ [test-gate] 检测到生产构建,先执行测试门禁 (${process.versions.node})...`);
|
||||
console.log('\n🛡️ [test-gate] 检测到生产构建,先执行测试门禁...');
|
||||
try {
|
||||
execSync(`"${process.execPath}" scripts/test-gate.js`, {
|
||||
execSync('node scripts/test-gate.js', {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user