Commit edf95011 by Hantao

feat(coupon): 新增优惠券页面及集成uview-plus组件库

- 添加优惠券页面,包含满减券、折扣券、服务券三种类型
- 集成uview-plus组件库,配置easycom自动导入
- 在侧边栏添加优惠券页面入口
- 添加优惠券相关静态资源图片
- 更新uni.scss和App.vue以支持uview-plus主题
- 添加dayjs和clipboard依赖
parent 5f8720de
......@@ -48,6 +48,9 @@
"@dcloudio/uni-mp-toutiao": "3.0.0-3090620231104001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090620231104001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090620231104001",
"clipboard": "^2.0.11",
"dayjs": "^1.11.19",
"uview-plus": "^3.7.0",
"vue": "^3.2.45",
"vue-i18n": "^9.1.9"
},
......@@ -57,7 +60,7 @@
"@dcloudio/uni-cli-shared": "3.0.0-3090620231104001",
"@dcloudio/uni-stacktracey": "3.0.0-3090620231104001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090620231104001",
"vite": "^4.0.3",
"sass": "^1.77.8"
"sass": "^1.77.8",
"vite": "^4.0.3"
}
}
<script>
export default {
onLaunch: function() {
uni.hideTabBar()
console.log('App Launch')
},
onShow: function() {
......@@ -13,6 +12,7 @@
}
</script>
<style>
/*每个页面公共css */
<style lang="scss">
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
@import "uview-plus/index.scss";
</style>
import App from './App'
import uviewPlus from 'uview-plus'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
......@@ -15,6 +15,7 @@ app.$mount()
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(uviewPlus)
return {
app
}
......
{
"easycom": {
"autoscan": true,
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
"custom": {
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
}
},
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
......@@ -19,6 +28,12 @@
}
},
{
"path": "pages/coupon/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/second/index",
"style": {
"navigationStyle": "custom"
......
......@@ -28,7 +28,7 @@ const badges = computed(() => [
{ id: 5, title: '水质专家', sub: '完成全部关卡', iconKey: 'badge5', unlocked: badgeImgs.value >= 5 }
]);
const badgeImgs = ref(1);
const badgeImgs = ref(5);
const unlockedCount = computed(() => badgeImgs.value);
......
<script setup>
import { ref } from 'vue';
const ASSETS = Object.freeze({
bg: '/static/coupon/bg.webp',
border: '/static/coupon/border.webp',
borderGrey: '/static/coupon/borderGrey.webp',
expired: '/static/coupon/expired.webp',
use: '/static/coupon/use.webp',
used: '/static/coupon/used.webp'
});
// 优惠券类型: 1-满减券, 2-折扣券, 3-服务券
// 状态: available-可用, used-已使用, expired-已过期
const couponList = ref([
{
id: 1,
type: 1, // 满减券
status: 'available',
title: '小游戏奖励优惠券',
value: 150,
condition: '满1000元可用',
expireTime: '2026.03.31 23:59',
tips: '仅限获得当日使用',
expanded: false,
scope: '仅适用于滤芯类产品',
code: 'BM123453215523'
},
{
id: 2,
type: 2, // 折扣券
status: 'available',
title: '滤芯套装8折券',
value: 8,
condition: '',
expireTime: '2026.03.31 23:59',
tips: '必须滤芯包商品使用',
expanded: false,
scope: '仅适用于滤芯类产品',
code: 'BM123453215524'
},
{
id: 3,
type: 3, // 服务券
status: 'available',
title: '上门检测水质服务',
value: '',
condition: '',
expireTime: '2026.03.31 23:59',
tips: '',
expanded: false,
scope: '仅适用于滤芯类产品',
code: 'BM123453215525'
},
{
id: 4,
type: 1, // 满减券-已使用
status: 'used',
title: '小游戏奖励优惠券',
value: 150,
condition: '满1000元可用',
expireTime: '2026.03.31 23:59',
tips: '仅限获得当日使用',
expanded: false,
scope: '仅适用于滤芯类产品',
code: 'BM123453215526'
},
{
id: 5,
type: 1, // 满减券-已过期
status: 'expired',
title: '小游戏奖励优惠券',
value: 150,
condition: '满1000元可用',
expireTime: '2026.03.31 23:59',
tips: '仅限获得当日使用',
expanded: false,
scope: '仅适用于滤芯类产品',
code: 'BM123453215527'
}
]);
// 获取边框图片
const getBorderImg = (status) => {
return status === 'available' ? ASSETS.border : ASSETS.borderGrey;
};
// 获取状态图片
const getStatusImg = (status) => {
if (status === 'used') return ASSETS.used;
if (status === 'expired') return ASSETS.expired;
return ASSETS.use;
};
// 切换展开/收起
const toggleExpand = (coupon) => {
coupon.expanded = !coupon.expanded;
};
</script>
<template>
<view class="container">
<view class="title">我的优惠券</view>
<scroll-view scroll-y class="coupon-list">
<view
v-for="coupon in couponList"
:key="coupon.id"
class="coupon"
:class="{ 'coupon-disabled': coupon.status !== 'available' }"
>
<!-- 优惠券主体 -->
<view class="coupon-main">
<image :src="ASSETS.bg" mode="scaleToFill" class="coupon-bg" />
<image :src="getBorderImg(coupon.status)" mode="scaleToFill" class="coupon-border" />
<!-- 优惠券内容 -->
<view class="coupon-content">
<!-- 左侧金额/折扣区域(服务券不显示) -->
<view v-if="coupon.type !== 3" class="coupon-left">
<!-- 满减券 -->
<template v-if="coupon.type === 1">
<view class="price-wrap">
<text class="currency">¥</text>
<text class="price">{{ coupon.value }}</text>
</view>
<text class="condition">{{ coupon.condition }}</text>
</template>
<!-- 折扣券 -->
<template v-else-if="coupon.type === 2">
<view class="discount-wrap">
<text class="discount">{{ coupon.value }}</text>
<text class="discount-unit"></text>
</view>
</template>
</view>
<!-- 右侧信息区域 -->
<view class="coupon-right" :class="{ 'coupon-right-service': coupon.type === 3 }">
<text class="coupon-title">{{ coupon.title }}</text>
<text class="expire-time">有效期至{{ coupon.expireTime }}</text>
<view v-if="coupon.tips" class="tips-wrap">
<text class="tips">使用须知:{{ coupon.tips }}</text>
</view>
</view>
<!-- 状态按钮 -->
<image
:src="getStatusImg(coupon.status)"
mode="scaleToFill"
class="status-img"
:class="{ 'status-expired': coupon.status === 'expired' }"
/>
<!-- 展开按钮 -->
<view v-if="coupon.status === 'available' && !coupon.expanded" class="expand-btn" @click="toggleExpand(coupon)">
<text class="expand-text">展开</text>
<up-icon name="arrow-down" size="24rpx" color="#666666"></up-icon>
</view>
</view>
</view>
<!-- 展开详情 -->
<view v-if="coupon.status === 'available' && coupon.expanded" class="expand-detail">
<view class="detail-row">
<text class="detail-label">适用范围:</text>
<text class="detail-value">{{ coupon.scope }}</text>
</view>
<view class="detail-row">
<text class="detail-label">券编码:</text>
<text class="detail-value">{{ coupon.code }}</text>
</view>
<view class="collapse-btn" @click="toggleExpand(coupon)">
<text class="collapse-text">收起</text>
<up-icon name="arrow-up" size="24rpx" color="#666666"></up-icon>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<style lang="scss" scoped>
.container {
width: 100vw;
height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
background-color: #73D8FF;
box-sizing: border-box;
}
.title {
margin-top: 180rpx;
margin-bottom: 30rpx;
justify-content: space-between;
align-items: center;
color: #2D4B5D;
font-size: 48rpx;
text-shadow: 1rpx 1rpx 0 #FFF, -1rpx -1rpx 0 #FFF, 1rpx -1rpx 0 #FFF, -1rpx 1rpx 0 #FFF;
}
.coupon-list {
flex: 1;
width: 100%;
padding: 0 32rpx;
box-sizing: border-box;
overflow-y: hidden;
}
.coupon {
width: 686rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
margin-bottom: 20rpx;
.coupon-main {
width: 100%;
height: 180rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.coupon-bg {
width: 704rpx;
height: 180rpx;
position: absolute;
z-index: 1;
}
.coupon-border {
width: 660rpx;
height: 132rpx;
position: absolute;
z-index: 2;
}
.coupon-content {
width: 660rpx;
height: 132rpx;
position: relative;
z-index: 3;
display: flex;
align-items: center;
padding: 0 20rpx;
box-sizing: border-box;
}
.coupon-left {
width: 160rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.price-wrap {
display: flex;
align-items: baseline;
color: #2D4B5D;
.currency {
font-size: 20rpx;
}
.price {
font-size: 48rpx;
font-weight: bold;
}
}
.condition {
font-size: 18rpx;
padding: 0 12rpx;
background: #73d8ff33;
border-radius: 12rpx;
}
.discount-wrap {
display: flex;
align-items: baseline;
color: #3BA7D5;
.discount {
font-size: 56rpx;
font-weight: bold;
}
.discount-unit {
font-size: 28rpx;
margin-left: 4rpx;
}
}
}
.coupon-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 20rpx;
.coupon-title {
font-size: 28rpx;
color: #2D4B5D;
font-weight: bold;
margin-bottom: 6rpx;
}
.expire-time {
font-size: 24rpx;
color: #666666;
margin-bottom: 6rpx;
}
.tips-wrap {
display: flex;
align-items: center;
.tips {
font-size: 24rpx;
color: #666666;
}
}
&.coupon-right-service {
padding-left: 40rpx;
.coupon-title {
font-size: 34rpx;
}
.expire-time {
font-size: 28rpx;
}
}
}
.status-img {
width: 114rpx;
height: 48rpx;
position: absolute;
right: 20rpx;
z-index: 4;
&.status-expired {
width: 108rpx;
height: 108rpx;
}
}
.expand-btn {
position: absolute;
right: 20rpx;
bottom: 10rpx;
display: flex;
align-items: center;
z-index: 4;
.expand-text {
font-size: 24rpx;
color: #666666;
margin-right: 4rpx;
}
.arrow-icon {
font-size: 24rpx;
color: #3BA7D5;
}
}
.expand-detail {
width: 694rpx;
background: #F4FCFF;
border-radius: 0 0 16rpx 16rpx;
padding: 20rpx 30rpx;
box-sizing: border-box;
position: relative;
z-index: 3;
margin-top: -10rpx;
.detail-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
color: #333333;
.detail-label {
font-size: 24rpx;
}
.detail-value {
font-size: 24rpx;
}
}
.collapse-btn {
position: absolute;
right: 30rpx;
bottom: 20rpx;
display: flex;
align-items: center;
.collapse-text {
font-size: 24rpx;
color: #666666;
margin-right: 4rpx;
}
}
}
}
.coupon-disabled {
.coupon-left {
.price-wrap, .discount-wrap {
color: #B0C4CE;
}
.condition {
color: #B0C4CE;
}
}
.coupon-right {
.coupon-title {
color: #8CA3AF;
}
.expire-time, .tips-wrap .tips {
color: #B0C4CE;
}
}
}
</style>
......@@ -2,7 +2,7 @@
import { ref } from 'vue';
// 图片路径
const ASSETS = {
const ASSETS = Object.freeze ({
bg: '/static/start/bg.webp',
title: '/static/start/title.webp',
startBtn: '/static/start/start.webp',
......@@ -23,7 +23,7 @@ const ASSETS = {
badge4: '/static/start/badge4.webp',
badge5: '/static/start/badge5.webp',
finishBtn: '/static/start/finishBtn.webp',
};
});
const progressImgs = ref(5);
......@@ -82,6 +82,12 @@ const navToBadgePage = () => {
});
};
const navToCouponPage = () => {
uni.navigateTo({
url: '/pages/coupon/index'
});
};
const toggleSidebar = () => {
isSidebarExpanded.value = !isSidebarExpanded.value;
};
......@@ -141,8 +147,8 @@ const closeSidebar = () => {
<!-- 侧边栏 -->
<view class="sidebar-container" :class="{ expanded: isSidebarExpanded }">
<view v-if="isSidebarExpanded" class="sidebar-custom-content">
<view class="badge-nav-btn" @click.stop="navToBadgePage">
</view>
<view class="badge-nav-btn" @click.stop="navToBadgePage"></view>
<view class="badge-nav-btn" @click.stop="navToCouponPage"></view>
</view>
<view class="sidebar-wrapper" @click.stop="toggleSidebar">
<image
......
......@@ -6,6 +6,9 @@
*
*/
/* uview-plus 主题变量 */
@import 'uview-plus/theme.scss';
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment