Files
cashier_wx/distribution/shop-detail/index.vue
2025-12-16 17:00:32 +08:00

908 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="min-h-100vh bg-gray">
<up-navbar
bg-color="transparent"
title="分销中心"
@leftClick="back"
:fixed="true"
></up-navbar>
<view class="top">
<image
class="top_bg"
src="/distribution/static/top_bg.png"
:style="imageStyle"
></image>
<view class="box" :class="{ type1: isActivated }">
<view class="u-flex align-center justify-between">
<view class="u-flex align-center">
<!-- <up-avatar size="62rpx" /> -->
<text class="u-m-l-14 font-14 color-333 font-700">{{
state.shopName
}}</text>
</view>
<view>
<template v-if="state.parentPhone">
<view class="font-12 color-666">
上级{{ state.parentName }}{{ state.parentPhone }}
</view>
</template>
<template v-if="!state.parentPhone">
<view class="bind" @click="showBindShangji = true">
绑定上级
</view>
</template>
</view>
</view>
<view
class="top_content type1 u-m-t-32"
v-if="state.distributionUser && state.distributionUser.level"
>
<view class="font-14">
<view class="u-flex align-center">
<text class="color-666">我的分销等级</text>
<text
class="color-333 font-700 u-m-r-6"
v-if="state.distributionUser"
>{{ state.distributionUser.level }}
{{ state.distributionUser.levelName }}</text
>
<up-icon
name="question-circle"
size="24rpx"
color="#666"
@click="questionClick('等级升级条件')"
/>
</view>
<view
class="u-m-t-28 u-flex align-center"
v-if="
juNextLvMoney &&
state.distributionUser &&
state.distributionUser.isAssignLevel == 0
"
>
<text class="color-666">距离下一级还差:</text>
<text class="color-333 font-700 u-m-r-18"
>{{ juNextLvMoney }}
</text>
<text class="color-333 font-700 u-m-r-18"
>{{ config.upgradeType == "cost" ? "元" : "人" }}
</text>
<!-- <up-icon
name="question-circle"
size="24rpx"
color="#666"
@click="showRule = true"
/> -->
</view>
<view class="u-flex u-m-t-28">
<view class="u-flex-1">
<view class="u-flex align-center">
<text
class="u-m-r-10 font-12 color-666"
@click="toShouyiDetail('')"
>总收益</text
>
<up-icon
name="question-circle"
size="24rpx"
color="#666"
@click="questionClick('总收益')"
/>
</view>
<view
class="u-m-t-16 price"
@click="toShouyiDetail('')"
v-if="state.distributionUser"
>{{ state.distributionUser.totalIncome }}</view
>
</view>
<view class="u-flex-1">
<view class="u-flex align-center">
<text
class="u-m-r-10 font-12 color-666"
@click="toShouyiDetail('待入账')"
>待入账</text
>
<up-icon
name="question-circle"
size="24rpx"
color="#666"
@click="questionClick('待入账')"
/>
</view>
<view
class="u-m-t-16 price"
@click="toShouyiDetail('待入账')"
v-if="state.distributionUser"
>{{ state.distributionUser.pendingIncome }}</view
>
</view>
</view>
</view>
</view>
<template v-else>
<view class="top_content u-m-t-32" v-if="config">
<template v-if="config.openType == 'auto'">
<view class="color-333 font-16 font-700"> 如何成为分销员 </view>
<view class="u-m-t-16 color-666 font-14">
<view> 需要邀请人数:{{ config.inviteCount }}人</view>
<view
>是否需要邀请人数下单:{{
config.inviteConsume ? "是" : "否"
}}
</view>
<view
>每人可获得的分销奖励次数:{{
config.rewardCount == -1
? "永久"
: config.rewardCount + "次"
}}
</view>
</view>
</template>
<template v-if="config.openType == 'pay'">
<view class="color-333 font-16 font-700 text-center">
如何成为分销员
</view>
<view class="u-m-t-16 color-666 font-14 text-center">
<view
>只需付费{{ config.payAmount || "" }}元,即可成为分销员</view
>
</view>
</template>
<template v-if="config.openType == 'manual'">
<view class="color-333 font-16 font-700 text-center">
如何成为分销员
</view>
<view class="u-m-t-16 color-666 font-14 text-center">
<view>请联系商家咨询详情</view>
</view>
</template>
</view>
</template>
</view>
</view>
<view class="bottom type1" v-if="isActivated">
<view class="u-flex justify-between align-center">
<view class="u-flex align-center">
<view class="color-333 font-16 u-m-r-6"
>我的邀请({{ inviteUserRes.totalRow }}</view
>
<up-icon
name="question-circle"
size="24rpx"
@click="showRule = true"
color="#666"
/>
</view>
<view class="font-10 color-999 u-flex align-center">
<text
>分成比例{{ state.distributionUser.levelOneCommission || 0 }}%</text
>
<up-icon
name="question-circle"
size="24rpx"
color="#666"
@click="questionClick('等级分成比例')"
/>
</view>
</view>
<view class="tabs" v-if="false">
<view
class="tabs-item"
:class="{ active: activeTab == 'distributor' }"
@click="activeTab = 'distributor'"
>分销员</view
>
<view
class="tabs-item"
:class="{ active: activeTab == 'inviter' }"
@click="activeTab = 'inviter'"
>邀请人</view
>
</view>
<view class="u-m-t-48 font-14">
<view class="u-flex justify-between color-333">
<view>用户</view>
<view>获得利益(元)</view>
<view>邀请时间</view>
</view>
<view class="u-m-t-16">
<view
v-for="(item, index) in userList"
:key="index"
class="u-flex justify-between align-center recoder-item color-666 font-12"
>
<view class="">
<view class="u-line-1" style="max-width: 160rpx">
<text>
{{ item.shopUserName }}
</text>
</view>
<view class="u-line-1" style="max-width: 160rpx">
<text v-if="item.levelName"> ( {{ item.levelName }}) </text>
</view>
<view>{{ desensitizePhone(item.shopUserPhone) }}</view>
</view>
<view>
<text>{{ item.oneIncome }}</text>
<text v-if="item.distributionLevelName"
>{{ item.distributionLevelName }}</text
>
</view>
<view>
<text v-if="item.inviteTime">{{
item.inviteTime.split(" ")[0]
}}</text>
<text style="color: #fff" v-else>{{ "yyyy-MM-dd" }}</text>
</view>
</view>
</view>
</view>
</view>
<template v-else>
<view class="bottom" v-if="config">
<view class="u-flex">
<view class="title">规则说明</view>
</view>
<view>
<view class="font-12 color-666 u-m-t-16">
<view>
<view> 我的收益什么时候可以到账?</view>
<view> 分销的结算时长为{{ config.settlementDay || 0 }}天</view>
</view>
<template
v-if="
config.upgradeType != 'not_upgrade' &&
config.levelConfigList &&
config.levelConfigList.length >= 2
"
>
<view class="u-m-t-40">
<view>怎么样才能升级分销员等级?</view>
<template v-if="config.upgradeType == 'invite' && nextLvMoney">
<view>邀请的有效人数达到{{ nextLvMoney }}人即可升级</view>
<view class="u-m-t-40"> 什么是有效邀请人数?</view>
<view> 被邀请人在店铺消费过,即有一笔订单完成才算有效</view>
</template>
<template v-if="config.upgradeType == 'cost' && nextLvMoney">
<view> 消费金额总计达到{{ nextLvMoney }}元即可升级</view>
</template>
<template v-else>
<view>请联系商家</view>
</template>
</view>
</template>
<view class="u-m-t-40" v-if="config.upgradeType == 'cost'">
<view>消费金额如何计算?</view>
<view
>消费金额是计算您和您邀请的人在店铺消费的总金额,但退款订单不计入</view
>
</view>
</view>
</view>
</view>
<view class="parse-html">
<up-parse :content="content"></up-parse>
</view>
<view style="height: 240rpx"></view>
</template>
<view
class="tips u-m-t-32"
v-if="state.distributionUser && state.distributionUser.status"
>您的分销员身份已取消,不再获得分成有疑问可联系商家</view
>
<view class="u-flex justify-center bottom-btn" v-if="showInviteCode">
<view class="copy" @click="copyCode">
<view>复制邀请码</view>
<view v-if="inviteCode">{{ inviteCode }}</view>
</view>
<view class="u-flex u-flex-col justify-center">
<view class="share" @click="showSharePopup = true">分享邀请</view>
</view>
</view>
<view
class="buy"
v-if="config.openType == 'pay' && config.payAmount && !isActivated"
@click="buy"
>
付费{{ config.payAmount }}元开通
</view>
<bindShangji
v-model="showBindShangji"
@confirm="confirmBindShangji"
></bindShangji>
<sharePopup
v-model="showSharePopup"
v-if="
(state.distributionUser && state.distributionUser.inviteCode) ||
inviteCode
"
:inviteCode="
state.distributionUser ? state.distributionUser.inviteCode : inviteCode
"
:shopUserInfo="shopUserInfo"
></sharePopup>
<TipsPopup v-model="showPopup" :tips-type="tipsType"></TipsPopup>
<commissionPopup
:tipsType="commissionTipsType"
v-model="showCommission"
:config="config"
:levelConfigList="config.levelConfigList || []"
></commissionPopup>
<rulePopup
v-model="showRule"
:config="config"
:distributionUser="state.distributionUser"
></rulePopup>
</view>
</template>
<script setup>
import bindShangji from "./components/bind-shangji.vue";
import sharePopup from "./components/share-popup.vue";
import TipsPopup from "../components/tips-popup.vue";
import commissionPopup from "../components/commission.vue";
import rulePopup from "../components/rule.vue";
import { desensitizePhone } from "@/utils/util.js";
import BigNumber from "bignumber.js";
import * as distributionApi from "@/common/api/market/distribution.js";
import { distributionLtPayOrder } from "@/common/api/order/index.js";
import { APIshopUserInfo } from "@/common/api/member.js";
import { pay } from "@/utils/pay.js";
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
const showBindShangji = ref(false);
const showSharePopup = ref(false);
import { ref, reactive, computed, watch } from "vue";
const content = ref("");
const showPopup = ref(false);
const showRule = ref(false);
const showCommission = ref(false);
const tipsType = ref("");
const commissionTipsType = ref("");
function toShouyiDetail(name) {
uni.navigateTo({
url:
"/distribution/income-details/index?name=" +
name +
"&shopId=" +
options.shopId,
});
}
function questionClick(title) {
if (title == "总收益") {
tipsType.value = "总收益";
showPopup.value = true;
}
if (title == "待入账") {
tipsType.value = "待入账";
showPopup.value = true;
}
if (title == "等级分成比例") {
commissionTipsType.value = "等级分成比例";
showCommission.value = true;
}
if (title == "等级升级条件") {
commissionTipsType.value = "等级升级条件";
showCommission.value = true;
}
}
function back() {
uni.navigateBack({
delta: 1,
});
}
async function confirmBindShangji(code) {
const res = await distributionApi.bindInviteUser({
shopId: options.shopId,
inviteCode: code,
id: shopUserInfo.value.id,
});
if (res) {
uni.showToast({
title: "绑定成功",
icon: "none",
});
setTimeout(() => {
init();
}, 1500);
}
// else {
// uni.showToast({
// title: "绑定失败",
// icon: "none",
// });
// }
}
function copyCode() {
uni.setClipboardData({
data: inviteCode.value,
success: function () {
console.log("success");
},
});
}
const shopUserInfo = ref();
const config = reactive({});
//邀请码
const inviteCode = ref("");
async function init() {
const shopUserRes = await APIshopUserInfo({
shopId: options.shopId,
});
const configRes = await distributionApi.getConfig({
shopId: options.shopId,
});
Object.assign(config, configRes);
if (shopUserRes) {
shopUserInfo.value = shopUserRes;
}
if (configRes && configRes.openType == "auto") {
const codeRes = await distributionApi.getInviteCode({
shopId: options.shopId,
shopUserId: shopUserRes.id,
});
if (codeRes) {
inviteCode.value = codeRes;
}
}
const res = await distributionApi.centerConfig({
shopId: options.shopId,
});
if (res) {
if (res.distributionId) {
options.type = "activates";
}
Object.assign(state, res);
if (res.distributionUser) {
inviteCode.value = res.distributionUser.inviteCode;
}
content.value = res.config ? res.config.notActivatedPage : "";
}
}
const options = reactive({ type: "" });
const imageStyle = computed(() => {
return {
height: isActivated.value ? "580rpx" : "580rpx",
};
});
async function buy() {
const res = await distributionLtPayOrder({
shopId: options.shopId,
userId: uni.cache.get("userInfo") ? uni.cache.get("userInfo").id : "",
returnUrl: "",
buyerRemark: "",
amount: "",
remark: "",
code: "",
});
const payRes = await pay(res);
if (payRes) {
uni.showToast({
title: "购买成功",
icon: "none",
});
setTimeout(() => {
init();
getRecoders();
}, 1500);
}
}
const state = reactive({
parentPhone: "",
parentName: "",
shopName: "",
});
const query = reactive({
page: 1,
size: 10,
});
const isEnd = ref(false);
const activeTab = ref("inviter");
const userList = ref([]);
const inviteUserRes = reactive({
records: [],
totalRow: 0,
totalPage: 0,
});
async function getRecoders() {
if (state.config) return;
const ajaxQuery = {
...query,
shopId: options.shopId,
};
if (activeTab.value == "distributor") {
ajaxQuery.parentId = state.distributionUser.distributionId;
} else {
ajaxQuery.id = state.distributionUser.distributionId;
}
const res =
activeTab.value == "distributor"
? await distributionApi.childUser(ajaxQuery)
: await distributionApi.inviteUser(ajaxQuery);
if (res) {
Object.assign(inviteUserRes, res);
if (query.page == 1) {
userList.value = res.records || [];
} else {
userList.value.push(...(res.records || []));
}
isEnd.value = query.page >= res.totalPage * 1;
}
}
const nextLvMoney = computed(() => {
let nextLv = undefined;
if (!config.levelConfigList || !config.levelConfigList.length) {
return "";
}
const nowLevel = state.distributionUser
? state.distributionUser.level || 1
: 1;
if (!nowLevel) {
nextLv = config.levelConfigList[0];
} else {
nextLv = config.levelConfigList.find((v) => v.level == nowLevel + 1);
}
if (nextLv) {
if (config.upgradeType == "cost") {
return nextLv.costAmount;
}
if (config.upgradeType == "invite") {
return nextLv.inviteCount;
}
}
return "";
});
const juNextLvMoney = computed(() => {
if (!nextLvMoney.value) {
return "";
}
let total = 0;
if (config.upgradeType == "cost" && state.distributionUser) {
total = new BigNumber(nextLvMoney.value).minus(state.distributionUser.consumeAmount || 0).toNumber();
}
if (config.upgradeType == "invite" && state.distributionUser) {
total = new BigNumber(nextLvMoney.value).minus(config.inviteCount || 0).toNumber();
}
return Math.max(total, 0);
});
//是否显示邀请码
const showInviteCode = computed(() => {
if (config.upgradeType == "invite") {
return true;
}
if (
config.openType == "manual" &&
(!state.distributionUser || !state.distributionUser.level)
) {
return false;
}
if (
state.distributionUser &&
state.distributionUser.level &&
inviteCode.value
) {
return true;
}
if( config.openType == "auto" ){
return true;
}
if (!state.distributionUser && config.openType == "manual") {
return true;
}
return false;
});
//是否已成为分销员
const isActivated = computed(() => {
return state.distributionUser && state.distributionUser.level;
});
watch(
() => activeTab.value,
(newVal, oldVal) => {
query.page = 1;
isEnd.value = false;
if (newVal != oldVal) {
getRecoders();
}
}
);
function parseQueryString(queryString) {
const queryParams = queryString.split("&").map((param) => param.split("="));
const params = {};
for (const [key, value] of queryParams) {
params[key] = value;
}
return params;
}
onLoad(async (opt) => {
if (opt.q) {
const q = decodeURIComponent(opt.q);
const params = parseQueryString(q.split("?")[1]);
Object.assign(options, params);
} else {
Object.assign(options, opt);
}
console.log(options);
await init();
getRecoders();
});
onReachBottom(async () => {
if (!isEnd.value) {
query.page++;
await getRecoders();
}
});
</script>
<style scoped lang="scss">
.input-number-box {
width: 428rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #999;
font-size: 28rpx;
align-items: baseline;
.fuhao {
font-size: 64rpx;
color: #333;
}
.input-number {
flex: 1;
height: 100%;
font-size: 28rpx;
color: #333;
padding-left: 24rpx;
padding-right: 10rpx;
}
.all-in {
font-size: 28rpx;
color: #fe7e00;
}
}
.list {
.shop-item {
padding: 32rpx 28rpx;
border-bottom: 2rpx solid #ededed;
font-size: 28rpx;
color: #666;
display: flex;
align-items: center;
&:last-child {
border-bottom: none;
}
.fufei {
color: #e8ad7b;
}
.tag {
font-size: 24rpx;
color: #ff1c1c;
background-color: #ffe4e4;
padding: 8rpx 20rpx;
border-radius: 8rpx;
}
.name {
color: #333;
font-weight: 700;
}
.shouxufei {
}
.shouyi {
font-size: 24rpx;
color: #666;
text-align: center;
}
}
}
.status {
font-size: 28rpx;
font-weight: 700;
text-align: right;
color: #333333;
&.fail {
color: #ff1c1c;
}
}
.lingqu {
font-size: 28rpx;
border-radius: 8rpx;
background: #fe6d11;
padding: 8rpx 16rpx;
color: #ffffff;
}
.price {
font-weight: 700;
font-size: 40rpx;
color: #333;
}
.top {
position: relative;
.box {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 28rpx 28rpx 52rpx 28rpx;
&.type1 {
padding-bottom: 0;
}
}
.top_content {
border: 1px solid rgba(255, 255, 255, 0.8);
border-radius: 16rpx;
flex-shrink: 0;
fill: #ffffff3b;
stroke-width: 2rpx;
padding: 32rpx 28rpx;
stroke: #fff;
filter: drop-shadow(2rpx -4rpx 13.4rpx #ff6f0124);
backdrop-filter: blur(5.1rpx);
&.type1 {
filter: none;
border: none;
background-color: #fcf5ed;
border-radius: 36rpx 36rpx 0 0;
padding: 32rpx 36rpx;
}
}
}
.btn-group {
position: absolute;
right: 28rpx;
top: 50%;
transform: translateY(-50%);
.btn {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
border: 2rpx solid #fe6d11;
&.shiming {
color: #fe6d11;
}
&.tixian {
color: #fff;
background-color: #fe6d11;
}
}
}
.tips {
padding: 16rpx 18rpx;
background: #ffe2e2;
padding: 16rpx 18rpx;
font-size: 28rpx;
line-height: 48rpx;
color: #ff1c1c;
}
.bind {
padding: 8rpx 32rpx;
border-radius: 8rpx;
font-size: 24rpx;
border: 2rpx solid #fe6d11;
color: #fff;
background-color: #fe6d11;
}
.top_bg {
width: 100%;
}
.bottom {
margin: 0 28rpx;
border-radius: 36rpx;
background-color: #fff;
transform: translateY(-20rpx);
padding: 32rpx 28rpx;
&.type1 {
transform: translateY(0);
margin: 0;
padding-bottom: 42rpx;
}
}
.title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.small-title {
font-size: 28rpx;
font-weight: 700;
color: #333;
}
.parse-html {
margin: 32rpx 28rpx;
font-size: 28rpx;
}
.recoder-item {
padding: 16rpx 0;
border-bottom: 2rpx solid #ededed;
}
.share {
border-radius: 16rpx;
background: #e8ad7b;
padding: 14rpx 76rpx;
font-size: 32rpx;
line-height: 48rpx;
color: #fff;
}
.copy {
padding: 4rpx 30rpx;
border-radius: 18rpx;
border: 2rpx solid #e8ad7b;
background: #fff;
font-size: 28rpx;
color: #e8ad7b;
line-height: 48rpx;
text-align: center;
}
.bottom-btn {
position: fixed;
left: 84rpx;
right: 84rpx;
bottom: 100rpx;
white-space: nowrap;
gap: 54rpx;
}
.buy {
padding: 32rpx 224rpx;
border-radius: 40rpx;
background: linear-gradient(98deg, #fe6d1100 40.64%, #ffd1b4 105.2%),
linear-gradient(259deg, #fe6d11 50.14%, #ffd1b4 114.93%);
box-shadow: 0 14rpx 30.4rpx 0 #fe8b435e;
font-size: 32rpx;
color: #fff;
font-weight: 700;
position: fixed;
left: 28rpx;
right: 28rpx;
bottom: 62rpx;
white-space: nowrap;
}
.tabs {
display: flex;
margin: 20rpx 0;
gap: 30rpx;
.tabs-item {
flex: 1;
padding: 4rpx 30rpx;
border-radius: 18rpx;
border: 2rpx solid #e8ad7b;
background: #fff;
font-size: 28rpx;
color: #e8ad7b;
line-height: 48rpx;
text-align: center;
transition: all 0.3s ease-in-out;
&.active {
background-color: #e8ad7b;
color: #fff;
}
}
}
</style>