refactor: 重构多个组件以支持 Vue 3 语法和功能

- 将多个组件转换为 `<script setup>` 语法,提升可读性和性能
- 优化状态管理和事件处理逻辑,简化代码结构
- 更新样式和布局以适应新组件结构
- 添加新功能和修复已知问题,提升用户体验
This commit is contained in:
Yer11214
2026-07-08 18:37:11 +08:00
parent 4d0b0fb5e4
commit 349ffa4f34
189 changed files with 18278 additions and 20758 deletions

View File

@@ -64,88 +64,82 @@
</view>
</template>
<script>
import * as API_Members from "@/api/members.js";
export default {
data() {
return {
lightColor: this.$lightColor,
commDetail: {},
readMoreInited: {},
grade: "",
params: {
pageNumber: 1,
pageSize: 10,
grade: "",
},
};
},
props: {
goodsDetail: {
default: () => ({}),
type: Object,
},
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import * as API_Members from '@/api/members.js'
import { noPassByName } from '@/utils/filters.js'
import { useStore } from '@/store'
const props = defineProps<{
goodsDetail?: Record<string, any>
}>()
const store = useStore()
const lightColor = computed(() => store.getters.lightColor)
const commDetail = ref<any>({})
const readMoreInited = ref<Record<number, boolean>>({})
const grade = ref('')
const params = {
pageNumber: 1,
pageSize: 10,
grade: '',
}
const uReadMore = ref<any>()
watch(
() => props.goodsDetail,
(val) => {
if (val && val.goodsId) {
grade.value = val.grade
getGoodsCommentsMethods()
}
},
{ deep: true, immediate: true }
)
watch: {
goodsDetail: {
handler(val) {
if (val && val.goodsId) {
this.grade = val.grade;
this.getGoodsCommentsMethods();
}
},
deep: true,
immediate: true,
},
},
function commentImages(item: any) {
const images = item.images || item.image
if (!images) return []
return typeof images === 'string' ? images.split(',').filter(Boolean) : images
}
methods: {
commentImages(item) {
const images = item.images || item.image;
if (!images) return [];
return typeof images === "string" ? images.split(",").filter(Boolean) : images;
},
function initReadMore(index: number) {
if (readMoreInited.value[index]) return
readMoreInited.value[index] = true
nextTick(() => {
const refs = uReadMore.value
const target = Array.isArray(refs) ? refs[index] : refs
target?.init?.()
})
}
initReadMore(index) {
if (this.readMoreInited[index]) return;
this.readMoreInited[index] = true;
this.$nextTick(() => {
const refs = this.$refs.uReadMore;
const target = Array.isArray(refs) ? refs[index] : refs;
target?.init?.();
});
},
function getGoodsCommentsMethods() {
if (!props.goodsDetail?.goodsId) return
API_Members.getGoodsComments(props.goodsDetail.goodsId, params).then((res) => {
readMoreInited.value = {}
commDetail.value = res.data.result || {}
nextTick(() => {
const records = commDetail.value.records || []
records.slice(0, 2).forEach((_: any, index: number) => {
setTimeout(() => initReadMore(index), 50 * (index + 1))
})
})
})
}
getGoodsCommentsMethods() {
if (!this.goodsDetail.goodsId) return;
API_Members.getGoodsComments(this.goodsDetail.goodsId, this.params).then((res) => {
this.readMoreInited = {};
this.commDetail = res.data.result || {};
this.$nextTick(() => {
const records = this.commDetail.records || [];
records.slice(0, 2).forEach((_, index) => {
setTimeout(() => this.initReadMore(index), 50 * (index + 1));
});
});
});
},
function toComment(id: string | number, gradeVal: string | number) {
uni.navigateTo({
url: `/pages/product/comment?id=${id}&grade=${gradeVal}`,
})
}
toComment(id, grade) {
uni.navigateTo({
url: `/pages/product/comment?id=${id}&grade=${grade}`,
});
},
previewImg(urls, index) {
uni.previewImage({
urls,
indicator: "number",
current: index,
});
},
},
};
function previewImg(urls: string[], index: number) {
uni.previewImage({
urls,
indicator: 'number',
current: index,
})
}
</script>
<style lang="scss" scoped>

View File

@@ -2,12 +2,9 @@
</template>
<script>
export default {
}
<script setup lang="ts">
</script>
<style lang="scss" scoped>
</style>
</style>

View File

@@ -39,30 +39,32 @@
</view>
</template>
<script>
import { getGoodsMessage } from "@/api/goods";
export default {
data() {
return {
goodsDetail: "",
style: {
img:"display:block;width:100%"
}
};
},
props: ["res", "goodsId", "goodsParams"],
computed: {
hasDetailContent() {
return !!(this.res?.mobileIntro || (this.goodsParams && this.goodsParams.length));
},
},
async mounted() {
let res = await getGoodsMessage(this.goodsId);
if (res.data.success) {
this.goodsDetail = res.data.result;
}
},
};
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { getGoodsMessage } from '@/api/goods'
const props = defineProps<{
res?: any
goodsId?: string | number
goodsParams?: any[]
}>()
const goodsDetail = ref('')
const style = {
img: 'display:block;width:100%',
}
const hasDetailContent = computed(() => {
return !!(props.res?.mobileIntro || (props.goodsParams && props.goodsParams.length))
})
onMounted(async () => {
if (!props.goodsId) return
const res = await getGoodsMessage(props.goodsId)
if (res.data.success) {
goodsDetail.value = res.data.result
}
})
</script>
<style lang="scss" scoped>

View File

@@ -5,14 +5,12 @@
</view>
</template>
<script>
<script setup lang="ts">
import goodsList from '@/components/m-goods-list/list.vue'
export default {
props: ["res"],
components:{goodsList},
methods: {
}
};
defineProps<{
res?: any[]
}>()
</script>
<style lang="scss" scoped>

View File

@@ -27,30 +27,33 @@
<view class="swiper-dots">{{ current }}/{{ video ? res.length + 1 : res.length }}</view>
</view>
</template>
<script>
export default {
data() {
return {
current: 1,
html: ""
};
},
props: ["res", 'video'],
watch: {
video(val) {
this.html = '<video muted="muted" ref="videoPlay" style="width:100%; height:100%;" src=' + val + ' page-gesture show-mute-btn autoplay webkit-playsinline="" playsinline="" ></video>'
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps<{
res?: string[]
video?: string
}>()
const current = ref(1)
const html = ref('')
watch(
() => props.video,
(val) => {
if (val) {
html.value =
'<video muted="muted" ref="videoPlay" style="width:100%; height:100%;" src=' +
val +
' page-gesture show-mute-btn autoplay webkit-playsinline="" playsinline="" ></video>'
}
},
methods: {
// 轮播图对应的dot
swiperChange(e) {
this.current = e.detail.current + 1;
},
},
mounted() {
console.log(this.video)
}
};
{ immediate: true }
)
function swiperChange(e: any) {
current.value = e.detail.current + 1
}
</script>
<style lang="scss" scoped>
.carousel {

View File

@@ -1,6 +1,13 @@
.showBack {
margin-top: calc( var(--status-bar-height) + 20px ) !important;
}
margin-top: calc(var(--status-bar-height) + 20px) !important;
}
/* 商品详情底栏改为 flex 布局后,避免全局 mp-iphonex-bottom 把高度撑乱 */
.page-bottom.mp-iphonex-bottom {
height: auto !important;
min-height: 100rpx;
box-sizing: border-box;
padding-top: 10rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}

View File

@@ -26,83 +26,81 @@
<m-city :provinceData="cityList" headTitle="区域选择" ref="cityPicker" pickerSize="4"></m-city>
</u-popup>
</template>
<script>
import setup from "@/components/m-buy/popup.js";
/************请求存储***************/
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import setup from '@/components/m-buy/popup.js'
import * as API_Address from '@/api/address.js'
import { clearStrComma, isLogin } from '@/utils/filters.js'
import * as API_Address from "@/api/address.js";
export default {
data() {
return {
checked: "",
setup,
addressDetail: "",
cityList: [
{
id: "",
localName: "请选择",
children: [],
},
],
};
const props = defineProps<{
goodsId?: string | number
addressFlag?: boolean
}>()
const emit = defineEmits<{
closeAddress: [val: boolean]
deliveryData: [val: any]
}>()
const checked = ref<any>('')
const addressDetail = ref<any>('')
const cityList = ref([
{
id: '',
localName: '请选择',
children: [],
},
props: ["goodsId", "addressFlag"],
])
mounted() {
if (this.isLogin("auth")) {
this.getShippingAddress();
} else {
uni.navigateTo({
url: "/pages/passport/login",
});
}
},
onMounted(() => {
if (isLogin('auth')) {
getShippingAddress()
} else {
uni.navigateTo({
url: '/pages/passport/login',
})
}
})
methods: {
/**关闭地址 */
closeAddress() {
this.$emit("closeAddress", false);
this.$emit("deliveryData", this.checked);
},
function closeAddress() {
emit('closeAddress', false)
emit('deliveryData', checked.value)
}
getpicker() {
uni.navigateTo({
url: "/pages/mine/address/add",
});
this.closeAddress();
},
function getpicker() {
uni.navigateTo({
url: '/pages/mine/address/add',
})
closeAddress()
}
/**获取地址 */
getShippingAddress() {
if (this.isLogin("auth")) {
API_Address.getAddressList(1, 50).then((res) => {
if (res.data.success) {
this.addressDetail = res.data.result.records;
let addr = res.data.result.records.filter((item) => {
return item.isDefault == 1;
});
function getShippingAddress() {
if (isLogin('auth')) {
API_Address.getAddressList(1, 50).then((res) => {
if (res.data.success) {
addressDetail.value = res.data.result.records
const addr = res.data.result.records.filter((item: any) => {
return item.isDefault == 1
})
if (addr[0]) {
this.checked = addr[0];
this.$emit("deliveryData", this.checked);
}
}
});
if (addr[0]) {
checked.value = addr[0]
emit('deliveryData', checked.value)
}
}
},
})
}
}
/**点击地址返回父级商品状态 */
clickAddress(val) {
this.checked = val;
function clickAddress(val: any) {
checked.value = val
this.addressDetail.forEach((item) => {
item.isDefault = false;
});
val.isDefault = !val.isDefault;
this.$emit("deliveryData", this.checked);
},
},
};
addressDetail.value.forEach((item: any) => {
item.isDefault = false
})
val.isDefault = !val.isDefault
emit('deliveryData', checked.value)
}
</script>
<style lang="scss" scoped>
.light {
@@ -165,4 +163,4 @@ export default {
line-height: 90rpx;
font-size: 34rpx;
}
</style>
</style>

View File

@@ -23,66 +23,57 @@
</view>
</template>
<script>
import * as API_Promotions from "@/api/promotions";
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import * as API_Promotions from '@/api/promotions'
import configs from '@/config/config'
export default {
data() {
return {
configs,
userImage:configs.defaultUserPhoto,
import { noPassByName } from '@/utils/filters.js'
import { useStore } from '@/store'
joinBtnStyle: {
background: this.$lightColor,
color: "#fff",
},
/** 待成团订单 */
assembleOrder: "",
const props = defineProps<{
res?: any
}>()
/** 查看更多待成团订单 */
assembleOrderAll: "",
};
},
props: ["res"],
const emit = defineEmits<{
'to-assemble-buy-now': [order: any]
}>()
watch: {
res: {
handler() {
if (this.res && this.res.length != 0) {
Object.keys(this.res).forEach((item) => {
let key = item.split("-");
if (key && key[0] == "PINTUAN") {
this.getAssembleInfo(item);
}
});
const store = useStore()
const userImage = configs.defaultUserPhoto
const assembleOrder = ref<any[]>([])
const joinBtnStyle = computed(() => ({
background: store.getters.lightColor,
color: '#fff',
}))
watch(
() => props.res,
(res) => {
if (res && res.length != 0) {
Object.keys(res).forEach((item) => {
const key = item.split('-')
if (key && key[0] == 'PINTUAN') {
getAssembleInfo(item)
}
},
immediate: true,
},
// assembleOrder(val) {
// this.$emit("assembleOrder", val);
// },
})
}
},
computed: {},
mounted() {},
methods: {
// 获取此商品所有待成团的订单
getAssembleInfo(val) {
let id = this.res[val].id;
API_Promotions.getPromotionGroupMember(id).then((res) => {
if (res.data.success) {
console.warn(res.data.result);
this.assembleOrder = res.data.result;
}
});
},
{ immediate: true }
)
buy(order) {
this.$emit("to-assemble-buy-now", order);
},
},
};
function getAssembleInfo(val: string) {
const id = props.res[val].id
API_Promotions.getPromotionGroupMember(id).then((res) => {
if (res.data.success) {
assembleOrder.value = res.data.result
}
})
}
function buy(order: any) {
emit('to-assemble-buy-now', order)
}
</script>
<style lang="scss" scoped>

View File

@@ -106,119 +106,118 @@
</view>
</template>
<script>
export default {
data() {
return {
countdownData: {},
countdownTimer: null,
};
<script setup lang="ts">
import { computed, onUnmounted, ref, watch } from 'vue'
import { goodsFormatPrice } from '@/utils/filters.js'
const props = defineProps<{
res?: any
detail?: any
}>()
const countdownData = ref<Record<number, any>>({})
let countdownTimer: ReturnType<typeof setInterval> | null = null
const promotionList = computed(() => {
if (!props.res || typeof props.res !== 'object') return []
return Object.keys(props.res).map((key) => ({
...(props.res[key] || {}),
__key: key.split('-')[0],
}))
})
watch(
promotionList,
(list) => {
refreshCountdown(list)
startCountdownTimer()
},
props: {
res: {
type: null,
default: {},
},
detail: {
type: null,
default: {},
},
},
computed: {
promotionList() {
if (!this.res || typeof this.res !== "object") return [];
return Object.keys(this.res).map((key) => ({
...(this.res[key] || {}),
__key: key.split("-")[0],
}));
},
},
watch: {
promotionList: {
handler(list) {
this.refreshCountdown(list);
this.startCountdownTimer();
},
immediate: true,
},
},
beforeUnmount() {
this.clearCountdownTimer();
},
methods: {
clearCountdownTimer() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
this.countdownTimer = null;
}
},
startCountdownTimer() {
this.clearCountdownTimer();
this.countdownTimer = setInterval(() => {
this.refreshCountdown(this.promotionList);
}, 1000);
},
refreshCountdown(list = this.promotionList) {
const next = {};
list.forEach((promotion, index) => {
next[index] = this.msToTimeData(this.getCountDownMs(promotion));
});
this.countdownData = next;
},
padTime(val) {
return String(val ?? 0).padStart(2, "0");
},
countdownDisplayHours(index) {
const data = this.countdownData[index];
if (!data) return 0;
return data.days * 24 + data.hours;
},
msToTimeData(ms) {
const DAY = 86400000;
const HOUR = 3600000;
const MINUTE = 60000;
const SECOND = 1000;
const time = Math.max(Number(ms) || 0, 0);
return {
days: Math.floor(time / DAY),
hours: Math.floor((time % DAY) / HOUR),
minutes: Math.floor((time % HOUR) / MINUTE),
seconds: Math.floor((time % MINUTE) / SECOND),
};
},
parsePromotionTime(val) {
if (val == null || val === "") return 0;
if (typeof val === "number" && !Number.isNaN(val)) {
return val < 1e12 ? val * 1000 : val;
}
const text = String(val).trim();
if (/^\d+$/.test(text)) {
const num = Number(text);
return num < 1e12 ? num * 1000 : num;
}
const parsed = Date.parse(text.replace(/-/g, "/"));
return Number.isNaN(parsed) ? 0 : parsed;
},
getCountDownMs(promotion) {
if (!promotion || !promotion.endTime) return 0;
const now = Date.now();
const startMs = this.parsePromotionTime(promotion.startTime || promotion.start_time);
const endMs = this.parsePromotionTime(promotion.endTime);
if (!endMs) return 0;
const target = startMs && now < startMs ? startMs : endMs;
const remain = target - now;
return remain > 0 ? remain : 0;
},
getIsTimer(val) {
const now = Date.now();
const startMs = this.parsePromotionTime(val.startTime || val.start_time);
if (startMs && now < startMs) {
return "距离活动开始";
}
return "距离活动结束";
},
},
};
{ immediate: true }
)
onUnmounted(() => {
clearCountdownTimer()
})
function clearCountdownTimer() {
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
}
function startCountdownTimer() {
clearCountdownTimer()
countdownTimer = setInterval(() => {
refreshCountdown(promotionList.value)
}, 1000)
}
function refreshCountdown(list = promotionList.value) {
const next: Record<number, any> = {}
list.forEach((promotion, index) => {
next[index] = msToTimeData(getCountDownMs(promotion))
})
countdownData.value = next
}
function padTime(val: number | undefined) {
return String(val ?? 0).padStart(2, '0')
}
function countdownDisplayHours(index: number) {
const data = countdownData.value[index]
if (!data) return 0
return data.days * 24 + data.hours
}
function msToTimeData(ms: number) {
const DAY = 86400000
const HOUR = 3600000
const MINUTE = 60000
const SECOND = 1000
const time = Math.max(Number(ms) || 0, 0)
return {
days: Math.floor(time / DAY),
hours: Math.floor((time % DAY) / HOUR),
minutes: Math.floor((time % HOUR) / MINUTE),
seconds: Math.floor((time % MINUTE) / SECOND),
}
}
function parsePromotionTime(val: any) {
if (val == null || val === '') return 0
if (typeof val === 'number' && !Number.isNaN(val)) {
return val < 1e12 ? val * 1000 : val
}
const text = String(val).trim()
if (/^\d+$/.test(text)) {
const num = Number(text)
return num < 1e12 ? num * 1000 : num
}
const parsed = Date.parse(text.replace(/-/g, '/'))
return Number.isNaN(parsed) ? 0 : parsed
}
function getCountDownMs(promotion: any) {
if (!promotion || !promotion.endTime) return 0
const now = Date.now()
const startMs = parsePromotionTime(promotion.startTime || promotion.start_time)
const endMs = parsePromotionTime(promotion.endTime)
if (!endMs) return 0
const target = startMs && now < startMs ? startMs : endMs
const remain = target - now
return remain > 0 ? remain : 0
}
function getIsTimer(val: any) {
const now = Date.now()
const startMs = parsePromotionTime(val.startTime || val.start_time)
if (startMs && now < startMs) {
return '距离活动开始'
}
return '距离活动结束'
}
</script>
<style scoped lang="scss">

View File

@@ -1,79 +1,95 @@
<template>
<view class="wrapper">
<div class="coupon-empty" v-if="!res">暂无优惠券</div>
<view class="coupon-List" v-for="(item, index) in couponRes" :key="index">
<view class="coupon-empty" v-if="!couponRes.length">暂无优惠券</view>
<view class="coupon-List" v-for="(item, index) in couponRes" :key="item.id || index">
<view class="coupon-item">
<view class="top">
<div class="price">
<span class="price-num" v-if="item.couponType == 'DISCOUNT'">{{ item.couponDiscount }}</span>
<span class="price-num" v-if="item.couponType == 'PRICE'">{{unitPrice(item.price) }}</span>
</div>
<view class="text">
<div class="coupon-List-title">
<view v-if="item.scopeType">
<span v-if="item.scopeType == 'ALL' && item.storeId == '0'">全平台</span>
<span v-if="item.scopeType == 'PORTION_GOODS_CATEGORY'">仅限品类</span>
<view v-else>{{
item.storeName == "platform" ? "全平台" : item.storeName + "店铺"
}}使用</view>
</view>
</div>
<div>{{unitPrice(item.consumeThreshold) }}可用</div>
<view class="price">
<text class="price-num" v-if="item.couponType == 'DISCOUNT'">{{ item.couponDiscount }}</text>
<text class="price-num" v-else-if="item.couponType == 'PRICE'">{{ unitPrice(item.price) }}</text>
</view>
<view class="lingqu-btn" @click="getCoupon(item, index)">
<div class="lingqu-text" :class="yhqFlag[index] ? 'cur' : ''">
{{ yhqFlag[index] ? "已领取或领完" : "立即领取" }}
</div>
<view class="text">
<view class="coupon-List-title">
<text v-if="item.scopeType == 'ALL' && item.storeId == '0'">全平台</text>
<text v-else-if="item.scopeType == 'PORTION_GOODS_CATEGORY'">仅限品类</text>
<text v-else-if="item.scopeType == 'PORTION_GOODS'">部分商品</text>
<text v-else>{{ item.storeName == 'platform' ? '全平台' : item.storeName + '店铺' }}使用</text>
</view>
<text>{{ unitPrice(item.consumeThreshold) }}可用</text>
</view>
<view class="lingqu-btn" @tap="getCoupon(item, index)">
<view class="lingqu-text" :class="{ cur: yhqFlag[index] }">
{{ yhqFlag[index] ? '已领取或领完' : '立即领取' }}
</view>
</view>
</view>
<view class="line"></view>
<view class="time">{{ item.startTime /unixToDate(1000) }} - {{ item.endTime /unixToDate(1000) }}</view>
<view class="time">{{ formatCouponTime(item.startTime) }} - {{ formatCouponTime(item.endTime) }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
yhqFlag: [], //获取优惠券判断是否点击
couponRes: [],
};
},
props: {
res: {
type: null,
default: "",
},
},
watch: {
res: {
handler() {
if (this.res && this.res.length != 0) {
Object.keys(this.res).forEach((item) => {
let key = item.split("-")[0];
if (key === "COUPON") {
this.couponRes.push(this?.res[item]);
<script setup lang="ts">
import { ref, watch } from 'vue'
import { unitPrice, unixToDate } from '@/utils/filters.js'
}
});
}
},
immediate: true,
},
},
methods: {
// 提交优惠券
getCoupon(item, index) {
this.yhqFlag[index] = true;
this.$emit("getCoupon", item);
},
},
};
const props = defineProps<{
res?: any
}>()
const emit = defineEmits<{
getCoupon: [item: any]
}>()
const yhqFlag = ref<boolean[]>([])
const couponRes = ref<any[]>([])
function formatCouponTime(val: any) {
if (val == null || val === '') return ''
const text = String(val).trim()
if (!text) return ''
if (text.includes('-') || text.includes('/')) {
return text.length > 10 ? text.slice(0, 16) : text
}
const num = Number(text)
if (!Number.isFinite(num)) return text
const seconds = num < 1e12 ? num : Math.floor(num / 1000)
return unixToDate(seconds, 'yyyy-MM-dd')
}
watch(
() => props.res,
(res) => {
const list: any[] = []
if (res && typeof res === 'object') {
Object.keys(res).forEach((item) => {
if (item.split('-')[0] === 'COUPON') {
list.push(res[item])
}
})
}
couponRes.value = list
yhqFlag.value = list.map(() => false)
},
{ immediate: true, deep: true }
)
function getCoupon(item: any, index: number) {
if (yhqFlag.value[index]) return
yhqFlag.value[index] = true
yhqFlag.value = [...yhqFlag.value]
emit('getCoupon', item)
}
</script>
<style lang="scss" scoped>
.coupon-empty {
color: #999;
font-size: 26rpx;
padding: 20rpx 0;
}
.coupon-item {
width: 100%;
height: 100%;
@@ -85,10 +101,12 @@
.coupon-List {
display: flex;
flex-direction: column;
height: 230rpx;
min-height: 230rpx;
background: #e9ebfb;
margin: 30rpx 0;
padding: 10rpx 30rpx;
border-radius: 12rpx;
box-sizing: border-box;
.line {
height: 1px;
@@ -119,15 +137,17 @@
.time {
flex: 1;
font-size: 24rpx;
align-items: center;
color: #666;
display: flex;
align-items: center;
padding: 12rpx 0 8rpx;
}
}
.top {
height: 140rpx;
min-height: 140rpx;
display: flex;
align-items: center;
.price {
width: 33%;
@@ -135,48 +155,56 @@
color: #6772e5;
font-size: 40rpx;
display: flex;
height: 100%;
align-items: center;
flex-shrink: 0;
.price-num {
font-size: 50rpx;
line-height: 1.2;
}
}
.text {
width: 33%;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
font-size: 26rpx;
color: 333;
margin-left: 40rpx;
color: #333;
margin-left: 20rpx;
min-width: 0;
.coupon-List-title {
font-size: 30rpx;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
line-height: 1.4;
}
}
.lingqu-btn {
display: flex;
align-items: center;
margin-left: 40rpx;
flex-shrink: 0;
margin-left: 16rpx;
.lingqu-text {
width: 140rpx;
height: 40rpx;
min-width: 140rpx;
height: 56rpx;
padding: 0 16rpx;
text-align: center;
line-height: 40rpx;
line-height: 56rpx;
color: #fff;
background: #6772e5;
border-radius: 5px;
font-size: 26rpx;
border-radius: 28rpx;
font-size: 24rpx;
box-sizing: border-box;
&.cur {
background: none;
transform: rotate(45deg) translate(10rpx, -46rpx);
background: #ccc;
color: #fff;
transform: none;
}
}
}

View File

@@ -1,140 +1,134 @@
<template>
<view class="wrapper" v-if="res">
<view v-for="(prom, index) in Object.keys(res)" :key="index">
<view>
<view v-if="prom.split('-')[0] == 'FULL_DISCOUNT'">
<div class="res_prom_item" v-if="res[prom].fullMinus">
<u-tag text="满减" type="error"></u-tag>
<!-- TODO 后续将优化为可点击的商品以及优惠券显示明细 -->
<span class="pro-text"
>{{ res[prom].fullMoney }} 立减现金
<span class="price">{{ res[prom].fullMinus }}</span>
<span v-if="res[prom].couponFlag"> 赠送<span>优惠券</span></span>
<span v-if="res[prom].pointFlag"> 赠送{{ res[prom].point }}积分</span>
<span v-if="res[prom].giftFlag"> 赠送商品</span>
<span v-if="res[prom].freeFreightFlag">赠送包邮服务</span>
</span>
</div>
<div class="res_prom_item" v-if="res[prom].fullRate && res[prom].fullRateFlag">
<u-tag text="打折" type="error"></u-tag>
<span class="pro-text"
>{{ res[prom].fullMoney }}立享<span class="price"
>{{ res[prom].fullRate }}</span
>优惠</span
>
</div>
<view v-for="prom in promotionKeys" :key="prom">
<view v-if="getPromType(prom) == 'FULL_DISCOUNT'">
<view class="res_prom_item" v-if="res[prom].fullMinus">
<view class="deg_tag">满减</view>
<text class="pro-text">
{{ res[prom].fullMoney }} 立减现金
<text class="price">{{ res[prom].fullMinus }}</text>
<text v-if="res[prom].couponFlag"> 赠送优惠券</text>
<text v-if="res[prom].pointFlag"> 赠送{{ res[prom].point }}积分</text>
<text v-if="res[prom].giftFlag"> 赠送商品</text>
<text v-if="res[prom].freeFreightFlag"> 赠送包邮服务</text>
</text>
</view>
<view v-if="prom.split('-')[0] == 'PINTUAN'">
<div class="res_prom_item" v-if="res[prom].requiredNum">
<u-tag text="拼团" type="error"></u-tag>
<span class="pro-text"
>{{ res[prom].requiredNum }}人拼团 限购<span class="price"
>{{ res[prom].limitNum }}</span
></span
>
</div>
<view class="res_prom_item" v-if="res[prom].fullRate && res[prom].fullRateFlag">
<view class="deg_tag">打折</view>
<text class="pro-text">
{{ res[prom].fullMoney }}立享
<text class="price">{{ res[prom].fullRate }}</text>
优惠
</text>
</view>
</view>
<view v-if="prom.split('-')[0] == 'SECKILL'">
<div class="res_prom_item">
<u-tag text="限时抢购" type="error"></u-tag>
<span class="pro-text">限时抢购</span>
</div>
<view v-if="getPromType(prom) == 'PINTUAN'">
<view class="res_prom_item" v-if="res[prom].requiredNum">
<view class="deg_tag">拼团</view>
<text class="pro-text">
{{ res[prom].requiredNum }}人拼团 限购
<text class="price">{{ res[prom].limitNum }}</text>
</text>
</view>
</view>
<view v-if="prom.split('-')[0] == 'POINTS_GOODS'">
<div class="res_prom_item">
<u-tag text="积分活动" type="error"></u-tag>
<span class="pro-text">当前商品参与积分活动<span @click="handClickToJoinPromotion(prom)" class="href">点击此处参与活动</span></span>
</div>
<view v-if="getPromType(prom) == 'SECKILL'">
<view class="res_prom_item">
<view class="deg_tag">限时抢购</view>
<text class="pro-text">限时抢购</text>
</view>
</view>
<view v-if="prom.split('-')[0] == 'KANJIA'">
<div class="res_prom_item">
<u-tag text="砍价活动" type="error"></u-tag>
<span class="pro-text">当前商品参与砍价活动<span @click="handClickToJoinPromotion(prom)" class="href">点击此处参与活动</span></span>
</div>
<view v-if="getPromType(prom) == 'POINTS_GOODS'">
<view class="res_prom_item">
<view class="deg_tag">积分活动</view>
<text class="pro-text">
当前商品参与积分活动
<text @tap="handClickToJoinPromotion(prom)" class="href">点击此处参与活动</text>
</text>
</view>
</view>
<view v-if="getPromType(prom) == 'KANJIA'">
<view class="res_prom_item">
<view class="deg_tag">砍价活动</view>
<text class="pro-text">
当前商品参与砍价活动
<text @tap="handClickToJoinPromotion(prom)" class="href">点击此处参与活动</text>
</text>
</view>
</view>
</view>
<view v-if="!res">暂无促销活动</view>
<view v-if="!promotionKeys.length">暂无促销活动</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
watch: {
res: {
handler() {
if (this.res && this.res.length != 0) {
Object.keys(this.res).forEach((item) => {
if (item != "COUPON") {
let key = item.split("-")[0];
this.res[item]._key = key;
}
});
}
},
immediate: true,
},
},
<script setup lang="ts">
import { computed } from 'vue'
props: {
// 父组件传递回来的数据
res: {
type: null,
default: "",
},
},
mounted() {},
methods: {
// 跳转到参与商品活动的详情列表中
handClickToJoinPromotion(val){
const promotion = {
"POINTS_GOODS": `/pages/promotion/point/detail?id=${this.res[val].id}`,
"KANJIA": `/pages/promotion/bargain/detail?id=${this.res[val].id}`,
}
const props = defineProps<{
res?: any
}>()
uni.navigateTo({
url:promotion[val.split('-')[0]]
})
const promotionKeys = computed(() => {
if (!props.res || typeof props.res !== 'object') return []
return Object.keys(props.res).filter((key) => getPromType(key) !== 'COUPON')
})
}
},
};
function getPromType(key: string) {
return key.split('-')[0]
}
function handClickToJoinPromotion(val: string) {
const type = getPromType(val)
const promotionMap: Record<string, string> = {
POINTS_GOODS: `/pages/promotion/point/detail?id=${props.res[val].id}`,
KANJIA: `/pages/promotion/bargain/detail?id=${props.res[val].id}`,
}
const url = promotionMap[type]
if (url) {
uni.navigateTo({ url })
}
}
</script>
<style lang="scss" scoped>
.deg_tag {
flex-shrink: 0;
color: $price-color;
padding: 4rpx 12rpx;
border: 2rpx solid $price-color;
border-radius: 6rpx;
font-size: 22rpx;
line-height: 1.4;
white-space: nowrap;
}
.pro-text {
flex: 1;
min-width: 0;
font-size: 26rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
text-align: left;
line-height: 1.6;
color: #333333;
margin-left: 20rpx;
word-spacing: 15rpx;
}
.wrapper {
display: block;
}
::v-deep .u-mode-light-error {
border: none;
}
.res_prom_item {
display: flex;
align-items: flex-start;
gap: 16rpx;
margin: 20rpx 0;
}
.price_image {
display: block;
.price {
color: $price-color;
}
.href{
.href {
color: $main-color;
}
</style>

View File

@@ -50,51 +50,45 @@
</view>
</view>
<view v-if="this.res != null && Object.keys(res).length == 0">暂无促销信息</view>
<view v-if="res != null && Object.keys(res).length == 0">暂无促销信息</view>
</view>
</template>
<script>
import promotion from './promotion_type';
export default {
data() {
return {
promotion,
couponList: ''
};
},
props: {
// 父组件传递回来的数据
res: {
type: null,
default: {}
}
},
watch: {
res: {
handler() {
if (this.res && this.res.length != 0 && this.res != null) {
Object.keys(this.res).forEach(item => {
let key = item.split('-')[0];
this.res[item].__key = key;
<script setup lang="ts">
import { ref, watch } from 'vue'
import promotion from './promotion_type'
if (item.split('-')[0] == 'COUPON') {
this.couponList = 'COUPON';
}
});
}
},
immediate: true
}
},
mounted() {},
methods: {
// 此方法条用父级方法
shutMask(val) {
this.$emit('shutMasks', val);
}
}
};
const props = defineProps<{
res?: any
}>()
const emit = defineEmits<{
shutMasks: [val: number]
}>()
const couponList = ref('')
watch(
() => props.res,
(res) => {
couponList.value = ''
if (res && res.length != 0 && res != null) {
Object.keys(res).forEach((item) => {
const key = item.split('-')[0]
res[item].__key = key
if (item.split('-')[0] == 'COUPON') {
couponList.value = 'COUPON'
}
})
}
},
{ immediate: true }
)
function shutMask(val: number) {
emit('shutMasks', val)
}
</script>
<style lang="scss" scoped>

View File

@@ -39,33 +39,31 @@
</view>
</template>
<script>
export default {
data() {
return {};
},
props: ["res", "goodsDetail", "storeDetail"],
computed: {
recommendGoods() {
return (this.res || []).filter((item) => item && item.thumbnail && item.goodsName);
},
},
mounted() {},
methods: {
// 点击商品
clickGoods(val) {
uni.navigateTo({
url: `/pages/product/goods?id=${val.id}&goodsId=${val.goodsId}`,
});
},
<script setup lang="ts">
import { computed } from 'vue'
import { goodsFormatPrice } from '@/utils/filters.js'
tostorePage(val) {
uni.navigateTo({
url: "../product/shopPage?id=" + val.storeId,
});
},
},
};
const props = defineProps<{
res?: any[]
goodsDetail?: any
storeDetail?: any
}>()
const recommendGoods = computed(() => {
return (props.res || []).filter((item) => item && item.thumbnail && item.goodsName)
})
function clickGoods(val: any) {
uni.navigateTo({
url: `/pages/product/goods?id=${val.id}&goodsId=${val.goodsId}`,
})
}
function tostorePage(val: any) {
uni.navigateTo({
url: '../product/shopPage?id=' + val.storeId,
})
}
</script>
<style lang="scss" scoped>

View File

@@ -30,7 +30,15 @@ page {
font-size: 24rpx;
}
.cuxiao{
padding:16rpx 32rpx;
padding:16rpx 32rpx 32rpx;
}
.con-cuxiao {
margin-bottom: 24rpx;
&.coupons {
padding-top: 8rpx;
}
}
.detail-btn {
display: flex;
@@ -171,6 +179,7 @@ page {
.scroll-hide {
opacity: 0;
transition: all 0.5s;
pointer-events: none;
}
.cur {
color: $main-color;
@@ -188,52 +197,88 @@ page {
.header-only-back {
background: transparent;
z-index: 9;
}
.header,
.header-only-back {
padding-left: 10rpx;
position: fixed;
top: var(--status-bar-height);
width: 100%;
z-index: 8;
height: 90rpx;
font-size: 30rpx;
transition: all 0.5s;
:deep(.u-navbar__content__left) {
width: auto !important;
min-width: 150rpx;
padding: 0 8rpx 0 0 !important;
z-index: 2;
}
:deep(.u-navbar__content) {
padding: 0 150rpx;
box-sizing: border-box;
}
}
::v-deep .u-navbar {
padding-left: 10rpx;
}
.nav-item {
flex: 2;
flex: 0 0 auto;
position: relative;
justify-content: center;
height: 100%;
display: flex;
align-items: center;
padding: 0 20rpx;
font-size: 28rpx;
white-space: nowrap;
}
.header {
color: #666666;
background-color: #fff;
:deep(.u-navbar__content) {
background-color: #fff !important;
}
.tab-bar {
width: 100%;
color: #666;
font-weight: 400;
view {
padding: 0 3px;
}
}
&.bg-none {
background: transparent;
:deep(.u-navbar__content) {
background-color: transparent !important;
}
}
}
.headerRow--tabs {
justify-content: center;
gap: 8rpx;
width: 100%;
}
.headerList {
width: 100%;
}
.nav-action {
display: flex;
align-items: center;
justify-content: center;
min-width: 56rpx;
min-height: 56rpx;
padding: 0 6rpx;
}
.page-bottom {
position: fixed;
bottom: 0;
left: 0;
position: relative;
flex-shrink: 0;
z-index: 9;
background: #fff;
height: 100rpx;
min-height: 100rpx;
width: 100%;
display: flex;
border-top: 2rpx solid #f2f2f2;
@@ -267,15 +312,24 @@ page {
}
.main-page {
height: calc(100% - var(--status-bar-height));
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.icon-back {
padding-right: 10rpx;
.index {
position: relative;
z-index: 10;
flex-shrink: 0;
}
.icon-back,
.icon-list {
padding: 0;
}
.nav-action + .nav-action {
border-left: 2rpx solid rgb(194, 194, 194);
padding-left: 10rpx;
}
.backs,
.bg-back {
@@ -284,8 +338,9 @@ page {
border-radius: 100px;
display: flex;
align-items: center;
justify-content: center;
justify-content: space-around;
padding: 8rpx 0;
box-sizing: border-box;
}
.bg-back {
background: rgba($color: #fff, $alpha: 0.8);
@@ -324,6 +379,12 @@ page {
}
.product-container {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
position: relative;
.header-line {
height: 1px;
background: #f2f2f2;
@@ -340,16 +401,16 @@ page {
}
.scroll-page {
flex: 1;
height: 0;
width: 100%;
height: 100%;
background: #f0f0f0;
}
.scroll-inner {
min-height: 100%;
box-sizing: border-box;
background: #f0f0f0;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
padding-bottom: 24rpx;
}
}