修改样式

This commit is contained in:
2026-04-11 11:55:03 +08:00
parent ef9210a567
commit ec9282a64f
59 changed files with 8708 additions and 2558 deletions
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { debounce } from 'throttle-debounce'
import {appCollectListPost, appCollectCollectPost} from "@/service";
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
import Collection from "@/components/collection/index.vue";
import {CollectionType} from "@/constant/enums";
const {t} = useI18n()
const emit = defineEmits<{
(e: 'batch-delete-success'): void
}>()
const props = defineProps({
currentIndex: {
type: Number,
@@ -15,6 +17,18 @@ const props = defineProps({
type: Number,
default: 0,
},
batchDeleteMode: {
type: Boolean,
default: false,
},
})
const selectedIds = ref<string[]>([])
watch(() => props.batchDeleteMode, (v) => {
if (!v) {
selectedIds.value = []
}
})
// (1, "菜谱")(2, "菜品")(3, "配菜")(4, "商家")
@@ -35,7 +49,7 @@ const {paging, dataList, queryList, loading} = usePage<any>((pageNum: number, pa
pageSize,
},
body: {
targetType: typeList[props.currentIndex]
targetType: typeList[props.index]
}
}),
)
@@ -51,6 +65,106 @@ function navigateToDishes(item: any) {
})
}
function getDishInfo(item: any) {
return item?.merchantDishVo || item || {}
}
function getDishImage(item: any) {
const dish = getDishInfo(item)
return dish?.dishImage?.split?.(',')?.[0] || dish?.dishImage || ''
}
/** 当前列表 tab 对应的收藏目标 id(与 appCollectCollectPost 一致) */
function getCollectTargetId(item: any): string {
const i = props.index
if (i === 0) {
return String(item.merchantVo?.id ?? item.merchantId ?? getDishInfo(item).merchantId ?? item.id ?? '')
}
if (i === 1) {
return String(item.merchantDishVo?.id ?? getDishInfo(item).id ?? item.id ?? '')
}
if (i === 2) {
return String(item.merchantRecipeVo?.id ?? item.id ?? '')
}
return ''
}
function toggleSelect(item: any) {
if (!props.batchDeleteMode) return
const id = getCollectTargetId(item)
if (!id) return
const idx = selectedIds.value.indexOf(id)
if (idx >= 0) {
selectedIds.value.splice(idx, 1)
} else {
selectedIds.value.push(id)
}
}
function isSelected(item: any) {
const id = getCollectTargetId(item)
return id ? selectedIds.value.includes(id) : false
}
function onStoreOrDishRowClick(item: any) {
if (props.batchDeleteMode) {
toggleSelect(item)
return
}
navigateToDishes(getDishInfo(item))
}
function onRecipeCardClick(item: any) {
if (props.batchDeleteMode) {
toggleSelect(item)
return
}
navigateToRecipeDetail(item.merchantRecipeVo?.id ?? item.id)
}
function runBatchDelete() {
if (selectedIds.value.length === 0) {
uni.showToast({
title: t('pages.mine.collectionSelectFirst'),
icon: 'none',
})
return
}
uni.showModal({
title: t('common.prompt.system-prompt'),
content: t('pages.mine.collectionBatchDeleteConfirm', { count: selectedIds.value.length }),
success: (res) => {
if (!res.confirm) return
const targetType = typeList[props.index] as number
const ids = [...selectedIds.value]
;(async () => {
try {
for (const targetId of ids) {
await appCollectCollectPost({
body: {
targetId,
targetType,
},
})
}
selectedIds.value = []
emit('batch-delete-success')
paging.value?.reload()
uni.showToast({
title: t('toast.deleteSuccess'),
icon: 'none',
})
} catch {
uni.showToast({
title: t('common.prompt.request-incorrect'),
icon: 'none',
})
}
})()
},
})
}
// 收藏菜谱
function handleSubmitCollectRecipe(item:any) {
debouncedEmit(item.merchantRecipeVo?.isCollect, item.merchantRecipeVo?.id, CollectionType.RECIPE, ()=> {
@@ -58,12 +172,6 @@ function handleSubmitCollectRecipe(item:any) {
})
}
// 收藏菜品
function handleDishCollectionClick(item: any) {
debouncedEmit(item.isCollect, item.id, CollectionType.DISH, ()=> {
item.isCollect = !item.isCollect
})
}
// 防抖处理函数
const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: CollectionType, callback: ()=> void) => {
// 收藏接口
@@ -94,6 +202,7 @@ function refresh() {
defineExpose({
reload,
refresh,
runBatchDelete,
})
</script>
@@ -101,71 +210,66 @@ defineExpose({
<view class="h-full">
<z-paging ref="paging" v-model="dataList" @query="queryList" :fixed="false" :auto="false">
<view class="p-30rpx">
<template v-if="currentIndex == 0">
<!--商家-->
<view v-for="item in dataList" :key="item.id">
<food-box :item="item.merchantVo" />
</view>
</template>
<template v-if="currentIndex == 1">
<!--菜品-->
<view class="grid grid-cols-2 gap-30rpx">
<template v-for="item in dataList">
<view @click="navigateToDishes(item.merchantDishVo)" class="w-100% mb-10rpx rounded-16rpx">
<view class="relative h-248rpx rounded-24rpx mb-28rpx">
<view @click.stop="handleDishCollectionClick(item.merchantDishVo)" class="w-68rpx h-68rpx absolute z-2 top-0 right-0">
<image
v-if="!item.merchantDishVo.isCollect"
src="@img-store/1334.png"
mode="aspectFill"
class="w-full h-full"
/>
<image
v-else
src="@img-store/1337.png"
mode="aspectFill"
class="w-full h-full"
/>
</view>
<image
:src="item.merchantDishVo?.dishImage?.split(',')[0]"
mode="aspectFill"
class="w-full h-full rounded-24rpx"
/>
</view>
<view class="line-clamp-1 text-30rpx lh-30rpx text-#333 font-500 mb-12rpx">
{{ item.merchantDishVo.dishName }}
</view>
<view class="flex-center-sb">
<text class="text-30rpx lh-30rpx text-#333 font-500">US${{ item.merchantDishVo.discountPrice }}</text>
<view
v-if="Number(item.merchantDishVo.memberPrice) > 0"
class="member-price-tag text-[#FBE3C3] text-18rpx center pl-6rpx"
>
{{ t('pages-store.store.members') }}: ${{ item.merchantDishVo.memberPrice }}
</view>
</view>
<view class="flex-center-sb mt-12rpx">
<view class="text-28rpx text-#999">
<view class="line-through">US${{ item.merchantDishVo.originalPrice }}</view>
<view>{{ t('pages-store.store.sales') }}:{{ item.merchantDishVo.salesCount }}</view>
</view>
<view class="center w-64rpx h-64rpx rounded-50% bg-white shadow-lg">
<image
src="@img/chef/1285.png"
class="w-30rpx h-30rpx shrink-0"
></image>
</view>
<template v-if="currentIndex == 0 || currentIndex == 1">
<view class="collection-list">
<view
v-for="item in dataList"
:key="item.id"
class="collection-item"
@click="onStoreOrDishRowClick(item)"
>
<view
v-if="batchDeleteMode"
class="collection-item__check"
@click.stop="toggleSelect(item)"
>
<view class="check-box" :class="{ 'is-on': isSelected(item) }">
<text v-if="isSelected(item)" class="check-tick"></text>
</view>
</view>
</template>
<image
:src="getDishImage(item)"
mode="aspectFill"
class="collection-item__image"
/>
<view class="collection-item__content">
<view class="collection-item__name line-clamp-1">{{ getDishInfo(item).dishName }}</view>
<view class="collection-item__price-row mt-10rpx">
<text class="collection-item__price">${{ getDishInfo(item).discountPrice }}</text>
<text
v-if="Number(getDishInfo(item).originalPrice) > 0"
class="collection-item__original ml-10rpx"
>
${{ getDishInfo(item).originalPrice }}
</text>
</view>
<view v-if="Number(getDishInfo(item).memberPrice) > 0" class="collection-item__member mt-12rpx">
<image src="@img-store/1339.png" class="w-22rpx h-22rpx mr-6rpx" />
{{ t('pages.browse.brandTag') }} {{ t('pages-store.store.members') }}: ${{ getDishInfo(item).memberPrice }}
</view>
<view class="collection-item__sales mt-10rpx">
{{ t('pages-store.store.sales') }} : {{ getDishInfo(item).salesCount || 0 }}
</view>
</view>
<view v-if="!batchDeleteMode" class="collection-item__add center">
<text class="text-42rpx text-#333">+</text>
</view>
</view>
</view>
</template>
<template v-if="currentIndex == 2">
<view class="grid grid-cols-2 gap-30rpx">
<view v-for="item in dataList">
<view @click="navigateToRecipeDetail(item.id)" class="w-312rpx">
<view v-for="item in dataList" :key="item.id" class="recipe-card-wrap">
<view
v-if="batchDeleteMode"
class="recipe-card__check"
@click.stop="toggleSelect(item)"
>
<view class="check-box" :class="{ 'is-on': isSelected(item) }">
<text v-if="isSelected(item)" class="check-tick"></text>
</view>
</view>
<view @click="onRecipeCardClick(item)" class="w-312rpx">
<image
:src="item.merchantRecipeVo?.recipeImage?.split(',')[0]"
class="w-310rpx h-296rpx rounded-24rpx mb-26rpx"
@@ -175,7 +279,7 @@ defineExpose({
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
>{{ item.merchantRecipeVo?.recipeName }}</text
>
<view class="w-40rpx h-40rpx ml-14rpx shrink-0">
<view v-if="!batchDeleteMode" class="w-40rpx h-40rpx ml-14rpx shrink-0">
<collection
:is-collected="item.merchantRecipeVo?.isCollect"
@collectionChange="handleSubmitCollectRecipe(item)"
@@ -192,5 +296,123 @@ defineExpose({
</template>
<style scoped lang="scss">
.collection-list {
.collection-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f1f1f1;
}
.collection-item__image {
width: 168rpx;
height: 168rpx;
border-radius: 20rpx;
background: #f4f4f4;
flex-shrink: 0;
}
.collection-item__content {
flex: 1;
margin-left: 20rpx;
overflow: hidden;
}
.collection-item__name {
font-size: 30rpx;
line-height: 38rpx;
color: #222;
font-weight: 500;
}
.collection-item__price-row {
display: flex;
align-items: baseline;
}
.collection-item__price {
color: #e7362f;
font-size: 42rpx;
line-height: 42rpx;
font-weight: 600;
}
.collection-item__original {
color: #a5a5a5;
font-size: 28rpx;
line-height: 28rpx;
text-decoration: line-through;
}
.collection-item__member {
display: inline-flex;
align-items: center;
color: #c5883f;
font-size: 26rpx;
line-height: 26rpx;
}
.collection-item__sales {
width: fit-content;
height: 42rpx;
padding: 0 14rpx;
border-radius: 10rpx;
background: #f1f2f4;
color: #9b9b9b;
font-size: 24rpx;
line-height: 42rpx;
}
.collection-item__add {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
border: 1rpx solid #e6e6e6;
margin-left: 14rpx;
flex-shrink: 0;
}
.collection-item__check {
width: 44rpx;
margin-right: 12rpx;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
}
.check-box {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #c8c8c8;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.check-box.is-on {
border-color: #111;
background: #111;
}
.check-tick {
font-size: 24rpx;
line-height: 24rpx;
color: #fff;
font-weight: 700;
}
.recipe-card-wrap {
position: relative;
}
.recipe-card__check {
position: absolute;
z-index: 3;
left: 8rpx;
top: 8rpx;
}
</style>
+189 -9
View File
@@ -1,6 +1,26 @@
<script setup lang="ts">
import OrderSwiperList from "./components/order-swiper-list/order-swiper-list.vue";
import { useUserStore } from "@/store";
import { onShow } from "@dcloudio/uni-app";
const {t} = useI18n()
const userStore = useUserStore();
const cartBadgeTotal = computed(() => {
const list = userStore.userCartAllData;
if (!Array.isArray(list) || list.length === 0) return 0;
let n = 0;
for (const m of list) {
n +=
(m as { merchantCartVoList?: unknown[] })?.merchantCartVoList
?.length || 0;
}
return n;
});
onShow(() => {
userStore.getUserCartAllData();
});
const segmentedValue = ref(0);
const segmentedList = [
'Store',
@@ -8,26 +28,106 @@ const segmentedList = [
'Recipe',
]
function handleSwiperChange(e) {
function handleSwiperChange(e: any) {
segmentedValue.value = e.detail.current;
}
const orderSwiperListRef = ref()
const batchDeleteMode = ref(false)
onMounted(()=> {
nextTick(()=> {
orderSwiperListRef.value[segmentedValue.value].reload()
})
})
function exitBatchDeleteMode() {
batchDeleteMode.value = false
}
function handleBatchDeleteSuccess() {
batchDeleteMode.value = false
}
function navigateBack() {
uni.navigateBack({
delta: 1,
})
}
function handleDeleteCollection() {
if (!batchDeleteMode.value) {
batchDeleteMode.value = true
uni.showToast({
title: t('pages.mine.collectionBatchModeHint'),
icon: 'none',
})
return
}
const inst = orderSwiperListRef.value?.[segmentedValue.value]
inst?.runBatchDelete?.()
}
function navigateToCart() {
uni.navigateTo({
url: '/pages-user/pages/cart/index'
})
}
</script>
<template>
<view>
<view class="collection-page">
<z-paging-swiper>
<template #top>
<navbar/>
<view class="px-30rpx">
<view class="text-46rpx lh-46rpx text-#333 font-bold mb-52rpx">{{ t('pages.mine.collection') }}</view>
<l-segmented v-model="segmentedValue" :options="segmentedList" shape="round" bg-color="#F2F2F2" active-color="#333" />
<status-bar />
<view class="header-wrap px-30rpx pt-14rpx">
<view class="flex items-center justify-between mb-34rpx">
<image
@click="navigateBack"
src="@img/chef/1327.png"
mode="aspectFill"
class="w-48rpx h-48rpx"
/>
<view class="text-38rpx lh-38rpx text-#111 font-600">{{ t('pages.mine.collection') }}</view>
<view class="flex items-center gap-16rpx">
<text
v-if="batchDeleteMode"
class="text-28rpx text-#666"
@click="exitBatchDeleteMode"
>{{ t('common.cancel') }}</text>
<view
v-if="!batchDeleteMode"
class="collection-header-cart w-66rpx h-66rpx rounded-50% bg-#F5F5F5 center shrink-0"
@click="navigateToCart"
>
<view class="i-carbon:shopping-cart text-36rpx text-#14181b"></view>
<view
v-if="userStore.isLogin && cartBadgeTotal > 0"
class="collection-header-cart-badge"
>{{ cartBadgeTotal > 99 ? "99+" : cartBadgeTotal }}</view
>
</view>
<view
v-if="!batchDeleteMode"
class="w-66rpx h-66rpx rounded-50% bg-#F5F5F5 center"
@click="handleDeleteCollection"
>
<image src="@img/chef/1278.png" mode="aspectFill" class="w-66rpx h-66rpx" />
</view>
</view>
</view>
<l-segmented
v-model="segmentedValue"
:options="segmentedList"
shape="round"
bg-color="#F2F3F5"
active-color="#fff"
color="#5E5E5E"
padding="0rpx"
slider-color="#000"
height="62rpx"
/>
</view>
</template>
@@ -35,15 +135,95 @@ onMounted(()=> {
:current="segmentedValue"
@change="handleSwiperChange">
<swiper-item class="swiper-item" v-for="(item, index) in segmentedList" :key="index">
<order-swiper-list ref="orderSwiperListRef" :currentIndex="segmentedValue" :index="index"></order-swiper-list>
<order-swiper-list
ref="orderSwiperListRef"
:currentIndex="segmentedValue"
:index="index"
:batch-delete-mode="batchDeleteMode"
@batch-delete-success="handleBatchDeleteSuccess"
/>
</swiper-item>
</swiper>
</z-paging-swiper>
<view class="cart-bar-wrap px-24rpx pb-24rpx" v-if="batchDeleteMode">
<view class="delete-bar">
<view class="text-30rpx text-#1d1d1d ml-20rpx" style="color: #fff;">{{ t('common.delete') || '删除' }}</view>
</view>
</view>
</view>
</template>
<style>
<style scoped lang="scss">
page {
background-color: white;
background-color: #fff;
}
.collection-page {
min-height: 100vh;
background: #fff;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.header-wrap {
background: #fff;
}
:deep(.l-segmented) {
height: 62rpx !important;
border-radius: 34rpx !important;
}
:deep(.l-segmented-item) {
font-size: 30rpx !important;
color: #666 !important;
}
:deep(.l-segmented-item--active) {
color: #fff !important;
font-weight: 600 !important;
background: #111 !important;
}
.cart-bar-wrap {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
background: linear-gradient(to top, #fff 65%, rgba(255, 255, 255, 0));
}
.collection-header-cart {
position: relative;
}
.collection-header-cart-badge {
position: absolute;
top: 2rpx;
right: 2rpx;
min-width: 28rpx;
height: 28rpx;
padding: 0 6rpx;
font-size: 18rpx;
line-height: 28rpx;
font-weight: 600;
color: #fff;
text-align: center;
background: #e23636;
border-radius: 999rpx;
}
.delete-bar {
height: 96rpx;
border-radius: 48rpx;
background: #000;
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
border: 1rpx solid #efefef;
padding: 0 26rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>
+33 -2
View File
@@ -42,8 +42,13 @@ function handleClickLeft() {
<view class="i-carbon:chevron-left text-50rpx text-#3D3D3D ml-[-10rpx]"></view>
</view>
</template>
<template #title>
<view class="invite-navbar-title-wrap">
<text class="invite-navbar-title">{{ t('pages.mine.the-person-invited') }}</text>
<text class="invite-navbar-count">({{ dataList.length }})</text>
</view>
</template>
</wd-navbar>
<view class="mb-46rpx mt-18rpx pl-30rpx text-#333 text-46rpx lh-46rpx font-bold">{{ t('pages.mine.the-person-invited') }} ({{ dataList.length }})</view>
</template>
<view class="px-18rpx">
<view
@@ -111,4 +116,30 @@ function handleClickLeft() {
</view>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.invite-navbar-title-wrap {
display: flex;
align-items: baseline;
align-items: center;
align-content: center;
height: 100%;
justify-content: center;
width: 100%;
padding-left: 8rpx;
gap: 8rpx;
}
.invite-navbar-title {
font-size: 34rpx;
line-height: 42rpx;
font-weight: 700;
color: #333;
}
.invite-navbar-count {
font-size: 28rpx;
line-height: 34rpx;
font-weight: 600;
color: #666;
}
</style>
+25 -2
View File
@@ -15,6 +15,29 @@ import {formatTimestamp, formatTimestampWithMonthName} from "@/utils/utils";
import {useUserStore, useConfigStore} from "@/store";
const userStore = useUserStore()
const configStore = useConfigStore()
function normalizeTimestamp(input: unknown): number | null {
if (input == null || input === '') return null
const raw = Number(input)
if (!Number.isFinite(raw) || raw <= 0) return null
// 10位秒级、13位毫秒级、16位微秒级都做兼容
if (raw < 1e11) return Math.trunc(raw * 1000)
if (raw > 1e14) return Math.trunc(raw / 1000)
return Math.trunc(raw)
}
function formatRecipeTime(value: unknown): string {
const ts = normalizeTimestamp(value)
if (!ts) return '--'
return formatTimestamp(ts)
}
function formatCommentTime(value: unknown): string {
const ts = normalizeTimestamp(value)
if (!ts) return '--'
return formatTimestampWithMonthName(ts)
}
// 加载状态
const loading = ref(true);
// 获取菜谱详情
@@ -108,7 +131,7 @@ function getCommentList() {
user_name: userInfo.user_name, // 用户名
user_avatar: userInfo.user_avatar, // 用户头像地址
user_content: item.content, // 用户评论内容
create_time: formatTimestampWithMonthName(item.createTime), // 创建时间
create_time: formatCommentTime(item.createTime), // 创建时间
}
})
tableTotal.value = res.total
@@ -192,7 +215,7 @@ function handleSend() {
>{{ recipeDetail?.recipeName || '' }}</view
>
<view class="flex-center-sb text-28rpx text-#fff">
<text>{{ formatTimestamp(recipeDetail?.createTime) }}</text>
<text>{{ formatRecipeTime(recipeDetail?.createTime) }}</text>
<view class="flex items-center">
<image
src="@img/chef/1326.png"
+1
View File
@@ -6,6 +6,7 @@ export const useLogicStore = defineStore('store-list-logic', () => {
const searchLoading = ref(false)
const setPlacesList = (list: any) => {
console.log('setPlacesList', list)
if (Array.isArray(list)) {
placesList.value = list
} else {