| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- <template>
- <view class="app-content level-page">
- <view class="page-header">
- <view class="page-title">会员等级设置</view>
- <view class="page-subtitle">充值或消费1元等于1个成长值,余额消费没有成长值</view>
- </view>
- <view class="main-content">
- <view class="table-head">
- <view class="col-icon">图标</view>
- <view class="col-level">等级</view>
- <view class="col-amount">成长值</view>
- <view class="col-discount">折扣</view>
- </view>
- <view class="level-card" v-for="(item, index) in map" :key="index">
- <view class="card-top">
- <view class="member-icon col-icon">
- <image v-if="item.member == 1" class="member-badge-icon" src="@/static/member-icons/member-1.svg" mode="aspectFit" />
- <image v-else-if="item.member == 2" class="member-badge-icon" src="@/static/member-icons/member-2.svg" mode="aspectFit" />
- <image v-else-if="item.member == 3" class="member-badge-icon" src="@/static/member-icons/member-3.svg" mode="aspectFit" />
- <image v-else-if="item.member == 4" class="member-badge-icon" src="@/static/member-icons/member-4.svg" mode="aspectFit" />
- <image v-else-if="item.member == 5" class="member-badge-icon" src="@/static/member-icons/member-5.svg" mode="aspectFit" />
- </view>
- <view class="col-level">
- <view class="level-name">{{ item.name }}</view>
- </view>
- <view class="field-inline col-amount">
- <input v-model="item.amount" type="digit" class="field-inp" placeholder="金额" @focus="() => clearDiscount(index)" />
- </view>
- <view class="field-inline col-discount">
- <view
- v-if="!item.discountEnabled"
- class="discount-toggle off"
- @click="enableDiscount(index)"
- >
- <text class="discount-off-text">关闭</text>
- </view>
- <view v-else class="discount-toggle on">
- <input
- v-model="item.discount"
- type="digit"
- class="discount-inp"
- placeholder="8.8"
- placeholder-style="color:#cccccc"
- :focus="focusDiscountIndex === index"
- @blur="onDiscountBlur"
- />
- <text class="discount-unit">折</text>
- <view class="discount-close" @click="disableDiscount(index)">
- <text class="discount-close-icon">×</text>
- </view>
- </view>
- </view>
- </view>
- <view class="card-section">
- <view class="section-label">生日权益</view>
- <input
- v-model="item.birthdayBenefit"
- class="section-inp"
- maxlength="50"
- placeholder="请输入生日权益内容"
- />
- <view class="char-count">{{ (item.birthdayBenefit || '').length }}/10</view>
- </view>
- <view class="card-section">
- <view class="section-label">权益说明</view>
- <textarea
- v-model="item.benefitDesc"
- class="section-textarea"
- maxlength="500"
- placeholder="请输入会员权益说明内容"
- />
- <view class="char-count">{{ (item.benefitDesc || '').length }}/500</view>
- </view>
- </view>
- </view>
- <view class="app-footer">
- <view class="footer-btn admin-button-com big default" style="border:1upx solid #CCCCCC;" @click="goBack()">返回上页</view>
- <view class="footer-btn admin-button-com big blue" @click="saveData">提交修改</view>
- </view>
- </view>
- </template>
- <script>
- import list from '@/mixins/list';
- import { getLevelInitData, modifyLevel } from '@/api/member';
- export default {
- name: "MemberLevel",
- mixins: [list],
- data() {
- return {
- map: [],
- reduceDay: 30,
- // 开启折扣后要聚焦的输入框下标,-1 表示不聚焦
- focusDiscountIndex: -1
- };
- },
- methods: {
- init() {
- getLevelInitData().then(res => {
- if (res.code === 1) {
- this.map = res.data.member || [];
- this.map.forEach((item, index) => {
- if (!item.member) {
- item.member = index + 1;
- }
- if (item.discount === undefined || item.discount === null || item.discount === '') {
- item.discount = 10;
- }
- this.$set(item, 'discountEnabled', Number(item.discount) < 10);
- if (!item.birthdayBenefit) {
- item.birthdayBenefit = '';
- }
- if (!item.benefitDesc) {
- item.benefitDesc = '';
- }
- });
- this.reduceDay = res.data.reduceDay || 30;
- }
- }).catch(err => {
- console.error('获取会员等级数据失败:', err);
- this.$msg('获取数据失败,请重试');
- });
- },
- clearDiscount(index) {
- this.$set(this.map[index], 'amount', '');
- },
- enableDiscount(index) {
- this.$set(this.map[index], 'discountEnabled', true);
- if (!this.map[index].discount || Number(this.map[index].discount) >= 10) {
- this.$set(this.map[index], 'discount', '');
- }
- // 先重置再 nextTick 设焦点,兼容小程序 :focus 需 false→true 切换
- this.focusDiscountIndex = -1;
- this.$nextTick(() => {
- this.focusDiscountIndex = index;
- });
- },
- disableDiscount(index) {
- this.$set(this.map[index], 'discountEnabled', false);
- this.$set(this.map[index], 'discount', 10);
- this.focusDiscountIndex = -1;
- },
- onDiscountBlur() {
- this.focusDiscountIndex = -1;
- },
- // 取提交/校验用的有效折扣:未开启视为 10 折(无优惠)
- getEffectiveDiscount(item) {
- if (!item.discountEnabled) {
- return 10;
- }
- return parseFloat(item.discount);
- },
- getSubmitData() {
- return this.map.map(item => {
- const row = Object.assign({}, item);
- if (!row.discountEnabled) {
- row.discount = 10;
- }
- delete row.discountEnabled;
- return row;
- });
- },
- goBack() {
- uni.navigateBack();
- },
- saveData() {
- if (!this.validateData()) {
- return;
- }
- this.$util.confirmModal({ content: '确认提交?' }, () => {
- modifyLevel({
- data: this.getSubmitData(),
- reduceDay: this.reduceDay
- }).then(res => {
- if (res.code === 1) {
- this.$msg(res.msg);
- setTimeout(() => uni.navigateBack(), 900);
- }
- });
- });
- },
- validateData() {
- const hasEmptyAmount = this.map.some(item => !item.amount || item.amount === '');
- if (hasEmptyAmount) {
- this.$msg('请填写完整的消费金额');
- return false;
- }
- const hasInvalidAmount = this.map.some(item => {
- const amount = parseFloat(item.amount);
- return isNaN(amount) || amount < 0;
- });
- if (hasInvalidAmount) {
- this.$msg('请输入有效的消费金额');
- return false;
- }
- const hasInvalidDiscount = this.map.some(item => {
- if (!item.discountEnabled) {
- return false;
- }
- const discount = parseFloat(item.discount);
- return item.discount === '' || item.discount === undefined || item.discount === null || isNaN(discount) || discount <= 0 || discount >= 10;
- });
- if (hasInvalidDiscount) {
- this.$msg('开启折扣后请输入小于10的有效折扣');
- return false;
- }
- // 等级越高,折扣数值须 ≤ 上一等级(如上一级 9.5 折,本级最高只能 9.5 折)
- for (let i = 1; i < this.map.length; i++) {
- const prevItem = this.map[i - 1];
- const currItem = this.map[i];
- const prevDiscount = this.getEffectiveDiscount(prevItem);
- const currDiscount = this.getEffectiveDiscount(currItem);
- if (currDiscount > prevDiscount) {
- const prevLabel = prevItem.discountEnabled ? `${prevDiscount}折` : '无折扣';
- this.$msg(`${currItem.name}的折扣不能高于${prevItem.name}(${prevLabel})`);
- return false;
- }
- }
- const hasLongBenefit = this.map.some(item => (item.birthdayBenefit || '').length > 50);
- if (hasLongBenefit) {
- this.$msg('生日权益不超过50字');
- return false;
- }
- const hasLongDesc = this.map.some(item => (item.benefitDesc || '').length > 500);
- if (hasLongDesc) {
- this.$msg('会员权益说明不超过500字');
- return false;
- }
- return true;
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .level-page {
- position: relative;
- background: linear-gradient(180deg, #eff9ef 0%, #f8fcf8 44%, #f7fbf7 100%);
- min-height: 100vh;
- padding: 0 16upx 160upx;
- box-sizing: border-box;
- overflow: hidden;
- }
- .page-header {
- position: relative;
- z-index: 1;
- padding: 44upx 30upx 28upx;
- .page-title {
- font-size: 42upx;
- font-weight: 700;
- color: #1f6f40;
- text-align: center;
- letter-spacing: 2upx;
- }
- .page-subtitle {
- font-size: 28upx;
- color: #a5aaa8;
- text-align: center;
- margin-top: 12upx;
- }
- .title-decor {
- width: 10upx;
- height: 10upx;
- margin: 18upx auto 0;
- position: relative;
- background: #4ec56a;
- transform: rotate(45deg);
- }
- .title-decor::before,
- .title-decor::after {
- content: '';
- position: absolute;
- top: 4upx;
- width: 28upx;
- height: 1upx;
- background: #d8ead6;
- transform: rotate(-45deg);
- }
- .title-decor::before {
- right: 24upx;
- }
- .title-decor::after {
- left: 24upx;
- }
- }
- .main-content {
- position: relative;
- z-index: 1;
- background: #fff;
- border-radius: 18upx;
- padding: 0 18upx 2upx;
- box-shadow: 0 8upx 28upx rgba(35, 121, 63, 0.08);
- }
- .table-head {
- display: flex;
- align-items: center;
- height: 74upx;
- font-size: 28upx;
- font-weight: 600;
- color: #28884d;
- border-bottom: 1upx solid #edf3ed;
- box-sizing: border-box;
- }
- .table-head .col-icon,
- .table-head .col-level,
- .table-head .col-amount,
- .table-head .col-discount {
- text-align: center;
- }
- .level-card {
- padding: 22upx 0 24upx;
- border-bottom: 1upx solid #f1f4f0;
- }
- .level-card:last-child {
- border-bottom: 0;
- }
- .card-top {
- display: flex;
- align-items: center;
- margin-bottom: 18upx;
- }
- .col-icon {
- width: 112upx;
- flex-shrink: 0;
- text-align: center;
- }
- .col-level {
- width: 160upx;
- flex-shrink: 0;
- }
- .col-amount {
- flex: 1;
- margin-left: 6upx;
- }
- .col-discount {
- width: 180upx;
- flex-shrink: 0;
- margin-left: 18upx;
- }
- .member-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .member-badge-icon {
- width: 82upx;
- height: 82upx;
- }
- .level-name {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 152upx;
- height: 56upx;
- background: linear-gradient(90deg, #67d85a, #41b94e);
- color: #fff;
- padding: 0 14upx;
- border-radius: 20upx;
- font-size: 28upx;
- font-weight: 600;
- box-shadow: 0 4upx 10upx rgba(67, 183, 78, 0.2);
- box-sizing: border-box;
- }
- .field-inline {
- display: flex;
- align-items: center;
- }
- .field-inp {
- width: 100%;
- height: 54upx;
- line-height: 54upx;
- border: 1upx solid #edf0ed;
- border-radius: 8upx;
- padding: 0 8upx;
- font-size: 32upx;
- font-weight: 600;
- color: #368753;
- text-align: center;
- box-sizing: border-box;
- background: #fff;
- }
- .discount-toggle {
- width: 100%;
- height: 54upx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 27upx;
- box-sizing: border-box;
- padding: 0 6upx 0 12upx;
- }
- .discount-toggle.off {
- justify-content: center;
- padding: 0;
- background: #f1f3f1;
- border: 1upx solid #e3e7e3;
- }
- .discount-off-text {
- font-size: 24upx;
- color: #9ca3a0;
- }
- .discount-toggle.on {
- background: #fff;
- border: 1upx solid #41b94e;
- }
- .discount-inp {
- flex: 1;
- min-width: 0;
- height: 100%;
- line-height: 54upx;
- font-size: 30upx;
- font-weight: 600;
- color: #368753;
- text-align: center;
- }
- .discount-unit {
- flex-shrink: 0;
- font-size: 28upx;
- color: #368753;
- margin-left: 2upx;
- }
- .discount-close {
- flex-shrink: 0;
- width: 44upx;
- height: 44upx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-left: 2upx;
- }
- .discount-close-icon {
- font-size: 44upx;
- color: #b7c0b7;
- line-height: 1;
- transform: translateY(-4upx);
- }
- .card-section {
- position: relative;
- display: flex;
- align-items: flex-start;
- margin-top: 18upx;
- }
- .section-label {
- width: 132upx;
- flex-shrink: 0;
- line-height: 58upx;
- font-size: 28upx;
- font-weight: 600;
- color: #3b3f3c;
- }
- .section-inp {
- flex: 1;
- height: 62upx;
- line-height: 62upx;
- border: 1upx solid #edf0ed;
- border-radius: 8upx;
- padding: 0 18upx;
- font-size: 28upx;
- box-sizing: border-box;
- color: #333;
- }
- .section-textarea {
- flex: 1;
- height: 140upx;
- min-height: 130upx;
- border: 1upx solid #edf0ed;
- border-radius: 8upx;
- padding: 14upx 18upx 28upx;
- font-size: 28upx;
- line-height: 38upx;
- box-sizing: border-box;
- color: #333;
- }
- .char-count {
- position: absolute;
- right: 16upx;
- bottom: 8upx;
- font-size: 26upx;
- color: #a8a8a8;
- line-height: 1;
- }
- .app-footer {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- padding: 20upx 24upx;
- background: #fff;
- box-shadow: 0 -4upx 12upx rgba(0, 0, 0, 0.06);
- .footer-btn {
- flex: 1;
- margin: 0 10upx;
- }
- }
- </style>
|