Переглянути джерело

新增我的推广积分模块

wyling007 3 роки тому
батько
коміт
da03a0bef5

+ 42 - 7
src/api/modules/branch.ts

@@ -2,13 +2,48 @@ import { AxiosPromise } from "axios";
 import request from "../request";
 
 class Branch {
-  /**
-   * 查询一二级绑定用户
-   * @returns
-   */
-  spreadRelationList(): AxiosPromise<BranchType.spreadRelationListRes> {
-    return request("/student/spread/relation/spreadRelationList");
-  }
+	/**
+	 * 查询一二级绑定用户
+	 * @returns
+	 */
+	spreadRelationList(): AxiosPromise<BranchType.spreadRelationListRes> {
+		return request("/student/spread/relation/spreadRelationList");
+	}
+
+	/**下级用户积分列表 */
+	integralList(): AxiosPromise<BranchTypeTest.IntegralRes> {
+		return request("/student/extension/income/extensionPoints");
+	}
+
+	/**积分结算 */
+	integralSettlement(params: BranchTypeTest.IntegralSettlementParams): AxiosPromise<BranchTypeTest.IntegralSettlementRes> {
+		return request("/student/extension/income/extensionIncomePrice", {
+			params,
+		});
+	}
 }
 
 export const branch = new Branch();
+
+export declare namespace BranchTypeTest {
+	type Integral = {
+		openid: string;
+		nickName: string;
+		headImage: string;
+		achievement: number;
+		achievementSettled: number;
+	};
+
+	interface IntegralRes extends Common.Res {
+		data: Integral[];
+	}
+
+	type IntegralSettlementParams = {
+		openid: string;
+		settlePoints: number;
+	};
+
+	interface IntegralSettlementRes extends Common.Res {
+		data: {};
+	}
+}

+ 21 - 6
src/dataModel/myBranchList.ts

@@ -1,11 +1,26 @@
-import { branch } from "@/api";
+import { branch, BranchTypeTest } from "@/api";
 
 /**下级代理列表模型 */
 export class MyBranchListModel {
-  private api = branch.spreadRelationList;
+	private api = branch.spreadRelationList;
 
-  async getData() {
-    const res = await this.api();
-    return res.data.data;
-  }
+	async getData() {
+		const res = await this.api();
+		return res.data.data;
+	}
+}
+
+/**代理模型 */
+export class BranchModel {
+	private api = branch;
+
+	async getIntegralList() {
+		const { data } = await this.api.integralList();
+		return data.data;
+	}
+
+	async integralSettlement(params: BranchTypeTest.IntegralSettlementParams) {
+		const { data } = await this.api.integralSettlement(params);
+		return data;
+	}
 }

+ 6 - 0
src/hooks/index.ts

