增加二级分销相关设置以及页面

This commit is contained in:
pikachu1995@126.com
2026-08-05 18:03:45 +08:00
parent 24f829033f
commit 4be36d61c7
37 changed files with 5961 additions and 904 deletions

View File

@@ -0,0 +1,600 @@
<template>
<div class="distribution-recruit" style="background-color: #fff; padding: 10px">
<el-form
ref="planFormRef"
:model="planForm"
label-width="160px"
label-position="right"
label-suffix=""
>
<el-divider content-position="left">招募设置</el-divider>
<el-form-item label="分销员加入条件">
<div class="join-condition-wrap">
<el-radio-group v-model="planForm.joinConditionType">
<el-radio value="NONE">无条件</el-radio>
<el-radio value="CONDITIONAL">有条件</el-radio>
</el-radio-group>
<div v-if="planForm.joinConditionType === 'CONDITIONAL'" class="join-condition-block">
<div class="join-condition-item">
<el-checkbox v-model="planForm.requirePurchaseGoods">需购买指定商品</el-checkbox>
<div v-if="planForm.requirePurchaseGoods" class="join-condition-detail">
<div class="form-desc">
请确保以下商品已上架若配置前已购买任一指定商品的用户则已经满足条件无需再次购买
</div>
<div class="goods-toolbar">
<a class="link-btn" @click.prevent="openGoodsSelect">选择商品</a>
</div>
<el-table
v-if="recruitGoodsList.length"
border
:data="recruitGoodsList"
class="recruit-goods-table"
>
<el-table-column label="商品图" width="90" align="center">
<template #default="{ row }">
<img
v-if="row.thumbnail"
:src="row.thumbnail"
alt="商品图"
class="recruit-goods-thumb"
/>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="200">
<template #default="{ row }">
<div class="recruit-goods-name">{{ row.goodsName }}</div>
</template>
</el-table-column>
<el-table-column prop="sn" label="商品编码" min-width="120" show-overflow-tooltip />
<el-table-column label="SKU信息" min-width="140" show-overflow-tooltip>
<template #default="{ row }">
{{ row.simpleSpecs ? row.simpleSpecs.trim() : "-" }}
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ $index }">
<a class="link-btn delete-btn" @click.prevent="removeRecruitGoods($index)">删除</a>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="join-condition-item">
<el-checkbox v-model="planForm.requireSelfPurchaseAmount">
自购金额满
<el-input-number
v-model="planForm.minSelfPurchaseAmount"
:min="0.01"
:precision="2"
:disabled="!planForm.requireSelfPurchaseAmount"
controls-position="right"
class="inline-number"
/>
</el-checkbox>
<div class="form-desc join-condition-desc">
客户支付后即计入自购金额自购金额最低设置为0.01
</div>
</div>
<div class="join-condition-item">
<el-checkbox v-model="planForm.requireConsumeCount">
消费笔数满
<el-input-number
v-model="planForm.minConsumeCount"
:min="1"
:precision="0"
:disabled="!planForm.requireConsumeCount"
controls-position="right"
class="inline-number"
/>
</el-checkbox>
<div class="form-desc join-condition-desc">
客户支付后即计入消费笔数消费笔数最低设置为1笔
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="分销员申请方式" required>
<div class="apply-mode-block">
<el-radio-group v-model="planForm.applyMode">
<el-radio value="MANUAL">手动申请</el-radio>
<el-radio value="AUTO">满足条件自动申请</el-radio>
</el-radio-group>
<div v-if="planForm.applyMode === 'MANUAL'" class="manual-apply-extra">
<el-checkbox v-model="planForm.requireApplyInfo">需要填写申请信息</el-checkbox>
<div class="form-desc">申请人需填写对应字段方便审核分销员信息</div>
<template v-if="planForm.requireApplyInfo">
<div class="recruit-field-toolbar">
<a class="link-btn" @click.prevent="addRecruitField">+ 添加字段</a>
</div>
<div
v-for="(field, index) in recruitFields"
:key="field._key"
class="recruit-field-row"
>
<div class="recruit-field-row-main">
<span class="field-label">字段{{ index + 1 }}</span>
<el-input
v-model="field.fieldName"
placeholder="如:姓名"
maxlength="64"
style="width: 200px"
/>
<el-select v-model="field.fieldType" style="width: 140px; margin-left: 8px">
<el-option
v-for="item in fieldTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<a class="link-btn delete-btn" @click.prevent="removeRecruitField(index)">删除字段</a>
</div>
<div class="recruit-field-row-required">
<el-radio-group v-model="field.required">
<el-radio :value="true">必填</el-radio>
<el-radio :value="false">选填</el-radio>
</el-radio-group>
</div>
</div>
</template>
</div>
</div>
</el-form-item>
<el-form-item label="审核方式">
<el-radio-group v-model="planForm.auditMode">
<el-radio value="MANUAL">人工审核</el-radio>
<el-radio value="AUTO_PASS">无需审核满足条件自动通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="展示线上招募入口">
<el-radio-group v-model="planForm.onlineEntryVisible">
<el-radio :value="true">展示</el-radio>
<el-radio :value="false">不展示</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="招募海报">
<div class="recruit-poster-block">
<div v-if="planForm.posterBackground" class="poster-preview-wrap">
<img :src="planForm.posterBackground" alt="招募海报" class="poster-thumb" />
</div>
<upload-pic-input v-model="planForm.posterBackground" :show-input="false" />
</div>
</el-form-item>
<el-form-item label="分销推广服务协议">
<editor v-if="editorReady" ref="agreementEditor" open-xss v-model="planForm.agreementContent" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="planSaveLoading" @click="savePlan">保存</el-button>
</el-form-item>
</el-form>
<sku-select ref="skuSelect" @selectedGoodsData="selectedGoodsData" />
</div>
</template>
<script>
import {
getCurrentRecruitPlan,
saveRecruitPlan,
} from "@/api/distribution";
import uploadPicInput from "@/components/lili/upload-pic-input";
import tinymec from "@/components/editor/index.vue";
import skuSelect from "@/components/lili-dialog";
const MAX_RECRUIT_FIELDS = 5;
const FIELD_TYPE_OPTIONS = [
{ value: "NAME", label: "姓名格式" },
{ value: "MOBILE", label: "手机号格式" },
{ value: "IMAGE", label: "图片格式" },
{ value: "GENDER", label: "性别格式" },
];
const buildDefaultPlan = () => ({
id: "",
planVersion: null,
publishStatus: false,
joinConditionType: "NONE",
applyMode: "MANUAL",
auditMode: "MANUAL",
onlineEntryVisible: true,
requireApplyInfo: false,
agreementContent: "",
requirePurchaseGoods: false,
requireSelfPurchaseAmount: false,
requireConsumeCount: false,
minSelfPurchaseAmount: 0.01,
minConsumeCount: 1,
goodsIds: [],
posterEnabled: false,
posterTitle: "",
posterBenefitText: "",
posterRuleText: "",
posterContact: "",
posterBackground: "",
});
const buildDefaultField = () => ({
_key: `field_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
fieldName: "",
fieldType: "NAME",
required: true,
placeholder: "",
});
export default {
name: "distributionRecruit",
components: { uploadPicInput, editor: tinymec, skuSelect },
data() {
return {
planSaveLoading: false,
editorReady: true,
planForm: buildDefaultPlan(),
recruitFields: [],
recruitGoodsList: [],
fieldTypeOptions: FIELD_TYPE_OPTIONS,
};
},
mounted() {
this.loadPlan();
},
watch: {
"planForm.applyMode"(value) {
if (value !== "MANUAL") {
this.planForm.requireApplyInfo = false;
this.recruitFields = [];
}
},
"planForm.requireApplyInfo"(value) {
if (!value) {
this.recruitFields = [];
}
},
"planForm.joinConditionType"(value) {
if (value !== "CONDITIONAL") {
this.planForm.requirePurchaseGoods = false;
this.planForm.requireSelfPurchaseAmount = false;
this.planForm.requireConsumeCount = false;
this.recruitGoodsList = [];
}
},
"planForm.requirePurchaseGoods"(value) {
if (!value) {
this.recruitGoodsList = [];
}
},
"planForm.requireSelfPurchaseAmount"(value) {
if (value && (!this.planForm.minSelfPurchaseAmount || this.planForm.minSelfPurchaseAmount < 0.01)) {
this.planForm.minSelfPurchaseAmount = 0.01;
}
},
"planForm.requireConsumeCount"(value) {
if (value && (!this.planForm.minConsumeCount || this.planForm.minConsumeCount < 1)) {
this.planForm.minConsumeCount = 1;
}
},
},
methods: {
normalizeFields(fields) {
if (!fields || !fields.length) {
return [];
}
return fields.map((item, index) => ({
...buildDefaultField(),
...item,
_key: item._key || item.id || `field_${index}_${Date.now()}`,
required: item.required !== false,
}));
},
addRecruitField() {
if (this.recruitFields.length >= MAX_RECRUIT_FIELDS) {
this.$Message.warning("最多添加5个字段");
return;
}
this.recruitFields.push(buildDefaultField());
},
removeRecruitField(index) {
this.recruitFields.splice(index, 1);
},
validateRecruitFields() {
if (!this.planForm.requireApplyInfo) {
return true;
}
if (!this.recruitFields.length) {
this.$Message.warning("请至少添加一个申请字段");
return false;
}
for (let i = 0; i < this.recruitFields.length; i++) {
const field = this.recruitFields[i];
if (!field.fieldName || !field.fieldName.trim()) {
this.$Message.warning(`请填写字段${i + 1}名称`);
return false;
}
}
return true;
},
validateJoinConditions() {
if (this.planForm.joinConditionType !== "CONDITIONAL") {
return true;
}
const {
requirePurchaseGoods,
requireSelfPurchaseAmount,
requireConsumeCount,
} = this.planForm;
if (!requirePurchaseGoods && !requireSelfPurchaseAmount && !requireConsumeCount) {
this.$Message.warning("请至少选择一个加入条件");
return false;
}
if (requirePurchaseGoods && !this.recruitGoodsList.length) {
this.$Message.warning("请选择指定商品");
return false;
}
if (requireSelfPurchaseAmount && this.planForm.minSelfPurchaseAmount < 0.01) {
this.$Message.warning("自购金额最低设置为0.01元");
return false;
}
if (requireConsumeCount && this.planForm.minConsumeCount < 1) {
this.$Message.warning("消费笔数最低设置为1笔");
return false;
}
return true;
},
openGoodsSelect() {
this.$refs.skuSelect.open("goods");
const data = this.recruitGoodsList.map((item) => ({
id: item.skuId,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
price: item.price,
quantity: item.quantity,
small: item.thumbnail,
}));
this.$refs.skuSelect.goodsData = data;
},
selectedGoodsData(items) {
this.recruitGoodsList = (items || []).map((item) => ({
skuId: item.id,
goodsId: item.goodsId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
price: item.price,
quantity: item.quantity,
thumbnail: item.small || item.thumbnail || "",
}));
},
removeRecruitGoods(index) {
this.recruitGoodsList.splice(index, 1);
},
buildSavePayload() {
const fields = this.planForm.requireApplyInfo
? this.recruitFields.map((field, index) => ({
id: field.id,
fieldName: field.fieldName.trim(),
fieldType: field.fieldType,
required: field.required !== false,
placeholder: field.placeholder || "",
sortOrder: index,
}))
: [];
return {
...this.planForm,
id: this.planForm.id || undefined,
publishStatus: true,
posterEnabled: !!(this.planForm.posterBackground && this.planForm.posterBackground.trim()),
recruitGoods: this.planForm.requirePurchaseGoods
? this.recruitGoodsList.map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn,
simpleSpecs: item.simpleSpecs,
thumbnail: item.thumbnail,
price: item.price,
}))
: [],
goodsIds: this.planForm.requirePurchaseGoods
? this.recruitGoodsList.map((item) => item.goodsId)
: [],
fields,
};
},
applyPlanResult(result) {
this.planForm = { ...buildDefaultPlan(), ...result };
this.recruitFields = this.normalizeFields(result.fields);
this.recruitGoodsList = (result.recruitGoods || []).map((item) => ({
goodsId: item.goodsId,
skuId: item.skuId,
goodsName: item.goodsName,
sn: item.sn || "",
simpleSpecs: item.simpleSpecs || "",
thumbnail: item.thumbnail,
price: item.price,
}));
},
loadPlan() {
getCurrentRecruitPlan().then((res) => {
if (res.success && res.result) {
this.applyPlanResult(res.result);
}
});
},
savePlan() {
if (!this.validateRecruitFields() || !this.validateJoinConditions()) {
return;
}
this.planSaveLoading = true;
saveRecruitPlan(this.buildSavePayload())
.then((res) => {
if (res.success) {
this.$Message.success("保存成功");
if (res.result) {
this.applyPlanResult(res.result);
} else {
this.loadPlan();
}
}
})
.finally(() => {
this.planSaveLoading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.apply-mode-block {
width: 100%;
}
.manual-apply-extra {
margin-top: 12px;
.form-desc {
margin-top: 8px;
margin-left: 24px;
}
}
.recruit-field-toolbar {
margin: 12px 0 8px 24px;
}
.link-btn {
color: #2d8cf0;
cursor: pointer;
user-select: none;
}
.recruit-field-row {
margin: 12px 0 0 24px;
padding-bottom: 12px;
border-bottom: 1px dashed #e8eaec;
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
}
.recruit-field-row-main {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.field-label {
min-width: 56px;
color: #606266;
}
.delete-btn {
margin-left: 8px;
}
.recruit-field-row-required {
margin-top: 10px;
padding-left: 56px;
}
.form-desc {
margin-top: 8px;
color: #909399;
font-size: 12px;
line-height: 1.6;
}
.join-condition-wrap {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.join-condition-block {
margin-top: 12px;
padding: 16px;
background: #f8f8f9;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
.join-condition-item + .join-condition-item {
margin-top: 16px;
}
.join-condition-detail {
margin-top: 8px;
margin-left: 24px;
}
.join-condition-desc {
margin-top: 8px;
margin-left: 24px;
}
.goods-toolbar {
margin: 8px 0;
}
.recruit-goods-table {
margin-top: 8px;
:deep(.el-table__cell) {
vertical-align: top;
}
:deep(.el-table__cell .cell) {
white-space: normal;
line-height: 1.5;
}
}
.recruit-goods-name {
white-space: normal;
word-break: break-all;
line-height: 1.5;
}
.recruit-goods-thumb {
display: block;
width: 56px;
height: 56px;
margin: 4px auto;
object-fit: contain;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.inline-number {
width: 120px;
margin: 0 6px;
vertical-align: middle;
}
.recruit-poster-block {
width: 100%;
max-width: 520px;
}
.poster-preview-wrap {
margin-bottom: 12px;
}
.poster-thumb {
display: block;
max-width: 240px;
max-height: 240px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
</style>