新增限时折扣并增加头部底部按钮公共组件

This commit is contained in:
gyq
2025-11-19 10:23:17 +08:00
parent 4567206abb
commit fbcb67440b
9 changed files with 529 additions and 169 deletions

View File

@@ -0,0 +1,55 @@
<template>
<view class="fixed-wrap" :style="{ '--num': showCancel ? '63px' : '0px' }">
<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>
</template>
<script setup>
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
const props = defineProps({
confirmText: {
type: String,
default: '添加'
},
showCancel: {
type: Boolean,
default: false
},
shape: {
type: String,
default: 'squre' // squre circle
}
});
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;
padding: 10px 14px calc(20px + env(safe-area-inset-bottom) / 2) 10px;
background-color: #fff;
.btn {
&:first-child {
margin-bottom: 14px;
}
}
}
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<view class="item-doc" :style="{ height: headHeight + 'px' }">
<view class="item" :style="{ height: headHeight + 'px' }">
<view class="left">
<image :src="`/static/applocation/${options.icon}.png`" mode="aspectFit" class="icon"></image>
<view class="info">
<view class="title">
<text class="t">{{ options.name }}</text>
</view>
<view class="intro">
<text class="t">{{ options.intro }}</text>
</view>
</view>
</view>
<view class="right" v-if="showSwitch">
<u-switch :active-value="1" :inactive-value="0" v-model="isOpen" @change="defineEmits(['update:isOpen'])"></u-switch>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
const props = defineProps({
options: {
type: Object,
default: {
name: '标题',
intro: '说明',
icon: 'xszk'
}
},
showSwitch: {
type: Boolean,
default: false
}
});
const headHeight = ref(70);
const isOpen = defineModel('isOpen', {
type: [Boolean, String, Number],
default: 0
});
const emits = defineEmits(['load']);
onMounted(() => {
nextTick(() => {
emits('load', { height: headHeight.value });
});
});
</script>
<style scoped lang="scss">
.item-doc {
width: 100%;
}
.item {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 99;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
padding: 20upx 28upx;
.left {
display: flex;
.icon {
$size: 80upx;
width: $size;
height: $size;
flex-shrink: 0;
}
.info {
display: flex;
padding-left: 20upx;
flex-direction: column;
.title {
.t {
font-size: 28upx;
font-weight: bold;
}
}
.intro {
.t {
font-size: 28upx;
color: #999;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,134 @@
<template>
<view>
<up-radio-group v-model="useType" placement="row">
<up-radio v-for="item in useTypeList" :key="item.value" :name="item.value" :label="item.label" :customStyle="customStyle"></up-radio>
</up-radio-group>
<view class="box" v-if="useType == 'custom'" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="selShops.length == 0">请选择</text>
<view class="u-font-24 shop-item u-flex u-p-r-20 u-col-center u-p-r-16" v-for="(item, index) in selShops" :key="item.shopId">
<text>{{ returnShopName(item) }}</text>
<view @click.stop="delShop(index)">
<up-icon name="close" size="14" @click="delShop(index)" color="#999"></up-icon>
</view>
</view>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择门店</view>
<scroll-view class="scroll-view u-m-t-30" scroll-y="true" max-height="40vh">
<view class="u-m-b-10" v-for="item in list" :key="item.shopId">
<up-checkbox usedAlone :name="item.shopId" :label="item.shopName" v-model:checked="item.checked"></up-checkbox>
</view>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import { onMounted, reactive, ref, watch } from 'vue';
import { getShopList } from '@/http/api/shop.js';
const customStyle = ref({
marginRight: '15px'
});
const show = ref(false);
const selShops = defineModel('selShops', {
default: () => [],
type: [Array, String]
});
const useType = defineModel('useType', {
default: () => 'only',
type: String
});
const useTypeList = [
{
value: 'only',
label: '仅本店'
},
{
value: 'all',
label: '全部门店'
},
{
value: 'custom',
label: '指定门店可用'
}
];
function returnShopName(shopId) {
const item = list.value.find((v) => v.shopId == shopId);
return item?.shopName || '';
}
function close() {
show.value = false;
}
function submit() {
selShops.value = list.value.filter((item) => item.checked).map((v) => v.shopId);
show.value = false;
}
function delShop(index) {
selShops.value.splice(index, 1);
}
const list = ref([]);
function openPopup() {
show.value = true;
list.value = list.value.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId)
}));
}
async function init() {
const res = await getShopList();
console.log('selShops.value', selShops.value);
if (res) {
list.value = res.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId)
}));
}
}
onMounted(init);
</script>
<style lang="scss">
.box {
border-radius: 8upx;
margin-top: 16rpx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 10rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
</style>

View File

@@ -1,157 +1,130 @@
<template>
<view>
<up-radio-group v-model="useType" placement="row">
<up-radio
v-for="item in useTypeList"
:key="item.value"
:name="item.value"
:label="item.label"
></up-radio>
</up-radio-group>
<view class="box" v-if="useType == 'part'" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16">请选择</text>
<view
class="u-font-24 shop-item u-flex u-p-r-20 u-col-center u-p-r-16"
v-for="(item, index) in selShops"
:key="item.shopId"
>
<text>{{ returnShopName(item) }}</text>
<view @click.stop="delShop(index)">
<up-icon
name="close"
size="14"
@click="delShop(index)"
color="#999"
></up-icon>
</view>
</view>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup
:show="show"
placement="bottom"
round="18rpx"
closeOnClickOverlay
@close="close"
>
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择门店</view>
<scroll-view
class="scroll-view u-m-t-30"
scroll-y="true"
max-height="40vh"
>
<view class="u-m-b-10" v-for="item in list" :key="item.shopId">
<up-checkbox
usedAlone
:name="item.shopId"
:label="item.shopName"
v-model:checked="item.checked"
></up-checkbox>
</view>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
<view>
<up-radio-group v-model="useType" placement="row">
<up-radio v-for="item in useTypeList" :key="item.value" :name="item.value" :label="item.label" :customStyle="customStyle"></up-radio>
</up-radio-group>
<view class="box" v-if="useType == 'part'" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="selShops.length == 0">请选择</text>
<view class="u-font-24 shop-item u-flex u-p-r-20 u-col-center u-p-r-16" v-for="(item, index) in selShops" :key="item.shopId">
<text>{{ returnShopName(item) }}</text>
<view @click.stop="delShop(index)">
<up-icon name="close" size="14" @click="delShop(index)" color="#999"></up-icon>
</view>
</view>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择门店</view>
<scroll-view class="scroll-view u-m-t-30" scroll-y="true" max-height="40vh">
<view class="u-m-b-10" v-for="item in list" :key="item.shopId">
<up-checkbox usedAlone :name="item.shopId" :label="item.shopName" v-model:checked="item.checked"></up-checkbox>
</view>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import { getShopList } from "@/http/api/shop.js";
import { onMounted, reactive, ref, watch } from 'vue';
import { getShopList } from '@/http/api/shop.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
const selShops = defineModel("selShops", {
default: () => [],
type: Array,
const selShops = defineModel('selShops', {
default: () => [],
type: Array
});
const useType = defineModel("useType", {
default: () => "all",
type: String,
const useType = defineModel('useType', {
default: () => 'all',
type: String
});
const useTypeList = [
{
value: "all",
label: "全部门店",
},
{
value: "part",
label: "指定门店可用",
},
{
value: 'all',
label: '全部门店'
},
{
value: 'part',
label: '指定门店可用'
}
];
function returnShopName(shopId) {
const item = list.value.find((v) => v.shopId == shopId);
return item?.shopName || "";
const item = list.value.find((v) => v.shopId == shopId);
return item?.shopName || '';
}
function close() {
show.value = false;
show.value = false;
}
function submit() {
selShops.value = list.value
.filter((item) => item.checked)
.map((v) => v.shopId);
show.value = false;
selShops.value = list.value.filter((item) => item.checked).map((v) => v.shopId);
show.value = false;
}
function delShop(index) {
selShops.value.splice(index, 1);
selShops.value.splice(index, 1);
}
const list = ref([]);
function openPopup() {
show.value = true;
list.value = list.value.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId),
}));
show.value = true;
list.value = list.value.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId)
}));
}
async function init() {
const res = await getShopList();
console.log("selShops.value", selShops.value);
const res = await getShopList();
console.log('selShops.value', selShops.value);
if (res) {
list.value = res.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId),
}));
}
if (res) {
list.value = res.map((item) => ({
shopId: item.shopId,
shopName: item.shopName,
checked: selShops.value.includes(item.shopId)
}));
}
}
onMounted(init);
</script>
<style lang="scss">
.box {
margin-top: 16rpx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 10rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
border-radius: 8upx;
margin-top: 16rpx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 10rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
</style>
</style>

View File

@@ -0,0 +1,22 @@
<template>
<u-radio-group v-model="modelValue" @change="defineEmits(['update:modelValue'])">
<u-radio label="仅本店" name="only" :customStyle="customStyle"></u-radio>
<u-radio label="全部" name="all" :customStyle="customStyle"></u-radio>
<u-radio label="指定" name="custom" :customStyle="customStyle"></u-radio>
</u-radio-group>
</template>
<script setup>
import { ref } from 'vue';
const customStyle = ref({
marginRight: '20px'
});
const modelValue = defineModel('modelValue', {
type: String,
default: 'only'
});
</script>
<style></style>