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

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>