This commit is contained in:
qsh
2026-02-07 11:26:49 +08:00
parent 5c5d3d8598
commit 3943c5a70e
6 changed files with 259 additions and 215 deletions

View File

@@ -2,8 +2,17 @@ import request from '@/utils/request';
export const getRulesInfo = params => {
return request({
url: '/applet/xunjia/tenant/get',
url: '/applet/xunjia/profit/rule/person/list',
method: 'get',
params
});
};
// 保存规则
export const saveRulesInfo = data => {
return request({
url: '/applet/xunjia/profit/rule/batch/save',
method: 'post',
data
});
};

19
src/api/member/code.js Normal file
View File

@@ -0,0 +1,19 @@
import request from '@/utils/request';
// 生成会员码
export const createVipCode = async data => {
return await request({
url: '/applet/xunjia/member/code/create',
method: 'post',
data
});
};
// 获取会员码列表
export const getVipCodeList = async params => {
return await request({
url: '/applet/xunjia/member/code/page',
method: 'get',
params
});
};

37
src/api/member/quota.js Normal file
View File

@@ -0,0 +1,37 @@
import request from '@/utils/request';
// 获得租户每月会员额度配置
export const getQuotaInfoWithMonth = async params => {
return await request({
url: '/applet/xunjia/tenant/get-memberNum',
method: 'get',
params: params
});
};
// 保存会员额度分配
export const saveQuotaInfo = async data => {
return await request({
url: '/applet/xunjia/member/quota/save',
method: 'post',
data
});
};
// 获得个人会员额度分配
export const getQuotaInfoWithUser = async params => {
return await request({
url: '/applet/xunjia/member/quota/person/get',
method: 'get',
params: params
});
};
// 获得会员额度分配列表
export const getQuotaList = async params => {
return await request({
url: '/applet/xunjia/member/quota/list',
method: 'get',
params: params
});
};

View File

