新增积分锁客页面,基础设置,商品设置,兑换记录

This commit is contained in:
gyq
2025-12-11 19:27:22 +08:00
parent ea5ce3caa1
commit 214026e859
15 changed files with 2188 additions and 78 deletions

View File

@@ -1,78 +1,90 @@
<template>
<view
class="fixed-wrap"
:style="{ '--num': showCancel ? '63px' : '0px' }"
v-if="isShow"
>
<view class="fixed-btn" id="targetRef">
<div class="btn">
<u-button
type="primary"
:shape="shape"
size="large"
@click="emits('confirm')"
>{{ confirmText }}</u-button
>
</div>
<div class="btn" v-if="showCancel">
<u-button :shape="shape" size="large" @click="emits('cancel')"
>取消</u-button
>
</div>
</view>
</view>
<view class="fixed-wrap" :style="{ '--num': `${numValue}px` }" v-if="isShow">
<view class="fixed-btn" :class="[type]" id="targetRef">
<div class="btn">
<u-button type="primary" :shape="shape" size="large" @click="emits('confirm')">{{ confirmText }}</u-button>
</div>
<div class="btn" v-if="showCancel">
<u-button :shape="shape" size="large" @click="emits('cancel')">取消</u-button>
</div>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick, getCurrentInstance ,computed} from "vue";
import { isMainShop } from "@/store/account.js";
import { ref, onMounted, nextTick, getCurrentInstance, computed } from 'vue';
import { isMainShop } from '@/store/account.js';
const props = defineProps({
isOpenPermission: {
type: Boolean,
default: true,
},
confirmText: {
type: String,
default: "保存",
},
showCancel: {
type: Boolean,
default: false,
},
shape: {
type: String,
default: "circle", // squre circle
},
type: {
type: String,
default: 'vertical' // horizontal横向 vertical竖向
},
isOpenPermission: {
type: Boolean,
default: true
},
confirmText: {
type: String,
default: '保存'
},
showCancel: {
type: Boolean,
default: false
},
shape: {
type: String,
default: 'circle' // squre circle
}
});
const isShow = computed(() => {
if (props.isOpenPermission) {
return isMainShop();
}
return true;
if (props.isOpenPermission) {
return isMainShop();
}
return true;
});
const emits = defineEmits(["confirm", "cancel"]);
const numValue = computed(() => {
let num = 0;
if (props.type == 'vertical' && props.showCancel) {
num = 63;
return num;
}
if (props.type == 'horizontal') {
num = 0;
return num;
}
});
const emits = defineEmits(['confirm', 'cancel']);
</script>
<style scoped lang="scss">
.fixed-wrap {
--height: calc(83px + var(--num) + env(safe-area-inset-bottom) / 2);
width: 100%;
height: var(--height);
.fixed-btn {
width: 100%;
height: var(--height);
position: fixed;
bottom: 0;
left: 0;
z-index: 99;
padding: 10px 14px calc(20px + env(safe-area-inset-bottom) / 2) 10px;
background-color: #fff;
.btn {
&:first-child {
margin-bottom: 14px;
}
}
}
--height: calc(83px + var(--num) + env(safe-area-inset-bottom) / 2);
width: 100%;
height: var(--height);
.fixed-btn {
width: 100%;
height: var(--height);
position: fixed;
bottom: 0;
left: 0;
z-index: 99;
padding: 10px 14px calc(20px + env(safe-area-inset-bottom) / 2) 10px;
background-color: #fff;
&.horizontal {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
.btn {
&:first-child {
margin-bottom: 14px;
}
}
}
}
</style>

View File

@@ -83,6 +83,7 @@ onMounted(() => {
padding-left: 20upx;
flex-direction: column;
.title {
margin-top: -4upx;
.t {
font-size: 28upx;
font-weight: bold;

View File

@@ -3,7 +3,7 @@
<view @click="show = true">
<slot v-if="$slots.default"></slot>
<view v-else class="choose-goods u-flex u-row-between">
<text class="color-999" v-if="!modelValue">请选择商品</text>
<text class="color-999" v-if="!modelValue">请选择优惠券</text>
<text class="color-333 u-m-r-32 u-line-1" v-else>{{ goodsName }}</text>
<up-icon size="14" name="arrow-down"></up-icon>
</view>

View File

@@ -0,0 +1,181 @@
<template>
<view class="upload-file-wrap">
<!-- 多图展示区域 -->
<view class="upload-file-item" v-for="(img, index) in modelValue" :key="index">
<image class="img" :src="img" mode="aspectFill" @click="previewImage(index)"></image>
<view class="close" @click.stop="removeImg(index)">
<up-icon name="close-circle" color="#333" size="14"></up-icon>
</view>
</view>
<!-- 上传按钮未达上限时显示 -->
<view class="upload-file-btn" @click="chooseImage" v-if="modelValue.length < props.maxCount">
<slot v-if="$slots.default"></slot>
<view class="icon" v-else>+</view>
</view>
</view>
</template>
<script setup>
import { uploadFile } from '@/http/api/index.js';
import { ref, watch, defineProps, defineEmits } from 'vue';
// 1. 定义Props扩展配置项
const props = defineProps({
// 最大上传数量默认9可外部传参
maxCount: {
type: Number,
default: 9
},
// 是否压缩(默认开启)
isCompressed: {
type: Boolean,
default: true
},
// 图片来源(相册/相机,默认都支持)
sourceType: {
type: Array,
default: () => ['album', 'camera']
}
});
// 2. 双向绑定多图列表Array类型
const modelValue = defineModel({
type: Array,
default: () => []
});
// 3. 选择图片(支持多图)
async function chooseImage() {
// 计算剩余可上传数量
const remainCount = props.maxCount - modelValue.value.length;
if (remainCount <= 0) {
uni.showToast({ title: `最多只能上传${props.maxCount}张图片`, icon: 'none' });
return;
}
uni.chooseImage({
count: remainCount, // 仅能选择剩余数量的图片
sizeType: props.isCompressed ? ['compressed'] : ['original', 'compressed'],
sourceType: props.sourceType,
success: async function (res) {
uni.showLoading({ title: '上传中' });
try {
// 批量上传选中的图片
const uploadPromises = res.tempFiles.map((file) => uploadFile(file));
const fileResList = await Promise.all(uploadPromises);
// 过滤上传失败的图片仅保留成功的URL
const successUrls = fileResList.filter((url) => !!url);
if (successUrls.length > 0) {
modelValue.value = [...modelValue.value, ...successUrls]; // 追加到列表
uni.showToast({ title: `成功上传${successUrls.length}张图片`, icon: 'success' });
}
} catch (error) {
uni.showToast({ title: '部分图片上传失败', icon: 'none' });
console.error('图片上传失败:', error);
} finally {
uni.hideLoading();
}
},
fail: () => {
uni.showToast({ title: '取消选择图片', icon: 'none' });
}
});
}
// 4. 删除单张图片
function removeImg(index) {
uni.showModal({
title: '提示',
content: '确定要删除这张图片吗?',
success: (res) => {
if (res.confirm) {
modelValue.value.splice(index, 1); // 删除对应索引的图片
}
}
});
}
// 5. 预览图片(支持左右滑动)
function previewImage(currentIndex) {
uni.previewImage({
urls: modelValue.value, // 所有已上传的图片列表
current: modelValue.value[currentIndex], // 当前预览的图片
loop: true // 支持循环预览
});
}
// 6. 扩展:批量清空图片(可暴露给父组件调用)
const clearAllImg = () => {
uni.showModal({
title: '提示',
content: '确定要清空所有图片吗?',
success: (res) => {
if (res.confirm) {
modelValue.value = [];
}
}
});
};
// 暴露方法给父组件
defineExpose({ clearAllImg });
</script>
<style lang="scss" scoped>
.upload-file-wrap {
display: flex;
flex-wrap: wrap;
gap: 20rpx; // 图片之间的间距
}
.upload-file-item {
$size: 128rpx;
width: $size;
height: $size;
border: 1px dashed #d9d9d9;
border-radius: 8rpx;
position: relative;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
.close {
position: absolute;
right: -10rpx;
top: -10rpx;
background: #fff;
border-radius: 50%;
padding: 2rpx;
z-index: 10;
}
}
.upload-file-btn {
$size: 128rpx;
width: $size;
height: $size;
display: flex;
justify-content: center;
align-items: center;
border: 1px dashed #d9d9d9;
border-radius: 8rpx;
.icon {
width: 36rpx;
height: 36rpx;
border: 4rpx solid #999;
border-radius: 8rpx;
font-weight: 700;
color: #999;
font-size: 32rpx;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
}
}
</style>

121
http/api/market/point.js Normal file
View File

@@ -0,0 +1,121 @@
import http from '@/http/http.js'
const request = http.request
const MARKET_URL = 'market'
const ORDER_URL = 'order'
/**
* 积分:配置:新增/更新
* @param {Object} data
*/
export function pointsConfigPost(data) {
return request({
url: `${MARKET_URL}/admin/points/config`,
method: "POST",
data
})
}
/**
* 积分:配置:详情
* @param {Object} data
*/
export function pointsConfigGet() {
return request({
url: `${MARKET_URL}/admin/points/config`,
method: "GET"
})
}
/**
* 积分:商品:列表
* @param {Object} data
*/
export function pointsGoodsPage(data) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/page`,
method: "GET",
data
})
}
/**
* 积分:商品:新增/修改
* @param {Object} data
*/
export function pointsGoodsPost(data) {
return request({
url: `${MARKET_URL}/admin/pointsGoods`,
method: "POST",
data
})
}
/**
* 积分-商品-删除
* @param {Object} data
*/
export function pointsGoodsDel(id) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/${id}`,
method: "DELETE"
})
}
/**
* 积分:商品:详情
* @param {Object} data
*/
export function pointsGoodsDetail(id) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/${id}`,
method: "GET"
})
}
/**
* 积分:积分商品:兑换记录
* @param {Object} data
*/
export function goodsRecordPage(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/page`,
method: "GET",
data
})
}
/**
* 积分:积分商品:商家核销
* @param {Object} data
*/
export function goodsRecordCkecout(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/checkout`,
method: "post",
data
})
}
/**
* 积分:积分商品:同意退单
* @param {Object} data
*/
export function goodsRecordAgreeRefund(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/agreeRefund`,
method: "post",
data
})
}
/**
* 积分:积分商品:驳回退单
* @param {Object} data
*/
export function goodsRecordRejectRefund(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/rejectRefund`,
method: "post",
data
})
}

View File

@@ -15,8 +15,8 @@ import infoBox from "@/commons/utils/infoBox.js";
import go from "@/commons/utils/go.js";
import { reject } from "lodash";
// 设置node环境
envConfig.changeEnv(storageManage.env('production')) //正式
// envConfig.changeEnv(storageManage.env("development")); //测试
// envConfig.changeEnv(storageManage.env('production')) //正式
envConfig.changeEnv(storageManage.env("development")); //测试
// 测试服
// #ifdef H5

View File

@@ -0,0 +1,415 @@
<template>
<view class="container">
<u-form ref="formRef" :model="form" :rules="rules" label-position="top">
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">商品类型</text>
</view>
<view class="info">
<u-radio-group v-model="form.goodsCategory">
<u-radio label="优惠券" name="优惠券"></u-radio>
<u-radio label="其它商品" name="其它商品"></u-radio>
</u-radio-group>
</view>
</view>
</u-form-item>
<u-form-item prop="couponId" v-if="includesString(form.goodsCategory, '优惠券')">
<view class="switch-wrap">
<view class="top">
<text class="t">选择优惠券</text>
</view>
<view class="info">
<view class="ipt">
<my-select-coupon v-model="form.couponId"></my-select-coupon>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="goodsName">
<view class="switch-wrap">
<view class="top">
<text class="t">商品名称</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="30" v-model="form.goodsName"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="goodsImageUrl" v-if="includesString(form.goodsCategory, '其它商品')">
<view class="switch-wrap">
<view class="top">
<text class="t">商品图片</text>
</view>
<view class="info">
<my-upload-img v-model="form.goodsImageUrl"></my-upload-img>
</view>
</view>
</u-form-item>
<u-form-item prop="requiredPoints">
<view class="switch-wrap">
<view class="top">
<text class="t">所需积分</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.requiredPoints" @change="requiredPointsInput">
<template #suffix>
<text>积分</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">额外价格</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.extraPrice" @change="extraPriceInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="quantity">
<view class="switch-wrap">
<view class="top">
<text class="t">数量</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.quantity" @change="quantityInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">排序值</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.sort" @change="sortInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">每人限购</text>
<u-switch v-model="isLimitQuota" :active-value="1" :inactive-value="0" @change="isLimitQuotaChange"></u-switch>
</view>
<view class="info" v-if="isLimitQuota == 1">
<view class="ipt">
<u-input placeholder="请输入限购数量" :maxlength="8" v-model="form.limitQuota" @change="limitQuotaInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">发放方式</text>
<text class="intro">
<template class="tips" v-if="includesString(form.goodsCategory, '优惠券')">系统发放</template>
<template class="tips" v-if="includesString(form.goodsCategory, '其它商品')">需要用户到店内领取核销</template>
</text>
</view>
</view>
</u-form-item>
</view>
<view class="card" v-if="includesString(form.goodsCategory, '其它商品')">
<view class="switch-wrap">
<view class="top">
<text class="t">商品图片</text>
</view>
<view class="info">
<my-upload-imgs v-model="imgs"></my-upload-imgs>
</view>
</view>
</view>
<view class="card">
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">是否上架</text>
<u-switch v-model="form.status" :active-value="1" :inactive-value="0"></u-switch>
</view>
</view>
</u-form-item>
</view>
</u-form>
<my-footer-btn type="horizontal" showCancel @confirm="submitHandle"></my-footer-btn>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { pointsGoodsPost, pointsGoodsDetail } from '@/http/api/market/point.js';
import { includesString, filterNumberInput } from '@/utils/index.js';
const formRef = ref(null);
const isLimitQuota = ref(0);
const imgs = ref([]);
const form = ref({
id: '',
goodsCategory: '优惠券', // 商品类型 优惠券 其它商品
couponId: '', // 优惠券id
goodsName: '', // 商品名称/优惠券名称
goodsImageUrl: '', // 商品图片URL
requiredPoints: '', // 所需积分
extraPrice: '', // 额外价格
quantity: '', // 数量
sort: 0,
status: 1, // 是否上架 1-是 0-否
receiveType: '', // 领取方式 店内自取、系统发放
limitQuota: '', // 限购数量
goodsDescription: '' // 商品详情
});
function isLimitQuotaChange() {
form.value.limitQuota = '';
}
const rules = ref({
couponId: [
{
required: true,
trigger: ['blur'],
message: '请选择优惠券',
validator: (rule, value, callback) => {
if (form.value.couponId === '') {
return false;
} else {
return true;
}
}
}
],
goodsName: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.goodsName === '') {
return false;
} else {
return true;
}
}
}
],
goodsImageUrl: [
{
required: true,
trigger: ['change'],
message: '请上传商品封面图',
validator: (rule, value, callback) => {
if (form.value.goodsImageUrl === '') {
return false;
} else {
return true;
}
}
}
],
requiredPoints: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.requiredPoints < 0 || form.value.requiredPoints === '') {
return false;
} else {
return true;
}
}
}
],
quantity: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.quantity < 0 || form.value.quantity === '') {
return false;
} else {
return true;
}
}
}
],
limitQuota: [
{
trigger: ['blur'],
message: '请输入限购数量',
validator: (rule, value, callback) => {
if (isLimitQuota.value == 1 && form.value.limitQuota === '') {
return false;
} else {
return true;
}
}
}
]
});
function requiredPointsInput(e) {
setTimeout(() => {
form.value.requiredPoints = filterNumberInput(e, 1);
}, 50);
}
function extraPriceInput(e) {
setTimeout(() => {
form.value.extraPrice = filterNumberInput(e);
}, 50);
}
function quantityInput(e) {
setTimeout(() => {
form.value.quantity = filterNumberInput(e, 1);
}, 50);
}
function sortInput(e) {
setTimeout(() => {
form.value.sort = filterNumberInput(e, 1);
}, 50);
}
function limitQuotaInput(e) {
setTimeout(() => {
form.value.limitQuota = filterNumberInput(e, 1);
}, 50);
}
// 提交
function submitHandle() {
formRef.value
.validate()
.then(async () => {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
const data = { ...form.value };
if (imgs.value.length > 0) {
data.goodsDescription = JSON.stringify(imgs.value);
}
await pointsGoodsPost(data);
uni.showToast({
title: '保存成功',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1000);
} catch (error) {
console.log(error);
}
uni.hideLoading();
})
.catch(() => {});
}
// 积分:商品:详情
async function pointsGoodsDetailAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const obj = await pointsGoodsDetail(form.value.id);
form.value = obj;
if (obj.goodsDescription !== '') {
imgs.value = JSON.parse(obj.goodsDescription);
}
if (obj.limitQuota !== '' && obj.limitQuota !== null) {
isLimitQuota.value = 1;
} else {
isLimitQuota.value = 0;
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
onLoad((options) => {
if (options.id) {
form.value.id = options.id;
uni.setNavigationBarTitle({
title: '编辑商品'
});
pointsGoodsDetailAjax();
}
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 28upx;
}
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
}
}
</style>

View File

@@ -0,0 +1,142 @@
<!-- 优惠券图标 -->
<template>
<view class="container">
<view class="icon icon1" v-if="props.item[props.typeKey] == 1">
<view class="top">
<text class="i"></text>
<text class="num">{{ props.item.discountAmount }}</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 2">
<view class="top">
<text class="i">{{ props.item.discountNum }}</text>
<text class="num">商品兑换</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon3" v-if="props.item[props.typeKey] == 3">
<view class="top">
<text class="num">{{ props.item.discountRate / 10 }}</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 4">
<view class="top">
<text class="i">第二件</text>
<text class="num">半价券</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 6">
<view class="top">
<text class="i">买一送</text>
<text class="num">一券</text>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
item: {
type: Object,
default: {}
},
typeKey: {
type: String,
default: 'type'
}
});
</script>
<style scoped lang="scss">
$color: #ff1c1c;
.container {
width: 100%;
height: 100%;
.icon {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&.icon1 {
.top {
.i {
color: $color;
font-size: 24upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 72upx;
font-weight: bold;
}
}
}
&.icon2 {
.top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.i {
color: $color;
font-size: 34upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 34upx;
font-weight: bold;
}
}
}
&.icon3 {
.top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.i {
color: $color;
font-size: 34upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 52upx;
font-weight: bold;
}
}
}
.intro {
display: flex;
justify-content: center;
.t {
font-size: 22upx;
color: #999;
}
}
}
}
</style>

View File

@@ -0,0 +1,326 @@
<template>
<view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">排序值{{ item.sort }}</text>
<view class="quantity" @click="showEdtorQuantityHandle(item)">
<text class="t">库存{{ item.quantity }}</text>
<u-icon name="edit-pen" color="#999"></u-icon>
</view>
</view>
<view class="goods-wrap">
<view class="left">
<view class="icon" v-if="includesString(item.goodsCategory, '其它商品')">
<image class="img" :src="item.goodsImageUrl" mode="aspectFill"></image>
</view>
<view class="icon border" v-if="includesString(item.goodsCategory, '优惠券')">
<couponIcon :item="item.couponInfo" typeKey="couponType" />
</view>
<view class="info-wrap">
<text class="name">{{ item.goodsName }}</text>
<text class="num">{{ item.requiredPoints }}积分 + {{ item.extraPrice || 0 }}</text>
</view>
</view>
<view class="status-wrap">
<u-switch v-model="item.status" :active-value="1" :inactive-value="0" @change="statusChange($event, item)"></u-switch>
<text class="t">
<template v-if="item.status == 0">上架</template>
<template v-if="item.status == 1">下架</template>
</text>
</view>
</view>
<view class="footer-wrap">
<view class="btn">
<u-button shape="circle" @click="delHandle(item)">删除</u-button>
</view>
<view class="btn">
<u-button type="primary" shape="circle" @click="toEditor(item)">编辑</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-popup :show="showEdtorQuantity" mode="center" :safeAreaInsetBottom="false" @close="showEdtorQuantity = false">
<view class="quantity-wrap">
<view class="title">
<text class="t">修改库存</text>
</view>
<view class="form-content">
<u-form :model="quantityItem" :rules="quantityFormRules">
<u-form-item label="数量">
<u-input v-model="quantityItem.quantity" placeholder="请输入数量" @change="quantityInput" clearable></u-input>
</u-form-item>
</u-form>
</view>
<view class="quantity-footer">
<view class="btn">
<u-button style="width: 100%" shape="circle" @click="showEdtorQuantity = false">取消</u-button>
</view>
<view class="btn">
<u-button type="primary" style="width: 100%" shape="circle" @click="submitQuntity">确认</u-button>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue';
import { onReachBottom } from '@dcloudio/uni-app';
import { pointsGoodsPage, pointsGoodsPost, pointsGoodsDel } from '@/http/api/market/point.js';
import { filterNumberInput, includesString } from '@/utils/index.js';
import couponIcon from './coupon-icon.vue';
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
pointsGoodsPageAjax();
}
}
// 积分:商品:列表
async function pointsGoodsPageAjax(page = listData.page) {
try {
const res = await pointsGoodsPage({
page: page,
size: listData.size
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
}
// 状态修改
function statusChange(e, item) {
pointsGoodsPostAjax(item);
}
// 积分:商品:新增/修改
async function pointsGoodsPostAjax(item) {
try {
await pointsGoodsPost(item);
pointsGoodsPageAjax();
} catch (error) {
console.log(error);
}
}
// 显示修改库存
const quantityItem = ref({});
const showEdtorQuantity = ref(false);
const quantityFormRules = ref({
quantity: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (quantityItem.value.quantity < 0 || quantityItem.value.quantity === '') {
return false;
} else {
return true;
}
}
}
]
});
function quantityInput(e) {
setTimeout(() => {
quantityItem.value.quantity = filterNumberInput(e, 1);
}, 50);
}
function showEdtorQuantityHandle(item) {
quantityItem.value = { ...item };
showEdtorQuantity.value = true;
}
// 提交库存修改
async function submitQuntity() {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
await pointsGoodsPost(quantityItem.value);
uni.showToast({
title: '保存成功',
icon: 'none'
});
pointsGoodsPageAjax();
showEdtorQuantity.value = false;
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 删除
function delHandle(item) {
uni.showModal({
title: '注意',
content: `确认要删除${item.goodsName}商品吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '删除中...',
mask: true
});
await pointsGoodsDel(item.id);
uni.showToast({
title: '已删除',
icon: 'none'
});
let index = listData.list.findIndex((val) => val.id == item.id);
listData.list.splice(index, 1);
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
});
}
function toEditor(item) {
uni.navigateTo({
url: `/pageMarket/points/addProduct?id=${item.id}`
});
}
defineExpose({
reachBottom,
pointsGoodsPageAjax
});
onMounted(() => {
pointsGoodsPageAjax();
});
</script>
<style scoped lang="scss">
.list {
padding-bottom: 28upx;
.item {
border-radius: 16upx;
background-color: #fff;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
gap: 44upx;
.t {
font-size: 24upx;
color: #666;
}
.quantity {
display: flex;
align-items: center;
gap: 4upx;
}
}
.goods-wrap {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
align-items: center;
.icon {
$size: 180upx;
width: $size;
height: $size;
&.border {
border: 1px solid #ececec;
border-radius: 16upx;
padding: 16upx;
}
.img {
width: 100%;
height: 100%;
border-radius: 16upx;
}
}
.info-wrap {
display: flex;
flex-direction: column;
gap: 28upx;
padding-left: 20upx;
.name {
font-size: 28upx;
font-weight: bold;
color: #333;
}
}
}
.status-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4upx;
.t {
font-size: 24upx;
color: #666;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
.btn {
width: 140upx;
}
}
}
}
.quantity-wrap {
width: 80vw;
.title {
display: flex;
justify-content: center;
padding: 28upx;
.t {
font-size: 32upx;
font-weight: bold;
color: #333;
}
}
.form-content {
padding: 0 28upx;
}
.quantity-footer {
display: flex;
gap: 28upx;
padding: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,414 @@
<template>
<view class="container">
<view class="tab-wrap" :style="{ top: `${top}px` }">
<view class="item" :class="{ active: tabsActive == index }" v-for="(item, index) in tabs" :key="index" @click="changeTabHandle(index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">{{ item.orderNo }}</text>
<u-tag :type="statusFilter(item.status).type" plain plainFill>{{ item.status }}</u-tag>
</view>
<view class="row">
<text class="name">用户{{ item.nickName }} {{ item.phone }}</text>
</view>
<view class="goods-wrap">
<view class="left">
<view class="icon" v-if="includesString(item.goodsCategory, '其它商品')">
<image class="img" :src="item.goodsImageUrl" mode="aspectFill"></image>
</view>
<view class="icon border" v-if="includesString(item.goodsCategory, '优惠券')">
<couponIcon :item="item.couponInfo" typeKey="couponType" />
</view>
<view class="info-wrap">
<text class="name">{{ item.pointsGoodsName }}</text>
<text class="num">x{{ item.number }}</text>
</view>
</view>
<view class="status-wrap">
<text class="t">{{ item.spendPoints }}积分 + {{ item.extraPaymentAmount }}</text>
</view>
</view>
<view class="row">
<text class="t">下单时间{{ item.createTime }}</text>
</view>
<view class="row" v-if="item.checkoutTime">
<text class="t">核销时间{{ item.checkoutTime }}</text>
</view>
<view class="footer-wrap" v-if="includesString(item.status, '退款中') || includesString(item.status, '待核销')">
<view class="btn" v-if="includesString(item.status, '退款中')">
<u-button type="error" shape="circle" @click="refundCostHandle(item)">审核</u-button>
</view>
<view class="btn" v-if="includesString(item.status, '待核销')">
<u-button type="primary" shape="circle" @click="checkoutHandle(item)">核销</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-popup :show="showRefundPopup" :round="20" mode="bottom" closeable @close="showRefundPopup = false">
<view class="refund-popup-content">
<view class="refund-popup-title">
<text class="t">退款审核</text>
</view>
<view class="form">
<u-form :model="refundForm" label-width="70" label-position="left">
<u-form-item label="是否同意">
<u-radio-group v-model="refundForm.type">
<u-radio label="同意" :name="1" :customStyle="{ marginRight: '15px' }"></u-radio>
<u-radio label="驳回" :name="0"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="驳回原因" v-if="refundForm.type === 0">
<u-textarea type="textarea" v-model="refundForm.reason" placeholder="请输入驳回原因"></u-textarea>
</u-form-item>
</u-form>
</view>
<view class="dialog-footer">
<view class="btn">
<u-button @click="showRefundPopup = false" shape="circle" style="width: 100%">取消</u-button>
</view>
<div class="btn">
<u-button type="primary" shape="circle" @click="returnCostConfirmHandle" :loading="refundLoading" style="width: 100%">确认</u-button>
</div>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { goodsRecordPage, goodsRecordCkecout, goodsRecordAgreeRefund, goodsRecordRejectRefund } from '@/http/api/market/point.js';
import { includesString } from '@/utils/index.js';
import couponIcon from './coupon-icon.vue';
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const tabsActive = ref(0);
const tabs = ref([
{
value: '',
label: '全部'
},
{
value: '待核销',
label: '待核销'
},
{
value: '已完成',
label: '已完成'
},
{
value: '退款中',
label: '售后'
}
]);
const statusList = ref([
{
label: '待支付',
type: 'info'
},
{
label: '待核销',
type: 'warning'
},
{
label: '已完成',
type: 'info'
},
{
label: '退款中',
type: 'error'
},
{
label: '已退款',
type: 'info'
}
]);
function statusFilter(status) {
const obj = statusList.value.find((item) => item.label == status);
if (obj) {
return obj;
} else {
return {
label: status,
type: 'info'
};
}
}
// 切换tab
function changeTabHandle(index) {
tabsActive.value = index;
listData.status = 'loading';
listData.page = 1;
listData.list = [];
goodsRecordPageAjax();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
goodsRecordPageAjax();
}
}
// 确认核销
function checkoutHandle(item) {
uni.showModal({
title: '注意',
content: `确认要核销吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '核销中...',
mask: true
});
await goodsRecordCkecout(item.couponCode);
goodsRecordPageAjax();
uni.showToast({
title: '已核销',
icon: 'none'
});
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
});
}
const refundLoading = ref(false);
const showRefundPopup = ref(false);
const refundForm = ref({
type: 1,
recordId: '',
orderNo: '',
reason: ''
});
// 显示退款操作
function refundCostHandle(row) {
refundForm.value.recordId = row.id;
refundForm.value.orderNo = row.orderNo;
showRefundPopup.value = true;
}
// 退款操作
async function returnCostConfirmHandle() {
try {
refundLoading.value = true;
if (refundForm.value.type == 1) {
// 同意
await goodsRecordAgreeRefund(refundForm.value);
} else {
// 驳回
await goodsRecordRejectRefund(refundForm.value);
}
showRefundPopup.value = false;
uni.showToast({
title: '操作成功',
icon: 'none'
});
goodsRecordPageAjax();
} catch (error) {
console.log(error);
}
setTimeout(() => {
refundLoading.value = false;
}, 500);
}
// 积分:积分商品:兑换记录
async function goodsRecordPageAjax(page = listData.page) {
try {
const res = await goodsRecordPage({
page: page,
size: listData.size,
status: tabs.value[tabsActive.value].value
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
}
defineExpose({
reachBottom,
goodsRecordPageAjax
});
onMounted(() => {
goodsRecordPageAjax();
});
</script>
<style scoped lang="scss">
.container {
padding-top: 50px;
}
.tab-wrap {
$color: #318afe;
width: 100%;
height: 50px;
position: fixed;
left: 0;
background-color: #fff;
display: flex;
z-index: 999;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #fff;
.t {
font-size: 28upx;
color: #333;
}
&.active {
border-bottom-color: $color;
.t {
color: $color;
}
}
}
}
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 28upx;
color: #999;
}
}
.row {
.name {
font-size: 28upx;
color: #333;
}
.t {
font-size: 24upx;
color: #999;
}
}
.goods-wrap {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
align-items: center;
.icon {
$size: 180upx;
width: $size;
height: $size;
&.border {
border: 1px solid #ececec;
border-radius: 16upx;
padding: 16upx;
}
.img {
width: 100%;
height: 100%;
border-radius: 16upx;
}
}
.info-wrap {
display: flex;
flex-direction: column;
gap: 28upx;
padding-left: 20upx;
.name {
font-size: 28upx;
font-weight: bold;
color: #333;
}
}
}
.status-wrap {
display: flex;
align-items: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
padding-top: 28upx;
.btn {
width: 180upx;
}
}
}
}
.refund-popup-content {
padding: 0 28upx;
.refund-popup-title {
padding: 28upx;
display: flex;
justify-content: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.form {
padding: 28upx;
}
.dialog-footer {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<view class="form">
<u-form ref="formRef" :model="form" :rules="rules" label-width="200px" label-position="top">
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">开启积分</text>
<u-switch v-model="form.enableRewards" :active-value="1" :inactive-value="0"></u-switch>
</view>
<view class="info">
<text class="t">开启后所有用户可通过消费获得积分及可用积分抵扣支付</text>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="consumeAmount">
<view class="switch-wrap">
<view class="top">
<text class="t">消费送积分</text>
</view>
<view class="info">
<text class="i">每消费</text>
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.consumeAmount" @change="consumeAmountInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
<text class="i">获得1积分</text>
</view>
</view>
</u-form-item>
<u-form-item prop="minPaymentAmount">
<view class="switch-wrap">
<view class="top">
<text class="t">可抵扣门槛</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.minPaymentAmount" @change="minPaymentAmountInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="maxDeductionRatio">
<view class="switch-wrap">
<view class="top">
<text class="t">最高抵扣比例</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.maxDeductionRatio" @change="maxDeductionRatioInput">
<template #suffix>
<text>%</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="equivalentPoints">
<view class="switch-wrap">
<view class="top">
<text class="t">抵扣积分比例</text>
</view>
<view class="info">
<text class="i">1元等于</text>
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.equivalentPoints" @change="equivalentPointsInput">
<template #suffix>
<text>积分</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">开启积分商城</text>
<u-switch v-model="form.enablePointsMall" :active-value="1" :inactive-value="0"></u-switch>
</view>
</view>
</u-form-item>
</view>
</u-form>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { filterNumberInput } from '@/utils/index.js';
import { pointsConfigPost, pointsConfigGet } from '@/http/api/market/point.js';
const formRef = ref(null);
const form = ref({
enableRewards: 0,
consumeAmount: '', // 每消费xx元赠送1积分
minPaymentAmount: '', // 下单实付抵扣门槛
maxDeductionRatio: 100, // 下单最高抵扣比例
equivalentPoints: '', // 下单抵扣积分比例 1元=?积分
enablePointsMall: 0 // 开启积分商城
});
const rules = ref({
consumeAmount: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.consumeAmount < 0 || form.value.consumeAmount === '') {
return false;
} else {
return true;
}
}
}
],
minPaymentAmount: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.minPaymentAmount < 0 || form.value.minPaymentAmount === '') {
return false;
} else {
return true;
}
}
}
],
maxDeductionRatio: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.maxDeductionRatio < 0 || form.value.maxDeductionRatio === '') {
return false;
} else {
return true;
}
}
}
],
equivalentPoints: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.equivalentPoints < 0 || form.value.equivalentPoints === '') {
return false;
} else {
return true;
}
}
}
]
});
function consumeAmountInput(e) {
setTimeout(() => {
form.value.consumeAmount = filterNumberInput(e);
}, 50);
}
function minPaymentAmountInput(e) {
setTimeout(() => {
form.value.minPaymentAmount = filterNumberInput(e);
}, 50);
}
function maxDeductionRatioInput(e) {
setTimeout(() => {
form.value.maxDeductionRatio = filterNumberInput(e, 1);
if (form.value.maxDeductionRatio > 100) {
form.value.maxDeductionRatio = 100;
}
}, 50);
}
function equivalentPointsInput(e) {
setTimeout(() => {
form.value.equivalentPoints = filterNumberInput(e, 1);
}, 50);
}
// 提交
function submitHandle() {
formRef.value
.validate()
.then(async () => {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
await pointsConfigPost(form.value);
uni.showToast({
title: '保存成功',
icon: 'none'
});
} catch (error) {
console.log(error);
}
uni.hideLoading();
})
.catch(() => {});
}
// 获取配置
async function pointsConfigGetAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const res = await pointsConfigGet();
form.value = res;
} catch (error) {
console.log(error);
}
setTimeout(() => {
uni.hideLoading();
}, 300);
}
defineExpose({
submitHandle
});
onMounted(() => {
pointsConfigGetAjax();
});
</script>
<style scoped lang="scss">
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
}
}
</style>

155
pageMarket/points/index.vue Normal file
View File

@@ -0,0 +1,155 @@
<template>
<view class="container">
<my-header-card
:options="{
name: '积分锁客',
intro: '下单可获得积分,再次下单可使用积分抵扣',
icon: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/11e5482336a94c2a91a10e2bf289126e.png'
}"
@load="(e) => (headHeight = e.height)"
></my-header-card>
<view class="content">
<setting ref="settingRef" name="setting" key="setting" v-if="tabsActive == 0" />
<productPage ref="productPageRef" name="productPage" key="productPage" v-if="tabsActive == 1" />
<record ref="recordRef" name="record" key="record" :top="headHeight + 54" v-if="tabsActive == 2" />
</view>
<view class="tab-wrap" :style="{ top: `${headHeight}px` }">
<view class="tab-list">
<view class="item" v-for="(item, index) in tabs" :class="{ active: tabsActive == index }" :key="item.value" @click="tabClickHandle(item, index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<my-footer-btn type="horizontal" showCancel @confirm="settingRef.submitHandle()" @cancel="backHandle" v-if="tabsActive == 0"></my-footer-btn>
<my-footer-btn confirmText="添加商品" type="horizontal" @confirm="toAddProduct" v-if="tabsActive == 1"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { onLoad, onShow, onReachBottom } from '@dcloudio/uni-app';
import setting from './components/setting.vue';
import productPage from './components/productPage.vue';
import record from './components/record.vue';
const settingRef = ref(null);
const productPageRef = ref(null);
const recordRef = ref(null);
const headHeight = ref(0);
const tabsActive = ref(2);
const tabs = ref([
{
value: 1,
label: '基础设置'
},
{
value: 2,
label: '商品设置'
},
{
value: 3,
label: '兑换记录'
},
{
value: 4,
label: '用户积分'
}
]);
function tabClickHandle(item, index) {
tabsActive.value = index;
}
function backHandle() {
uni.navigateBack();
}
function toAddProduct() {
uni.navigateTo({
url: '/pageMarket/points/addProduct'
});
}
onReachBottom(() => {
switch (tabsActive.value) {
case 0:
break;
case 1:
productPageRef.value.reachBottom();
break;
case 2:
recordRef.value.reachBottom();
break;
case 3:
break;
default:
break;
}
});
onShow(() => {
switch (tabsActive.value) {
case 0:
break;
case 1:
productPageRef.value.pointsGoodsPageAjax(1);
break;
case 2:
recordRef.value.goodsRecordPageAjax(1);
break;
case 3:
break;
default:
break;
}
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
$bgColor: #e6f0ff;
$primarColor: #318afe;
.content {
padding: calc(54px + 28upx) 28upx 28upx;
}
.tab-wrap {
height: 54px;
padding: 10px 14px;
background-color: #fff;
width: 100%;
position: fixed;
left: 0;
z-index: 999;
.tab-list {
width: 100%;
height: 100%;
display: flex;
background-color: $bgColor;
padding: 6upx;
border-radius: 12upx;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8upx;
.t {
color: $primarColor;
font-size: 28upx;
}
&.active {
background-color: $primarColor;
.t {
color: #fff;
}
}
}
}
}
</style>

View File

@@ -808,9 +808,21 @@
"style": {
// "navigationBarTitleText": "添加会员等级"
}
},
{
"pageId": "PAGES_MARKET_POINTS_INDEX",
"path": "points/index",
"style": {
"navigationBarTitleText": "积分锁客"
}
},
{
"pageId": "PAGES_MARKET_POINTS_ADD_PRODUCT",
"path": "points/addProduct",
"style": {
"navigationBarTitleText": "添加商品"
}
}
]
},
{
@@ -861,7 +873,7 @@
// "navigationBarTitleText": "查看优惠券"
// }
// }
// ]
// }

View File

@@ -40,7 +40,7 @@ const menuList = ref([
{
title: '积分锁客',
icon: '',
pageUrl: '',
pageUrl: 'PAGES_MARKET_POINTS_INDEX',
intro: '设置充值消费的N倍当前订单立即免单'
},
{
@@ -219,21 +219,19 @@ const menuList = ref([
]
}
]);
console.log(menusStore.adminPages);
// console.log(
// 'menusStore.adminPages===',
// menusStore.adminPages.map((item) => item.title)
// );
const computedMenus = computed(() => {
// const arr = menusStore.adminPages.filter((v) => {
// return navList.find((navItem) => navItem.title == v.title);
// });
return menuList.value.map((v) => {
v.menus = v.menus.filter((menu) => {
const hasPermission = menusStore.adminPages.find((navItem) => navItem.title == menu.title);
console.log('hasPermission', hasPermission);
if (hasPermission) {
menu.icon = hasPermission.miniIcon;
console.log(menu);
}
return hasPermission;
});
return v;

View File

@@ -71,4 +71,47 @@ export const convertTimeFormat = (timeStr) => {
// 非法格式兜底
return '00:00';
};
};
/**
* 判断目标值是否包含指定字符串
* @param {string|number|array} target - 目标值(字符串/数字/数组)
* @param {string} searchStr - 要查找的子字符串
* @param {object} [options] - 可选配置
* @param {boolean} [options.ignoreCase=false] - 是否忽略大小写
* @param {boolean} [options.allowEmpty=false] - 当 searchStr 为空时,是否返回 true默认 false
* @returns {boolean} 是否包含指定字符串
*/
export function includesString(target, searchStr, options = {}) {
// 解构配置,设置默认值
const {
ignoreCase = false, allowEmpty = false
} = options;
// 1. 处理 searchStr 为空的情况
if (searchStr === '' || searchStr === null || searchStr === undefined) {
return allowEmpty;
}
// 2. 统一将目标值转为字符串(兼容数字/数组等)
let targetStr = '';
if (typeof target === 'string') {
targetStr = target;
} else if (typeof target === 'number') {
targetStr = String(target);
} else if (Array.isArray(target)) {
// 数组:拼接为字符串(也可改为 "数组中某一项包含",根据需求调整)
targetStr = target.join(',');
} else {
// 其他类型(对象/布尔等):转为字符串或返回 false
targetStr = String(target);
}
// 3. 处理大小写忽略
const processedTarget = ignoreCase ? targetStr.toLowerCase() : targetStr;
const processedSearch = ignoreCase ? searchStr.toLowerCase() : searchStr;
// 4. 执行包含判断
return processedTarget.includes(processedSearch);
}