@@ -84,6 +84,12 @@ export class RouterBus {
 	goCashOut = () => {
 		this.router.push("/cashOut");
 	};
+	/**
+	 * 我的推广积分
+	 */
+	goMyIntegral = () => {
+		this.router.push("/myIntegral");
+	};
 	/**
 	 * 我的下级页
 	 */

+ 28 - 0
src/utils/mount-component.ts

@@ -0,0 +1,28 @@
+import { createApp, Component, getCurrentInstance } from "vue";
+
+export const useUnmountComponent = () => {
+	const instance = getCurrentInstance();
+	const unmountComponent = () => {
+		instance?.appContext.app.unmount();
+		document.body.removeChild(instance?.appContext.app._container);
+	};
+
+	return {
+		unmountComponent,
+	};
+};
+
+export function mountComponent(RootComponent: Component, rootProps?: Record<string, unknown> | null | undefined) {
+	const app = createApp(RootComponent, rootProps);
+	const root = document.createElement("div");
+
+	document.body.appendChild(root);
+
+	return {
+		instance: app.mount(root),
+		unmount() {
+			app.unmount();
+			document.body.removeChild(root);
+		},
+	};
+}

+ 0 - 205
src/views/cashOut copy/index.vue

@@ -1,205 +0,0 @@
-<template>
-	<div class="header-back">
-		<m-nav-bar :transparent="true" title="提现页面" style="color: #ffffff" />
-		<div class="user-data">
-			<div class="left">
-				<m-user-avatar />
-				<div class="name">
-					<m-user-name />
-					<span
-						>可提现余额<span class="grade">{{ profitPrice }}</span
-						>元</span
-					>
-				</div>
-			</div>
-			<m-button @click="fn" class="continue" width="90px" height="30px" text="我要提现" />
-		</div>
-	</div>
-	<div class="summary content-box">
-		<form action="/" class="search">
-			<van-search v-model="searchValue" shape="round" placeholder="请输入下级代理昵称" />
-		</form>
-	</div>
-	<div class="test-scores content-box">
-		<table class="table">
-			<tr>
-				<th>头像</th>
-				<th>昵称</th>
-				<th>代理等级</th>
-				<!-- <th>分成比例</th> -->
-				<th>分成金额</th>
-			</tr>
-			<tr v-for="(item, index) in otherUserInfoList.filter((item) => item.nickName.includes(searchValue))" :key="index">
-				<td>
-					<van-image round width="50px" height="50px" :src="item.headImage" />
-				</td>
-				<td>{{ item.nickName }}</td>
-				<td>{{ item.hierarchy }}</td>
-				<!-- <td>{{ item.percentage }}</td> -->
-				<td>{{ item.profitPrice }}</td>
-			</tr>
-		</table>
-	</div>
-	<van-dialog v-model="show" title="我要提现" show-cancel-button>
-		<img src="https://img01.yzcdn.cn/vant/apple-3.jpg" />
-	</van-dialog>
-</template>
-
-<script lang="ts">
-	import { CashOutModel } from "@/dataModel/cashOut";
-	import { ref, onBeforeMount } from "vue";
-	import { RouterBus } from "@/hooks";
-	const cashOutModel = new CashOutModel();
-	/** 获取下级用户列表 */
-	const useOtherUserInfoList = () => {
-		const otherUserInfoList = ref<
-			{
-				profitPrice: string;
-				headImage: string;
-				nickName: string;
-				createTime: string;
-				hierarchy: string;
-				openid: string;
-				percentage: string;
-			}[]
-		>([]);
-		onBeforeMount(async () => {
-			const res = await cashOutModel.extensionIncomeList();
-			otherUserInfoList.value.push(...res.rows);
-		});
-		return {
-			otherUserInfoList,
-		};
-	};
-	/** 获取用户可提现信息 */
-	const useUserInfo = () => {
-		const userInfo = ref<{
-			extractPrice: string;
-			remainderPrice: string;
-			totalPrice: string;
-			beneficiaryOpenid: string;
-			headImage: string;
-			nickName: string;
-		}>();
-		onBeforeMount(async () => {
-			const res = await cashOutModel.extensionIncomePrice();
-			userInfo.value = res;
-		});
-		return {
-			userInfo,
-		};
-	};
-</script>
-
-<script lang="ts" setup>
-	import { useProfitPrice } from "@/hooks/user";
-	import { Dialog } from "vant";
-	const { otherUserInfoList } = useOtherUserInfoList();
-	const { userInfo } = useUserInfo();
-	const searchValue = ref("");
-	const { goMockTest } = new RouterBus();
-
-	const { profitPrice } = useProfitPrice();
-
-	console.log(profitPrice.value);
-
-	const show = ref(false);
-	const fn = () => {
-		console.log(123);
-		Dialog({
-			title: "暂未开放",
-		});
-		show.value = true;
-	};
-</script>
-
-<style scoped lang="scss">
-	.header-back {
-		width: 375px;
-		padding-bottom: 82px;
-		background: linear-gradient(180deg, #498ef5 0%, #4da8e6 100%);
-		border-radius: 0px 0px 82px 82px;
-		.user-data {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			padding: 19px 17px 24px;
-			.left {
-				display: flex;
-				justify-content: space-between;
-				align-items: center;
-				.name {
-					display: flex;
-					flex-direction: column;
-					font-size: 13px;
-					color: #ffffff;
-					justify-content: space-between;
-					margin-left: 6px;
-					.grade {
-						font-size: 24px;
-						padding: 4px;
-					}
-				}
-			}
-			.continue {
-				font-size: 13px;
-				font-family: PingFang SC;
-				font-weight: 400;
-				line-height: 19px;
-				color: #ffffff;
-				background: #01c18d;
-			}
-		}
-	}
-	.content-box {
-		width: 345px;
-		background: #ffffff;
-		box-shadow: 0px 0px 8px rgba(124, 129, 136, 0.2);
-		border-radius: 10px;
-		position: relative;
-		left: 50%;
-		transform: translateX(-50%);
-		top: -82px;
-		margin-top: 10px;
-	}
-	.summary {
-		display: flex;
-		justify-content: space-around;
-		box-sizing: border-box;
-		overflow: hidden;
-		.search {
-			width: 100%;
-		}
-	}
-	.test-scores {
-		font-size: 13px;
-		font-family: PingFang SC;
-		font-weight: 400;
-		line-height: 19px;
-		color: #0a1a33;
-		padding: 15px;
-		box-sizing: border-box;
-		.table {
-			width: 100%;
-			border-collapse: collapse;
-			font-size: 13px;
-			th {
-				padding: 5px;
-				color: #0a1a33;
-			}
-			td {
-				text-align: center;
-				padding: 5px;
-				color: #8a9099;
-			}
-			tr {
-				&:nth-of-type(n) {
-					background: #ffffff;
-				}
-				&:nth-of-type(2n) {
-					background: rgba(73, 142, 245, 0.15);
-				}
-			}
-		}
-	}
-</style>

+ 6 - 1
src/views/home/children/user/index.vue

@@ -19,6 +19,11 @@
 					<m-icon type="hyyxq" class="cell-icon" />
 				</template>
 			</van-cell>
+			<van-cell title="我的推广积分" value="" is-link center @click="goMyIntegral">
+				<template #icon>
+					<m-icon type="hyyxq" class="cell-icon" />
+				</template>
+			</van-cell>
 			<van-cell title="会员有效期" :value="expireTime ? expireTime : '开通会员'" is-link center @click="goBuyVip">
 				<template #icon>
 					<m-icon type="huiyuan" class="cell-icon" />
@@ -48,7 +53,7 @@
 <script lang="ts" setup>
 	import { loopPrepareOrder } from "@/api";
 	const { expireTime } = useExpireTime();
-	const { goBuyVip, goCashOut, goMyBranch } = new RouterBus();
+	const { goBuyVip, goCashOut, goMyBranch, goMyIntegral } = new RouterBus();
 	let doubleClickNumber = 0;
 	const showVConsole = () => {
 		if (doubleClickNumber !== 9) {

+ 42 - 0
src/views/myIntegral/components/settlement.ts

@@ -0,0 +1,42 @@
+import { mountComponent, useUnmountComponent } from "@/utils/mount-component";
+import Settlement from "./settlement.vue";
+import { BranchModel } from "@/dataModel/myBranchList";
+import { ref } from "vue";
+import { Toast } from "vant";
+
+const branchModel = new BranchModel();
+
+export type UserInfo = {
+	openid: string;
+	nickName: string;
+	headImage: string;
+	achievement: number;
+	achievementSettled: number;
+};
+
+export const showSettlement = (props: UserInfo) => {
+	const { unmount } = mountComponent(Settlement, props);
+	return {
+		unmount,
+	};
+};
+
+export const useSettlementIntegral = (props: { openid: string; settlePoints: number }) => {
+	const { unmountComponent } = useUnmountComponent();
+	const loading = ref(false);
+	const settlementIntegral = async () => {
+		loading.value = true;
+		const data = await branchModel.integralSettlement(props);
+		if (data.code === 200) {
+			Toast.success("结算成功");
+			unmountComponent();
+		} else {
+			Toast.fail(data.msg);
+		}
+		loading.value = false;
+	};
+	return {
+		loading,
+		settlementIntegral,
+	};
+};

+ 69 - 0
src/views/myIntegral/components/settlement.vue

@@ -0,0 +1,69 @@
+<template>
+	<Overlay :show="true">
+		<div class="wrapper" @click.stop>
+			<div class="block">
+				<Form @submit="settlementIntegral">
+					<CellGroup inset>
+						<div class="user-img">
+							<Image round width="50px" height="50px" :src="props.headImage" />
+							<span style="margin-left: 5px">{{ props.nickName }}</span>
+						</div>
+						<Field label="未结算积分" :model-value="props.achievement" disabled />
+						<Field label="已结算积分" :model-value="props.achievementSettled" disabled />
+						<Field v-model="settlePoints" label="结算积分" type="number" placeholder="请输入结算积分" />
+					</CellGroup>
+					<div class="submit-box">
+						<Button :loading="loading" type="primary" hairline native-type="submit" loading-text="结算中..."> 提交 </Button>
+						<Button type="default" hairline @click="unmountComponent"> 取消 </Button>
+					</div>
+				</Form>
+			</div>
+		</div>
+	</Overlay>
+</template>
+
+<script lang="ts" setup>
+	import { Overlay, Field, CellGroup, Form, Button, Image } from "vant";
+	import { ref } from "vue";
+	import { useUnmountComponent } from "@/utils/mount-component";
+	import { useSettlementIntegral } from "./settlement";
+	const { unmountComponent } = useUnmountComponent();
+	const props = defineProps<{
+		openid: string;
+		nickName: string;
+		headImage: string;
+		achievement: number;
+		achievementSettled: number;
+	}>();
+	const settlePoints = ref<number>(props.achievement);
+
+	const { loading, settlementIntegral } = useSettlementIntegral({ openid: props.openid, settlePoints: settlePoints.value });
+</script>
+
+<style scoped lang="scss">
+	.wrapper {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 100%;
+	}
+	.block {
+		width: 300px;
+		background-color: #fff;
+		padding: 20px;
+		border-radius: 10px;
+	}
+	.user-img {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		font-size: 20px;
+		margin-bottom: 10px;
+	}
+	.submit-box {
+		display: flex;
+		justify-content: space-around;
+		align-items: center;
+		margin-top: 10px;
+	}
+</style>

+ 143 - 0
src/views/myIntegral/index.vue

@@ -0,0 +1,143 @@
+<template>
+	<div class="header-back">
+		<m-nav-bar :transparent="true" title="我的推广积分" style="color: #ffffff" />
+		<div class="user-data">
+			<m-user-avatar />
+			<m-user-name />
+		</div>
+	</div>
+	<div class="integral-box">
+		<van-search class="search" shape="round" placeholder="请输入下级代理昵称" v-model="searchValue" />
+		<div class="test-scores content-box">
+			<table class="table" v-if="integralList.length > 0">
+				<tr>
+					<th>头像</th>
+					<th>昵称</th>
+					<th>未结算</th>
+					<th>已结算</th>
+					<th>按钮</th>
+				</tr>
+				<tr v-for="(userInfo, index) in integralList.filter((userInfo) => userInfo.nickName.includes(searchValue))" :key="index">
+					<td>
+						<van-image round width="50px" height="50px" :src="userInfo.headImage" />
+					</td>
+					<td>{{ userInfo.nickName }}</td>
+					<td>{{ userInfo.achievement }}</td>
+					<td>{{ userInfo.achievementSettled }}</td>
+					<td><van-button type="primary" @click="showSettlement(userInfo)">结算</van-button></td>
+				</tr>
+			</table>
+			<div v-else style="text-align: center">暂无数据</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+	import { BranchModel } from "@/dataModel/myBranchList";
+	import { ref, onBeforeMount } from "vue";
+
+	export type UserInfo = {
+		openid: string;
+		nickName: string;
+		headImage: string;
+		achievement: number;
+		achievementSettled: number;
+	};
+
+	const branchModel = new BranchModel();
+
+	const useMyIntegral = () => {
+		const integralList = ref<Array<UserInfo>>([]);
+
+		onBeforeMount(async () => {
+			const data = await branchModel.getIntegralList();
+			integralList.value = data;
+		});
+
+		const integralSettlement = branchModel.integralSettlement;
+
+		return {
+			integralList,
+		};
+	};
+</script>
+
+<script lang="ts" setup>
+	import { showSettlement } from "./components/settlement";
+	const { integralList } = useMyIntegral();
+	const searchValue = ref("");
+</script>
+
+<style scoped lang="scss">
+	.integral-box {
+		margin-top: -90px;
+		padding: 10px;
+	}
+	.header-back {
+		width: 375px;
+		padding-bottom: 82px;
+		background: linear-gradient(180deg, #498ef5 0%, #4da8e6 100%);
+		border-radius: 0px 0px 82px 82px;
+		.user-data {
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+			align-items: center;
+			padding: 19px 17px 24px;
+			font-size: 23px;
+			color: #ffffff;
+		}
+	}
+	.content-box {
+		width: 345px;
+		background: #ffffff;
+		box-shadow: 0px 0px 8px rgba(124, 129, 136, 0.2);
+		border-radius: 10px;
+		position: relative;
+		left: 50%;
+		transform: translateX(-50%);
+		margin-top: 10px;
+	}
+	.summary {
+		display: flex;
+		justify-content: space-around;
+		box-sizing: border-box;
+		overflow: hidden;
+		.search {
+			width: 100%;
+			border-radius: 10px;
+			overflow: hidden;
+		}
+	}
+	.test-scores {
+		font-size: 13px;
+		font-family: PingFang SC;
+		font-weight: 400;
+		line-height: 19px;
+		color: #0a1a33;
+		padding: 15px;
+		box-sizing: border-box;
+		.table {
+			width: 100%;
+			border-collapse: collapse;
+			font-size: 13px;
+			th {
+				padding: 5px;
+				color: #0a1a33;
+			}
+			td {
+				text-align: center;
+				padding: 5px;
+				color: #8a9099;
+			}
+			tr {
+				&:nth-of-type(n) {
+					background: #ffffff;
+				}
+				&:nth-of-type(2n) {
+					background: rgba(73, 142, 245, 0.15);
+				}
+			}
+		}
+	}
+</style>