@@ -40,14 +40,7 @@
<view class="quota-item">
<view class="quota-label">月度赠会员总额度</view>
<view class="quota-control">
<input
v-model="totalQuota"
type="number"
class="quota-input"
placeholder="请输入总额度"
min="0"
max="1000"
/>
<view class="quota-value">{{ totalQuota }}</view>
<view class="quota-unit"></view>
</view>
</view>
@@ -68,17 +61,17 @@
class="quota-item"
>
<view class="distributor-info">
<view class="distributor-name">{{ distributor.name }}</view>
<view class="distributor-id">ID: {{ distributor.id }}</view>
<view class="distributor-name">{{ distributor.distributorName }}</view>
<view class="distributor-id">ID: {{ distributor.distributorId }}</view>
</view>
<view class="quota-control">
<input
v-model="distributor.quota"
v-model="distributor.num"
type="number"
class="quota-input"
placeholder="0"
min="0"
:max="maxDistributorQuota"
:max="maxDistributorQuota(distributor.num)"
/>
<view class="quota-unit"></view>
</view>
@@ -104,10 +97,11 @@
<script setup>
import { ref, computed, onMounted } from "vue"
import { getQuotaInfoWithUser, saveQuotaInfo, getQuotaList } from "@/api/member/quota"
// 当前月份
const currentMonth = ref('2026-01')
const currentMonthDisplay = ref('2026年01月')
const currentMonth = ref(new Date().toISOString().substring(0, 7))
const currentMonthDisplay = ref(new Date().toISOString().substring(0, 7))
// 总额度
const totalQuota = ref(100)
@@ -136,15 +130,26 @@
}
])
// 计算单个分销员最大可分配额度
const maxDistributorQuota = computed(() => {
return Math.floor(totalQuota.value * 0.5)
const loadCurrentMonthQuota = () => {
getQuotaInfoWithUser({
year: currentMonth.value.split('-')[0],
month: currentMonth.value.split('-')[1]
}).then(res => {
totalQuota.value = res.data.num || 0
})
getQuotaList({
year: currentMonth.value.split('-')[0],
month: currentMonth.value.split('-')[1]
}).then(res => {
distributors.value = res.data || []
})
}
// 计算已分配额度
const allocatedQuota = computed(() => {
return distributors.value.reduce((sum, distributor) => {
return sum + parseInt(distributor.quota || 0)
return sum + parseInt(distributor.num || 0)
}, 0)
})
@@ -153,6 +158,10 @@
return totalQuota.value - allocatedQuota.value
})
const maxDistributorQuota = (count) => {
return remainingQuota.value ? count + remainingQuota.value : count
}
// 返回上一页
function goBack() {
uni.navigateBack({ delta: 1 })
@@ -162,9 +171,8 @@
function onMonthChange(e) {
currentMonth.value = e.detail.value
const date = new Date(e.detail.value)
currentMonthDisplay.value = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}`
// 实际项目中应根据选择的月份加载对应的额度配置
// loadQuotaByMonth(currentMonth.value)
currentMonthDisplay.value = new Date(date).toISOString().substring(0, 7)
loadCurrentMonthQuota()
}
// 保存配额
@@ -187,22 +195,26 @@
uni.showLoading({ title: '保存中...' })
setTimeout(() => {
saveQuotaInfo({
year: currentMonth.value.split('-')[0],
month: currentMonth.value.split('-')[1],
nums: distributors.value.map(distributor => {
return {
distributorId: distributor.distributorId,
num: distributor.num
}
})
}).then(() => {
uni.hideLoading()
uni.showToast({
title: '保存成功',
icon: 'success'
})
// 实际项目中应调用接口保存配额配置
// saveQuotaConfig()
}, 1500)
})
}
onMounted(() => {
// 实际项目中应从接口获取分销员列表和当前月份的配额配置
// loadDistributors()
// loadCurrentMonthQuota()
loadCurrentMonthQuota()
})
</script>

View File

@@ -31,11 +31,11 @@
</view>
<view class="rule-card">
<!-- 注册分润 -->
<view class="rule-item" v-for="(rule, idx) in profitRules" :key="rule.id || idx">
<view class="rule-item" v-for="(rule, idx) in profitRules" :key="idx">
<view class="rule-header">
<view class="rule-title">
{{ rule.type === 'register' ? '注册分润' : '消费分润' }}
<view v-if="rule.type === 'consume'" class="rule-actions">
{{ rule.profitScene== '1' ? '注册分润' : '消费分润' }}
<view v-if="rule.profitScene== '2'" class="rule-actions">
<view class="delete-btn" @click="deleteConsumeRule(idx)">
<text style="color: #f56c6c; font-size: 24rpx;">删除</text>
</view>
@@ -44,9 +44,13 @@
<view class="rule-toggle">
<view class="rule-control">
<radio-group @change="e => onProfitTypeChange(idx, e.detail.value)">
<label v-for="(option, index) in profitTypeOptions" :key="index" class="radio-item">
<radio :value="option.value" :checked="rule.profitType === option.value" />
<text class="radio-label">{{ option.label }}</text>
<label class="radio-item">
<radio value="1" :checked="rule.profitType== '1'" />
<text class="radio-label">固定金额</text>
</label>
<label class="radio-item" v-if="rule.profitScene== '2'">
<radio value="2" :checked="rule.profitType== '2'" />
<text class="radio-label">比例分润</text>
</label>
</radio-group>
</view>
@@ -57,7 +61,7 @@
<view class="rule-label">分润金额</view>
<view class="rule-control">
<input
v-model="rule.amount"
v-model="rule.profitValue"
type="number"
class="rule-input"
placeholder="请输入分润金额"
@@ -72,7 +76,7 @@
<view class="rule-label">分润比例</view>
<view class="rule-control">
<input
v-model="rule.percentage"
v-model="rule.profitValue"
type="number"
class="rule-input"
placeholder="请输入分润比例"
@@ -85,13 +89,13 @@
</view>
<!-- 消费分润VIP选择 -->
<view v-if="rule.type === 'consume'">
<view v-if="rule.profitScene == '2'">
<view class="rule-label" style="margin-bottom: 10rpx;">选择会员类型</view>
<view class="rule-control">
<view class="vip-list">
<label v-for="vip in vipList" :key="vip.memberId" class="vip-item checkbox-item"
@click="onVipChange(idx, vip.memberId)">
<view class="custom-checkbox" :class="{ 'checked': rule.selectedVips && rule.selectedVips.includes(vip.memberId), 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }"></view>
<view class="custom-checkbox" :class="{ 'checked': rule.memberIds && rule.memberIds.includes(vip.memberId), 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }"></view>
<view class="checkbox-label" :class="{ 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }">
<view v-if="vip.carName" class="car-tag">{{ vip.carName }}</view>
<text>{{ vip.memberName || '' }}</text>
@@ -115,41 +119,50 @@
</template>
<script setup>
import { ref, onMounted } from "vue"
import { ref } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { getVipTypeList } from "@/api/member"
import { getRulesInfo, saveRulesInfo } from "@/api/distribution"
// 分润规则数组
const profitRules = ref([
{
type: 'register', // 注册分润
profitScene: '1', // 注册分润
profitType: '1', // 固定金额
amount: 10,
percentage: 0
profitValue: 0
},
{
id: Date.now(), // 唯一标识
type: 'consume', // 消费分润
profitScene: '2', // 消费分润
profitType: '2', // 比例分润
amount: 0,
percentage: 20,
selectedVips: [] // 选中的VIP
profitValue: 20,
memberIds: [] // 选中的VIP
}
])
const vipList = ref([])
const vipList = ref([])
// 分润方式选项
const profitTypeOptions = [{ label: '固定金额', value: '1' }, { label: '比例分润', value: '2' }]
const distributorId = ref('')
onMounted(() => {
// 实际项目中应从接口获取分润规则配置
// loadProfitRules()
onLoad((options) => {
distributorId.value = options.id
loadProfitRules(distributorId.value )
getVipTypeList().then(res => {
vipList.value = res.data?.list?.filter(item => item.useTypes.includes(1)) || []
})
})
// 加载规则
function loadProfitRules() {
getRulesInfo({
distributorId: distributorId.value
}).then(res => {
profitRules.value = res.data || []
// 初始化消费分润的会员选中状态
})
}
// 返回上一页
function goBack() {
uni.navigateBack({ delta: 1 })
@@ -162,17 +175,16 @@ const vipList = ref([])
content: '确定要保存分润规则配置吗?',
success: function(res) {
if (res.confirm) {
uni.showLoading({ title: '保存中...' })
setTimeout(() => {
uni.hideLoading()
saveRulesInfo({
distributorId: distributorId.value,
ruleList: profitRules.value
}).then(res => {
uni.showToast({
title: '保存成功',
icon: 'success'
})
// 实际项目中应调用接口保存分润规则
// saveProfitRules()
}, 1500)
goBack()
})
}
}
})
@@ -184,10 +196,10 @@ const vipList = ref([])
if (rule) {
rule.profitType = profitType
// 重置对应的值
if (profitType === '1') {
rule.percentage = 0
} else if (profitType === '2') {
rule.amount = 0
if (profitType== '1') {
rule.profitValue = 0
} else if (profitType== '2') {
rule.profitValue = 0
}
}
}
@@ -198,15 +210,15 @@ const vipList = ref([])
id: Date.now(), // 唯一标识
type: 'consume', // 消费分润
profitType: '2', // 比例分润
amount: 0,
percentage: 20,
selectedVips: [] // 选中的VIP
profitValue: 0,
profitValue: 20,
memberIds: [] // 选中的VIP
})
}
// 删除消费分润规则
function deleteConsumeRule(idx) {
if (profitRules.value[idx].type === 'consume') {
if (profitRules.value[idx].type== 'consume') {
profitRules.value.splice(idx, 1)
}
}
@@ -214,9 +226,9 @@ const vipList = ref([])
// 检查会员类型是否在其他消费分润规则中被选中
function isVipSelectedInOtherRules(currentIdx, vipId) {
return profitRules.value.some((otherRule, otherIdx) => {
return otherIdx !== currentIdx &&
otherRule.type === 'consume' &&
otherRule.selectedVips.includes(vipId)
return otherIdx != currentIdx &&
otherRule.profitScene == '2' &&
otherRule.memberIds.includes(vipId)
})
}
@@ -225,20 +237,20 @@ const vipList = ref([])
if (profitRules.value[idx]) {
const rule = profitRules.value[idx]
// 确保selectedVips存在
if (!rule.selectedVips) {
rule.selectedVips = []
if (!rule.memberIds) {
rule.memberIds = []
}
const vipIndex = rule.selectedVips.indexOf(vipId)
const vipIndex = rule.memberIds.indexOf(vipId)
if (vipIndex > -1) {
// 取消选择
rule.selectedVips.splice(vipIndex, 1)
rule.memberIds.splice(vipIndex, 1)
} else {
// 检查是否在其他消费分润规则中已选中
const isDuplicate = isVipSelectedInOtherRules(idx, vipId)
if (!isDuplicate) {
rule.selectedVips.push(vipId)
rule.memberIds.push(vipId)
} else {
uni.showToast({
title: '该会员类型已在其他规则中选中',

View File

@@ -6,8 +6,7 @@
<view class="back-icon"></view>
</view>
<view class="header-title">赠送会员管理</view>
<view class="header-right" @click="generateBatchCode">
<view class="batch-btn">批量生成</view>
<view class="header-right">
</view>
</view>
@@ -20,11 +19,12 @@
<view class="generate-control">
<picker
:range="memberTypeOptions"
:range-key="'memberName'"
:value="memberTypeIndex"
@change="onMemberTypeChange"
class="picker"
>
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex] }}</view>
<view class="picker-text">{{ memberTypeOptions[memberTypeIndex]?.memberName || '请选择' }}</view>
</picker>
</view>
</view>
@@ -41,17 +41,6 @@
/>
</view>
</view>
<view class="generate-row">
<view class="generate-label">有效期</view>
<view class="generate-control">
<input
v-model="validityPeriod"
class="period-input"
placeholder="请选择有效期"
@click="showDatePicker"
/>
</view>
</view>
<view class="generate-action">
<view class="action-btn primary-btn" @click="generateCode">
生成核验码
@@ -71,25 +60,25 @@
>
<view class="code-header">
<view class="code-value">{{ code.code }}</view>
<view class="code-status" :class="code.statusClass">{{ code.status }}</view>
<view class="code-status" :class="getStatusClass(code)">{{ getStatusText(code) }}</view>
</view>
<view class="code-body">
<view class="code-info">
<view class="info-item">
<view class="info-label">会员类型</view>
<view class="info-value">{{ code.memberType }}</view>
<view class="info-value">{{ getMemberName(code.memberId) }}</view>
</view>
<view class="info-item">
<view class="info-label">生成时间</view>
<view class="info-value">{{ code.createTime }}</view>
<view class="info-value">{{ new Date(code.createTime).toLocaleString() }}</view>
</view>
<view class="info-item">
<view class="info-label">有效期至</view>
<view class="info-value">{{ code.validUntil }}</view>
</view>
<view class="info-item" v-if="code.usedTime">
<view class="info-item" v-if="code.writeOffTime">
<view class="info-label">使用时间</view>
<view class="info-value">{{ code.usedTime }}</view>
<view class="info-value">{{ new Date(code.writeOffTime).toLocaleString() }}</view>
</view>
<view class="info-item" v-if="code.writeOffUser">
<view class="info-label">使用人</view>
<view class="info-value">{{ code.writeOffUser }}</view>
</view>
</view>
</view>
@@ -98,9 +87,6 @@
<view class="action-btn copy-btn" @click="copyCode(code.code)">
复制
</view>
<view class="action-btn delete-btn" @click="deleteCode(code.id)">
删除
</view>
</view>
</view>
</view>
@@ -108,7 +94,7 @@
</view>
<!-- 空状态 -->
<view v-if="codeList.length === 0" class="empty-state">
<view v-if="codeList.length == 0" class="empty-state">
<view class="empty-icon">🎁</view>
<view class="empty-text">暂无核验码数据</view>
</view>
@@ -121,14 +107,14 @@
<view class="page-controls">
<view
class="page-btn"
:class="{ disabled: currentPage === 1 }"
:class="{ disabled: currentPage == 1 }"
@click="goToPage(currentPage - 1)"
>
上一页
</view>
<view
class="page-btn"
:class="{ disabled: currentPage === totalPages }"
:class="{ disabled: currentPage == totalPages }"
@click="goToPage(currentPage + 1)"
>
下一页
@@ -140,52 +126,23 @@
<script setup>
import { ref, computed, onMounted } from "vue"
import { createVipCode, getVipCodeList } from "@/api/member/code"
import { getVipTypeList } from "@/api/member"
// 会员类型选项
const memberTypeOptions = ['月度会员', '季度会员', '年度会员']
const memberTypeOptions = ref([])
const memberTypeIndex = ref(0)
// 生成数量
const generateCount = ref('1')
// 有效期
const validityPeriod = ref('')
// 核验码列表数据
const codeList = ref([
{
id: 1,
code: 'GC20260129001',
memberType: '月度会员',
createTime: '2026-01-29 15:30:00',
validUntil: '2026-02-29 23:59:59',
status: '未使用',
statusClass: 'status-unused'
},
{
id: 2,
code: 'GC20260129002',
memberType: '季度会员',
createTime: '2026-01-29 14:20:00',
validUntil: '2026-04-29 23:59:59',
status: '已使用',
statusClass: 'status-used',
usedTime: '2026-01-29 14:30:00'
},
{
id: 3,
code: 'GC20260129003',
memberType: '年度会员',
createTime: '2026-01-29 13:10:00',
validUntil: '2027-01-29 23:59:59',
status: '已过期',
statusClass: 'status-expired'
}
])
const codeList = ref([])
// 分页信息
const currentPage = ref(1)
const totalCodes = ref(100)
const totalCodes = ref(0)
const pageSize = ref(10)
// 计算总页数
@@ -194,67 +151,39 @@
})
onMounted(() => {
// 实际项目中应从接口获取核验码列表
// loadCodeList()
getVipTypeList().then(res => {
if (res.code == 0) {
memberTypeOptions.value = res.data.list
}
})
loadCodeList()
})
// 加载核验码列表
function loadCodeList() {
getVipCodeList({
pageNo: currentPage.value,
pageSize: pageSize.value
}).then(res => {
if (res.code == 0) {
codeList.value = res.data.list
totalCodes.value = res.data.total
}
})
}
// 返回上一页
function goBack() {
uni.navigateBack({ delta: 1 })
}
// 批量生成
function generateBatchCode() {
uni.showModal({
title: '批量生成核验码',
content: '请输入要生成的核验码数量',
editable: true,
placeholderText: '请输入数量',
success: function(res) {
if (res.confirm && res.content) {
const count = parseInt(res.content)
if (isNaN(count) || count <= 0 || count > 100) {
uni.showToast({
title: '请输入1-100之间的数字',
icon: 'none'
})
return
}
uni.showLoading({ title: '生成中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: `成功生成${count}个核验码`,
icon: 'success'
})
// 实际项目中应调用接口批量生成核验码
// generateBatchCodes(count)
}, 1500)
}
}
})
}
// 会员类型变更
function onMemberTypeChange(e) {
const value = e.detail.value
memberTypeIndex.value = value
}
// 显示日期选择器
function showDatePicker() {
const now = new Date()
uni.showDatePicker({
year: now.getFullYear(),
month: now.getMonth(),
day: now.getDate(),
success: function(res) {
validityPeriod.value = `${res.year}-${res.month + 1}-${res.day}`
}
})
}
// 生成核验码
function generateCode() {
@@ -266,25 +195,22 @@
return
}
if (!validityPeriod.value) {
uni.showToast({
title: '请选择有效期',
icon: 'none'
})
return
}
uni.showLoading({ title: '生成中...' })
setTimeout(() => {
createVipCode({
memberId: memberTypeOptions.value[memberTypeIndex.value].memberId,
num: parseInt(generateCount.value),
}).then(res => {
if (res.code == 0) {
uni.hideLoading()
uni.showToast({
title: '核验码生成成功',
icon: 'success'
})
// 实际项目中应调用接口生成核验码
// generateVerificationCode()
}, 1500)
loadCodeList()
}
})
}
// 复制核验码
@@ -308,7 +234,7 @@
success: function(res) {
if (res.confirm) {
// 实际项目中应调用接口删除核验码
const index = codeList.value.findIndex(item => item.id === id)
const index = codeList.value.findIndex(item => item.id == id)
if (index !== -1) {
codeList.value.splice(index, 1)
}
@@ -330,6 +256,34 @@
// 实际项目中应加载指定页码的核验码
// loadCodePage(page)
}
// 获取状态文本
function getStatusText(code) {
if (code.isExpired == 1) {
return '已过期'
} else if (code.isWrittenOff == 1) {
return '已使用'
} else {
return '未使用'
}
}
// 获取状态样式类
function getStatusClass(code) {
if (code.isExpired == 1) {
return 'status-expired'
} else if (code.isWrittenOff == 1) {
return 'status-used'
} else {
return 'status-unused'
}
}
// 根据memberId获取会员类型名称
function getMemberName(memberId) {
const memberType = memberTypeOptions.value.find(item => item.memberId == memberId)
return memberType?.memberName || '未知'
}
</script>
<style lang="scss" scoped>
@@ -363,6 +317,7 @@
height: 120rpx;
background-color: #fff;
padding: 0 32rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
@@ -547,7 +502,7 @@
.info-label {
color: #606266;
width: 120rpx;
width: 140rpx;
}
.info-value {