feat:迁移过去一部分

This commit is contained in:
dzc committed 2026-03-10 15:12:09 +08:00
1 parent f31015deb0
commit 370024dc76
37 files changed
+3650 -2

No files matched your search

+28
View File
@@ -38,6 +38,34 @@
}
}
],
"subPackages": [
{
"root": "pages/wallet",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "recharge",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "浦江县机关事务智慧后勤一体化平台app",
+111
View File
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { merchantApi } from "@@/api"
import { hooks, router } from "@@/index"
import { filePath } from "@@/utils/file"
const pageRef = ref<any>(null)
const state = reactive<any>({
pa: {
pagesize: 10,
type: "2",
parentId: "",
},
})
const pageHook = hooks.usePage(pageRef, merchantApi.childrenPage, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
onLoad((options: any) => {
const app = getApp()
app._initTabbar()
if (options.parentId) {
state.pa.parentId = options.parentId
}
})
onShow(async () => {
pageRef.value && pageRef.value.reload(false)
uni.showLoading({
title: "加载中...",
mask: true,
})
try {
await getDetail()
} catch (error) {
uni.showToast({
title: "加载失败",
icon: "error",
})
} finally {
uni.hideLoading()
}
uni.setNavigationBarTitle({ title: state.detail.name })
})
const getDetail = async () => {
const { data } = await merchantApi.detail({ id: state.pa.parentId })
state.detail = data
}
const toDetail = async (v: any) => {
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
router.jump("/pages/merchant/detail", { id: v.id })
}
}
</script>
<template>
<view class="h-full overflow-hidden">
<swiper class="w-100vw h-30vh" circular :indicator-dots="true" indicator-active-color="#0779FD"
indicator-color="#ffffff" :autoplay="true">
<swiper-item v-for="(v, i) in state.detail?.swiperPic?.split(',')" :key="i">
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
</swiper-item>
</swiper>
<crud-layout>
<z-paging ref="pageRef" height="70%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-20rpx" />
<view class="flex-row card p-20rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i"
@tap="toDetail(v)">
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(v.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="sub-text-34">{{ v.name }}</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view class="flex-1">
<view v-if="v.phone" class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ v.phone }}</view>
</view>
</view>
<tm-tag class="max-w-160rpx" size="xs">
{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}
</tm-tag>
</view>
<view class="second-text-24">
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
<view>{{ v.jobTime?.split(",")[0] ? "早:" + v.jobTime?.split(",")[0] : "" }}</view>
<view>{{ v.jobTime?.split(",")[1] ? "中:" + v.jobTime?.split(",")[1] : "" }}</view>
<view>{{ v.jobTime?.split(",")[2] ? "晚:" + v.jobTime?.split(",")[2] : "" }}</view>
</view>
<view v-if="v.addressDes" class="second-text-28">
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
<text class="ml-1">地址:</text>
<text class="m-l-10rpx">{{ v.addressDes }}</text>
</view>
</view>
</view>
</view>
</z-paging>
</crud-layout>
</view>
</template>
+80
View File
@@ -0,0 +1,80 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import uqrcode from "@@/utils/uqrcode"
import { applyApi } from "@@/api"
const showQrCodeDialog = ref(false)
const state = reactive({
actId: ''
})
onLoad((options: any) => {
state.actId = options.actId
getQrcodeDetail()
})
const getQrcodeDetail = async () => {
const { data = {} } = await applyApi.getCodeById({ id: state.actId })
if (!data.shortCode) {
uni.showToast({ title: "优惠券错误", icon: "none" })
return
}
uqrcode.make({
canvasId: "qrcodeId",
text: data.shortCode,
size: 100,
margin: 0,
success: (res) => { },
complete: () => { },
})
}
const doViewQrcode = async () => {
const { data = {} } = await applyApi.getCodeById({ id: state.actId })
if (!data.shortCode) {
uni.showToast({ title: "优惠券错误", icon: "none" })
return
}
showQrCodeDialog.value = true
uqrcode.make({
canvasId: "viewQrcodeId",
text: data.shortCode,
size: 200,
margin: 0,
success: (res) => { },
complete: () => { },
})
}
</script>
<template>
<crud-layout tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
<view class="h-30rpx"></view>
<view class="size-full relative">
<image class="w-650rpx h-1030rpx p-10rpx rounded-12rpx" src="/static/icon/birthday_coupon.jpg"
mode="aspectFit" />
<view class="z-100 size-120px absolute left-20rpx top-600rpx flex-row-center bg-white p-20rpx rounded-12rpx"
@tap="doViewQrcode">
<canvas canvas-id="qrcodeId" class="size-100px" />
</view>
</view>
<tm-drawer z-index="100" v-model:show="showQrCodeDialog" title="兑换券核销二维码" :show-close="true" position="bottom"
size="60%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="viewQrcodeId" class="size-200px" />
</view>
</tm-drawer>
</crud-layout>
</template>
<style lang="scss">
.layout_main {
padding: 0 !important;
}
</style>
@@ -0,0 +1,215 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { serviceEnum } from "@@/constant"
const { actCouponTypeEnum } = serviceEnum
const emit = defineEmits(["doVisibleQrcode"])
const props = withDefaults(defineProps<{ detail: any }>(), {
detail: {},
})
const doVisibleQrcode = () => {
emit("doVisibleQrcode", { type: 'ACT_CANCEL', detail: props.detail })
}
</script>
<template>
<view class="coupon-item">
<!-- 左侧主体 -->
<view class="coupon-left relative">
<view class="size-140rpx rounded-8rpx flex-row-center flex-shrink-0 bg-#f0f0f0 m-r-10rpx">
<image class="size-110rpx" src="/static/icon/act_cancel.png" mode="aspectFill" />
</view>
<view class="absolute top-0rpx right-80rpx">
<!-- 未使用 -->
<view v-if="detail.actStatus == 1"
class="iconfont icon-weishiyong absolute font-size-80rpx! color-blue" />
<!-- 已使用 -->
<view v-if="detail.actStatus == 2"
class="iconfont icon-weishiyong1 absolute font-size-80rpx! color-#cccccc" />
<!-- 已过期 -->
<view v-if="detail.actStatus == 3"
class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
</view>
<view class="info-box">
<view class="price-row">
<view v-if="detail.faceValue" class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ detail.faceValue }}</text>
</view>
<text class="name">{{ detail.actTitlel }}</text>
</view>
<view class="condition">
活动商户:{{ detail.mchName }}
</view>
<view class="scope-text flex-col">
<text>{{ actCouponTypeEnum[detail.couponType] }}</text>
<text>有限期至 {{ detail.expireTime }}</text>
</view>
</view>
</view>
<!-- 虚线分割与缺口 -->
<view class="split-line">
<view class="circle-top"></view>
<view class="dashed-line"></view>
<view class="circle-bottom"></view>
</view>
<!-- 右侧按钮 -->
<view class="coupon-right">
<view v-if="detail.actStatus == 1" class="action-btn" @tap="doVisibleQrcode">核销码</view>
<view v-if="detail.actStatus == 2" class="action-btn disabled">已使用</view>
<view v-if="detail.actStatus == 3" class="action-btn disabled">已过期</view>
</view>
</view>
</template>
<style scoped lang="scss">
.coupon-item {
display: flex;
align-items: stretch;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 16rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 148, 255, 0.08);
}
/* 左侧布局 */
.coupon-left {
flex: 1;
display: flex;
align-items: center;
padding: 24rpx 0 24rpx 24rpx;
background: radial-gradient(circle at right top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at right bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
/* 备用背景色,防止渐变失效 */
background-color: #fff;
}
.goods-img {
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
background-color: #f0f0f0;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 140rpx;
}
.price-row {
display: flex;
align-items: baseline;
color: #0094ff;
}
.symbol {
font-size: 28rpx;
font-weight: 600;
}
.amount {
font-size: 48rpx;
font-weight: bold;
margin: 0 8rpx 0 2rpx;
line-height: 1;
}
.name {
font-size: 28rpx;
color: #333;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.condition {
font-size: 24rpx;
color: #0094ff;
opacity: 0.8;
margin-top: 4rpx;
}
.scope-text {
font-size: 22rpx;
color: #999;
margin-top: auto;
}
/* 中间分割线区域 */
.split-line {
width: 30rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.dashed-line {
width: 0;
height: 80%;
border-left: 2rpx dashed #0094ff44;
}
.circle-top {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
/* 假设页面背景接近此色,或者设为透明如果用 mask */
}
.circle-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
}
/* 右侧按钮区 */
.coupon-right {
width: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at left top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at left bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
background-color: #fff;
}
.action-btn {
background: #0094ff;
color: #fff;
font-size: 26rpx;
padding: 10rpx 24rpx;
border-radius: 30rpx;
font-weight: 500;
&.disabled {
background: #cccccc;
}
}
</style>
@@ -0,0 +1,212 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { serviceEnum } from "@@/constant"
const { actCouponTypeEnum } = serviceEnum
const emit = defineEmits(["doVisibleQrcode"])
const props = withDefaults(defineProps<{ detail: any }>(), {
detail: {},
})
const doVisibleQrcode = () => {
emit("doVisibleQrcode", { type: 'ACT_COUPON', detail: props.detail })
}
</script>
<template>
<view class="coupon-item">
<!-- 左侧主体 -->
<view class="coupon-left relative">
<image class="size-140rpx rounded-8rpx flex-shrink-0 m-r-10rpx bg-#f0f0f0" src="/static/icon/act_coupon.png"
mode="aspectFill" />
<view class="absolute top-0rpx right-80rpx">
<!-- 未使用 -->
<view v-if="detail.status == 1" class="iconfont icon-weishiyong absolute font-size-80rpx! color-blue" />
<!-- 已使用 -->
<view v-if="detail.status == 2"
class="iconfont icon-weishiyong1 absolute font-size-80rpx! color-#cccccc" />
<!-- 已过期 -->
<view v-if="detail.status == 3" class="iconfont icon-yiguoqi absolute font-size-80rpx! color-#cccccc" />
</view>
<view class="info-box">
<view class="price-row">
<view v-if="detail.faceValue" class="flex items-center">
<text class="symbol">¥</text>
<text class="amount">{{ detail.faceValue }}</text>
</view>
<text class="name">{{ detail.couponName }}</text>
</view>
<view v-if="detail.minPoint" class="condition">
满{{ detail.minPoint }}可用
</view>
<view class="scope-text flex-col">
<text>{{ actCouponTypeEnum[detail.couponType] }}</text>
<text>在{{ detail.startTime }}至{{ detail.endTime }}时间段使用</text>
</view>
</view>
</view>
<!-- 虚线分割与缺口 -->
<view class="split-line">
<view class="circle-top"></view>
<view class="dashed-line"></view>
<view class="circle-bottom"></view>
</view>
<!-- 右侧按钮 -->
<view class="coupon-right">
<view v-if="detail.status == 1" class="action-btn" @tap="doVisibleQrcode">核销码</view>
<view v-if="detail.status == 2" class="action-btn disabled">已使用</view>
<view v-if="detail.status == 3" class="action-btn disabled">已过期</view>
</view>
</view>
</template>
<style scoped lang="scss">
.coupon-item {
display: flex;
align-items: stretch;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 16rpx;
overflow: hidden;
position: relative;
box-shadow: 0 4rpx 16rpx rgba(0, 148, 255, 0.08);
}
/* 左侧布局 */
.coupon-left {
flex: 1;
display: flex;
align-items: center;
padding: 24rpx 0 24rpx 24rpx;
background: radial-gradient(circle at right top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at right bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
/* 备用背景色,防止渐变失效 */
background-color: #fff;
}
.goods-img {
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
background-color: #f0f0f0;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-box {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 140rpx;
}
.price-row {
display: flex;
align-items: baseline;
color: #0094ff;
}
.symbol {
font-size: 28rpx;
font-weight: 600;
}
.amount {
font-size: 48rpx;
font-weight: bold;
margin: 0 8rpx 0 2rpx;
line-height: 1;
}
.name {
font-size: 28rpx;
color: #333;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.condition {
font-size: 24rpx;
color: #0094ff;
opacity: 0.8;
margin-top: 4rpx;
}
.scope-text {
font-size: 22rpx;
color: #999;
margin-top: auto;
}
/* 中间分割线区域 */
.split-line {
width: 30rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.dashed-line {
width: 0;
height: 80%;
border-left: 2rpx dashed #0094ff44;
}
.circle-top {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
/* 假设页面背景接近此色,或者设为透明如果用 mask */
}
.circle-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #f5f5f5;
}
/* 右侧按钮区 */
.coupon-right {
width: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at left top, transparent 12rpx, #fff 0) top left / 100% 51% no-repeat,
radial-gradient(circle at left bottom, transparent 12rpx, #fff 0) bottom left / 100% 51% no-repeat;
background-color: #fff;
}
.action-btn {
background: #0094ff;
color: #fff;
font-size: 26rpx;
padding: 10rpx 24rpx;
border-radius: 30rpx;
font-weight: 500;
&.disabled {
background: #cccccc;
}
}
</style>
+109
View File
@@ -0,0 +1,109 @@
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<tm-input class="mt-2" v-model="params.receiverUserName" type="text" height="72" minHeight="72"
fontSize="28" :showClear="true" :focus="false" placeholder="请输入领取人姓名"
style="border: 4rpx solid #0779fd; border-radius: 14rpx" :focus-highlight="false" color="#ECF4F9"
@clear="inputClear">
<template v-slot:rightIcon>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
</template>
</tm-input>
<view class="bg-white rounded-16rpx my-20rpx">
<z-tabs :list="tabs" @change="doTab" />
</view>
</template>
<view class="card w-full h-400rpx relative activity-card overflow-hidden mb-2 py-2"
:style="{ backgroundColor: getActStatus(item.actStatus)?.color }" v-for="(item, index) in pageHook.list"
:key="index">
<view class="ribbon" :style="{ backgroundColor: getActStatus(item.actStatus)?.color }">
{{ getActStatus(item.actStatus)?.name }}
</view>
<view class="text-28 c-#fff text-right px-2">ID {{ item.id }}</view>
<view class="flex justify-between font-bold mt-1 px-8">
<text class="text-32 c-#FFD941">限时领取</text>
<text class="text-30 c-#FFD941 px-1 rounded-full bg-#eeeeee44">
{{ (item.actStartTime?.split(" ")[0] || "") + "~" + (item.actExpireTime?.split(" ")[0] || "") }}
</text>
</view>
<view class="w-4/5 py-2 text-28 c-#fff px-8">{{ item.actTitlel }}</view>
<view class="absolute bottom-100 left-60 text-28 bg-#eeeeee44 c-#fff px-2 rounded-full">限量领取 先领先得</view>
<view v-if="item.receiverTime" class="absolute bottom-40 right-20 text-22 c-#fff">
<text class="mr-2">领取人 {{ item.receiverUsername }}</text>
<text>领取时间 {{ item.receiverTime }}</text>
</view>
<view v-if="item.cancelTime" class="absolute bottom-10 right-20 text-22 c-#fff">
<text class="mr-2">核销人 {{ item.cancelUsername }}</text>
<text>核销时间 {{ item.cancelTime }}</text>
</view>
</view>
</z-paging>
</crud-layout>
</template>
<script setup lang="ts">
import { actCancel } from "@/sheep/api"
import { reactive, ref } from "vue"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import { hooks } from "@/sheep"
const tabs = [
{ id: null, name: "全部", disabled: false, color: "#7f7f7f77" },
{ id: 1, name: "未核销", disabled: false, color: "#1879ff77" },
{ id: 2, name: "已核销", disabled: false, color: "#25944577" },
{ id: 3, name: "已过期", disabled: false, color: "#FF672B77" },
]
const params = reactive({
actStatus: tabs[0].id,
receiverUserName: "",
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, actCancel.appMchPage, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, params)
}
const doTab = (e: any) => {
params.actStatus = tabs[e].id
pageRef.value && pageRef.value.reload(false)
}
const getActStatus = (actStatus: string | number) => {
return tabs.find((item: any) => item.id == actStatus)
}
const search = () => {
pageRef.value && pageRef.value.reload(false)
}
const inputClear = () => {
pageRef.value && pageRef.value.reload(false)
}
</script>
<style scoped>
.activity-card {
background-image: url("../../static/page/activity-card-bg.png");
background-repeat: no-repeat;
background-size: contain;
background-position: 150%;
}
.ribbon {
width: 160rpx;
height: 40rpx;
position: absolute;
left: -40rpx;
top: 20rpx;
transform: rotateZ(-45deg);
text-align: center;
color: #fff;
font-size: 24rpx;
}
</style>
+420
View File
@@ -0,0 +1,420 @@
<template>
<crud-layout mainIsScroll tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
<tm-sticky>
<swiper class="w-100vw h-30vh" circular :indicator-dots="true" indicator-active-color="#0779FD"
indicator-color="#ffffff" :autoplay="true">
<swiper-item v-for="(v, i) in state.detail?.swiperPic?.split(',')" :key="i">
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
</swiper-item>
</swiper>
<tm-sticky-header>
<view class="m-x-30rpx">
<view class="h-15rpx" />
<!-- start 简介 start -->
<view class="flex-row card p-20rpx m-b-20rpx">
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(state.detail.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="sub-text-34">{{ state.detail.name }}</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view class="flex-1">
<view v-if="state.detail.phone"
v-for="(item, index) in state.detail.phone.split('/')" :key="index"
class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ item.trim() }}</view>
<view
class="second-text-20 bg-#f8f8f8 ml-1 p-0.3 flex items-center rounded-1 border border-solid border-b-coolGray"
@tap="doPhone(item)">
联系商家
</view>
</view>
</view>
<tm-tag class="max-w-160rpx" size="xs">
{{ state.detail.deptName?.length > 5 ? state.detail.deptName.substring(0, 5) +
"..." : state.detail.deptName }}
</tm-tag>
</view>
<view class="second-text-24">
<view class="second-text-28">{{ state.detail.jobTime == ",," ? "" : "营业时间:" }}
</view>
<view>{{ state.detail.jobTime?.split(",")[0] ? "早:" +
state.detail.jobTime?.split(",")[0] : "" }}</view>
<view>{{ state.detail.jobTime?.split(",")[1] ? "中:" +
state.detail.jobTime?.split(",")[1] : "" }}</view>
<view>{{ state.detail.jobTime?.split(",")[2] ? "晚:" +
state.detail.jobTime?.split(",")[2] : "" }}</view>
</view>
<view v-if="state.detail.addressDes" class="second-text-28">
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
<text class="ml-1">地址:</text>
<text class="m-l-10rpx">{{ state.detail.addressDes }}</text>
</view>
</view>
</view>
</view>
<!-- end 简介 end -->
</view>
</tm-sticky-header>
<tm-alert v-if="state.detail?.discountsPrice" color="#0779FD" icon="notification-line"
closeIcon="close-line" :border="['2']" :borderColor="['#89c0ff']">
<marquee class="flex items-center">{{ state.detail.discountsPrice }}</marquee>
</tm-alert>
<!-- start 商品 start -->
<view v-if="false" class="flex-row">
<scroll-view v-if="state.classifyList.length" scroll-y :style="{ height: getHeight() }"
class="menu w-200rpx h-full p-l-12rpx p-b-20rpx bg-#f6f6f6">
<view class="menu_item flex-row-center" v-for="(v, i) in state.classifyList" :key="v.id"
:class="[{ menu_item_active: i == state.currentClassifyIndex }]" @tap="doClassify(i)">
<view class="menu_item_title show-line">{{ v.name }}</view>
</view>
</scroll-view>
<scroll-view v-if="state.goodsList.length" scroll-y :style="{ height: getHeight() }"
class="flex-1 p-b-20rpx p-x-15rpx">
<!-- <product-row
v-for="goodsItem in state.goodsList"
:key="goodsItem.id"
:productId="goodsItem.id"
:merchantId="goodsItem.merchantId"
:picUrl="filePath(goodsItem.picUrl)"
:productName="goodsItem.productName"
:salesCount="goodsItem.salesCount"
:stock="goodsItem.stock"
:unit="goodsItem.unit"
:retailPrice="goodsItem.retailPrice"
:promotionPrice="goodsItem.promotionPrice"
@onClickAddToCart="onClickAddToCart(goodsItem)"
/> -->
</scroll-view>
</view>
</tm-sticky>
<!-- end 商品 end -->
<template #main-bottom>
<view v-if="false && state.classifyList.length" class="car z-200 p-x-30rpx p-y-30rpx bg-white">
<!-- <product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCart" @submit="doBuy" /> -->
</view>
</template>
</crud-layout>
<!-- 购物车弹框 -->
<!-- <product-cart
v-model="state.showCartDialog"
:cartList="state.cartList"
:totPrice="state.totPrice"
:totCount="state.totCount"
@add="doAdd"
@remove="doRemove"
@submit="doBuy"
/> -->
<!-- 规格与数量弹框 -->
<!-- <product-sku v-model="state.showSkuDialog" :productData="state.curProductData" @onSkuAddToCart="onSkuAddToCart" /> -->
</template>
<script setup lang="ts">
import { reactive, nextTick } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { foodApi, merchantApi, orderApi, payApi, shopApi } from "@@/api"
import { filePath } from "@@/utils/file"
// import ProductRow from "./components/product-row.vue"
// import ProductCart from "./components/product-cart.vue"
// import ProductTotal from "./components/product-total.vue"
// import ProductSku from "./components/product-sku.vue"
import { getAppInfo } from "@/sheep/utils/app"
import { APPENV } from "@/sheep/constant"
// 商品详情相关数据
const state = reactive<any>({
merchantId: null,
detail: {},
goodsList: [],
cartList: [],
totPrice: 0,
totCount: 0,
classifyList: [],
currentClassifyIndex: 0,
showCartDialog: false,
showSkuDialog: false,
curProductData: {}, // 当前点击加入到购物车的商品信息
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.merchantId = options.id
}
})
onShow(async () => {
uni.showLoading({ title: "加载中...", mask: true })
try {
await getDetail()
// await getShopCart()
// await getClassify()
} catch (error: any) {
uni.showToast({ title: error, icon: "error" })
} finally {
uni.hideLoading()
}
})
const getHeight = () => {
const rpx = 480
return `calc(100vh - ${uni.upx2px(rpx)}px)`
}
const getDetail = async () => {
const { data } = await merchantApi.detail({ id: state.merchantId })
state.detail = data
}
const getShopCart = async () => {
const { data } = await orderApi.getShopCar({ merchantId: state.merchantId, hardType: "SUPERMARKET" })
state.cartList = data ?? []
state.totPrice = state.cartList.map((v) => Number(v.totalPrice)).reduce((pre, next) => pre + next, 0)
state.totCount = state.cartList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0)
}
const getClassify = async () => {
if (state.merchantId) {
const { data } = await shopApi.getSupermarketSpuCategory({ merchantId: state.merchantId })
state.classifyList = data ?? []
if (!isEmpty(state.classifyList)) {
getPorductList()
}
}
}
const getPorductList = async () => {
const target = state.classifyList[state.currentClassifyIndex]
if (isEmpty(target)) return
try {
uni.showLoading({ title: "加载中...", mask: true })
const { code, data }: any = await shopApi.getSupermarketSpuList({
merchantId: state.merchantId,
categoryId: target.id,
current: 1,
size: 99999,
})
if (code == 0) {
state.goodsList = data.records
return
}
state.goodsList = []
} finally {
uni.hideLoading()
}
}
const doClassify = async (i: any) => {
state.currentClassifyIndex = i
await getPorductList()
}
// 点击添加到购物车
const onClickAddToCart = (goodsItem: any) => {
state.curProductData = goodsItem
state.showSkuDialog = true
}
// 选择sku加入到购物车
const onSkuAddToCart = (skuGoodsInfo: any) => {
chentProductCount(skuGoodsInfo) // 同步购物车中的数据
state.showSkuDialog = false
}
// 同步购物车中的数据
const chentProductCount = async (item: any, type?: "add" | "remove") => {
try {
uni.showLoading({ title: "加载中", mask: true })
// 在购物车中增减商品数量
let count = item.productCount
if (type === "add") {
count += 1
const { code }: any = await shopApi.checkSpuStock({ spuId: item.spuId, incrCount: count })
if (code != 0) throw new Error()
} else if (type === "remove") {
count -= 1
}
const targetIndex = state.cartList.findIndex((v: any) => v.productId == item.productId)
if (targetIndex !== -1) {
state.cartList[targetIndex].productCount = count
state.cartList[targetIndex].productId = item.productId
state.cartList[targetIndex].spuId = item.spuId
} else {
state.cartList.push(item)
}
nextTick(async () => await updateShopCart()) // 保存购物车数据
} catch (e) {
console.error(e)
} finally {
uni.hideLoading()
}
}
// 保存购物车数据
const updateShopCart = async () => {
if (isEmpty(state.cartList)) return
const list = state.cartList
.filter((v) => v.productCount && v.productCount > 0)
.map((v) => {
return {
productCount: v.productCount,
productId: v.productId,
}
})
const { code }: any = await orderApi.updateShopCar({ merchantId: state.merchantId, list, hardType: "SUPERMARKET" })
if (code == 0) await getShopCart()
}
// 打开购物车
const doVisibleCart = () => (state.showCartDialog = true)
const doAdd = async (item: any) => {
chentProductCount(item, "add")
}
const doRemove = async (item: any) => {
chentProductCount(item, "remove")
}
// 点击提交订单
const doBuy = async () => {
uni.showModal({
title: "提示",
content: "是否确定购买",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return
submitPay() // 提交订单
},
})
}
// 提交订单
const submitPay = async () => {
try {
if (isEmpty(state.cartList)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
uni.showLoading({ title: "支付中...", mask: false })
const { data, code }: any = await payApi.createHardwareOrder({ carParentId: state.cartList[0].parentCarId, hardType: "SUPERMARKET" })
if (code != 0) throw new Error()
uni.showToast({ title: "支付成功", icon: "none" })
router.jump("/pages/tabbar/order")
} catch (error) {
console.error(error)
} finally {
uni.hideLoading()
}
}
// 联系商家
const doPhone = async (item: string) => {
if (isEmpty(item)) return uni.showToast({ title: "该商户没有预留电话", icon: "none" })
hooks.useMakePhoneCall(item.trim())
}
</script>
<style lang="scss" scoped>
:deep() {
.layout_main>scroll-view {
padding: 0 !important;
}
}
.menu {
&_item {
width: 100%;
height: 88rpx;
position: relative;
transition: all linear 0.2s;
&_title {
width: 100%;
line-height: 32rpx;
font-size: 30rpx;
font-weight: 400;
color: #333;
margin-left: 28rpx;
position: relative;
z-index: 0;
&::before {
content: "";
width: 64rpx;
height: 12rpx;
background: linear-gradient(90deg, #0094ff, #e9f5fe) !important;
position: absolute;
left: -64rpx;
bottom: 0;
z-index: -1;
transition: all linear 0.2s;
}
}
&.menu_item_active {
background-color: #fff;
border-radius: 20rpx 0 0 20rpx;
&::before {
content: "";
position: absolute;
right: 0;
bottom: -20rpx;
width: 20rpx;
height: 20rpx;
background: radial-gradient(circle at 0 100%, transparent 20rpx, #fff 0);
}
&::after {
content: "";
position: absolute;
top: -20rpx;
right: 0;
width: 20rpx;
height: 20rpx;
background: radial-gradient(circle at 0% 0%, transparent 20rpx, #fff 0);
}
.menu_item_title {
font-weight: 600;
&::before {
left: 0;
}
}
}
}
}
.car {
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
.scroll-text {
display: inline-block;
white-space: nowrap;
animation: scroll-left 10s linear infinite;
}
@keyframes scroll-left {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
</style>
+271
View File
@@ -0,0 +1,271 @@
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<view v-if="state.actId" class="px-2 py-4">
<view v-if="state.actType == 'CANCEL_ACT'"
class="card rounded-lb-0! rounded-rb-0! w-full h-400rpx relative activity-card overflow-hidden">
<view class="ribbon">兑换券</view>
<view class="px-8 pt-5 flex justify-between items-center font-bold">
<text class="text-32 c-#FF4F3F">限时领取</text>
<text class="text-30 c-#FF4F3F px-1 rounded-full bg-#eeeeee44">
{{ (state.actDetail.startTime?.split(" ")[0] || "") + "~" +
(state.actDetail.expireTime?.split(" ")[0] || "") }}
</text>
</view>
<view class="w-4/5 px-8 pt-4 text-28 c-#fff">{{ state.actDetail.actTitle }}</view>
<view class="absolute bottom-100 left-60 text-40 italic">
<text class="color-red mr-2">¥{{ hooks.useMoney(state.actDetail.activeCancelPrice) }}</text>
<text class="text-30 line-through color-red">¥{{ hooks.useMoney(state.actDetail.activeOrgPrice)
}}</text>
</view>
<view class="absolute bottom-40 left-60 text-28 bg-#eeeeee44 c-#fff px-2 rounded-full">限量领取 先领先得</view>
</view>
<view class="card relative rounded-lb-0! rounded-rb-0! w-full activity-sr-card overflow-hidden"
v-if="state.actType == 'COUPON_ACT' && state.actDetail.uniqueCode === '1qRgFo'">
<image class="w-full block" src="/static/page/act_li_quan.jpg" mode="widthFix" />
<view class="absolute top-0 left-0 w-full h-full">
<view class="ribbon">{{ actCouponTypeEnum[state.actDetail.type] }}</view>
</view>
</view>
<view v-else-if="state.actType == 'COUPON_ACT'"
class="card relative rounded-lb-0! rounded-rb-0! w-full activity-card overflow-hidden">
<view class="ribbon">{{ actCouponTypeEnum[state.actDetail.type] }}</view>
<view class="px-8 pt-5 flex justify-between items-center font-bold">
<text class="text-32 c-#FF4F3F">{{ state.actDetail.name }}</text>
<text class="text-30 c-#FF4F3F px-1 rounded-full bg-#eeeeee44">
<view v-if="state.actDetail.validType == 1" class="flex items-center">
<text>{{ state.actDetail.validStartTime }}</text>
<text>~</text>
<text>{{ state.actDetail.validEndTime }}</text>
</view>
</text>
</view>
<view class="w-4/5 px-8 pt-4 text-28 c-#fff">
<view v-if="state.actDetail.validType == '1'">
<text>领取后请在</text>
<text>{{ state.actDetail.validStartTime }}</text>
<text>~</text>
<text>{{ state.actDetail.validEndTime }}</text>
<text>时间段内使用</text>
</view>
<view v-if="state.actDetail.validType == '2'">
<text>领取后请在</text>
<text>{{ state.actDetail.validDays }}</text>
<text>天内使用</text>
</view>
</view>
<view v-if="state.actDetail.description"
class="m-l-20rpx m-y-20rpx w-55% text-28 bg-#eeeeee44 c-#fff p-2 rounded-16rpx">
{{ state.actDetail.description }}
</view>
</view>
<view v-if="state.couponInfo.actStatus == '2'"
class="text-center card rounded-lt-0! rounded-rt-0! p-3 sub-text-30">
已 核 销
</view>
<template v-else>
<view v-if="state.actUserReceiveStatus == '99'"
class="text-center card rounded-lt-0! rounded-rt-0! p-3 sub-text-30" @tap="doReceive">立 即 领 取</view>
<view v-else class="text-center card rounded-lt-0! rounded-rt-0! p-3 sub-text-30">
{{ actUserReceiveEnum[state.actUserReceiveStatus] || "未知状态" }}
</view>
</template>
</view>
<template #main-bottom>
<view class="text-center">
<tm-button class="px-32 m-2" @click="doQrcode">我的礼券</tm-button>
</view>
</template>
</crud-layout>
<modify-user ref="modifyUserRef" warnText="填写出生日期即可领取生日兑换券" @close="modifyUserClose" />
</template>
<script setup lang="ts">
import { reactive, ref, nextTick } from "vue"
import { onLoad } from "@dcloudio/uni-app"
import { actCancel, actComApi, actCouponApi, userApi } from "@/sheep/api"
import { hooks, router } from "@/sheep"
import { serviceEnum, storageConstant } from "@@/constant"
import ModifyUser from "@@/components/modify-user.vue"
const { actUserReceiveEnum, actCouponTypeEnum } = serviceEnum
const modifyUserRef = ref<any>(null)
const state = reactive<any>({
actId: "",
actType: "",
actUserReceiveStatus: "", // 是否已领取
actDetail: {}, // 优惠券详情
couponInfo: {}, // 优惠券信息
userDetail: {},
})
onLoad((option: any) => {
const { actId, actType } = option
if (!actId || !actType) return uni.showToast({ title: "参数错误", icon: "error" })
state.actId = actId
state.actType = actType
getActDetail()
getIsEligibleForAct()
if (actType == "CANCEL_ACT") {
getCouponInfo()
}
if (actType == "COUPON_ACT") {
specialDispose()
}
})
// 获取优惠券详情
const getActDetail = async () => {
try {
const { data } = await actComApi.getActDetail({
actId: state.actId,
actType: state.actType,
})
state.actDetail = data?.detail
} catch (error: any) {
uni.showToast({ title: error, icon: "error" })
}
}
const getIsEligibleForAct = async () => {
try {
const { data } = await actComApi.isEligibleForAct({
actId: state.actId,
actType: state.actType,
})
state.actUserReceiveStatus = data
} catch (error: any) {
uni.showToast({ title: error, icon: "error" })
}
}
// 获取用户的唯一优惠券信息
const getCouponInfo = async () => {
try {
uni.showLoading({ title: "加载中...", mask: true })
const { code, data }: any = await actCancel.appFindUnique({ actId: state.actId })
if (!data) {
return
}
state.couponInfo = data
} catch (error) {
uni.showToast({ title: "加载失败", icon: "none" })
} finally {
uni.hideLoading()
}
}
// 领取优惠券
const doReceive = () => {
// 核销活动
if (state.actType == 'CANCEL_ACT') {
getCancelReceive()
}
// 优惠券活动
if (state.actType == 'COUPON_ACT') {
getCouponReceive()
}
}
// 核销活动
const getCancelReceive = async () => {
try {
uni.showLoading({ title: "正在领取...", mask: true })
const { code, msg }: any = await actCancel.appGet({
receiverUserId: uni.getStorageSync("user_info")?.sysUser?.userId,
actId: state.actId,
mchId: state.actDetail.mchId,
})
if (code != 0) return uni.showToast({ title: msg || "领取失败", icon: "none", mask: true })
uni.showToast({ title: "领取成功", icon: "success" })
await getCouponInfo()
await getIsEligibleForAct()
} catch (error: any) {
uni.showToast({ title: "领取失败", icon: "none" })
} finally {
uni.hideLoading()
}
}
// 优惠券活动
const getCouponReceive = async () => {
// 特殊处理
if (state.actDetail.uniqueCode == "1qRgFo") {
// 判断用户有没有输入生日
const isEligible = await specialDispose()
if (!isEligible) return
}
try {
uni.showLoading({ title: "正在领取...", mask: true })
const { code, msg }: any = await actCouponApi.receive({
couponId: state.actId,
receiveUserId: state.userDetail.userId,
})
if (code != 0) return uni.showToast({ title: msg || "领取失败", icon: "none", mask: true })
uni.showToast({ title: "领取成功", icon: "success" })
await getIsEligibleForAct()
} catch (error: any) {
uni.showToast({ title: "领取失败", icon: "none" })
} finally {
uni.hideLoading()
}
}
const specialDispose = async (): Promise<boolean> => {
await getUserInfo()
if (!state.userDetail.birthday) {
nextTick(() => {
modifyUserRef.value.open()
})
return false
}
return true
}
const getUserInfo = async () => {
const { data: userData } = await userApi.userInfo()
state.userDetail = userData?.sysUser ?? {}
uni.setStorageSync(storageConstant.USERINFO, userData)
}
const doQrcode = () => {
router.jump("/pages/merchant/my-act-list")
}
const modifyUserClose = () => {
getIsEligibleForAct()
}
</script>
<style scoped>
.activity-card {
background-image: url("../../static/page/activity-card-bg.png");
background-repeat: no-repeat;
background-size: contain;
background-position: 150%;
background-color: #1879ff77;
}
.activity-sr-card {
/* background-image: url("../../static/page/act_li_quan.jpg");
background-repeat: no-repeat;
background-size: contain;
background-position: 150%;
background-color: red; */
}
.ribbon {
width: 160rpx;
height: 40rpx;
background: #dd0d1f;
position: absolute;
left: -40rpx;
top: 20rpx;
transform: rotateZ(-45deg);
text-align: center;
color: #fff;
font-size: 24rpx;
}
</style>
+173
View File
@@ -0,0 +1,173 @@
<script setup lang="ts">
import { reactive, ref, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { router } from "@/sheep"
import uqrcode from "@@/utils/uqrcode"
import * as CryptoJS from "crypto-js"
import { actCancel, actComApi, actCouponApi, applyApi } from "@@/api"
import { hooks } from "@/sheep"
import { serviceEnum } from "@@/constant"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import CancelCard from "./components/cancel-card.vue"
import CouponCard from "./components/coupon-card.vue"
const { actCouponTypeEnum } = serviceEnum
const pageRef = ref<any>(null)
const showQrCodeDialog = ref(false)
const params = reactive({
})
const state = reactive({
tabs: [
{
id: 1,
name: "优惠券活动",
badge: {
count: 6
},
disabled: false
},
{
id: 2,
name: "核销券活动",
badge: {
count: 6
},
disabled: false
},
],
tabId: 1,
})
const dynamicPageHook = computed(() => {
if (state.tabId == 1) {
return hooks.usePage(pageRef, actCouponApi.userList, null, "data.records")
}
if (state.tabId == 2) {
return hooks.usePage(pageRef, actCancel.userList, null, "data.records")
}
})
onShow(() => {
getUserTotal()
})
const query = (page: number, pagesize: number) => {
dynamicPageHook.value?.startPage(page, pagesize, false, params)
getUserTotal()
}
const doTab = (e: any) => {
const target = state.tabs[e]
state.tabId = target.id
pageRef.value.reload(false)
getUserTotal()
}
const doVisibleQrcode = async (detail: any) => {
const { type, detail: actDetail } = detail
console.log("生成二维码:", type, actDetail);
// 查询优惠券对应的短码
if (type == "ACT_COUPON") {
// 判断核销时间 必须在`actDetail.startTime`和`actDetail.endTime`之间
const now = new Date().getTime()
const startTime = new Date(actDetail.startTime).getTime()
const endTime = new Date(actDetail.endTime).getTime()
if (now < startTime || now > endTime) {
uni.showToast({ title: "核销时间不在活动时间范围内", icon: "none" })
return
}
// 特殊情况
// 1. 生日
if (actDetail.actUniquecode == "1qRgFo") {
// 生日活动,直接跳转
router.jump("/pages/merchant/cancel-qrcode", { actId: actDetail.id })
return
}
try {
const { data = {} } = await applyApi.getCodeById({ id: actDetail.id })
if (!data.shortCode) {
uni.showToast({ title: "优惠券错误", icon: "none" })
return
}
showQrCodeDialog.value = true
uqrcode.make({
canvasId: "qrcodeId",
text: data.shortCode,
size: 200,
margin: 0,
success: (res) => { },
complete: () => { },
})
} catch (error) {
console.log("|查询优惠券对应的短码|意外的错误error:", error);
}
}
if (type == "ACT_CANCEL") {
genVerificationCode({ cancelId: actDetail.id })
}
}
// 用户参加活动的数量统计
const getUserTotal = async () => {
try {
const { data = {} }: any = await actComApi.userTotal({})
state.tabs[0].badge.count = data?.couponCount ?? null
state.tabs[1].badge.count = data?.cancelCount ?? null
} catch (error) {
console.log("|用户参加活动的数量统计|意外的错误error:", error);
}
}
// 生成核销二维码
const genVerificationCode = async (params: Record<string, any>) => {
showQrCodeDialog.value = true
const words = CryptoJS.enc.Utf8.parse(JSON.stringify(params))
uqrcode.make({
canvasId: "qrcodeId",
text: CryptoJS.enc.Base64.stringify(words),
size: 200,
margin: 0,
success: (res) => { },
complete: () => { },
})
}
</script>
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="dynamicPageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false"
@scroll="dynamicPageHook.scroll">
<template #top>
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx">
<z-tabs :list="state.tabs" @change="doTab" />
</view>
</template>
<view class="h-20rpx" />
<view v-if="state.tabId == 1">
<coupon-card v-for="item in dynamicPageHook.list" :key="item.id" :detail="item"
@do-visible-qrcode="doVisibleQrcode" />
</view>
<view v-if="state.tabId == 2">
<cancel-card v-for="item in dynamicPageHook.list" :key="item.id" :detail="item"
@do-visible-qrcode="doVisibleQrcode" />
</view>
</z-paging>
<tm-drawer z-index="100" v-model:show="showQrCodeDialog" title="兑换券核销二维码" :show-close="true" position="bottom"
size="60%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
</view>
</tm-drawer>
</crud-layout>
</template>
+3 -1
View File
@@ -34,10 +34,12 @@ const minAppGetphone = async (e) => {
}
try {
uni.showLoading({ title: '登录中...', mask: true })
const { code: loginCode }: any = await uni.login({ provider: 'weixin' });
// 获取用户手机号
const { data, code }: any = await miniPlatformApi.autoAdminLogin({
miniPlatformType: await getAppInfo(),
code: phoneDetail.code,
phoneCode: phoneDetail.code,
userCode: loginCode,
})
if (data.errorCode) {
state.loginErrorCode = data.errorCode
+77
View File
@@ -0,0 +1,77 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks } from "@@/index"
import { walletApi } from "@@/api"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
const data = {
tabs: ["全部", "收入", "支出"],
}
const state = reactive<any>({
walletId: null,
pa: {
createTime: null,
type: 0,
walletId: null,
},
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.walletId = options.id
state.pa.walletId = options.id
}
})
onShow(() => { })
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, walletApi.transaction, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
const doTab = (e: any) => {
if (state.pa.type != e) {
state.pa.type = e
pageRef.value.reload(true)
}
}
</script>
<template>
<crud-layout title="饭卡" bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-30rpx" />
<view class="bg-white rounded-16rpx px-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
</view>
<view class="h-20rpx" />
<view class="flex-row items-center justify-between card p-x-20rpx p-y-30rpx m-b-20rpx"
v-for="(v, i) in pageHook.list" :key="i">
<view class="flex">
<view class="w-200rpx flex justify-left p-r-25rpx">
<tm-tag v-if="v.title == '更新余额'" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '批量更新余额'" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '充值'" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '支付'" color="error" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '退款'" color="info" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '转账'" color="success" size="m">{{ v.title }}</tm-tag>
<tm-tag v-if="v.title == '转账退款'" color="info" size="m">{{ v.title }}</tm-tag>
</view>
<view class="flex-row-center">
<text class="sub-text-30">{{ hooks.useMoney(v.price) }}元</text>
</view>
</view>
<view class="sub-text-24 text-right">
{{ hooks.useTime(v.createTime, "") }}
</view>
</view>
</z-paging>
</crud-layout>
</template>
+223
View File
@@ -0,0 +1,223 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import uqrcode from "@@/utils/uqrcode"
import { hooks, router } from "@@/index"
import { walletApi } from "@@/api"
import { filePath } from "@@/utils/file"
import { usePayStore } from "@/sheep/stores/pay"
const state = reactive<any>({
showBindDialog: false,
showFreezeDialog: false,
showQrCodeDialog: false,
currentCard: {},
})
const bindForm = reactive<any>({
cardNumber: "",
})
const bindFormRules = reactive<any>({
cardNumber: [
{ required: true, message: "请输入卡号" },
{ required: true, min: 12, message: "饭卡长度在12位" },
{ required: true, max: 12, message: "饭卡长度在12位" },
],
})
const freezeForm = reactive<any>({
remark: "",
})
onLoad(() => { })
onShow(async () => {
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
if (pageRef.value) {
pageRef.value.reload(false)
}
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, walletApi.all, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
const openBindDialog = (v: any) => {
state.showBindDialog = true
state.currentCard = v
}
const closeBindDialog = () => {
state.showBindDialog = false
}
const bindFormRef = ref<any>(null)
const doBind = async () => {
bindFormRef.value.submit()
const { isPass } = bindFormRef.value.validate()
if (!isPass) return
try {
uni.showLoading({ title: "绑定中...", mask: false })
const _pa = {
walletId: state.currentCard.id,
cardNumber: bindForm.cardNumber,
}
const { data, code }: any = await walletApi.bind(_pa)
if (code != 0) throw new Error()
closeBindDialog()
pageRef.value.reload(true)
uni.hideLoading()
uni.showToast({ title: "绑定成功", icon: "none" })
} catch (error) {
console.error(error)
uni.hideLoading()
}
}
const openFreezeDialog = (v: any) => {
state.showFreezeDialog = true
state.currentCard = v
}
const closeFreezeDialog = () => {
state.showFreezeDialog = false
}
const doPreviewQrCode = async (v: any) => {
uni.showLoading({ title: "生成中...", mask: false })
const _pa = { walletId: v.id }
try {
const { data, code }: any = await walletApi.getQrCode(_pa)
if (code != 0) throw new Error()
uni.hideLoading()
state.showQrCodeDialog = true
uqrcode.make({
canvasId: "qrcodeId",
text: data,
size: 200,
margin: 0,
success: (res) => {
console.log(res, "res")
},
complete: () => {
uni.hideLoading()
},
})
} catch (error) {
console.error(error)
uni.hideLoading()
}
}
const doFreeze = async () => {
try {
uni.showLoading({ title: "申请中...", mask: false })
const _pa = {
walletTypeId: state.currentCard.walletTypeId,
remark: freezeForm.remark,
}
const { data, code }: any = await walletApi.freeze(_pa)
if (code != 0) throw new Error()
closeFreezeDialog()
pageRef.value.reload(true)
uni.hideLoading()
uni.showToast({ title: "申请成功", icon: "none" })
freezeForm.remark = ""
} catch (error) {
console.error(error)
uni.hideLoading()
}
}
// 转账
const doTransferMoney = (v: any) => {
if (v.walletTypeId) {
usePayStore().setSourceWalletData(v)
router.jump("/pages/pay/transferMoney")
return
}
uni.showToast({ title: "钱包类型id不存在", icon: "none" })
}
</script>
<template>
<crud-layout bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="5"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<view class="h-30rpx" />
<view class="card flex-col rounded-20rpx p-x-30rpx p-y-20rpx m-b-30rpx" v-for="(v, i) in pageHook.list"
:key="i" @tap="router.jump('/pages/wallet/detail', { id: v.id })">
<view class="flex-col">
<view class="flex-row items-center justify-between">
<view class="flex-row-center">
<text v-if="v.cardNumber" class="second-text-28">卡号: {{ v.cardNumber }}</text>
</view>
<view class="second-text-50">
<text v-if="v.cardNumber" class="iconfont icon-fuzhi"
@tap.stop="hooks.useCopy(v.cardNumber)" />
<text v-if="v.walletTypeUniqueCode != '2'" class="m-l-20rpx iconfont icon-erweima"
@tap.stop="doPreviewQrCode(v)" />
</view>
</view>
<view v-if="v.cardNumber" class="h-1px m-t-10rpx bg-#999 bg-opacity-5" />
</view>
<view class="flex-row items-center justify-between m-t-10rpx">
<view class="flex-row items-center">
<image class="size-50rpx rounded-full" :src="filePath(v.walletTypeIcon)" mode="aspectFill" />
<text class="m-l-25rpx sub-text-28">
{{ v.walletTypeName }}
</text>
</view>
<view v-if="v.cardNumber">
<tm-tag v-if="v.isFreeze == 0" size="s" color="success">正常</tm-tag>
<tm-tag v-if="v.isFreeze == 1" size="s" color="error">已冻结</tm-tag>
</view>
</view>
<view class="flex-row items-center justify-between m-t-10rpx">
<text class="sub-text-40 color-red!"> ¥{{ hooks.useMoney(v.balance) }} </text>
</view>
<view class="flex-row items-center justify-end space-x-20rpx m-t-10rpx">
<!-- <view v-if="v.walletTypeUniqueCode == 1 && !v.cardNumber" class="botder_btn" @tap.stop="openBindDialog(v)">绑定</view> -->
<view v-if="v.isRule == '1'" class="botder_btn" @tap.stop="doTransferMoney(v)">转账</view>
<view v-if="v.walletTypeUniqueCode == 1 && v.cardNumber && v.isFreeze != 1" class="botder_btn"
@tap.stop="openFreezeDialog(v)">
申请冻结
</view>
<view v-if="false && v.walletTypeUniqueCode == 2" class="botder_btn"
@tap.stop="router.jump('/pages/wallet/recharge', { id: v.id })">
去充值
</view>
</view>
</view>
</z-paging>
</crud-layout>
<tm-drawer z-index="100" v-model:show="state.showBindDialog" :overlayClick="false" title="绑定饭卡" :show-close="true"
position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="bindForm" :rules="bindFormRules">
<tm-form-item name="cardNumber" :showLabel="false" :showBottom="false">
<tm-input v-model="bindForm.cardNumber" showClear placeholder="请输入卡号..." />
</tm-form-item>
</tm-form>
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doBind">绑定</view>
</tm-drawer>
<tm-drawer z-index="100" v-model:show="state.showFreezeDialog" :overlayClick="true" title="申请冻结" :show-close="true"
position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="freezeForm">
<tm-form-item name="remark" :showLabel="false" :showBottom="false">
<tm-input v-model="freezeForm.remark" type="textarea" showClear placeholder="请输入备注.." />
</tm-form-item>
</tm-form>
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doFreeze">冻结</view>
</tm-drawer>
<tm-drawer z-index="100" v-model:show="state.showQrCodeDialog" :overlayClick="false" title="钱包二维码"
:show-close="true" position="bottom" size="50%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
</view>
</tm-drawer>
</template>
+162
View File
@@ -0,0 +1,162 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, constant } from "@@/index"
import { walletApi, payApi } from "@@/api"
const data = {
payType: [
// { text: "模拟支付", disabled: false, type: "0", value: "mock" },
{ text: "微信支付", disabled: true, type: "1", value: "wx_pub" },
{ text: "支付宝支付", disabled: true, type: "2", value: "alipay_wap" },
],
}
const state = reactive<any>({
walletId: null,
currentPayType: null,
currentRecharge: 0,
userInfo: {},
detail: {},
rechargeList: [],
})
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.walletId = options.id
}
})
onShow(() => {
getUserInfo()
getDetail()
getRechargeList()
})
const getDetail = async () => {
const _pa = { walletId: state.walletId }
const { data } = await walletApi.detail(_pa)
state.detail = data
}
const getUserInfo = () => {
const userInfoKey = constant.storageConstant.USERINFO
const userStorage = uni.getStorageSync(userInfoKey)
if (!isEmpty(userStorage)) {
state.userInfo = userStorage
}
}
const getRechargeList = async () => {
const _pa = {}
const { data } = await walletApi.rechargeList(_pa)
state.rechargeList = data
}
const doPay = async () => {
if (state.currentRecharge == -1) {
uni.showToast({ title: "请选择充值金额", icon: "none" })
return
}
if (state.currentPayType == undefined) {
uni.showToast({ title: "请选择支付方式", icon: "none" })
return
}
try {
uni.showLoading({ title: "充值中...", mask: true })
const targetRecharge = state.rechargeList[state.currentRecharge]
const targetPayType = data.payType[state.currentPayType]
if (isEmpty(targetRecharge)) throw new Error()
const _pa = {
payPrice: targetRecharge.payPrice,
walletId: state.walletId,
packageId: targetRecharge.id,
}
const { data: createData, code: createCode }: any = await walletApi.recharge(_pa)
console.log(createCode, "createCode")
if (createCode != 0) throw new Error()
const _payPa = {
id: createData.payOrderId,
walletId: state.walletId,
channelCode: targetPayType.value,
channelExtras: {},
}
getDetail()
uni.hideLoading()
uni.showToast({ title: "充值成功", icon: "none" })
} catch (error) {
console.error(error)
uni.hideLoading()
uni.showToast({ title: "充值失败", icon: "none" })
}
}
</script>
<template>
<crud-layout title="充值" bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<view class="flex-row items-center justify-between h-120rpx p-x-30rpx bg-#222222 rounded-20rpx">
<view class="flex-row items-center">
<view class="flex-row-center size-60rpx bg-white rounded-full">
<image class="size-50rpx rounded-full" :src="state.detail.walletIcon" mode="aspectFill" />
</view>
<view class="flex-row-center">
<view class="m-l-25rpx main-text-32 color-#fbddb0!">
{{ state.detail.walletTypeName }}
</view>
</view>
</view>
<view class="main-text-30 color-#fbddb0!"> ¥{{ hooks.useMoney(state.detail.balance) }} </view>
</view>
<view class="prefix_title m-t-35rpx">充值</view>
<view class="recharge p-30rpx bg-white rounded-20rpx m-t-20rpx shadow-main">
<view
:class="[state.currentRecharge == i ? 'recharge_item_active' : '']"
class="recharge_item"
v-for="(v, i) in state.rechargeList"
:key="i"
@tap="state.currentRecharge = i"
>
<text class="sub-text-30 text-center">¥{{ hooks.useMoney(v.payPrice) }}</text>
</view>
</view>
<view class="prefix_title m-t-35rpx">支付</view>
<view class="p-30rpx p-b-40rpx bg-white rounded-20rpx m-t-20rpx shadow-main">
<tm-radio-group direction="column" gap="40" v-model="state.currentPayType">
<tm-radio :disabled="v.disabled" v-for="(v, i) in data.payType" :key="i" :value="v.type" :label="v.text" />
</tm-radio-group>
</view>
<view
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
@tap="doPay"
>
确认充值
</view>
</crud-layout>
</template>
<style lang="scss" scoped>
.recharge_item_active {
background: #e9f5fe;
border: 1px solid #e9f5fe !important;
& > text {
color: #0094ff !important;
}
}
.recharge {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 20rpx;
border-bottom: 1px solid #f9f9f9;
&_item {
display: flex;
flex-direction: column;
border: 1px solid #cccccc;
border-radius: 16rpx;
padding: 20rpx 0;
}
}
</style>
+73
View File
@@ -0,0 +1,73 @@
import request from "@/sheep/utils/request"
export default {
/**
* app领取优惠券
*/
appGet: (data: any) =>
request({
url: "/wallet/act/cancel-item/app/get",
method: "POST",
data,
}),
/**
* app查询用户的唯一优惠券
* @param params.actId 活动id
*/
appFindUnique: (params: any) =>
request({
url: "/wallet/act/cancel-item/app/find-unique",
method: "GET",
params,
}),
/**
* app查询本店优惠券列表
* @param params.actStatus 核销类型
*/
appMchPage: (params: any) =>
request({
url: "/wallet/act/cancel-item/app/mch/page",
method: "GET",
params,
}),
/**
* app创建活动订单
* @param data.actId 活动id
* @param data.totalPrice 总金额
*/
appCreateActOrder: (data: any) =>
request({
url: "/hard/hard-order/app/create-act-order",
method: "POST",
data,
}),
/**
* 根据公告id查询活动详情
* @param data.id 公告id
*/
getDetailByNoticeId: (params: any) =>
request({
url: "/wallet/act/act-cancel/app/detail-by-notice-id",
method: "GET",
params,
}),
/**
* 根据活动id查询核销活动
* @param data.id 活动id
*/
getDetailById: (params: any) =>
request({
url: "/wallet/act/act-cancel/app/detail-by-id",
method: "GET",
params,
}),
/**
* 用户领取核销券列表
*/
userList: (params: any) =>
request({
url: "/wallet/act/cancel-item/app/user-list",
method: "GET",
params,
}),
}
+9
View File
@@ -61,4 +61,13 @@ export default {
method: "GET",
params,
}),
/**
* 用户领取核销券列表
*/
userList: (params: any) =>
request({
url: "/wallet/act/cancel-item/app/user-list",
method: "GET",
params,
}),
}
+40
View File
@@ -0,0 +1,40 @@
import request from "@/sheep/utils/request"
export default {
/**
* 根据公告id查询对应的活动
*/
getActByNotifyId: (params: any) =>
request({
url: "/wallet/act/com/app/get-act-by-notify-id",
method: "GET",
params,
}),
/**
* 获取活动详情
*/
getActDetail: (params: any) =>
request({
url: "/wallet/act/com/app/get-act-detail",
method: "GET",
params,
}),
/**
* 判断用户是否可以参加活动
*/
isEligibleForAct: (params: any) =>
request({
url: "/wallet/act/com/app/is-eligible-for-act",
method: "GET",
params,
}),
/**
* 用户参加活动的数量统计
*/
userTotal: (params: any) =>
request({
url: "/wallet/act/com/app/user-total",
method: "GET",
params,
}),
}
+22
View File
@@ -0,0 +1,22 @@
import request from "@/sheep/utils/request"
export default {
/**
* 领取优惠券
*/
receive: (data: any) =>
request({
url: "/wallet/act-coupon-record/app/receive",
method: "POST",
data,
}),
/**
* 用户领取核销券列表
*/
userList: (params: any) =>
request({
url: "/wallet/act-coupon-record/app/user-list",
method: "GET",
params,
}),
}
+52
View File
@@ -0,0 +1,52 @@
import request from "@/sheep/utils/request"
export default {
foreignList: (params: any) =>
request({
url: "wallet/wallet-foreign/app/page-join",
method: "GET",
params,
}),
foreignDetail: (params: any) =>
request({
url: "wallet/wallet-foreign/app/detail",
method: "GET",
params,
}),
codeList: (params: any) =>
request({
url: "wallet/wallet-code-use/app/code-list",
method: "GET",
params,
}),
getCodeById: (params: any) =>
request({
url: "wallet/wallet-code-use/app/user-code",
method: "GET",
params,
}),
foreignApply: (data: any) =>
request({
url: "wallet/wallet-foreign/app/apply",
method: "POST",
data,
}),
strideList: (params: any) =>
request({
url: "wallet/wallet-stride/app/page-join",
method: "GET",
params,
}),
strideDetail: (params: any) =>
request({
url: "wallet/wallet-stride/app/detail",
method: "GET",
params,
}),
strideApply: (data: any) =>
request({
url: "wallet/wallet-stride/app/apply",
method: "POST",
data,
}),
}
+5
View File
@@ -1,5 +1,9 @@
export { default as accessStatsApi } from "./access-stats"
export { default as actCancel } from "./act-cancel"
export { default as actComApi } from "./act-com"
export { default as actCouponApi } from "./act-coupon"
export { default as actCancelApi } from "./act-cancel"
export { default as applyApi } from "./apply"
export { default as adApi } from "./ad"
export { default as boxesApi } from "./boxes"
export { default as faceApi } from "./face"
@@ -10,6 +14,7 @@ export { default as orderApi } from "./order"
export { default as payApi } from "./pay"
export { default as productApi } from "./product"
export { default as refundApi } from "./refund"
export { default as shopApi } from "./shop"
export { default as totalApi } from "./total"
export { default as userCarApi } from "./user-car"
export { default as userApi } from "./user"
+2 -1
View File
@@ -2,7 +2,8 @@ import request from "@/sheep/utils/request"
type AutoAdminLoginDto = {
miniPlatformType?: string
code?: string
phoneCode?: string
userCode?: string
}
export default {
+29
View File
@@ -0,0 +1,29 @@
import request from "@/sheep/utils/request"
export default {
getSupermarketSpuCategory: (params: any) =>
request({
url: `/supermarket/supermarketCategory/app/simpleAll`,
method: "GET",
params,
}),
getSupermarketSpuList: (data: any) =>
request({
url: `/supermarket/supermarketSpu/app/selectListAll`,
method: "POST",
data,
}),
getSupermarketSpuDetail: (params: any) =>
request({
url: `/supermarket/supermarketSpu/app/SpuDetails`,
method: "GET",
params,
}),
checkSpuStock: (params) => {
return request({
url: "/supermarket/supermarketSpu/app/checkSpuStock",
method: "GET",
params,
})
},
}
+24
View File
@@ -61,6 +61,28 @@ const errorMsginiPlatformLoginErrorEnum = {
"30": "登录错误",
}
const actCouponTypeEnum = {
"1": "满减券",
"2": "折扣券",
"3": "立减券",
"4": "兑换券",
}
// 用户活动状态
const actUserReceiveEnum = {
"1": "活动不在进行中",
"2": "活动不存在",
"3": "活动未开启",
"4": "已领取,核销码在我的礼券中",
"5": "用户不能参加此活动",
"6": "其他错误",
"99": "用户可以参加活动",
// 特殊
"-100": "仅限生日当月使用",
"-101": "请填写出生日期",
"-102": "生日已结束",
}
export default {
hardwareOrderStatusEnum,
hardwareOrderTypeEnum,
@@ -71,4 +93,6 @@ export default {
carTypeEnum,
actCancelStatusEnum,
errorMsginiPlatformLoginErrorEnum,
actCouponTypeEnum,
actUserReceiveEnum,
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB