mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-06 17:09:08 +08:00
feat(member): 优化等级权益管理与买家展示
- 优化客户权益配置、编辑表单及独立发放明细页面 - 完善客户等级图标、背景配置提示与 PC 权益展示 - 复用图片选择器并补齐素材元数据回传
This commit is contained in:
51
buyer/src/pages/home/memberCenter/MemberBenefitPanel.vue
Normal file
51
buyer/src/pages/home/memberCenter/MemberBenefitPanel.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<section class="benefits-panel">
|
||||
<h3>会员权益</h3>
|
||||
<div class="grade-options">
|
||||
<button v-for="item in enabledGrades" :key="item.grade.id" :class="{active: selectedId === item.grade.id}" @click="selectedId = item.grade.id">
|
||||
{{ item.grade.gradeName }}<small v-if="item.grade.id === currentGradeId">当前等级</small>
|
||||
</button>
|
||||
</div>
|
||||
<p class="benefit-subtitle">{{ selected?.grade?.gradeName || '暂无等级' }} · 点击查看权益说明</p>
|
||||
<el-empty v-if="!selected?.benefits?.length" description="暂无权益" />
|
||||
<div v-else class="benefits-grid">
|
||||
<button v-for="benefit in selected.benefits" :key="benefit.id" class="benefit-card" :class="{locked: !owned(benefit)}" @click="detail = benefit">
|
||||
<img v-if="benefit.benefitLogo" :src="benefit.benefitLogo" alt="" />
|
||||
<strong>{{ benefit.benefitName }}</strong>
|
||||
<span>{{ owned(benefit) ? '已享有' : '该等级专享' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<el-dialog :model-value="!!detail" :title="detail?.benefitName || '权益说明'" width="520px" @close="detail = null">
|
||||
<template v-if="detail">
|
||||
<p>{{ owned(detail) ? '当前等级已享有该权益' : '此权益属于所选等级,当前尚未享有' }}</p>
|
||||
<p v-if="detail.benefitType === 'POINT_MULTIPLIER'">购物积分倍率:{{ config(detail).multiplier || 1 }} 倍,同类权益取最高值。</p>
|
||||
<p v-if="detail.benefitType === 'GIFT_POINT'">首次达到等级赠送 {{ config(detail).giftPoint || 0 }} 积分。</p>
|
||||
<div class="benefit-description" v-html="detail.benefitDesc || '暂无说明'" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
const props = defineProps({gradeDetails:{type:Array,default:()=>[]},currentGradeId:{type:String,default:''}})
|
||||
const selectedId = ref(''), detail = ref(null)
|
||||
const enabledGrades = computed(()=>props.gradeDetails.filter(item=>item.grade?.gradeState === 'OPEN'))
|
||||
const selected = computed(()=>enabledGrades.value.find(item=>item.grade.id === selectedId.value))
|
||||
const current = computed(()=>enabledGrades.value.find(item=>item.grade.id === props.currentGradeId))
|
||||
const owned = benefit => (current.value?.benefits || []).some(item=>item.id === benefit.id)
|
||||
function config(benefit) { try { return JSON.parse(benefit.benefitConfig || '{}') } catch { return {} } }
|
||||
watch([()=>props.currentGradeId,enabledGrades],()=>{
|
||||
if(!selectedId.value || !enabledGrades.value.some(item=>item.grade.id===selectedId.value)) selectedId.value=props.currentGradeId || enabledGrades.value[0]?.grade.id || ''
|
||||
},{immediate:true})
|
||||
</script>
|
||||
<style scoped>
|
||||
.benefits-panel { margin:20px 0; padding:24px; background:#fff; border:1px solid #eee; border-radius:8px; }
|
||||
h3 { margin:0 0 20px; font-size:18px; } .grade-options { display:flex; flex-wrap:wrap; gap:10px; }
|
||||
.grade-options button { padding:10px 18px; border:1px solid #ddd; background:#fff; border-radius:6px; cursor:pointer; }
|
||||
.grade-options button.active { background:#fff8eb; border-color:#c99a47; color:#866021; } small { margin-left:8px; }
|
||||
.benefit-subtitle { color:#777; margin:20px 0; }.benefits-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:16px; }
|
||||
.benefit-card { padding:20px 10px; display:flex; align-items:center; flex-direction:column; gap:12px; border:1px solid #f0e3cb; background:#fffcf6; border-radius:8px; cursor:pointer; }
|
||||
.benefit-card img { width:48px; height:48px; object-fit:contain; }.benefit-card span { font-size:12px; color:#986d2a; }
|
||||
.benefit-card.locked { background:#fafafa; border-color:#eee; color:#888; }.locked img { filter:grayscale(1); opacity:.5; }
|
||||
.benefit-description { line-height:1.8; overflow-wrap:anywhere; }.benefit-description :deep(img) { max-width:100%; }
|
||||
</style>
|
||||
@@ -25,6 +25,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MemberBenefitPanel :grade-details="gradeDetails" :current-grade-id="String(currentGradeId)" />
|
||||
|
||||
<div class="grade-panel">
|
||||
<div class="grade-tabs">
|
||||
<div class="grade-tab" :class="{ active: activeGradeTab === 'rules' }" @click="activeGradeTab = 'rules'">获取经验值方式</div>
|
||||
@@ -87,6 +89,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import MemberBenefitPanel from './MemberBenefitPanel.vue'
|
||||
import { memberGradeCurrent, memberGradeList, memberGradeRules } from '@/api/member.js'
|
||||
|
||||
defineOptions({ name: 'MemberGrade' })
|
||||
|
||||
@@ -239,3 +239,6 @@ export const deleteMemberBenefit = (id) => {
|
||||
export const getMemberBenefitTypes = () => {
|
||||
return getRequest("/member/benefit/types");
|
||||
};
|
||||
|
||||
export const getMemberBenefitDeliveries = params => getRequest('/member/benefit/deliveries', params);
|
||||
export const retryMemberBenefitDelivery = id => postRequest('/member/benefit/deliveries/' + id + '/retry');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- 使用 fullscreen 类来控制是否全屏显示 -->
|
||||
<div :class="{ fullscreen: fullscreen }" class="tinymce-container">
|
||||
<!-- 使用 tinymce-textarea 类作为编辑器的文本区域 -->
|
||||
<uploadImage @callback="insertImage" />
|
||||
<uploadImage :use-image-picker="imagePicker" @callback="insertImage" />
|
||||
<textarea :id="tinymceId" class="tinymce-textarea" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,10 +21,15 @@ import {
|
||||
} from "vue";
|
||||
import { initEditor } from "@/components/editor/config";
|
||||
import uploadImage from "@/components/editor/upload-image.vue";
|
||||
import { useFormItem } from "element-plus";
|
||||
|
||||
defineOptions({ name: "Tinymce" });
|
||||
|
||||
const props = defineProps({
|
||||
imagePicker: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "",
|
||||
@@ -39,6 +44,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input"]);
|
||||
const { formItem } = useFormItem();
|
||||
|
||||
// 引入编辑器的配置(data 中的 initEditor 与 import 同名,直接使用 import)
|
||||
const hasChange = ref(false); // 标记内容是否有更改
|
||||
@@ -66,6 +72,9 @@ watch(editorContent, (val) => {
|
||||
function emitContent(content) {
|
||||
emit("update:modelValue", content);
|
||||
emit("input", content);
|
||||
if (props.imagePicker && content) {
|
||||
nextTick(() => formItem?.validate("change").catch(() => {}));
|
||||
}
|
||||
}
|
||||
// 数据返回并给富文本框插入图片
|
||||
function insertImage(arr) {
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="showOssManager"
|
||||
title="选择图片"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
:z-index="3600"
|
||||
@@ -94,6 +95,7 @@ import { Message } from "@/utils/message";
|
||||
import OssManage from "@/views/sys/oss-manage/ossManage.vue";
|
||||
|
||||
defineOptions({ name: "upload-image" });
|
||||
const props = defineProps({ useImagePicker: { type: Boolean, default: false } });
|
||||
|
||||
const emit = defineEmits(["callback"]);
|
||||
|
||||
@@ -120,6 +122,10 @@ function draggableItemKey(item) {
|
||||
return item?.url || item;
|
||||
}
|
||||
function handleClickUploadImage() {
|
||||
if (props.useImagePicker) {
|
||||
importOSS();
|
||||
return;
|
||||
}
|
||||
show.value = true;
|
||||
}
|
||||
function handleView(url) {
|
||||
@@ -158,7 +164,16 @@ function handleUploadError(err) {
|
||||
Message.error(err?.message || String(err));
|
||||
}
|
||||
function confirmUrls() {
|
||||
applySelectedImages();
|
||||
if (props.useImagePicker) {
|
||||
const urls = [...new Set(selectedImage.value.map(parseOssSelectionUrl).filter(Boolean))];
|
||||
if (!urls.length) {
|
||||
Message.warning("请选择图片");
|
||||
return;
|
||||
}
|
||||
emit("callback", urls);
|
||||
} else {
|
||||
applySelectedImages();
|
||||
}
|
||||
showOssManager.value = false;
|
||||
}
|
||||
function cancelOssImport() {
|
||||
|
||||
@@ -105,6 +105,7 @@ const props = defineProps({
|
||||
default: false,
|
||||
},
|
||||
maxlength: Number,
|
||||
beforeSelect: Function,
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "input", "on-change"]);
|
||||
const attrs = useAttrs();
|
||||
@@ -131,6 +132,7 @@ function handleCLickImg(val, index) {
|
||||
});
|
||||
}
|
||||
function handleOssCallback(item) {
|
||||
if (item?.url && props.beforeSelect && props.beforeSelect(item) === false) return;
|
||||
picModalFlag.value = false;
|
||||
if (!item?.url) return;
|
||||
currentValue.value = item.url;
|
||||
|
||||
@@ -224,6 +224,12 @@ export const otherRouter = {
|
||||
name: "member-benefit",
|
||||
component: () => import("@/views/member/benefit/index.vue")
|
||||
},
|
||||
{
|
||||
path: "member-benefit-deliveries",
|
||||
title: "权益发放明细",
|
||||
name: "member-benefit-deliveries",
|
||||
component: () => import("@/views/member/benefit/BenefitDeliveryRecords.vue")
|
||||
},
|
||||
{
|
||||
path: "member-grade-experience",
|
||||
title: "等级权益设置",
|
||||
@@ -236,7 +242,7 @@ export const otherRouter = {
|
||||
name: "member-grade-experience-log",
|
||||
component: () => import("@/views/member/grade/experience-log.vue")
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
path: "goods/goods-info/goodsDetail",
|
||||
|
||||
40
manager/src/views/member/benefit/BenefitConfigFields.vue
Normal file
40
manager/src/views/member/benefit/BenefitConfigFields.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<el-form-item v-if="form.benefitType === 'POINT_MULTIPLIER'" label="积分倍率" required>
|
||||
<div class="number-field"><el-input-number v-model="form.multiplier" :min="1" :max="100" :precision="2" controls-position="right" /><span>倍</span></div>
|
||||
<div class="hint">购物积分基数 × 倍率,同类权益取最高倍率。</div>
|
||||
</el-form-item>
|
||||
<div v-if="['CUSTOM', 'GRADE_BADGE', 'EXCLUSIVE_CUSTOMER_SERVICE'].includes(form.benefitType)" class="rule-description">{{ typeDescription }}</div>
|
||||
<template v-if="form.benefitType === 'COUPON_PACKAGE'">
|
||||
<el-form-item label="发券规则" required>
|
||||
<el-radio-group v-model="form.grantRule">
|
||||
<el-radio value="ONCE">发放一次</el-radio>
|
||||
<el-radio value="MONTHLY">每月发放</el-radio>
|
||||
<el-radio value="PERIOD">按周期发放</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.grantRule === 'MONTHLY'" label="发放日期" required>
|
||||
<div class="number-field"><el-input-number v-model="form.grantDay" :min="1" :max="31" :precision="0" controls-position="right" /><span>号</span></div>
|
||||
<div class="hint">当月不存在指定日期时,取当月最后一天。</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.grantRule === 'PERIOD'" label="发放周期" required>
|
||||
<div class="number-field"><el-input-number v-model="form.periodMonths" :min="1" :max="12" :precision="0" controls-position="right" /><span>个月</span></div>
|
||||
<div class="hint">以首次达到等级的日期为起点,首期立即发放。</div>
|
||||
</el-form-item>
|
||||
<div class="rule-description coupon-description">最多选择 10 张平台优惠券,每券赠送 1~10 张。仅发放当前周期,暂停期间未生成的历史周期不追发。</div>
|
||||
</template>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps({ form: { type: Object, required: true } });
|
||||
const typeDescription = computed(() => ({
|
||||
GRADE_BADGE: '展示会员的等级徽章。请在下方配置权益图标和介绍,并在客户等级中关联使用。',
|
||||
EXCLUSIVE_CUSTOMER_SERVICE: '展示会员专属客服权益。会员可通过商城现有客服入口联系,具体服务内容请在权益介绍中说明。',
|
||||
CUSTOM: '展示自定义会员权益。请在权益介绍中说明适用条件、参与方式及服务内容。',
|
||||
}[props.form.benefitType] || ''));
|
||||
</script>
|
||||
<style scoped>
|
||||
.number-field { display: flex; align-items: center; gap: 10px; }
|
||||
.hint { width: 100%; margin-top: 6px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
.rule-description { padding: 12px 16px; color: var(--el-text-color-regular); background: #f6f8fb; border-radius: 6px; font-size: 13px; line-height: 22px; }
|
||||
.coupon-description { margin-bottom: 20px; }
|
||||
</style>
|
||||
60
manager/src/views/member/benefit/BenefitDeliveryRecords.vue
Normal file
60
manager/src/views/member/benefit/BenefitDeliveryRecords.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="search benefit-delivery-page">
|
||||
<el-card shadow="never">
|
||||
<el-form :model="query" inline class="search-form" label-width="70px" @submit.prevent @keyup.enter="search">
|
||||
<el-form-item label="会员ID"><el-input v-model="query.memberId" placeholder="请输入会员ID" clearable style="width:240px" /></el-form-item>
|
||||
<el-form-item label="状态"><el-select v-model="query.status" clearable placeholder="全部状态" style="width:240px">
|
||||
<el-option label="成功" value="SUCCESS" /><el-option label="失败" value="FAILED" /><el-option label="待发放" value="PENDING" />
|
||||
</el-select></el-form-item>
|
||||
<el-form-item><el-button type="primary" class="search-btn" @click="search">搜索</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mt_10">
|
||||
<template #header><div class="records-header"><span>权益发放明细</span><el-button @click="router.push({ name: 'member-benefit' })">返回客户权益</el-button></div></template>
|
||||
<el-table :data="rows" v-loading="loading" border>
|
||||
<el-table-column prop="memberId" label="会员ID" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="benefitName" label="权益" min-width="130" />
|
||||
<el-table-column prop="sourceRef" label="来源等级ID" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="cycleKey" label="发放周期" min-width="130" />
|
||||
<el-table-column label="状态" width="100"><template #default="{row}">{{ statuses[row.status] || row.status }}</template></el-table-column>
|
||||
<el-table-column prop="attempts" label="尝试次数" width="90" />
|
||||
<el-table-column prop="errorMessage" label="失败原因" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="updateTime" label="更新时间" width="170" />
|
||||
<el-table-column label="操作" width="90"><template #default="{row}"><el-button v-if="row.status === 'FAILED'" link type="primary" :loading="retrying === row.id" @click="retry(row)">重试</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="query.pageNumber" :total="total" :page-size="query.pageSize" layout="total, prev, pager, next" @current-change="load" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { getMemberBenefitDeliveries, retryMemberBenefitDelivery } from '@/api/member';
|
||||
defineOptions({ name: 'memberBenefitDeliveries' });
|
||||
const router = useRouter();
|
||||
const loading = ref(false), rows = ref([]), total = ref(0), retrying = ref('');
|
||||
const query = reactive({memberId:'',status:'',pageNumber:1,pageSize:20});
|
||||
const statuses = {SUCCESS:'成功',FAILED:'失败',PENDING:'待发放'};
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try { const res = await getMemberBenefitDeliveries({ ...query }); if(res.success) { rows.value = res.result.records || []; total.value = res.result.total || 0; } }
|
||||
catch { ElMessage.error('发放明细加载失败,请重新搜索'); }
|
||||
finally { loading.value = false; }
|
||||
}
|
||||
function search() { query.pageNumber = 1; load(); }
|
||||
function reset() { query.memberId = ''; query.status = ''; search(); }
|
||||
async function retry(row) {
|
||||
try { await ElMessageBox.confirm('按原发放配置补发此权益,系统会检查会员当前资格。', '重试权益发放'); }
|
||||
catch { return; }
|
||||
retrying.value = row.id;
|
||||
try { const res = await retryMemberBenefitDelivery(row.id); if(res.success) ElMessage.success('补发成功'); await load(); }
|
||||
catch { ElMessage.error('补发失败,请稍后重试'); }
|
||||
finally { retrying.value = ''; }
|
||||
}
|
||||
onMounted(load);
|
||||
</script>
|
||||
<style scoped>
|
||||
.records-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||||
.el-pagination { margin-top: 20px; justify-content: flex-end; }
|
||||
</style>
|
||||
129
manager/src/views/member/benefit/BenefitFormFields.vue
Normal file
129
manager/src/views/member/benefit/BenefitFormFields.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<section class="benefit-section">
|
||||
<div class="benefit-section-heading"><h3>基本信息</h3></div>
|
||||
<div class="benefit-form-grid">
|
||||
<el-form-item label="权益名称" prop="benefitName">
|
||||
<el-input v-model="form.benefitName" maxlength="50" show-word-limit placeholder="请输入权益名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权益类型" prop="benefitType">
|
||||
<el-select v-model="form.benefitType" :disabled="editing" :clearable="!editing" placeholder="请选择权益类型" @change="$emit('type-change', $event)">
|
||||
<el-option v-for="item in typeOptions" :key="item.value" :value="item.value" :label="item.description" />
|
||||
</el-select>
|
||||
<div v-if="editing" class="benefit-field-hint">权益创建后不支持修改类型</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="form.benefitType" class="benefit-section">
|
||||
<div class="benefit-section-heading"><h3>权益规则</h3><span>{{ typeLabel }}</span></div>
|
||||
<el-form-item v-if="form.benefitType === 'GIFT_POINT'" label="赠送积分" prop="giftPoint" required>
|
||||
<div class="benefit-number-field">
|
||||
<el-input-number v-model="form.giftPoint" :min="0" :max="99999" :precision="0" controls-position="right" placeholder="请输入积分数量" />
|
||||
<span class="benefit-input-unit">积分</span>
|
||||
</div>
|
||||
<div class="benefit-field-hint">首次达到关联等级时赠送,范围为 0~99999 积分。</div>
|
||||
</el-form-item>
|
||||
|
||||
<BenefitConfigFields :form="form" />
|
||||
|
||||
<el-form-item v-if="form.benefitType === 'COUPON_PACKAGE'" label="赠送优惠券" prop="couponPackageRows" required class="benefit-coupon-form-item">
|
||||
<div class="benefit-coupon-field">
|
||||
<div class="benefit-coupon-toolbar">
|
||||
<span>已选择 <strong>{{ form.couponPackageRows.length }}</strong> / 10 张优惠券</span>
|
||||
<el-button type="primary" plain @click="$emit('select-coupons', which)">添加优惠券</el-button>
|
||||
</div>
|
||||
<el-table border size="small" class="benefit-coupon-table" :data="form.couponPackageRows" empty-text="暂未选择优惠券,请点击右上方添加">
|
||||
<el-table-column prop="couponName" label="优惠券名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="有效期" min-width="200"><template #default="{ row }"><span v-if="row" v-html="row.validRange || '-'" /></template></el-table-column>
|
||||
<el-table-column prop="faceValueLabel" label="面额" min-width="95" />
|
||||
<el-table-column label="赠送张数" width="135"><template #default="{ row, $index }">
|
||||
<el-input-number v-if="row" :model-value="row.quantity" :min="1" :max="10" :precision="0" controls-position="right" style="width: 104px" @change="value => $emit('quantity-change', which, $index, value)" />
|
||||
</template></el-table-column>
|
||||
<el-table-column label="操作" width="70" align="center"><template #default="{ row, $index }">
|
||||
<el-button v-if="row" link type="danger" @click="$emit('remove-coupon', which, $index)">删除</el-button>
|
||||
</template></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="benefit-section">
|
||||
<div class="benefit-section-heading"><h3>展示内容</h3><span>用于会员端权益展示</span></div>
|
||||
<el-form-item label="权益图标" prop="benefitLogo">
|
||||
<div class="benefit-logo-field">
|
||||
<div class="benefit-logo-preview">
|
||||
<el-image v-if="form.benefitLogo" :src="form.benefitLogo" fit="contain" :preview-src-list="[form.benefitLogo]" preview-teleported>
|
||||
<template #error><span class="benefit-logo-placeholder">图片不可用</span></template>
|
||||
</el-image>
|
||||
<span v-else class="benefit-logo-placeholder">图标预览</span>
|
||||
</div>
|
||||
<BenefitLogoInput v-model="form.benefitLogo" class="benefit-logo-upload" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="权益介绍" prop="benefitDesc">
|
||||
<RichEditor v-model="form.benefitDesc" image-picker height="300px" class="benefit-rich-editor" />
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="benefit-section">
|
||||
<div class="benefit-section-heading"><h3>发布设置</h3></div>
|
||||
<div class="benefit-form-grid">
|
||||
<el-form-item label="启用状态" prop="benefitState">
|
||||
<el-radio-group v-model="form.benefitState"><el-radio value="OPEN">开启</el-radio><el-radio value="CLOSE">关闭</el-radio></el-radio-group>
|
||||
<div class="benefit-field-hint">关闭后,会员端隐藏该权益并停止后续发放。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示排序" prop="benefitSort">
|
||||
<el-input-number v-model="form.benefitSort" :min="1" :max="9999" :precision="0" controls-position="right" />
|
||||
<div class="benefit-field-hint">数字越小越靠前,范围为 1~9999。</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import BenefitConfigFields from './BenefitConfigFields.vue';
|
||||
import BenefitLogoInput from './BenefitLogoInput.vue';
|
||||
import RichEditor from '@/components/editor/index.vue';
|
||||
const props = defineProps({
|
||||
form: { type: Object, required: true },
|
||||
typeOptions: { type: Array, default: () => [] },
|
||||
editing: { type: Boolean, default: false },
|
||||
which: { type: String, required: true },
|
||||
});
|
||||
defineEmits(['type-change', 'select-coupons', 'quantity-change', 'remove-coupon']);
|
||||
const typeLabel = computed(() => props.typeOptions.find(item => item.value === props.form.benefitType)?.description || '');
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.benefit-section { padding: 24px; border: 1px solid var(--el-border-color-lighter); border-radius: 8px; background: #fff; }
|
||||
.benefit-section + .benefit-section { margin-top: 16px; }
|
||||
.benefit-section-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 22px; }
|
||||
.benefit-section-heading h3 { margin: 0; color: var(--el-text-color-primary); font-size: 15px; font-weight: 600; line-height: 22px; }
|
||||
.benefit-section-heading span { color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
.benefit-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
|
||||
.benefit-field-hint { width: 100%; margin-top: 6px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
.benefit-section :deep(.el-form-item) { margin-bottom: 22px; }
|
||||
.benefit-section > :deep(.el-form-item:last-child), .benefit-form-grid > :deep(.el-form-item) { margin-bottom: 0; }
|
||||
.benefit-section :deep(.el-form-item__label) { padding: 0; margin-bottom: 8px; font-weight: 500; line-height: 22px; }
|
||||
.benefit-section :deep(.el-form-item__content) { min-width: 0; }
|
||||
.benefit-section :deep(.el-select) { width: 100%; }
|
||||
.benefit-section :deep(.el-input-number) { width: 180px; max-width: 100%; }
|
||||
.benefit-number-field { display: flex; gap: 10px; align-items: center; }
|
||||
.benefit-input-unit { color: var(--el-text-color-regular); }
|
||||
.benefit-coupon-field, .benefit-rich-editor { width: 100%; min-width: 0; }
|
||||
.benefit-coupon-toolbar { display: flex; gap: 12px; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 12px; color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
.benefit-coupon-toolbar strong { color: var(--el-color-primary); font-weight: 500; }
|
||||
.benefit-coupon-table { border-radius: 6px; overflow: hidden; }
|
||||
.benefit-logo-field { display: flex; align-items: center; gap: 20px; width: 100%; }
|
||||
.benefit-logo-preview { width: 88px; height: 88px; flex: 0 0 88px; display: flex; align-items: center; justify-content: center; border: 1px dashed var(--el-border-color); border-radius: 8px; background-color: #fafbfc; overflow: hidden; }
|
||||
.benefit-logo-preview :deep(.el-image) { width: 64px; height: 64px; }
|
||||
.benefit-logo-placeholder { color: var(--el-text-color-placeholder); font-size: 12px; }
|
||||
.benefit-logo-upload { flex: 1; min-width: 0; }
|
||||
@media (max-width: 640px) {
|
||||
.benefit-section { padding: 18px 16px; }
|
||||
.benefit-form-grid { grid-template-columns: minmax(0, 1fr); gap: 22px; }
|
||||
.benefit-logo-field { flex-direction: column; align-items: flex-start; gap: 12px; }
|
||||
.benefit-logo-upload { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
30
manager/src/views/member/benefit/BenefitLogoInput.vue
Normal file
30
manager/src/views/member/benefit/BenefitLogoInput.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="logo-input">
|
||||
<UploadPicInput :model-value="modelValue" :before-select="validateSelection" placeholder="请选择图标或粘贴图片地址" @update:model-value="updateLogo" />
|
||||
<span class="hint">透明PNG,建议100×100像素,上传大小不超过10KB。</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import UploadPicInput from '@/components/lili/upload-pic-input.vue';
|
||||
import { nextTick } from 'vue';
|
||||
import { ElMessage, useFormItem } from 'element-plus';
|
||||
defineProps({modelValue:{type:String,default:''}});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const { formItem } = useFormItem();
|
||||
function validateSelection({ file }) {
|
||||
if (!file || file.fileType !== 'image/png' || Number(file.fileSize) > 10240) {
|
||||
ElMessage.error('请选择不超过10KB的PNG图标');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async function updateLogo(value) {
|
||||
emit('update:modelValue', value);
|
||||
await nextTick();
|
||||
formItem?.validate('change').catch(() => {});
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.logo-input { width: 100%; min-width: 0; }
|
||||
.hint { display: block; margin-top: 8px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
</style>
|
||||
@@ -3,10 +3,38 @@
|
||||
<div class="search member-benefit">
|
||||
|
||||
<el-card>
|
||||
<el-form
|
||||
:model="searchForm"
|
||||
inline
|
||||
label-width="70px"
|
||||
class="search-form"
|
||||
@submit.prevent
|
||||
@keyup.enter="searchBenefits"
|
||||
>
|
||||
<el-form-item label="权益名称" prop="keyword">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="请输入权益名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="state">
|
||||
<el-select v-model="searchForm.state" placeholder="全部状态" clearable style="width: 240px">
|
||||
<el-option label="开启" value="OPEN" />
|
||||
<el-option label="关闭" value="CLOSE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="search-btn" @click="searchBenefits">搜索</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="operation padding-row benefit-actions">
|
||||
<el-card>
|
||||
<div class="operation padding-row">
|
||||
<el-button type="primary" @click="openAdd">添加权益设置</el-button>
|
||||
<el-button @click="getData">刷新</el-button>
|
||||
<el-button type="primary" @click="router.push({ name: 'member-benefit-deliveries' })">发放明细</el-button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -31,7 +59,7 @@
|
||||
|
||||
<!-- 权益ID -->
|
||||
|
||||
<el-table-column prop="id" label="权益ID" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="id" label="权益ID" width="100" align="center" show-overflow-tooltip />
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +111,7 @@
|
||||
|
||||
<!-- 权益编码 -->
|
||||
|
||||
<el-table-column prop="benefitCode" label="权益编码" min-width="140" show-overflow-tooltip />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -161,32 +189,6 @@
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="pagination-wrap mt_10">
|
||||
|
||||
<el-pagination
|
||||
|
||||
v-model:current-page="searchForm.pageNumber"
|
||||
|
||||
v-model:page-size="searchForm.pageSize"
|
||||
|
||||
:total="total"
|
||||
|
||||
:page-sizes="[20, 30, 50]"
|
||||
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
|
||||
size="small"
|
||||
|
||||
@current-change="changePage"
|
||||
|
||||
@size-change="changePageSize"
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
|
||||
|
||||
@@ -240,483 +242,53 @@
|
||||
|
||||
|
||||
<el-drawer
|
||||
|
||||
v-model="addFlag"
|
||||
|
||||
title="添加权益设置"
|
||||
|
||||
size="1120px"
|
||||
|
||||
direction="rtl"
|
||||
|
||||
:z-index="950"
|
||||
|
||||
:close-on-click-modal="false"
|
||||
|
||||
append-to-body
|
||||
|
||||
destroy-on-close
|
||||
|
||||
class="benefit-form-drawer"
|
||||
|
||||
v-model="addFlag" title="添加权益设置" size="1040px" direction="rtl"
|
||||
:z-index="950" :close-on-click-modal="false" append-to-body destroy-on-close class="benefit-form-drawer"
|
||||
>
|
||||
|
||||
<el-form ref="addForm" :model="formAdd" :rules="rulesAdd" label-width="110px">
|
||||
|
||||
<el-form-item label="权益类型" prop="benefitType">
|
||||
|
||||
<el-select
|
||||
|
||||
v-model="formAdd.benefitType"
|
||||
|
||||
clearable
|
||||
|
||||
placeholder="请选择权益类型"
|
||||
|
||||
style="width: 100%"
|
||||
|
||||
@change="onAddBenefitTypeChange"
|
||||
|
||||
>
|
||||
|
||||
<el-option
|
||||
|
||||
v-for="item in benefitTypeOptions"
|
||||
|
||||
:key="item.value"
|
||||
|
||||
:value="item.value"
|
||||
|
||||
:label="item.description"
|
||||
|
||||
/>
|
||||
|
||||
</el-select>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
|
||||
v-show="formAdd.benefitType === 'GIFT_POINT'"
|
||||
|
||||
label="赠送积分"
|
||||
|
||||
prop="giftPoint"
|
||||
|
||||
:required="formAdd.benefitType === 'GIFT_POINT'"
|
||||
|
||||
>
|
||||
|
||||
<el-input-number
|
||||
|
||||
v-model="formAdd.giftPoint"
|
||||
|
||||
:min="0"
|
||||
|
||||
:max="99999"
|
||||
|
||||
:precision="0"
|
||||
|
||||
style="width: 220px"
|
||||
|
||||
placeholder="必填,范围 0-99999"
|
||||
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
|
||||
v-show="formAdd.benefitType === 'COUPON_PACKAGE'"
|
||||
|
||||
class="benefit-coupon-form-item"
|
||||
|
||||
label="赠送优惠券"
|
||||
|
||||
prop="couponPackageRows"
|
||||
|
||||
:required="formAdd.benefitType === 'COUPON_PACKAGE'"
|
||||
|
||||
>
|
||||
|
||||
<div class="benefit-coupon-field">
|
||||
|
||||
<el-button plain class="mb_10" @click="openCouponPicker('add')">添加优惠券</el-button>
|
||||
|
||||
<el-table
|
||||
|
||||
border
|
||||
|
||||
size="small"
|
||||
|
||||
class="benefit-coupon-table"
|
||||
|
||||
:data="formAdd.couponPackageRows"
|
||||
|
||||
empty-text="请添加优惠券"
|
||||
|
||||
>
|
||||
|
||||
<el-table-column prop="couponName" label="优惠券名称" min-width="180" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="有效期" min-width="280">
|
||||
|
||||
<template #default="{ row }">
|
||||
|
||||
<span v-if="row" v-html="row.validRange || '-'" />
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="faceValueLabel" label="面额" min-width="100" />
|
||||
|
||||
<el-table-column label="赠送张数" width="150">
|
||||
|
||||
<template #default="{ row, $index }">
|
||||
|
||||
<el-input-number
|
||||
|
||||
v-if="row"
|
||||
|
||||
:model-value="row.quantity"
|
||||
|
||||
:min="1"
|
||||
|
||||
:max="10"
|
||||
|
||||
:precision="0"
|
||||
|
||||
style="width: 100px"
|
||||
|
||||
@change="(val) => onCouponQuantityChange('add', $index, val)"
|
||||
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
|
||||
<template #default="{ row, $index }">
|
||||
|
||||
<a v-if="row" class="link-text" @click="removeCouponRow('add', $index)">删除</a>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益名称" prop="benefitName">
|
||||
|
||||
<el-input v-model="formAdd.benefitName" maxlength="50" placeholder="请输入权益名称" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益LOGO" prop="benefitLogo">
|
||||
|
||||
<div class="benefit-logo-field">
|
||||
|
||||
<div v-if="formAdd.benefitLogo" class="benefit-logo-thumb">
|
||||
|
||||
<img :src="formAdd.benefitLogo" alt="" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="benefit-logo-upload">
|
||||
|
||||
<upload-pic-input v-model="formAdd.benefitLogo" placeholder="请上传权益 LOGO 图片地址" style="width: 100%" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益介绍" prop="benefitDesc">
|
||||
|
||||
<el-input v-model="formAdd.benefitDesc" type="textarea" :rows="4" maxlength="500" placeholder="请输入权益介绍" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态" prop="benefitState">
|
||||
|
||||
<el-radio-group v-model="formAdd.benefitState">
|
||||
|
||||
<el-radio value="OPEN">开启</el-radio>
|
||||
|
||||
<el-radio value="CLOSE">关闭</el-radio>
|
||||
|
||||
</el-radio-group>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序" prop="benefitSort">
|
||||
|
||||
<el-input-number v-model="formAdd.benefitSort" :min="1" :max="9999" :precision="0" style="width: 220px" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="benefit-drawer-footer-btns">
|
||||
|
||||
<el-button @click="addFlag = false">取消</el-button>
|
||||
|
||||
<el-button type="primary" :loading="submitAddLoading" @click="submitAdd">提交</el-button>
|
||||
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<div class="benefit-drawer-heading">
|
||||
<h2 :id="titleId" :class="titleClass">添加权益设置</h2>
|
||||
<p>配置权益规则、展示内容及发布状态</p>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="addForm" :model="formAdd" :rules="rulesAdd" label-position="top" class="benefit-editor-form" @submit.prevent>
|
||||
<BenefitFormFields
|
||||
:form="formAdd" :type-options="benefitTypeOptions" which="add"
|
||||
@type-change="onAddBenefitTypeChange" @select-coupons="openCouponPicker"
|
||||
@quantity-change="onCouponQuantityChange" @remove-coupon="removeCouponRow"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="benefit-drawer-footer-btns">
|
||||
<el-button @click="addFlag = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitAddLoading" @click="submitAdd">保存权益</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-drawer>
|
||||
|
||||
|
||||
|
||||
<el-drawer
|
||||
|
||||
v-model="editFlag"
|
||||
|
||||
title="编辑权益设置"
|
||||
|
||||
size="1120px"
|
||||
|
||||
direction="rtl"
|
||||
|
||||
:z-index="950"
|
||||
|
||||
:close-on-click-modal="false"
|
||||
|
||||
append-to-body
|
||||
|
||||
destroy-on-close
|
||||
|
||||
class="benefit-form-drawer"
|
||||
|
||||
v-model="editFlag" title="编辑权益设置" size="1040px" direction="rtl"
|
||||
:z-index="950" :close-on-click-modal="false" append-to-body destroy-on-close class="benefit-form-drawer"
|
||||
>
|
||||
|
||||
<el-form ref="editForm" :model="formEdit" :rules="rulesEdit" label-width="110px">
|
||||
|
||||
<el-input v-model="formEdit.id" v-show="false" />
|
||||
|
||||
<el-form-item label="权益类型" prop="benefitType">
|
||||
|
||||
<el-select
|
||||
|
||||
v-model="formEdit.benefitType"
|
||||
|
||||
clearable
|
||||
|
||||
placeholder="请选择权益类型"
|
||||
|
||||
style="width: 100%"
|
||||
|
||||
@change="onEditBenefitTypeChange"
|
||||
|
||||
>
|
||||
|
||||
<el-option
|
||||
|
||||
v-for="item in benefitTypeOptions"
|
||||
|
||||
:key="item.value"
|
||||
|
||||
:value="item.value"
|
||||
|
||||
:label="item.description"
|
||||
|
||||
/>
|
||||
|
||||
</el-select>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
|
||||
v-show="formEdit.benefitType === 'GIFT_POINT'"
|
||||
|
||||
label="赠送积分"
|
||||
|
||||
prop="giftPoint"
|
||||
|
||||
:required="formEdit.benefitType === 'GIFT_POINT'"
|
||||
|
||||
>
|
||||
|
||||
<el-input-number
|
||||
|
||||
v-model="formEdit.giftPoint"
|
||||
|
||||
:min="0"
|
||||
|
||||
:max="99999"
|
||||
|
||||
:precision="0"
|
||||
|
||||
style="width: 220px"
|
||||
|
||||
placeholder="必填,范围 0-99999"
|
||||
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
|
||||
v-show="formEdit.benefitType === 'COUPON_PACKAGE'"
|
||||
|
||||
class="benefit-coupon-form-item"
|
||||
|
||||
label="赠送优惠券"
|
||||
|
||||
prop="couponPackageRows"
|
||||
|
||||
:required="formEdit.benefitType === 'COUPON_PACKAGE'"
|
||||
|
||||
>
|
||||
|
||||
<div class="benefit-coupon-field">
|
||||
|
||||
<el-button plain class="mb_10" @click="openCouponPicker('edit')">添加优惠券</el-button>
|
||||
|
||||
<el-table
|
||||
|
||||
border
|
||||
|
||||
size="small"
|
||||
|
||||
class="benefit-coupon-table"
|
||||
|
||||
:data="formEdit.couponPackageRows"
|
||||
|
||||
empty-text="请添加优惠券"
|
||||
|
||||
>
|
||||
|
||||
<el-table-column prop="couponName" label="优惠券名称" min-width="180" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="有效期" min-width="280">
|
||||
|
||||
<template #default="{ row }">
|
||||
|
||||
<span v-if="row" v-html="row.validRange || '-'" />
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="faceValueLabel" label="面额" min-width="100" />
|
||||
|
||||
<el-table-column label="赠送张数" width="150">
|
||||
|
||||
<template #default="{ row, $index }">
|
||||
|
||||
<el-input-number
|
||||
|
||||
v-if="row"
|
||||
|
||||
:model-value="row.quantity"
|
||||
|
||||
:min="1"
|
||||
|
||||
:max="10"
|
||||
|
||||
:precision="0"
|
||||
|
||||
style="width: 100px"
|
||||
|
||||
@change="(val) => onCouponQuantityChange('edit', $index, val)"
|
||||
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
|
||||
<template #default="{ row, $index }">
|
||||
|
||||
<a v-if="row" class="link-text" @click="removeCouponRow('edit', $index)">删除</a>
|
||||
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益名称" prop="benefitName">
|
||||
|
||||
<el-input v-model="formEdit.benefitName" maxlength="50" placeholder="请输入权益名称" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益LOGO" prop="benefitLogo">
|
||||
|
||||
<div class="benefit-logo-field">
|
||||
|
||||
<div v-if="formEdit.benefitLogo" class="benefit-logo-thumb">
|
||||
|
||||
<img :src="formEdit.benefitLogo" alt="" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="benefit-logo-upload">
|
||||
|
||||
<upload-pic-input v-model="formEdit.benefitLogo" placeholder="请上传权益 LOGO 图片地址" style="width: 100%" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="权益介绍" prop="benefitDesc">
|
||||
|
||||
<el-input v-model="formEdit.benefitDesc" type="textarea" :rows="4" maxlength="500" placeholder="请输入权益介绍" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态" prop="benefitState">
|
||||
|
||||
<el-radio-group v-model="formEdit.benefitState">
|
||||
|
||||
<el-radio value="OPEN">开启</el-radio>
|
||||
|
||||
<el-radio value="CLOSE">关闭</el-radio>
|
||||
|
||||
</el-radio-group>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序" prop="benefitSort">
|
||||
|
||||
<el-input-number v-model="formEdit.benefitSort" :min="1" :max="9999" :precision="0" style="width: 220px" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="benefit-drawer-footer-btns">
|
||||
|
||||
<el-button @click="editFlag = false">取消</el-button>
|
||||
|
||||
<el-button type="primary" :loading="submitEditLoading" @click="submitEdit">提交</el-button>
|
||||
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<div class="benefit-drawer-heading">
|
||||
<h2 :id="titleId" :class="titleClass">编辑权益设置</h2>
|
||||
<p>配置权益规则、展示内容及发布状态</p>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="editForm" :model="formEdit" :rules="rulesEdit" label-position="top" class="benefit-editor-form" @submit.prevent>
|
||||
<BenefitFormFields
|
||||
:form="formEdit" :type-options="benefitTypeOptions" which="edit" editing
|
||||
@type-change="onEditBenefitTypeChange" @select-coupons="openCouponPicker"
|
||||
@quantity-change="onCouponQuantityChange" @remove-coupon="removeCouponRow"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="benefit-drawer-footer-btns">
|
||||
<el-button @click="editFlag = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitEditLoading" @click="submitEdit">保存修改</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
@@ -737,8 +309,8 @@ import { formatPromotionCouponValidityHtml } from "@/utils/promotions";
|
||||
|
||||
import couponTemplate from "@/views/promotions/coupon/coupon.vue";
|
||||
|
||||
import uploadPicInput from "@/components/lili/upload-pic-input.vue";
|
||||
|
||||
import BenefitFormFields from './BenefitFormFields.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
|
||||
@@ -747,6 +319,8 @@ defineOptions({ name: "memberBenefit" });
|
||||
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const GIFT_POINT = "GIFT_POINT";
|
||||
|
||||
const COUPON_PACKAGE = "COUPON_PACKAGE";
|
||||
@@ -774,6 +348,10 @@ const buildDefaultForm = () => ({
|
||||
benefitConfig: "",
|
||||
|
||||
giftPoint: null,
|
||||
multiplier: 1,
|
||||
grantRule: "ONCE",
|
||||
grantDay: 1,
|
||||
periodMonths: 1,
|
||||
|
||||
couponPackageRows: [],
|
||||
|
||||
@@ -799,13 +377,11 @@ const benefitTypeOptions = ref([]);
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const total = ref(0);
|
||||
|
||||
const searchForm = ref({
|
||||
|
||||
pageNumber: 1,
|
||||
keyword: "",
|
||||
|
||||
pageSize: 20,
|
||||
state: "",
|
||||
|
||||
});
|
||||
|
||||
@@ -903,6 +479,7 @@ function buildFormRules(which) {
|
||||
|
||||
benefitType: [{ required: true, message: "请选择权益类型", trigger: "change" }],
|
||||
|
||||
benefitDesc: [{ required: true, message: "请输入权益介绍", trigger: "change" }],
|
||||
benefitName: [{ required: true, message: "请输入权益名称", trigger: "blur" }],
|
||||
|
||||
benefitLogo: [{ required: true, message: "请配置权益LOGO", trigger: "change" }],
|
||||
@@ -979,6 +556,8 @@ function validateCouponPackage(form, callback) {
|
||||
|
||||
const rows = form.couponPackageRows || [];
|
||||
|
||||
if (rows.length > 10) return callback(new Error("最多选择10张优惠券"));
|
||||
|
||||
if (!rows.length) {
|
||||
|
||||
return callback(new Error("请添加至少一张优惠券"));
|
||||
@@ -989,7 +568,7 @@ function validateCouponPackage(form, callback) {
|
||||
|
||||
const q = Number(rows[i].quantity);
|
||||
|
||||
if (Number.isNaN(q) || q < 1 || q > 10) {
|
||||
if (!Number.isInteger(q) || q < 1 || q > 10) {
|
||||
|
||||
return callback(new Error("每张券赠送张数需在 1~10 之间"));
|
||||
|
||||
@@ -1047,32 +626,14 @@ function buildCouponDisplayRow(detail, quantity) {
|
||||
|
||||
}
|
||||
|
||||
function buildBenefitConfigString(slice) {
|
||||
|
||||
const { benefitType, giftPoint, couponPackageRows } = slice;
|
||||
|
||||
if (benefitType === GIFT_POINT) {
|
||||
|
||||
return JSON.stringify({ giftPoint: Number(giftPoint) });
|
||||
|
||||
}
|
||||
|
||||
if (benefitType === COUPON_PACKAGE) {
|
||||
|
||||
const coupons = (couponPackageRows || []).map((r) => ({
|
||||
|
||||
couponId: String(r.couponId),
|
||||
|
||||
quantity: Math.min(10, Math.max(1, Number(r.quantity) || 1)),
|
||||
|
||||
}));
|
||||
|
||||
return JSON.stringify({ coupons });
|
||||
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
function buildBenefitConfigString(form) {
|
||||
if(form.benefitType === GIFT_POINT) return JSON.stringify({giftPoint:Number(form.giftPoint)});
|
||||
if(form.benefitType === 'POINT_MULTIPLIER') return JSON.stringify({multiplier:Number(form.multiplier)});
|
||||
if(form.benefitType === COUPON_PACKAGE) return JSON.stringify({
|
||||
grantRule:form.grantRule, grantDay:form.grantDay, periodMonths:form.periodMonths,
|
||||
coupons:(form.couponPackageRows || []).map(r=>({couponId:String(r.couponId),quantity:Number(r.quantity)}))
|
||||
});
|
||||
return '{}';
|
||||
}
|
||||
|
||||
function parseGiftPointFromConfig(benefitType, benefitConfigStr) {
|
||||
@@ -1317,6 +878,11 @@ function confirmCouponPicker() {
|
||||
|
||||
const list = couponPicker.value?.getSelection?.() || [];
|
||||
|
||||
if (list.length > 10) {
|
||||
ElMessage.warning("最多选择10张优惠券");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!list.length) {
|
||||
|
||||
ElMessage.warning("请至少选择一张优惠券");
|
||||
@@ -1395,29 +961,11 @@ function loadBenefitTypes() {
|
||||
|
||||
}
|
||||
|
||||
function changePage(page) {
|
||||
|
||||
searchForm.value.pageNumber = page;
|
||||
|
||||
getData();
|
||||
|
||||
}
|
||||
|
||||
function changePageSize(size) {
|
||||
|
||||
searchForm.value.pageSize = size;
|
||||
|
||||
searchForm.value.pageNumber = 1;
|
||||
|
||||
getData();
|
||||
|
||||
}
|
||||
|
||||
function getData() {
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const params = { ...searchForm.value };
|
||||
const params = { ...searchForm.value, pageNumber: 1, pageSize: 999 };
|
||||
|
||||
API_Member.getMemberBenefitByPage(params).then((res) => {
|
||||
|
||||
@@ -1427,20 +975,18 @@ function getData() {
|
||||
|
||||
data.value = Array.isArray(res.result.records) ? res.result.records : [];
|
||||
|
||||
total.value = Number(res.result.total) || 0;
|
||||
|
||||
} else {
|
||||
|
||||
data.value = [];
|
||||
|
||||
total.value = 0;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function searchBenefits() { getData(); }
|
||||
|
||||
function openAdd() {
|
||||
|
||||
addFlag.value = true;
|
||||
@@ -1479,15 +1025,7 @@ function submitAdd() {
|
||||
|
||||
...rest,
|
||||
|
||||
benefitConfig: buildBenefitConfigString({
|
||||
|
||||
benefitType: formAdd.value.benefitType,
|
||||
|
||||
giftPoint,
|
||||
|
||||
couponPackageRows,
|
||||
|
||||
}),
|
||||
benefitConfig: buildBenefitConfigString(formAdd.value),
|
||||
|
||||
};
|
||||
|
||||
@@ -1549,6 +1087,8 @@ function openEdit(row) {
|
||||
|
||||
const bt = detail.benefitType || "";
|
||||
|
||||
let extension = {};
|
||||
try { extension = JSON.parse(detail.benefitConfig || '{}'); } catch { /* 旧数据按默认配置回显 */ }
|
||||
const parsed = parseGiftPointFromConfig(bt, detail.benefitConfig || "");
|
||||
|
||||
const couponSnippets = parseCouponsFromConfig(detail.benefitConfig || "");
|
||||
@@ -1574,6 +1114,10 @@ function openEdit(row) {
|
||||
benefitCode: detail.benefitCode || "",
|
||||
|
||||
benefitType: bt,
|
||||
multiplier: extension.multiplier ?? 1,
|
||||
grantRule: extension.grantRule || "ONCE",
|
||||
grantDay: extension.grantDay || 1,
|
||||
periodMonths: extension.periodMonths || 1,
|
||||
|
||||
benefitLogo: detail.benefitLogo || "",
|
||||
|
||||
@@ -1627,15 +1171,7 @@ function submitEdit() {
|
||||
|
||||
...rest,
|
||||
|
||||
benefitConfig: buildBenefitConfigString({
|
||||
|
||||
benefitType: formEdit.value.benefitType,
|
||||
|
||||
giftPoint,
|
||||
|
||||
couponPackageRows,
|
||||
|
||||
}),
|
||||
benefitConfig: buildBenefitConfigString(formEdit.value),
|
||||
|
||||
};
|
||||
|
||||
@@ -1755,16 +1291,6 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.benefit-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.benefit-actions .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
@@ -1853,18 +1379,6 @@ onMounted(() => {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.pagination-wrap {
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: flex-end;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.benefit-drawer-footer-btns {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -1902,15 +1416,21 @@ onMounted(() => {
|
||||
max-width: 100vw;
|
||||
.el-drawer__header {
|
||||
margin-bottom: 0;
|
||||
padding: 20px 24px;
|
||||
padding: 22px 28px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
.el-drawer__body { padding: 24px; }
|
||||
.el-drawer__body { padding: 20px 24px; background: #f5f7fa; }
|
||||
.el-drawer__footer {
|
||||
padding: 16px 24px;
|
||||
padding: 16px 28px;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
.el-form-item__content { min-width: 0; }
|
||||
.benefit-drawer-heading h2 { margin: 0; color: var(--el-text-color-primary); font-size: 18px; font-weight: 600; line-height: 26px; }
|
||||
.benefit-drawer-heading p { margin: 6px 0 0; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
.benefit-drawer-footer-btns .el-button { min-width: 92px; }
|
||||
.benefit-editor-form .tinymce-container { width: 100%; }
|
||||
.benefit-editor-form .tinymce-container > .wrapper { margin-bottom: 10px; }
|
||||
}
|
||||
|
||||
|
||||
@@ -2065,30 +1585,3 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.benefit-logo-field {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: flex-start;
|
||||
|
||||
gap: 12px;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
}
|
||||
|
||||
.benefit-logo-upload {
|
||||
|
||||
flex: 1;
|
||||
|
||||
min-width: 200px;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@@ -76,9 +76,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="等级图标" prop="gradeImage">
|
||||
<upload-pic-input v-model="formAdd.gradeImage" />
|
||||
<div class="image-size-hint">推荐 100 × 100 像素,使用透明背景 PNG。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="等级背景图" prop="gradeBackground">
|
||||
<upload-pic-input v-model="formAdd.gradeBackground" />
|
||||
<div class="image-size-hint">推荐 750 × 460 像素</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="字体颜色" prop="gradeFontColor">
|
||||
<el-input v-model="formAdd.gradeFontColor" maxlength="20" placeholder="如:#333333" />
|
||||
@@ -133,9 +135,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="等级图标" prop="gradeImage">
|
||||
<upload-pic-input v-model="formEdit.gradeImage" />
|
||||
<div class="image-size-hint">推荐 100 × 100 像素,使用透明背景 PNG。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="等级背景图" prop="gradeBackground">
|
||||
<upload-pic-input v-model="formEdit.gradeBackground" />
|
||||
<div class="image-size-hint">推荐 750 × 460 像素</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="字体颜色" prop="gradeFontColor">
|
||||
<el-input v-model="formEdit.gradeFontColor" maxlength="20" placeholder="如:#333333" />
|
||||
@@ -456,6 +460,13 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.image-size-hint {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.ops a {
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -986,7 +986,9 @@ function handleSelectConfirm() {
|
||||
return;
|
||||
}
|
||||
const last = selectedOss.value[selectedOss.value.length - 1];
|
||||
emit("callback", { url: last.split(",")[1] });
|
||||
const separator = last.indexOf(",");
|
||||
const id = last.slice(0, separator);
|
||||
emit("callback", { url: last.slice(separator + 1), file: data.value.find(item => String(item.id) === id) });
|
||||
}
|
||||
|
||||
function handleSelectCancel() {
|
||||
|
||||
Reference in New Issue
Block a user