新增积分锁客页面,基础设置,商品设置,兑换记录
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -83,6 +83,7 @@ onMounted(() => {
|
||||
padding-left: 20upx;
|
||||
flex-direction: column;
|
||||
.title {
|
||||
margin-top: -4upx;
|
||||
.t {
|
||||
font-size: 28upx;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -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>
|
||||
|
||||
181
components/my-components/my-upload-imgs.vue
Normal file
181
components/my-components/my-upload-imgs.vue
Normal 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>
|
||||
Reference in New Issue
Block a user