Просмотр исходного кода

销花宝总后台-邀请门店分佣信息展示

ouyang 3 дней назад
Родитель
Сommit
d4306d24de

+ 2 - 0
ghs/src/cool/request/index.js

@@ -10,6 +10,7 @@ import level from '@/service/level/index';
 import category from '@/service/category/index';
 import shopAdmin from '@/service/shop-admin/index';
 import invite from '@/service/invite/index';
+import shopInvite from '@/service/shop-invite/index';
 import coupon from '@/service/coupon/index';
 import ad from '@/service/ad/index';
 import staff from '@/service/staff/index';
@@ -127,6 +128,7 @@ export function SET_SERVICE({ store }) {
 		level,
 		category,
 		invite,
+		shopInvite,
 		coupon,
 		ad,
 		staff,

+ 5 - 2
ghs/src/mock/userInfo.js

@@ -193,8 +193,11 @@ let menu = {
 		{id: '10',parentId: null,name: '门店',type: 1,icon: 'iconguanggao',orderNum: 10,router: '/shop/list',viewPath: 'views/shop/list.vue',keepAlive: 1},
         {id: '1001',parentId: 10,name: '门店列表',type: 1,icon: 'iconguanggao',orderNum: 10,router: '/shop/list',viewPath: 'views/shop/list.vue',keepAlive: 1},
 
-		{id: '11',parentId: null,name: '邀请',type: 1,icon: 'iconyaoqingyouli',orderNum: 11,router: '/pages/admin/my-invite',viewPath: 'views/my-invite/index.vue',keepAlive: 1},
-        {id: '1101',parentId: 11,name: '邀请注册',type: 1,icon: 'iconyaoqingyouli',orderNum: 11,router: '/pages/admin/my-invite',viewPath: 'views/my-invite/index.vue',keepAlive: 1},
+		{id: '11',parentId: null,name: '邀请',type: 0,icon: 'iconyaoqingyouli',orderNum: 11,router: '/pages/admin/my-invite',viewPath: 'views/my-invite/index.vue',keepAlive: 1},
+        {id: '1101',parentId: 11,name: '邀请注册',type: 1,icon: 'iconyaoqingyouli',orderNum: 1,router: '/pages/admin/my-invite',viewPath: 'views/my-invite/index.vue',keepAlive: 1},
+        {id: '11021',parentId: 11,name: '分成比例管理',type: 1,icon: 'iconyaoqingyouli',orderNum: 2,router: '/invite/shop-invite/ratio',viewPath: 'views/shop-invite/ratio-manage.vue',keepAlive: 1},
+        {id: '11022',parentId: 11,name: '佣金分成明细',type: 1,icon: 'iconyaoqingyouli',orderNum: 3,router: '/invite/shop-invite/commission-list',viewPath: 'views/shop-invite/commission-list.vue',keepAlive: 1},
+        {id: '11023',parentId: 11,name: '佣金分成详情',type: 1,icon: 'iconyaoqingyouli',orderNum: 99,router: '/invite/shop-invite/commission-detail',viewPath: 'views/shop-invite/commission-detail.vue',keepAlive: 0},
 
 		{id: '12',parentId: null,name: '统计',type: 0,icon: 'icondingdanguanli',orderNum: 12,router: '/assets/list',viewPath: 'views/assets/list.vue',keepAlive: 1},
 		{id: '1201',parentId: '12',name: '余额明细',type: 1,icon: 'icondingdanguanli',orderNum: 12,router: '/assets/list',viewPath: 'views/assets/list.vue',keepAlive: 1},

+ 1 - 1
ghs/src/pages/layout/index.vue

@@ -75,7 +75,7 @@ export default {
 		background-color: #fff;
 		overflow: hidden;
 		height: 100%;
-		width: 190px;
+		width: 220px;
 	}
 
 	// .wrapper-right {

+ 24 - 13
ghs/src/pages/layout/slider/index.vue

@@ -49,7 +49,6 @@ export default {
 	computed: {
 		...mapGetters(['menuGroup']),
 		subData() {
-			// console.log('children', this.menuGroup[this.mainIndex]);
 			if (this.$util.isEmpty(this.menuGroup)) {
 				return [];
 			}
@@ -57,7 +56,8 @@ export default {
 			if (this.$util.isEmpty(arr)) {
 				return [];
 			}
-			return arr;
+			// orderNum >= 90 为隐藏路由(如详情页),不在侧栏展示
+			return arr.filter(item => Number(item.orderNum || 0) < 90);
 		}
 	},
 	mounted() {
@@ -68,16 +68,30 @@ export default {
 		init() {
 			let path = this.$route.path;
 			for (let i in this.menuGroup) {
-				if (this.menuGroup[i].children.length > 0) {
-					for (let j in this.menuGroup[i].children) {
-						if (path == this.menuGroup[i].children[j].path) {
+				const mainItem = this.menuGroup[i];
+				if (mainItem.children && mainItem.children.length > 0) {
+					for (let j in mainItem.children) {
+						const child = mainItem.children[j];
+						if (path === child.path) {
 							this.mainIndex = i;
 							this.subIndex = j;
+							return;
+						}
+						// 兼容三级菜单:子目录下页面
+						if (child.children && child.children.length > 0) {
+							for (let k in child.children) {
+								if (path === child.children[k].path) {
+									this.mainIndex = i;
+									this.subIndex = j;
+									return;
+								}
+							}
 						}
 					}
-				} else if (path == this.menuGroup[i].path) {
+				} else if (path === mainItem.path) {
 					this.mainIndex = i;
 					this.subIndex = 0;
+					return;
 				}
 			}
 		},
@@ -91,13 +105,10 @@ export default {
 			}
 		},
 		pageSubTo(item, index) {
-      if(index==0){
-        this.$router.push('/item-class/1');
-      }
-      this.subIndex = index;
-			this.$nextTick(()=>{
-			  this.$router.push(item.path);
-      })
+			this.subIndex = index;
+			this.$nextTick(() => {
+				this.$router.push(item.path);
+			});
 		}
 	}
 };

+ 90 - 0
ghs/src/service/shop-invite/index.js

@@ -0,0 +1,90 @@
+/**
+ * 门店邀请分佣管理 — ghs Web
+ * 对接 app-ghs MainInviteAdminController
+ */
+import { BaseService, Service } from '@/cool';
+
+@Service('main-invite-admin')
+export class ShopInviteService extends BaseService {
+	/** 基础分佣设置(只读 dict) */
+	registerConfig() {
+		return this.request({
+			url: '/register-config'
+		});
+	}
+
+	/** 批发商独立比例列表 */
+	wholesalerRatioList(params) {
+		return this.request({
+			url: '/wholesaler-ratio-list',
+			params
+		});
+	}
+
+	/** x-crud 分页入口:批发商独立比例 */
+	page(params) {
+		return this.wholesalerRatioList(params).then(res => {
+			return {
+				...res,
+				pagination: {
+					totalPage: res.totalPage,
+					total: parseInt(res.totalNum || 0, 10)
+				}
+			};
+		});
+	}
+
+	/** 批发商搜索(xhShop ptStyle=2) */
+	wholesalerSearch(params) {
+		return this.request({
+			url: '/wholesaler-search',
+			params
+		});
+	}
+
+	/** 保存批发商独立比例 */
+	saveWholesalerRatio(data) {
+		return this.request({
+			url: '/save-wholesaler-ratio',
+			method: 'POST',
+			data
+		});
+	}
+
+	/** 佣金汇总卡片 */
+	commissionSummary(params) {
+		return this.request({
+			url: '/commission-summary',
+			params
+		});
+	}
+
+	/** 佣金明细分页 */
+	commissionList(params) {
+		return this.request({
+			url: '/commission-list',
+			params
+		});
+	}
+
+	/** 佣金明细详情 */
+	commissionDetail(params) {
+		return this.request({
+			url: '/commission-detail',
+			params
+		});
+	}
+
+	/** 导出佣金明细 */
+	commissionExport(params) {
+		return this.request({
+			url: '/commission-list',
+			params: {
+				...params,
+				export: 1
+			}
+		});
+	}
+}
+
+export default new ShopInviteService();

+ 308 - 0
ghs/src/views/shop-invite/commission-detail.vue

@@ -0,0 +1,308 @@
+<!--
+  佣金分成详情 — ghs Web 门店邀请分佣
+  按设计稿展示分佣/批发商/受邀花店/会员订单四块信息
+-->
+<template>
+	<div class="app-content shop-invite-commission-detail" v-loading="loading">
+		<div class="detail-header">
+			<div class="detail-title">佣金分成详情</div>
+			<el-button size="mini" icon="el-icon-back" @click="goBack">返回列表</el-button>
+		</div>
+
+		<div
+			v-if="detail.commission"
+			class="status-banner"
+			:class="detail.commission.settleStatus === 1 ? 'is-settled' : 'is-pending'"
+		>
+			<div class="status-left">
+				<i class="status-icon el-icon-success" v-if="detail.commission.settleStatus === 1"></i>
+				<i class="status-icon el-icon-time" v-else></i>
+				<div>
+					<div class="status-main">{{ detail.commission.settleStatusText }}</div>
+					<div class="status-sub" v-if="detail.commission.settleStatus === 1">佣金已结算至批发商账户</div>
+					<div class="status-sub" v-else>佣金待结算</div>
+				</div>
+			</div>
+			<div class="status-time" v-if="detail.commission.settleStatus === 1 && detail.commission.settleTime">
+				结算时间:{{ detail.commission.settleTime }}
+			</div>
+		</div>
+
+		<div class="card-grid">
+			<div class="info-card">
+				<div class="card-title">分佣信息</div>
+				<div class="info-row"><span>分佣单号</span><span>{{ detail.commission.commissionSn || '-' }}</span></div>
+				<div class="info-row"><span>产生时间</span><span>{{ detail.commission.addTime || '-' }}</span></div>
+				<div class="info-row"><span>结算时间</span><span>{{ detail.commission.settleTime || '-' }}</span></div>
+				<div class="info-row">
+					<span>结算状态</span>
+					<span>
+						<el-tag size="mini" :type="settleTagType(detail.commission.settleStatus)">
+							{{ detail.commission.settleStatusText || '-' }}
+						</el-tag>
+					</span>
+				</div>
+				<div class="info-row highlight">
+					<span>分成金额</span>
+					<span class="money">¥ {{ moneyText(detail.commission.commissionAmount) }}</span>
+				</div>
+			</div>
+
+			<div class="info-card">
+				<div class="card-title">获佣批发商信息</div>
+				<div class="info-row"><span>批发商名称</span><span>{{ detail.inviter.shopName || '-' }}</span></div>
+				<div class="info-row"><span>邀请码</span><span>{{ detail.inviter.inviteCode || '-' }}</span></div>
+				<div class="info-row"><span>联系人</span><span>{{ detail.inviter.contactName || '-' }}</span></div>
+				<div class="info-row"><span>联系电话</span><span>{{ detail.inviter.mobile || '-' }}</span></div>
+				<div class="info-row highlight-sm">
+					<span>账户入账金额</span>
+					<span class="money">¥ {{ moneyText(detail.inviter.creditedAmount) }}</span>
+				</div>
+			</div>
+
+			<div class="info-card">
+				<div class="card-title">受邀花店信息</div>
+				<div class="info-row"><span>花店名称</span><span>{{ detail.invitee.shopName || '-' }}</span></div>
+				<div class="info-row"><span>联系人</span><span>{{ detail.invitee.contactName || '-' }}</span></div>
+				<div class="info-row"><span>注册时间</span><span>{{ detail.invitee.registerTime || '-' }}</span></div>
+				<div class="info-row"><span>联系电话</span><span>{{ detail.invitee.mobile || '-' }}</span></div>
+				<div class="info-row"><span>邀请关系</span><span>{{ detail.invitee.inviteRelation || '-' }}</span></div>
+			</div>
+
+			<div class="info-card">
+				<div class="card-title">会员订单信息</div>
+				<div class="info-row"><span>会员订单号</span><span>{{ detail.memberOrder.orderSn || '-' }}</span></div>
+				<div class="info-row highlight-sm">
+					<span>会员实付金额</span>
+					<span class="money">¥ {{ moneyText(detail.memberOrder.actPrice) }}</span>
+				</div>
+				<div class="info-row"><span>会员类型</span><span>{{ detail.memberOrder.memberTypeName || '-' }}</span></div>
+				<div class="info-row"><span>支付时间</span><span>{{ detail.memberOrder.payTime || '-' }}</span></div>
+				<div class="info-row"><span>会员原价</span><span>¥ {{ moneyText(detail.memberOrder.prePrice) }}</span></div>
+				<div class="info-row"><span>支付方式</span><span>{{ detail.memberOrder.payWayText || '-' }}</span></div>
+				<div class="info-row"><span>邀请减免</span><span>- ¥ {{ moneyText(detail.memberOrder.hdDiscountAmount) }}</span></div>
+				<div class="info-row">
+					<span>订单状态</span>
+					<span>
+						<el-tag size="mini" type="success">{{ detail.memberOrder.orderStatusText || '已完成' }}</el-tag>
+					</span>
+				</div>
+			</div>
+		</div>
+
+		<div class="footer-tip">
+			<i class="el-icon-info"></i>
+			<span>会员开通成功后,佣金直接结算至获佣批发商账户。</span>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'shop-invite-commission-detail',
+	data() {
+		return {
+			loading: false,
+			detail: {
+				commission: {},
+				inviter: {},
+				invitee: {},
+				memberOrder: {}
+			}
+		};
+	},
+	created() {
+		this.loadDetail();
+	},
+	methods: {
+		moneyText(val) {
+			const n = parseFloat(val);
+			return isNaN(n) ? '0.00' : n.toFixed(2);
+		},
+		settleTagType(status) {
+			if (Number(status) === 1) {
+				return 'success';
+			}
+			if (Number(status) === 2) {
+				return 'info';
+			}
+			return 'warning';
+		},
+		loadDetail() {
+			const id = this.$route.query.id;
+			if (!id) {
+				this.$message.error('缺少记录 id');
+				return;
+			}
+			this.loading = true;
+			this.$service.shopInvite.commissionDetail({ id }).then(res => {
+				if (res) {
+					this.detail = {
+						commission: res.commission || {},
+						inviter: res.inviter || {},
+						invitee: res.invitee || {},
+						memberOrder: res.memberOrder || {}
+					};
+				}
+			}).finally(() => {
+				this.loading = false;
+			});
+		},
+		goBack() {
+			this.$router.push({ path: '/invite/shop-invite/commission-list' });
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.shop-invite-commission-detail {
+	padding: 16px 20px 40px;
+	background: #f5f7fa;
+	min-height: calc(100vh - 120px);
+	box-sizing: border-box;
+}
+
+.detail-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	margin-bottom: 16px;
+}
+
+.detail-title {
+	font-size: 18px;
+	font-weight: 600;
+	color: #303133;
+}
+
+.status-banner {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 16px 20px;
+	border-radius: 6px;
+	margin-bottom: 16px;
+}
+
+.status-banner.is-settled {
+	background: #f0f9eb;
+	border: 1px solid #e1f3d8;
+	color: #67c23a;
+}
+
+.status-banner.is-pending {
+	background: #fdf6ec;
+	border: 1px solid #faecd8;
+	color: #e6a23c;
+}
+
+.status-left {
+	display: flex;
+	align-items: center;
+}
+
+.status-icon {
+	font-size: 28px;
+	margin-right: 12px;
+}
+
+.status-main {
+	font-size: 16px;
+	font-weight: 600;
+	margin-bottom: 4px;
+}
+
+.status-sub {
+	font-size: 13px;
+	opacity: 0.9;
+}
+
+.status-time {
+	font-size: 13px;
+	color: #606266;
+	white-space: nowrap;
+}
+
+.card-grid {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.info-card {
+	width: calc(50% - 8px);
+	background: #fff;
+	border: 1px solid #ebeef5;
+	border-radius: 6px;
+	padding: 16px 18px;
+	box-sizing: border-box;
+	margin-bottom: 16px;
+}
+
+.info-card:nth-child(odd) {
+	margin-right: 16px;
+}
+
+.card-title {
+	font-size: 15px;
+	font-weight: 600;
+	margin-bottom: 12px;
+	padding-bottom: 8px;
+	border-bottom: 1px solid #f2f2f2;
+	color: #303133;
+}
+
+.info-row {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	font-size: 13px;
+	line-height: 32px;
+	color: #606266;
+}
+
+.info-row span:first-child {
+	color: #909399;
+	flex-shrink: 0;
+	margin-right: 12px;
+}
+
+.info-row span:last-child {
+	text-align: right;
+	word-break: break-all;
+}
+
+.info-row.highlight {
+	margin-top: 8px;
+	padding-top: 8px;
+	border-top: 1px dashed #ebeef5;
+}
+
+.info-row.highlight-sm {
+	margin-top: 4px;
+}
+
+.info-row.highlight .money,
+.info-row.highlight-sm .money {
+	color: #67c23a;
+	font-size: 18px;
+	font-weight: 600;
+}
+
+.footer-tip {
+	display: flex;
+	align-items: center;
+	padding: 12px 16px;
+	background: #f0f9eb;
+	border: 1px solid #e1f3d8;
+	border-radius: 6px;
+	color: #67c23a;
+	font-size: 13px;
+	line-height: 1.6;
+
+	i {
+		font-size: 16px;
+		margin-right: 8px;
+	}
+}
+</style>

+ 266 - 0
ghs/src/views/shop-invite/commission-list.vue

@@ -0,0 +1,266 @@
+<!--
+  佣金分成明细 — ghs Web 门店邀请分佣
+  汇总卡片 + 筛选表格 + 跳转详情
+-->
+<template>
+	<div class="app-list-content shop-invite-commission-list">
+		<div class="summary-row">
+			<div class="summary-card" v-for="item in summaryCards" :key="item.key">
+				<div class="summary-label">{{ item.label }}</div>
+				<div class="summary-value">{{ item.value }}</div>
+			</div>
+		</div>
+
+		<x-crud class="liu-crud-wrap" @load="onLoad">
+			<template #slot-date-filter>
+				<el-date-picker
+					v-model="dateRange"
+					type="daterange"
+					size="mini"
+					range-separator="至"
+					start-placeholder="开始日期"
+					end-placeholder="结束日期"
+					value-format="yyyy-MM-dd"
+					@change="onDateChange"
+				></el-date-picker>
+			</template>
+
+			<template #slot-inviter-filter>
+				<el-input v-model="filters.inviterKeyword" size="mini" clearable placeholder="请输入批发商名称" style="width: 160px;"></el-input>
+			</template>
+
+			<template #slot-invitee-filter>
+				<el-input v-model="filters.inviteeKeyword" size="mini" clearable placeholder="请输入受邀花店名称" style="width: 160px;"></el-input>
+			</template>
+
+			<template #slot-code-filter>
+				<el-input v-model="filters.inviteCode" size="mini" clearable placeholder="请输入邀请码" style="width: 140px;"></el-input>
+			</template>
+
+			<template #slot-search-btn>
+				<el-button type="primary" size="mini" @click="doSearch">查询</el-button>
+				<el-button size="mini" @click="resetFilters">重置</el-button>
+			</template>
+
+			<template #slot-export-btn>
+				<el-button size="mini" @click="exportList">导出明细</el-button>
+			</template>
+
+			<template #table-column-memberPayAmount="{ scope }">
+				<span>¥ {{ moneyText(scope.row.memberPayAmount) }}</span>
+			</template>
+
+			<template #table-column-commissionAmount="{ scope }">
+				<span>¥ {{ moneyText(scope.row.commissionAmount) }}</span>
+			</template>
+
+			<template #table-column-settleStatus="{ scope }">
+				<el-tag size="mini" :type="settleTagType(scope.row.settleStatus)">{{ scope.row.settleStatusText }}</el-tag>
+			</template>
+
+			<template #slot-view-detail="{ scope }">
+				<el-button type="text" @click="goDetail(scope.row)">查看</el-button>
+			</template>
+
+			<template #slot-page-total>
+				<span class="page-total">当前页佣金合计 ¥ {{ moneyText(pageCommissionTotal) }}</span>
+			</template>
+		</x-crud>
+	</div>
+</template>
+
+<script>
+function getDefaultDateRange() {
+	const end = new Date();
+	const start = new Date();
+	start.setDate(start.getDate() - 29);
+	const fmt = d => {
+		const y = d.getFullYear();
+		const m = `${d.getMonth() + 1}`.padStart(2, '0');
+		const day = `${d.getDate()}`.padStart(2, '0');
+		return `${y}-${m}-${day}`;
+	};
+	return [fmt(start), fmt(end)];
+}
+
+export default {
+	name: 'shop-invite-commission-list',
+	data() {
+		const defaultRange = getDefaultDateRange();
+		return {
+			app: null,
+			summary: {
+				totalGenerated: 0,
+				settledAmount: 0,
+				inviterCount: 0
+			},
+			pageCommissionTotal: 0,
+			dateRange: defaultRange,
+			filters: {
+				inviterKeyword: '',
+				inviteeKeyword: '',
+				inviteCode: '',
+				startTime: defaultRange[0],
+				endTime: defaultRange[1],
+				searchTime: 'custom'
+			}
+		};
+	},
+	computed: {
+		summaryCards() {
+			return [
+				{ key: 'total', label: '产生佣金', value: `¥ ${this.moneyText(this.summary.totalGenerated)}` },
+				{ key: 'settled', label: '已结算佣金', value: `¥ ${this.moneyText(this.summary.settledAmount)}` },
+				{ key: 'count', label: '获佣批发商', value: `${this.summary.inviterCount || 0} 家` }
+			];
+		}
+	},
+	created() {
+		this.loadSummary();
+	},
+	methods: {
+		moneyText(val) {
+			const n = parseFloat(val);
+			return isNaN(n) ? '0.00' : n.toFixed(2);
+		},
+		settleTagType(status) {
+			if (Number(status) === 1) {
+				return 'success';
+			}
+			if (Number(status) === 2) {
+				return 'info';
+			}
+			return 'warning';
+		},
+		buildQuery(extra = {}) {
+			return {
+				...this.filters,
+				...extra
+			};
+		},
+		loadSummary() {
+			this.$service.shopInvite.commissionSummary(this.buildQuery()).then(res => {
+				if (res) {
+					this.summary = { ...this.summary, ...res };
+				}
+			});
+		},
+		onDateChange(val) {
+			if (val && val.length === 2) {
+				this.filters.startTime = val[0];
+				this.filters.endTime = val[1];
+				this.filters.searchTime = 'custom';
+			} else {
+				this.filters.startTime = '';
+				this.filters.endTime = '';
+				this.filters.searchTime = '';
+			}
+		},
+		doSearch() {
+			this.loadSummary();
+			if (this.app) {
+				this.app.refresh(this.buildQuery());
+			}
+		},
+		resetFilters() {
+			const defaultRange = getDefaultDateRange();
+			this.dateRange = defaultRange;
+			this.filters = {
+				inviterKeyword: '',
+				inviteeKeyword: '',
+				inviteCode: '',
+				startTime: defaultRange[0],
+				endTime: defaultRange[1],
+				searchTime: 'custom'
+			};
+			this.doSearch();
+		},
+		exportList() {
+			this.$service.shopInvite.commissionExport(this.buildQuery()).then(res => {
+				if (res && res.file) {
+					window.location.href = res.file;
+				} else {
+					this.$message.warning('导出失败');
+				}
+			});
+		},
+		goDetail(row) {
+			this.$router.push({
+				path: '/invite/shop-invite/commission-detail',
+				query: { id: row.id }
+			});
+		},
+		onLoad({ ctx, app }) {
+			this.app = app;
+			const pageFn = params => {
+				return this.$service.shopInvite.commissionList(this.buildQuery(params)).then(res => {
+					this.pageCommissionTotal = (res && res.pageCommissionTotal) || 0;
+					return res;
+				});
+			};
+			ctx.service({ page: pageFn })
+				.set('table', {
+					columns: [
+						{ prop: 'commissionSn', label: '分佣单号', align: 'center', 'min-width': 160 },
+						{ prop: 'inviterShopName', label: '批发商(获佣人)', align: 'center', 'min-width': 120 },
+						{ prop: 'inviteeShopName', label: '受邀花店', align: 'center', 'min-width': 120 },
+						{ prop: 'inviteCode', label: '邀请码', align: 'center', width: 120 },
+						{ prop: 'memberTypeName', label: '会员类型', align: 'center', width: 100 },
+						{ prop: 'memberPayAmount', label: '会员实付金额', align: 'center', width: 120 },
+						{ prop: 'commissionAmount', label: '产生佣金', align: 'center', width: 110 },
+						{ prop: 'settleStatus', label: '结算状态', align: 'center', width: 100 },
+						{ prop: 'settleTime', label: '结算时间', align: 'center', 'min-width': 150 }
+					],
+					op: {
+						visible: true,
+						props: { width: 80, align: 'center', fixed: 'right', label: '操作' },
+						layout: ['slot-view-detail']
+					}
+				})
+				.set('layout', [
+					['slot-date-filter', 'slot-inviter-filter', 'slot-invitee-filter', 'slot-code-filter', 'slot-search-btn', 'flex1', 'slot-export-btn', 'refresh-btn'],
+					['data-table'],
+					['slot-page-total', 'flex1', 'pagination']
+				])
+				.done();
+
+			app.refresh(this.buildQuery());
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.shop-invite-commission-list {
+	.summary-row {
+		display: flex;
+		margin-bottom: 16px;
+	}
+	.summary-card {
+		flex: 1;
+		background: #fff;
+		border-radius: 6px;
+		padding: 18px 20px;
+		border: 1px solid #ebeef5;
+	}
+	.summary-card + .summary-card {
+		margin-left: 16px;
+	}
+	.summary-label {
+		color: #909399;
+		font-size: 13px;
+		margin-bottom: 8px;
+	}
+	.summary-value {
+		font-size: 22px;
+		font-weight: 600;
+		color: #303133;
+	}
+	.page-total {
+		font-size: 14px;
+		font-weight: 600;
+		color: #303133;
+		line-height: 32px;
+	}
+}
+</style>

+ 419 - 0
ghs/src/views/shop-invite/ratio-manage.vue

@@ -0,0 +1,419 @@
+<!--
+  分成比例管理 — ghs Web 门店邀请分佣
+  Tab1 基础分佣只读展示 dict;Tab2 批发商独立比例 CRUD
+-->
+<template>
+	<div class="app-list-content shop-invite-ratio">
+		<el-tabs v-model="activeTab" type="border-card" class="ratio-tabs" @tab-click="handleTabClick">
+			<el-tab-pane label="基础分佣设置" name="basic">
+				<div class="basic-panel" v-loading="basicLoading">
+					<div class="panel-title">基础分佣设置</div>
+					<el-form label-width="140px" class="basic-form">
+						<el-form-item label="分佣功能">
+							<el-switch
+								:value="basicConfig.enabled === 1"
+								disabled
+								active-text="已开启"
+								inactive-text="已关闭"
+							></el-switch>
+						</el-form-item>
+						<el-form-item label="佣金计算基数">
+							<span class="readonly-text">{{ basicConfig.commissionBaseLabel || '会员实际支付金额' }}</span>
+						</el-form-item>
+						<el-form-item label="会员开通原价">
+							<span class="readonly-text">¥ {{ moneyText(basicConfig.price) }}</span>
+						</el-form-item>
+						<el-form-item label="默认分佣金额">
+							<span class="readonly-text">¥ {{ moneyText(basicConfig.commissionAmount) }}</span>
+							<span class="ratio-tip">(等效 {{ ratioText(basicConfig.commissionRatio) }})</span>
+						</el-form-item>
+						<el-form-item label="默认花店优惠">
+							<span class="readonly-text">¥ {{ moneyText(basicConfig.hdDiscountAmount) }}</span>
+							<span class="ratio-tip">(等效 {{ ratioText(basicConfig.discountRatio) }})</span>
+						</el-form-item>
+					</el-form>
+					<div class="info-banner">
+						未单独配置批发商时,统一按默认分佣金额计算佣金。全局配置修改请联系运维更新 dict.php 中的 hdRegisterConfig。
+					</div>
+				</div>
+			</el-tab-pane>
+			<el-tab-pane label="批发商独立比例设置" name="wholesaler"></el-tab-pane>
+		</el-tabs>
+
+		<!-- 仅在批发商 Tab 激活时挂载 x-crud,避免隐藏状态下表格高度计算为 0 -->
+		<div v-if="activeTab === 'wholesaler'" class="wholesaler-crud-panel">
+			<x-crud class="liu-crud-wrap" @load="onRatioLoad">
+				<template #slot-ratio-search>
+					<el-input
+						v-model="ratioKeyword"
+						placeholder="请输入批发商名称或手机号"
+						size="mini"
+						clearable
+						style="width: 240px;"
+						@keyup.enter.native="refreshRatioList"
+					></el-input>
+					<el-button size="mini" type="primary" style="margin-left: 10px;" @click="refreshRatioList">查询</el-button>
+				</template>
+
+				<template #slot-add-ratio>
+					<el-button type="primary" size="mini" @click="openRatioDialog()">新增比例</el-button>
+				</template>
+
+				<template #table-column-mobile="{ scope }">
+					<span>{{ scope.row.mobile || '-' }}</span>
+				</template>
+
+				<template #table-column-inviteCode="{ scope }">
+					<span>{{ scope.row.inviteCode || '-' }}</span>
+				</template>
+
+				<template #table-column-commissionAmount="{ scope }">
+					<span>¥ {{ moneyText(scope.row.commissionAmount) }}</span>
+				</template>
+
+				<template #table-column-hdDiscountAmount="{ scope }">
+					<span>¥ {{ moneyText(scope.row.hdDiscountAmount) }}</span>
+				</template>
+
+				<template #table-column-effectiveTime="{ scope }">
+					<span>{{ formatDate(scope.row.effectiveTime) }}</span>
+				</template>
+
+				<template #table-column-updateTime="{ scope }">
+					<span>{{ formatDateTime(scope.row.updateTime) }}</span>
+				</template>
+
+				<template #slot-column-option="{ scope }">
+					<el-button type="text" @click="openRatioDialog(scope.row)">编辑</el-button>
+				</template>
+			</x-crud>
+		</div>
+
+		<el-dialog
+			:title="ratioForm.id ? '编辑分成比例' : '新增分成比例'"
+			:visible.sync="ratioDialogVisible"
+			width="520px"
+			@closed="resetRatioForm"
+		>
+			<el-form ref="ratioFormRef" :model="ratioForm" :rules="ratioRules" label-width="120px">
+				<el-form-item label="批发商" prop="mainId">
+					<el-select
+						v-model="ratioForm.mainId"
+						filterable
+						remote
+						clearable
+						placeholder="请选择批发商"
+						:remote-method="searchWholesaler"
+						:loading="wholesalerLoading"
+						style="width: 100%;"
+						:disabled="!!ratioForm.id"
+						@focus="loadWholesalerOptions()"
+					>
+						<el-option
+							v-for="item in wholesalerOptions"
+							:key="item.mainId"
+							:label="`${item.shopName} (${item.mobile || '-'})`"
+							:value="item.mainId"
+						></el-option>
+					</el-select>
+				</el-form-item>
+				<el-form-item label="分佣金额" prop="commissionAmount">
+					<el-input-number
+						v-model="ratioForm.commissionAmount"
+						:min="0"
+						:precision="2"
+						:step="1"
+						style="width: 200px;"
+					></el-input-number>
+					<span class="ratio-tip">元</span>
+				</el-form-item>
+				<el-form-item label="花店优惠" prop="hdDiscountAmount">
+					<el-input-number
+						v-model="ratioForm.hdDiscountAmount"
+						:min="0"
+						:precision="2"
+						:step="1"
+						style="width: 200px;"
+					></el-input-number>
+					<span class="ratio-tip">元</span>
+				</el-form-item>
+			</el-form>
+			<div slot="footer">
+				<el-button @click="ratioDialogVisible = false">取消</el-button>
+				<el-button type="primary" :loading="ratioSaving" @click="submitRatio">确定</el-button>
+			</div>
+		</el-dialog>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'shop-invite-ratio-manage',
+	data() {
+		return {
+			activeTab: 'basic',
+			basicLoading: false,
+			basicConfig: {
+				enabled: 0,
+				price: 0,
+				commissionAmount: 0,
+				hdDiscountAmount: 0,
+				commissionRatio: 0,
+				discountRatio: 0,
+				commissionBaseLabel: '会员实际支付金额'
+			},
+			ratioApp: null,
+			ratioKeyword: '',
+			ratioDialogVisible: false,
+			ratioSaving: false,
+			wholesalerLoading: false,
+			wholesalerOptions: [],
+			ratioForm: {
+				id: 0,
+				mainId: '',
+				commissionAmount: 0,
+				hdDiscountAmount: 0
+			},
+			ratioRules: {
+				mainId: [{ required: true, message: '请选择批发商', trigger: 'change' }],
+				commissionAmount: [{ required: true, message: '请输入分佣金额', trigger: 'blur' }],
+				hdDiscountAmount: [{ required: true, message: '请输入花店优惠金额', trigger: 'blur' }]
+			}
+		};
+	},
+	created() {
+		this.loadBasicConfig();
+	},
+	methods: {
+		moneyText(val) {
+			const n = parseFloat(val);
+			return isNaN(n) ? '0.00' : n.toFixed(2);
+		},
+		formatDate(val) {
+			if (!val) {
+				return '-';
+			}
+			return String(val).slice(0, 10);
+		},
+		formatDateTime(val) {
+			if (!val) {
+				return '-';
+			}
+			const text = String(val).replace('T', ' ');
+			return text.length > 16 ? text.slice(0, 16) : text;
+		},
+		ratioText(val) {
+			const n = parseFloat(val);
+			return isNaN(n) ? '0.00%' : `${n.toFixed(2)}%`;
+		},
+		loadBasicConfig() {
+			this.basicLoading = true;
+			this.$service.shopInvite.registerConfig().then(res => {
+				if (res) {
+					this.basicConfig = { ...this.basicConfig, ...res };
+				}
+			}).finally(() => {
+				this.basicLoading = false;
+			});
+		},
+		handleTabClick(tab) {
+			// Tab 切换后重算表格高度(x-crud 内部 calcHeight 依赖可见容器)
+			if (tab.name === 'wholesaler') {
+				this.$nextTick(() => {
+					this.layoutRatioTable();
+				});
+			}
+		},
+		/** 批发商独立比例分页:合并搜索关键词并映射 x-crud 所需 pagination */
+		fetchRatioPage(params) {
+			return this.$service.shopInvite.wholesalerRatioList({
+				...params,
+				keyword: this.ratioKeyword
+			}).then(res => {
+				return {
+					list: Array.isArray(res.list) ? res.list : [],
+					pagination: {
+						page: parseInt(params.page || 1, 10),
+						size: parseInt(params.size || 20, 10),
+						total: parseInt(res.totalNum || 0, 10),
+						totalPage: parseInt(res.totalPage || 0, 10)
+					}
+				};
+			});
+		},
+		onRatioLoad({ ctx, app }) {
+			this.ratioApp = app;
+			ctx.service({ page: this.fetchRatioPage.bind(this) })
+				.set('table', {
+					// 固定表格可视高度,避免 calcHeight 在布局未稳定时算出 0
+					props: {
+						height: 520
+					},
+					columns: [
+						{ prop: 'shopName', label: '批发商', align: 'center', 'min-width': 140 },
+						{ prop: 'mobile', label: '联系手机号', align: 'center', 'min-width': 120 },
+						{ prop: 'inviteCode', label: '邀请码', align: 'center', 'min-width': 100 },
+						{ prop: 'commissionAmount', label: '分成金额', align: 'center', width: 110 },
+						{ prop: 'hdDiscountAmount', label: '花店优惠金额', align: 'center', width: 120 },
+						{ prop: 'effectiveTime', label: '生效时间', align: 'center', width: 110 },
+						{ prop: 'updateTime', label: '更新时间', align: 'center', 'min-width': 140 }
+					],
+					op: {
+						visible: true,
+						props: { width: 80, align: 'center', fixed: 'right', label: '操作' },
+						layout: ['slot-column-option']
+					}
+				})
+				.set('layout', [
+					['slot-ratio-search', 'flex1', 'slot-add-ratio', 'refresh-btn'],
+					['data-table'],
+					['flex1', 'pagination']
+				])
+				.on('refresh', async (params, { next, render }) => {
+					const res = await next({
+						...params,
+						keyword: this.ratioKeyword
+					});
+					render(Array.isArray(res.list) ? res.list : []);
+				})
+				.done();
+
+			// 仅在此处触发首次加载,避免与 Tab 切换重复 refresh 造成 refreshRd 竞态
+			this.$nextTick(() => {
+				app.refresh({ keyword: this.ratioKeyword });
+				this.layoutRatioTable();
+			});
+		},
+		refreshRatioList() {
+			if (!this.ratioApp) {
+				return;
+			}
+			this.ratioApp.refresh({ keyword: this.ratioKeyword });
+		},
+		/** 重算 x-crud 表格高度,解决 Tab/容器切换后 tbody 不显示 */
+		layoutRatioTable() {
+			const crud = this.ratioApp;
+			if (!crud || !crud.$refs || !crud.$refs.table) {
+				return;
+			}
+			const dataTable = crud.$refs.table;
+			if (dataTable && typeof dataTable.resize === 'function') {
+				dataTable.resize();
+			}
+		},
+		searchWholesaler(keyword) {
+			this.loadWholesalerOptions(keyword);
+		},
+		/** 加载批发商下拉:xhShop ptStyle=2,无关键词时返回默认列表 */
+		loadWholesalerOptions(keyword = '') {
+			const searchKey = typeof keyword === 'string' ? keyword.trim() : '';
+			this.wholesalerLoading = true;
+			this.$service.shopInvite.wholesalerSearch({ keyword: searchKey }).then(res => {
+				this.wholesalerOptions = (res && res.list) || [];
+			}).finally(() => {
+				this.wholesalerLoading = false;
+			});
+		},
+		openRatioDialog(row) {
+			if (row && row.mainId) {
+				this.ratioForm = {
+					id: row.id,
+					mainId: row.mainId,
+					commissionAmount: parseFloat(row.commissionAmount) || 0,
+					hdDiscountAmount: parseFloat(row.hdDiscountAmount) || 0
+				};
+				this.wholesalerOptions = [{
+					mainId: row.mainId,
+					shopName: row.shopName,
+					mobile: row.mobile
+				}];
+			} else {
+				this.resetRatioForm();
+				this.loadWholesalerOptions();
+			}
+			this.ratioDialogVisible = true;
+		},
+		resetRatioForm() {
+			this.ratioForm = {
+				id: 0,
+				mainId: '',
+				commissionAmount: parseFloat(this.basicConfig.commissionAmount) || 0,
+				hdDiscountAmount: parseFloat(this.basicConfig.hdDiscountAmount) || 0
+			};
+			this.wholesalerOptions = [];
+			if (this.$refs.ratioFormRef) {
+				this.$refs.ratioFormRef.clearValidate();
+			}
+		},
+		submitRatio() {
+			this.$refs.ratioFormRef.validate(valid => {
+				if (!valid) {
+					return;
+				}
+				this.ratioSaving = true;
+				this.$service.shopInvite.saveWholesalerRatio({
+					mainId: this.ratioForm.mainId,
+					commissionAmount: this.ratioForm.commissionAmount,
+					hdDiscountAmount: this.ratioForm.hdDiscountAmount
+				}).then(() => {
+					this.$message.success('保存成功');
+					this.ratioDialogVisible = false;
+					this.refreshRatioList();
+				}).finally(() => {
+					this.ratioSaving = false;
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.shop-invite-ratio {
+	min-height: calc(100vh - 120px);
+
+	.ratio-tabs {
+		background: #fff;
+	}
+
+	.wholesaler-crud-panel {
+		min-height: 520px;
+		margin-top: 12px;
+		background: #fff;
+		padding: 12px;
+		box-sizing: border-box;
+	}
+
+	.basic-panel {
+		padding: 20px 24px 30px;
+		max-width: 720px;
+	}
+
+	.panel-title {
+		font-size: 16px;
+		font-weight: 600;
+		margin-bottom: 20px;
+	}
+
+	.readonly-text {
+		color: #303133;
+	}
+
+	.ratio-tip {
+		margin-left: 8px;
+		color: #909399;
+		font-size: 12px;
+	}
+
+	.info-banner {
+		margin-top: 24px;
+		padding: 12px 16px;
+		background: #f0f9eb;
+		border: 1px solid #e1f3d8;
+		color: #67c23a;
+		border-radius: 4px;
+		line-height: 1.6;
+		font-size: 13px;
+	}
+}
+</style>