完成代码路径重构

This commit is contained in:
2026-03-18 08:36:49 +08:00
parent 4041933e42
commit c2cd6dcd95
290 changed files with 866 additions and 38459 deletions

View File

@@ -1,787 +0,0 @@
<template>
<view class="admin-marketing-coupon">
<view class="content-body">
<!-- 搜索过滤栏 -->
<view class="filter-card border-shadow">
<view class="filter-row">
<view class="filter-item item-w">
<text class="label-txt">优惠券名称:</text>
<view class="input-wrap">
<input class="search-input" v-model="filter.name" placeholder="请输入优惠券名称" />
<text class="count-txt">{{ filter.name.length }}/18</text>
</view>
</view>
<view class="filter-item item-w">
<text class="label-txt">优惠券类型:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow-down">▼</text>
</view>
</view>
<view class="filter-item item-w">
<text class="label-txt">是否有效:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow-down">▼</text>
</view>
</view>
</view>
<view class="filter-row mt-20">
<view class="filter-item item-w">
<text class="label-txt">发放方式:</text>
<view class="select-mock">
<text class="select-val">请选择</text>
<text class="arrow-down">▼</text>
</view>
</view>
<view class="btn-query" @click="handleQuery">
<text class="query-txt">查询</text>
</view>
</view>
</view>
<!-- 数据展示区域 -->
<view class="table-card border-shadow">
<view class="card-header">
<view class="btn-primary-blue" @click="handleAdd">
<text class="btn-txt">添加优惠券</text>
</view>
</view>
<!-- 表格主体 - 使用 CSS Grid 确保严格对齐 -->
<view class="table-container">
<!-- 表头 -->
<view class="table-header-row table-grid">
<view class="th p-1">ID</view>
<view class="th p-1 name-col">优惠券名称</view>
<view class="th p-2">优惠券类型</view>
<view class="th p-1">面值/门槛</view>
<view class="th p-3">领取/使用限制</view>
<view class="th p-3">领取日期</view>
<view class="th p-2">发布数量</view>
<view class="th p-2">状态</view>
<view class="th p-1 op-cell shadow-left">操作</view>
</view>
<!-- 表身 -->
<scroll-view class="table-body-scroll" scroll-x="true">
<view class="table-body">
<view v-for="(item, index) in dataList" :key="item.id" class="table-row table-grid">
<view class="td p-1"><text class="td-txt">{{ item.id }}</text></view>
<view class="td p-1 name-col">
<view class="name-box">
<text class="td-txt name-bold" :title="item.description">{{ item.name }}</text>
<text class="date-small">{{ item.createdAt }} 创建</text>
<text v-if="item.description" class="desc-tip">{{ item.description }}</text>
</view>
</view>
<view class="td p-2"><text class="td-txt">{{ item.type }}</text></view>
<view class="td p-1">
<view class="value-req">
<text class="td-txt price-txt">{{ item.value.toFixed(2) }}{{ item.type.includes('折扣') ? '折' : '元' }}</text>
<text class="date-small">{{ item.minOrderAmount > 0 ? '满' + item.minOrderAmount + '元可用' : '无门槛' }}</text>
<text v-if="item.maxDiscountAmount != null" class="date-small danger">最多减{{ item.maxDiscountAmount }}元</text>
</view>
</view>
<view class="td p-3">
<view class="limit-info">
<text class="td-txt">{{ item.receiveType }}</text>
<text class="date-small">每人限领: {{ item.perUserLimit }}张</text>
<text class="date-small">领取限制: {{ item.usageLimit === 0 ? '不限' : item.usageLimit + '次/天' }}</text>
</view>
</view>
<view class="td p-3"><text class="td-txt date-small">{{ item.receiveDate }}</text></view>
<view class="td p-2">
<view class="pub-info">
<text class="td-txt">{{ item.publishTotal === 0 ? '不限量' : '总: ' + item.publishTotal }}</text>
<text v-if="item.publishTotal > 0" class="pub-txt danger">剩: {{ item.remainingQuantity }}</text>
</view>
</view>
<view class="td p-2">
<StatusSwitch
:modelValue="item.isOpen"
@update:modelValue="(val : boolean) => onToggleStatus(index, val)"
/>
</view>
<!-- 操作列Sticky 固定 -->
<view class="td p-1 op-cell shadow-left">
<view class="op-links">
<text class="op-link" @click="handleShowRecords(item)">领取记录</text>
<text class="op-split">|</text>
<text class="op-link" @click="handleEdit(item)">编辑</text>
<text class="op-split">|</text>
<text class="op-link text-danger" @click="handleDelete(item)">删除</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 分页 -->
<CommonPagination
v-if="dataList.length > 0 || pageState.loading"
:total="pageState.total"
:loading="pageState.loading"
:currentPage="pageState.currentPage"
:pageSize="pageState.pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="pageState.jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val : string) => { pageState.jumpPageInput = val }"
@jump-page="handleJumpPage"
/>
<!-- 空数据状态 -->
<view class="table-empty" v-if="dataList.length === 0 && !pageState.loading">
<text class="empty-txt">暂无数据</text>
</view>
</view>
</view>
<!-- 领取记录模态框 -->
<view v-if="showRecordsModal" class="modal-mask" @click="showRecordsModal = false">
<view class="modal-container" @click.stop>
<view class="modal-header">
<text class="modal-title">领取记录 - {{ selectedCoupon?.name }}</text>
<text class="modal-close" @click="showRecordsModal = false">×</text>
</view>
<view class="modal-body">
<view class="record-table">
<view class="record-header record-grid">
<text class="r-th">ID</text>
<text class="r-th">用户名</text>
<text class="r-th">用户头像</text>
<text class="r-th">领取时间</text>
</view>
<scroll-view scroll-y="true" class="record-list">
<view v-for="rec in currentRecords" :key="rec.id" class="record-row record-grid">
<text class="r-td">{{ rec.id }}</text>
<text class="r-td">{{ rec.username }}</text>
<view class="r-td">
<image class="avatar-img" :src="rec.avatar" mode="aspectFill"></image>
</view>
<text class="r-td">{{ rec.time }}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import StatusSwitch from '@/components/StatusSwitch.uvue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
import supa from '@/components/supadb/aksupainstance.uts'
interface CouponItem {
id: number
id_uuid: string // 保持对 UUID 的引用以进行后续操作
name: string
description: string
type: string
value: number
minOrderAmount: number
maxDiscountAmount: number | null
receiveType: string
receiveDate: string
useTime: string
publishTotal: number
remainingQuantity: number
perUserLimit: number
usageLimit: number
isOpen: boolean
createdAt: string
}
interface RecordItem {
id: string
username: string
avatar: string
time: string
}
/**
* 🔐 权限和弹窗逻辑说明:
* - isAdmin: 模拟当前用户的 admin 权限状态。
* - handleDelete: 点击删除时首先弹出确认对话框。
* - 权限校验: 在确认删除的回调中检查 isAdmin 状态。
* - 拦截行为: 如果非 admin弹出错误警告并阻止执行删除操作。
*/
const isAdmin = ref(true) // 为了方便测试,设为 true
const filter = reactive({
name: ''
})
const dataList = ref<CouponItem[]>([])
// --- 分页与列表状态管理 ---
const pageState = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
loading: false,
jumpPageInput: ''
})
const pageSizeOptions = [10, 20, 30, 50, 100]
const pageSizeOptionLabels = computed((): string[] => pageSizeOptions.map((size: number): string => `${size} 条/页`))
const pageSizeIndex = computed((): number => {
const index = pageSizeOptions.indexOf(pageState.pageSize)
return index === -1 ? 0 : index
})
const totalPage = computed((): number => {
return Math.ceil(pageState.total / pageState.pageSize)
})
const visiblePages = computed((): number[] => {
const current = pageState.currentPage
const total = totalPage.value
if (total <= 7) {
const pages: number[] = []
for (let i = 1; i <= total; i++) {
pages.push(i)
}
return pages
}
if (current <= 4) {
return [1, 2, 3, 4, 5, -1, total]
}
if (current >= total - 3) {
return [1, -1, total - 4, total - 3, total - 2, total - 1, total]
}
return [1, -1, current - 1, current, current + 1, -1, total]
})
const handlePageSizeChange = (e: any) => {
if (pageState.loading) return
let val = 0
if (typeof e.detail.value === 'string') {
val = parseInt(e.detail.value)
} else {
val = e.detail.value as number
}
pageState.pageSize = pageSizeOptions[val]
pageState.currentPage = 1
fetchCouponTemplates()
}
const handlePageChange = (p: number) => {
if (pageState.loading || p < 1 || p > totalPage.value || p === pageState.currentPage) return
pageState.currentPage = p
pageState.jumpPageInput = ''
fetchCouponTemplates()
}
const handleJumpPage = () => {
if (pageState.loading) return
let jumpTo = parseInt(pageState.jumpPageInput)
if (isNaN(jumpTo)) return
if (jumpTo < 1) jumpTo = 1
if (jumpTo > totalPage.value) jumpTo = totalPage.value
pageState.jumpPageInput = String(jumpTo)
if (jumpTo !== pageState.currentPage) {
pageState.currentPage = jumpTo
fetchCouponTemplates()
}
}
const fetchCouponTemplates = async () => {
pageState.loading = true
uni.showLoading({ title: '加载中...', mask: true })
try {
// 兼容使用 { count: 'exact' } 获取精确总数
let query = supa.from('ml_coupon_templates').select('*', { count: 'exact' })
// 如果有名称筛选
if (filter.name.trim() != '') {
query = query.like('name', `%${filter.name}%`)
}
// 计算分页 range
const start = (pageState.currentPage - 1) * pageState.pageSize
const end = pageState.currentPage * pageState.pageSize - 1
const res = await query.order('created_at', { ascending: false }).range(start, end).execute()
if (res.error != null || res.status >= 400) {
const msg = res.error?.message ?? `获取数据失败 (${res.status})`
uni.showToast({ title: msg, icon: 'none' })
return
}
// 尝试安全获取总数。如果有的封装库剥离了 count 属性,则以当前返回的数据长度作为保底
let fCount = 0
if (res.count != null) {
fCount = res.count as number
} else if (res['total'] != null) {
fCount = res['total'] as number
}
if (!Array.isArray(res.data)) {
console.warn('Expected array but got:', res.data)
dataList.value = []
pageState.total = fCount
return
}
const rawData = res.data as Array<UTSJSONObject>
// 如果获取到的总数为 0 但实际有数据,说明接口 count 丢了,用当前拉取的数据量兜底防止分页区坍塌
pageState.total = Math.max(fCount, rawData.length)
dataList.value = rawData.map((item : UTSJSONObject) : CouponItem => {
// 优先获取 cid (自增 ID),如果没有则取 id (UUID) 的后几位或 0
const displayId = (item.get('cid') as number | null) ?? 0
const uuid = item.get('id') as string ?? ''
const couponType = item.get('coupon_type') as number ?? 1
let typeStr = '未知类型'
if (couponType === 1) typeStr = '满减券'
else if (couponType === 2) typeStr = '折扣券'
else if (couponType === 3) typeStr = '免运费券'
// 获取面值逻辑:如果是折扣券 (discount_type=2),展示方式可能不同
const discountType = item.get('discount_type') as number ?? 1
const discountValue = item.get('discount_value') as number ?? 0
let displayValue = discountValue
const startTime = item.get('start_time') as string ?? ''
const endTime = item.get('end_time') as string ?? ''
const createdAt = item.get('created_at') as string ?? ''
const dateStr = (startTime != '' && endTime != '')
? `${startTime.slice(0, 10)} 至 ${endTime.slice(0, 10)}`
: '不限时'
return {
id: displayId,
id_uuid: uuid,
name: item.get('name') as string ?? '未命名优惠券',
description: item.get('description') as string ?? '',
type: typeStr,
value: displayValue,
minOrderAmount: item.get('min_order_amount') as number ?? 0,
maxDiscountAmount: item.get('max_discount_amount') as number | null,
receiveType: (item.get('merchant_id') == null ? '平台发放' : '商家发放'),
receiveDate: dateStr,
useTime: dateStr,
publishTotal: (item.get('total_quantity') as number ?? 0),
remainingQuantity: (item.get('remaining_quantity') as number ?? 0),
perUserLimit: item.get('per_user_limit') as number ?? 1,
usageLimit: item.get('usage_limit') as number ?? 1,
isOpen: (item.get('status') as number) === 1,
createdAt: createdAt.slice(0, 16).replace('T', ' ')
} as CouponItem
})
} catch (e) {
console.error('Fetch Coupons Error:', e)
uni.showToast({ title: '访问数据库异常', icon: 'none' })
} finally {
pageState.loading = false
uni.hideLoading()
}
}
const handleQuery = () => {
pageState.currentPage = 1 // 重置到第一页
fetchCouponTemplates()
}
const handleAdd = () => {
uni.showToast({ title: '导航至添加页面', icon: 'none' })
}
const toggleStatus = async (index: number) => {
const item = dataList.value[index]
const newStatus = item.isOpen ? 2 : 1 // 1: 正常, 2: 暂停
try {
const res = await supa.from('ml_coupon_templates')
.update({ status: newStatus } as UTSJSONObject)
.eq('id', item.id_uuid)
.execute()
if (res.error != null || res.status >= 400) {
uni.showToast({ title: '状态更新失败: ' + (res.error?.message ?? `HTTP ${res.status}`), icon: 'none' })
return
}
item.isOpen = !item.isOpen
uni.showToast({ title: '状态更新成功', icon: 'success' })
} catch (e) {
uni.showToast({ title: '更新状态异常', icon: 'none' })
}
}
// 领取记录逻辑
const showRecordsModal = ref(false)
const selectedCoupon = ref<CouponItem | null>(null)
const currentRecords = ref<RecordItem[]>([])
const handleShowRecords = (item: CouponItem) => {
selectedCoupon.value = item
// 模拟接口数据
currentRecords.value = [
{ id: '70074', username: '131****7722', avatar: 'https://placeholder.com/40', time: '2025-02-25 21:33:35' },
{ id: '36794', username: '147****4489', avatar: 'https://placeholder.com/40', time: '2025-02-25 21:58:27' },
{ id: '60902', username: '花开花落', avatar: 'https://placeholder.com/40', time: '2025-02-25 21:59:14' },
{ id: '70312', username: '55454', avatar: 'https://placeholder.com/40', time: '2025-02-25 23:20:00' }
]
showRecordsModal.value = true
}
const handleEdit = (item: CouponItem) => {
uni.showToast({ title: `编辑: ${item.name}`, icon: 'none' })
}
const handleCopy = (item: CouponItem) => {
uni.showToast({ title: `复制: ${item.name}`, icon: 'success' })
}
// 删除逻辑与权限验证
const handleDelete = (item: CouponItem) => {
uni.showModal({
title: '确认提示',
content: `确定要删除“${item.name}”这一条优惠券数据吗?此操作不可逆。`,
cancelText: '取消',
confirmText: '确定',
success: async (res) => {
if (res.confirm) {
// 🔒 权限校验:检查用户是否具有 admin 权限
if (!isAdmin.value) {
uni.showModal({
title: '操作被拦截',
content: '您无权限删除,请联系系统管理员。',
showCancel: false,
confirmText: '阅读并关闭'
})
return
}
try {
// 实际从数据库删除
const delRes = await supa.from('ml_coupon_templates')
.eq('id', item.id_uuid)
.delete()
.execute()
if (delRes.error != null || delRes.status >= 400) {
uni.showToast({ title: '删除失败: ' + (delRes.error?.message ?? `HTTP ${delRes.status}`), icon: 'none' })
return
}
uni.showToast({ title: '删除成功', icon: 'success' })
// 如果当前页只有一条数据,且不是第一页,则退回上一页
if (dataList.value.length === 1 && pageState.currentPage > 1) {
pageState.currentPage--
}
// 重新拉取当前页数据
fetchCouponTemplates()
} catch (e) {
uni.showToast({ title: '操作数据库异常', icon: 'none' })
}
}
}
})
}
onMounted(() => {
console.log('Coupon list initializing and fetching data...')
fetchCouponTemplates()
})
</script>
<style scoped lang="scss">
.admin-marketing-coupon {
padding: 0px;
background-color: #f5f7f9;
min-height: 100vh;
}
.border-shadow {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.content-body {
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px;
}
/* 过滤栏 */
.filter-card { padding: 24px; }
.filter-row { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 24px; }
.mt-20 { margin-top: 20px; }
.filter-item { display: flex; flex-direction: row; align-items: center; gap: 8px; }
.item-w { width: 320px; }
.label-txt { font-size: 14px; color: #606266; min-width: 80px; text-align: right; }
.input-wrap {
flex: 1;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 10px;
}
.search-input { flex: 1; height: 100%; font-size: 14px; background: none; border: none; outline: none; }
.count-txt { font-size: 12px; color: #c0c4cc; }
.select-mock {
flex: 1;
height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 12px;
cursor: pointer;
}
.select-val { font-size: 14px; color: #c0c4cc; }
.arrow-down { font-size: 10px; color: #c0c4cc; }
.btn-query {
background-color: #2d8cf0;
padding: 0 20px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
}
.query-txt { color: #fff; font-size: 14px; }
/* 表格区域 */
.table-card { background-color: #fff; display: flex; flex-direction: column; overflow: hidden; }
.card-header { padding: 24px; }
.btn-primary-blue {
background-color: #2d8cf0;
padding: 8px 16px;
border-radius: 4px;
display: inline-flex;
cursor: pointer;
}
.btn-txt { color: #fff; font-size: 14px; }
.table-container {
padding: 0 24px 24px;
overflow: hidden;
}
/* 🧱 Grid 核心布局系统 */
.table-grid {
display: grid;
/* 列宽定义ID, 名称, 类型, 面值/门槛, 领取/限制, 日期, 数量, 状态, 操作 */
grid-template-columns: 60px 2.5fr 100px 150px 180px 160px 100px 80px 240px;
align-items: stretch;
}
.table-header-row {
background-color: #f8f8f9;
border-bottom: 1px solid #e8eaec;
}
.th {
padding: 12px 10px;
font-size: 14px;
color: #515a6e;
font-weight: bold;
text-align: left;
display: flex;
align-items: center;
}
.table-row {
border-bottom: 1px solid #e8eaec;
background-color: #fff;
}
.table-row:hover { background-color: #f0faff; }
.td {
padding: 12px 10px;
display: flex;
align-items: center;
overflow: hidden;
/* 文本溢出策略:支持正常换行 */
white-space: normal;
word-break: break-all;
}
.name-box, .value-req, .limit-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.desc-tip {
font-size: 11px;
color: #999;
background-color: #f9f9f9;
padding: 2px 6px;
border-radius: 4px;
margin-top: 4px;
display: none; /* 默认隐藏,在 hover 状态显示H5仿真 */
}
.table-row:hover .desc-tip {
display: block;
}
.td-txt { font-size: 14px; color: #515a6e; line-height: 1.5; }
.name-bold { font-weight: 500; color: #333; }
.price-txt { color: #f56c6c; font-weight: bold; }
.date-small { font-size: 12px; color: #999; }
.danger { color: #ed4014; }
.pub-info { display: flex; flex-direction: column; }
.pub-txt.danger { color: #ed4014; font-size: 11px; margin-top: 2px; }
/* 🚀 Sticky 操作列实现 */
.op-cell {
position: sticky;
right: 0;
background-color: #fff; /* 背景必须不透明 */
z-index: 10;
justify-content: center;
}
/* 鼠标悬停时保持背景色同步 */
.table-row:hover .op-cell {
background-color: #f0faff;
}
.shadow-left {
box-shadow: -6px 0 10px rgba(0, 0, 0, 0.05);
}
.op-links { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; justify-content: center; }
.op-link { color: #2d8cf0; font-size: 13px; cursor: pointer; margin: 2px 4px; white-space: nowrap; }
.op-split { color: #e8eaec; margin: 0; font-size: 12px; }
.text-danger { color: #ed4014; }
/* 📱 响应式媒体查询 (Priority 策略) */
/* 平板:隐藏次要列 */
@media screen and (max-width: 1450px) {
.table-grid {
grid-template-columns: 70px 2fr 90px 80px 0 150px 0 100px 90px 240px;
}
.p-3 { display: none; } /* 隐藏优先级 3领取方式、使用时间 */
}
@media screen and (max-width: 1100px) {
.table-grid {
grid-template-columns: 0 1.5fr 0 80px 0 140px 0 100px 0 220px;
}
.p-1:first-child, .p-2 { display: none; } /* 隐藏 ID, 类型, 状态 */
}
/* 手机:极致压缩,浮动操作 */
@media screen and (max-width: 768px) {
.table-grid {
grid-template-columns: 1fr 100px 140px;
}
.p-1:not(.name-col):not(.op-cell), .p-2, .p-3 { display: none; }
.op-cell { width: 140px; border-left: 1px solid #f0f0f0; }
.op-split { display: none; }
.op-links { flex-direction: column; align-items: center; }
}
/* 空数据状态 */
.table-empty {
padding: 60px 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
border-top: 1px solid #e8eaec;
}
.empty-txt {
font-size: 14px;
color: #909399;
}
/* 🎭 模态框样式 */
.modal-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0,0,0,0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-container {
width: 90%;
max-width: 760px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
max-height: 85vh;
box-shadow: 0 10px 50px rgba(0,0,0,0.2);
}
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.modal-title { font-size: 16px; font-weight: bold; color: #1f2d3d; }
.modal-close { font-size: 28px; color: #bfcbd9; cursor: pointer; line-height: 1; }
.modal-close:hover { color: #ff4949; }
.modal-body { padding: 24px; flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.record-table { border: 1px solid #ebeef5; border-radius: 4px; flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.record-grid {
display: grid;
grid-template-columns: 80px 1.5fr 100px 180px;
}
.record-header { background-color: #f5f7fa; border-bottom: 1px solid #ebeef5; }
.r-th, .r-td { padding: 12px 15px; font-size: 14px; color: #606266; display: flex; align-items: center; }
.r-th { font-weight: bold; color: #909399; }
.record-list { flex: 1; height: 300px; }
.record-row { border-bottom: 1px solid #f0f2f5; transition: background 0.2s; }
.record-row:hover { background-color: #f9fafc; }
.avatar-img { width: 32px; height: 32px; border-radius: 4px; background-color: #eee; }
@media screen and (max-width: 600px) {
.record-grid {
grid-template-columns: 50px 1fr 0 140px;
}
.record-grid .r-th:nth-child(1), .record-grid .r-td:nth-child(1),
.record-grid .r-th:nth-child(3), .record-grid .r-td:nth-child(3) { display: none; }
}
</style>

View File

@@ -1,65 +0,0 @@
<template>
<AdminLayout currentPage="coupon-receive">
<view class="page">
<view class="Header">
<text class="Title">领取情况</text>
<text class="SubTitle">marketing/coupon/receive</text>
</view>
<view class="Card">
<text class="Label">页面参数query</text>
<text class="Mono">{{ params }}</text>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
const params = ref('')
onLoad((options) => {
// options: Record<string, any>
params.value = JSON.stringify(options ?? {})
})
</script>
<style>
.Page {
padding: 0;
}
.Header {
padding: 24rpx;
border-radius: 16rpx;
background: #ffffff;
}
.Title {
font-size: 36rpx;
font-weight: 700;
}
.SubTitle {
margin-top: 8rpx;
font-size: 24rpx;
opacity: 0.7;
}
.Card {
margin-top: 24rpx;
padding: 24rpx;
border-radius: 16rpx;
background: #ffffff;
}
.Label {
font-size: 26rpx;
font-weight: 600;
margin-bottom: 12rpx;
}
.Mono {
font-size: 24rpx;
font-family: monospace;
line-height: 36rpx;
word-break: break-all;
}
</style>