Files
medical-mall/pages/mall/merchant/promotions.uvue
2026-04-13 11:32:31 +08:00

227 lines
8.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 机构端 - 关怀活动页面 -->
<template>
<view class="promotions-page">
<!-- #ifdef MP-WEIXIN -->
<view style="padding-top: var(--status-bar-height); background-color: #ffffff; display: flex; flex-direction: row; align-items: flex-end; border-bottom: 1rpx solid #eeeeee; box-sizing: border-box; height: calc(88rpx + var(--status-bar-height));">
<view style="display: flex; flex-direction: row; align-items: center; padding: 0 30rpx; height: 88rpx;" @click="uni.navigateBack()">
<text style="font-size: 44rpx; color: #333333; line-height: 1; margin-right: 6rpx;"></text>
<text style="font-size: 28rpx; color: #333333;">返回</text>
</view>
</view>
<!-- #endif -->
<view class="tabs">
<view class="tab" :class="{ active: currentTab === 'coupon' }" @click="switchTab('coupon')">护理套餐券</view>
<view class="tab" :class="{ active: currentTab === 'seckill' }" @click="switchTab('seckill')">康复体验</view>
<view class="tab" :class="{ active: currentTab === 'group' }" @click="switchTab('group')">慢病管理服务包</view>
</view>
<scroll-view class="promotions-list" scroll-y :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
<view v-if="loading && promotions.length === 0" class="loading-container"><text class="loading-text">加载中...</text></view>
<view v-else-if="promotions.length === 0" class="empty-container">
<text class="empty-icon">🎉</text>
<text class="empty-text">暂无关怀活动</text>
<view class="add-promotion-btn" @click="addPromotion">+ 创建关怀活动</view>
</view>
<view v-else>
<view v-for="promo in promotions" :key="promo.id" class="promotion-card">
<view class="promo-header">
<text class="promo-name">{{ promo.name }}</text>
<text class="promo-status" :class="'status-' + promo.status">{{ getStatusText(promo.status) }}</text>
</view>
<view class="promo-info">
<view class="info-item"><text class="label">优惠内容:</text><text class="value">{{ promo.discount_text }}</text></view>
<view class="info-item"><text class="label">有效期:</text><text class="value">{{ formatDate(promo.start_time) }} - {{ formatDate(promo.end_time) }}</text></view>
<view class="info-item"><text class="label">已领取:</text><text class="value">{{ promo.received_count || 0 }}</text></view>
</view>
<view class="promo-actions">
<view class="action-btn" @click="editPromotion(promo)">编辑</view>
<view class="action-btn danger" @click="deletePromotion(promo)">删除</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
import { USE_MOCK, MOCK_MERCHANT_ID, getMockPromotions } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
type PromotionType = {
id: string
name: string
type: string
discount_text: string
start_time: string
end_time: string
status: number
received_count: number
}
export default {
data() {
return {
currentTab: 'coupon',
promotions: [] as PromotionType[],
loading: false,
refreshing: false,
merchantId: ''
}
},
onLoad() {
this.initMerchantId()
},
onShow() {
if (this.merchantId !== '') {
this.loadPromotions()
}
},
methods: {
async initMerchantId() {
if (USE_MOCK) {
this.merchantId = MOCK_MERCHANT_ID
this.loadPromotions()
return
}
try {
const session = supa.getSession()
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
} catch (e) {}
this.loadPromotions()
},
async loadPromotions() {
if (USE_MOCK) {
this.promotions = getMockPromotions(this.currentTab)
this.loading = false
this.refreshing = false
return
}
this.loading = true
try {
const response = await supa
.from('ml_coupon_templates')
.select('*')
.eq('merchant_id', this.merchantId)
.eq('coupon_type', this.currentTab)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error != null || !response.data) {
this.promotions = []
return
}
const rawData = response.data as any[]
const promos: PromotionType[] = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
promos.push({
id: item.getString('id') || '',
name: item.getString('name') || '',
type: item.getString('coupon_type') || 'coupon',
discount_text: `满${item.getNumber('min_amount') || 0}减${item.getNumber('discount_amount') || item.getNumber('discount_value') || 0}`,
start_time: item.getString('start_time') || '',
end_time: item.getString('end_time') || '',
status: item.getNumber('status') || 1,
received_count: item.getNumber('received_count') || 0
} as PromotionType)
}
this.promotions = promos
} catch (e) {
console.error('加载活动失败:', e)
} finally {
this.loading = false
this.refreshing = false
}
},
switchTab(tab: string) {
this.currentTab = tab
this.loadPromotions()
},
onRefresh() {
this.refreshing = true
this.loadPromotions()
},
addPromotion() {
uni.showToast({ title: '活动管理功能开发中', icon: 'none' })
},
editPromotion(promo: PromotionType) {
uni.showToast({ title: '编辑功能开发中', icon: 'none' })
},
deletePromotion(promo: PromotionType) {
uni.showModal({
title: '确认删除',
content: '确定要删除该活动吗?',
success: async (res) => {
if (res.confirm) {
try {
await supa.from('ml_coupon_templates').delete().eq('id', promo.id).execute()
uni.showToast({ title: '删除成功', icon: 'success' })
this.loadPromotions()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
})
},
getStatusText(status: number): string {
if (status === 1) return '进行中'
if (status === 0) return '未开始'
if (status === 2) return '已结束'
return '未知'
},
formatDate(dateStr: string): string {
if (!dateStr) return '-'
const date = new Date(dateStr)
return `${date.getMonth() + 1}-${date.getDate()}`
}
}
}
</script>
<style>
.promotions-page { background-color: #f5f5f5; min-height: 100vh; padding-bottom: 140rpx; }
.tabs { display: flex; flex-direction: row; background-color: #fff; padding: 0 20rpx; position: sticky; top: 0; z-index: 10; }
.tab { flex: 1; text-align: center; padding: 24rpx 0; font-size: 28rpx; color: #666; position: relative; }
.tab.active { color: #09C39D; font-weight: bold; }
.tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background-color: #09C39D; border-radius: 2rpx; }
.promotions-list { padding: 20rpx; height: calc(100vh - 200rpx); }
.loading-container, .empty-container { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100rpx 0; }
.empty-icon { font-size: 100rpx; margin-bottom: 20rpx; }
.empty-text, .loading-text { font-size: 28rpx; color: #999; }
.add-btn { margin-top: 30rpx; padding: 20rpx 60rpx; background-color: #09C39D; color: #fff; font-size: 28rpx; border-radius: 40rpx; }
.promotion-card { background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; }
.promo-header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
.promo-name { font-size: 30rpx; font-weight: bold; color: #333; }
.promo-status { font-size: 22rpx; padding: 6rpx 16rpx; border-radius: 16rpx; }
.status-1 { background-color: #E8F5E9; color: #4CAF50; }
.status-0 { background-color: #FFF3E0; color: #FF9800; }
.status-2 { background-color: #F5F5F5; color: #999; }
.promo-info { margin-bottom: 20rpx; }
.info-item { display: flex; flex-direction: row; font-size: 26rpx; margin-bottom: 10rpx; }
.info-item .label { color: #999; min-width: 140rpx; }
.info-item .value { color: #333; }
.promo-actions { display: flex; flex-direction: row; justify-content: flex-end; gap: 16rpx; }
.action-btn { padding: 12rpx 24rpx; font-size: 24rpx; background-color: #F5F5F5; color: #666; border-radius: 24rpx; }
.action-btn.danger { background-color: #FFEBEE; color: #F44336; }
.add-promotion-btn { position: fixed; bottom: 30rpx; left: 50%; transform: translateX(-50%); width: 300rpx; height: 88rpx; background: linear-gradient(135deg, #A6F1E4 0%, #69DFC2 100%); border-radius: 44rpx; display: flex; flex-direction: row; align-items: center; justify-content: center; font-size: 30rpx; color: #fff; font-weight: bold; box-shadow: 0 8rpx 20rpx rgba(9,195,157,0.3); }
</style>