bug修改
This commit is contained in:
parent
01ada8da2b
commit
0d793d3f75
|
|
@ -16,12 +16,15 @@
|
||||||
<el-table-column label="抵扣金额" width="90">
|
<el-table-column label="抵扣金额" width="90">
|
||||||
<template #default="{ row }">{{ row.couponType === 'discount' ? `¥${row.discountAmount}` : '-' }}</template>
|
<template #default="{ row }">{{ row.couponType === 'discount' ? `¥${row.discountAmount}` : '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="适用门店" width="120">
|
<el-table-column label="适用门店" min-width="150">
|
||||||
<template #default="{ row }">{{ row.store?.name || '-' }}</template>
|
<template #default="{ row }">{{ row.storeNames || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="数量" width="100">
|
<el-table-column label="数量" width="100">
|
||||||
<template #default="{ row }">{{ row.remainingCount }}/{{ row.totalCount }}</template>
|
<template #default="{ row }">{{ row.remainingCount }}/{{ row.totalCount }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="有效期" min-width="180">
|
||||||
|
<template #default="{ row }">{{ formatValidity(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="pointsCost" label="所需积分" width="90" />
|
<el-table-column prop="pointsCost" label="所需积分" width="90" />
|
||||||
<el-table-column label="来源" width="90">
|
<el-table-column label="来源" width="90">
|
||||||
<template #default="{ row }">{{ row.source === 'platform' ? '平台券' : '驿公里券' }}</template>
|
<template #default="{ row }">{{ row.source === 'platform' ? '平台券' : '驿公里券' }}</template>
|
||||||
|
|
@ -47,7 +50,7 @@
|
||||||
<el-input v-model="form.name" placeholder="请输入名称" />
|
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用门店">
|
<el-form-item label="适用门店">
|
||||||
<el-select v-model="form.storeId" placeholder="请选择门店" style="width:100%">
|
<el-select v-model="form.storeIds" multiple placeholder="请选择门店(可多选)" style="width:100%">
|
||||||
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -61,7 +64,10 @@
|
||||||
<el-input-number v-model="form.discountAmount" :min="0" :precision="2" />
|
<el-input-number v-model="form.discountAmount" :min="0" :precision="2" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="有效期">
|
<el-form-item label="有效期">
|
||||||
<el-date-picker v-model="dateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" style="width:100%" />
|
<div style="display:flex;flex-direction:column;gap:8px;width:100%">
|
||||||
|
<el-checkbox v-model="form.isPermanent">永久有效</el-checkbox>
|
||||||
|
<el-date-picker v-if="!form.isPermanent" v-model="dateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" style="max-width:380px" />
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="总数量">
|
<el-form-item label="总数量">
|
||||||
<el-input-number v-model="form.totalCount" :min="1" />
|
<el-input-number v-model="form.totalCount" :min="1" />
|
||||||
|
|
@ -75,7 +81,7 @@
|
||||||
<el-option label="驿公里券" value="ygl" />
|
<el-option label="驿公里券" value="ygl" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="form.source === 'ygl'" label="驿公里券包码">
|
<el-form-item v-if="form.source === 'ygl'" label="驿公里券包码" required>
|
||||||
<el-input v-model="form.yglTicketPackageCode" placeholder="请输入券包码" />
|
<el-input v-model="form.yglTicketPackageCode" placeholder="请输入券包码" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态">
|
<el-form-item label="状态">
|
||||||
|
|
@ -91,7 +97,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getCouponTemplates, createCouponTemplate, updateCouponTemplate, deleteCouponTemplate, getStores } from '../api'
|
import { getCouponTemplates, createCouponTemplate, updateCouponTemplate, deleteCouponTemplate, getStores } from '../api'
|
||||||
|
|
||||||
|
|
@ -104,9 +110,9 @@ const isEdit = ref(false)
|
||||||
const editId = ref(null)
|
const editId = ref(null)
|
||||||
|
|
||||||
const defaultForm = () => ({
|
const defaultForm = () => ({
|
||||||
name: '', couponType: 'free', discountAmount: 0, storeId: null,
|
name: '', couponType: 'free', discountAmount: 0, storeIds: [],
|
||||||
totalCount: 100, pointsCost: 0, source: 'platform',
|
totalCount: 100, pointsCost: 0, source: 'platform',
|
||||||
yglTicketPackageCode: '', isActive: true
|
yglTicketPackageCode: '', isActive: true, isPermanent: false
|
||||||
})
|
})
|
||||||
const form = ref(defaultForm())
|
const form = ref(defaultForm())
|
||||||
const dateRange = ref([])
|
const dateRange = ref([])
|
||||||
|
|
@ -120,17 +126,32 @@ const fetchData = async () => {
|
||||||
try { const res = await getCouponTemplates(); list.value = res.data } catch {} finally { loading.value = false }
|
try { const res = await getCouponTemplates(); list.value = res.data } catch {} finally { loading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否为永久有效(年份>=2099)
|
||||||
|
const isPermanent = (row) => {
|
||||||
|
if (!row.validEnd) return false
|
||||||
|
return new Date(row.validEnd).getFullYear() >= 2099
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化有效期显示
|
||||||
|
const formatValidity = (row) => {
|
||||||
|
if (isPermanent(row)) return '永久有效'
|
||||||
|
const fmt = (d) => d ? d.substring(0, 10) : ''
|
||||||
|
return `${fmt(row.validStart)} 至 ${fmt(row.validEnd)}`
|
||||||
|
}
|
||||||
|
|
||||||
const openDialog = (row) => {
|
const openDialog = (row) => {
|
||||||
fetchStores()
|
fetchStores()
|
||||||
if (row) {
|
if (row) {
|
||||||
isEdit.value = true
|
isEdit.value = true
|
||||||
editId.value = row.id
|
editId.value = row.id
|
||||||
|
const permanent = isPermanent(row)
|
||||||
form.value = {
|
form.value = {
|
||||||
name: row.name, couponType: row.couponType, discountAmount: row.discountAmount,
|
name: row.name, couponType: row.couponType, discountAmount: row.discountAmount,
|
||||||
storeId: row.storeId, totalCount: row.totalCount, pointsCost: row.pointsCost,
|
storeIds: row.storeIds || [], totalCount: row.totalCount, pointsCost: row.pointsCost,
|
||||||
source: row.source, yglTicketPackageCode: row.yglTicketPackageCode || '', isActive: row.isActive
|
source: row.source, yglTicketPackageCode: row.yglTicketPackageCode || '', isActive: row.isActive,
|
||||||
|
isPermanent: permanent
|
||||||
}
|
}
|
||||||
dateRange.value = [new Date(row.validStart), new Date(row.validEnd)]
|
dateRange.value = permanent ? [] : [new Date(row.validStart), new Date(row.validEnd)]
|
||||||
} else {
|
} else {
|
||||||
isEdit.value = false
|
isEdit.value = false
|
||||||
editId.value = null
|
editId.value = null
|
||||||
|
|
@ -141,8 +162,24 @@ const openDialog = (row) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!form.value.storeIds.length) {
|
||||||
|
ElMessage.warning('请至少选择一个门店')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (form.value.source === 'ygl' && !form.value.yglTicketPackageCode?.trim()) {
|
||||||
|
ElMessage.warning('驿公里券来源必须填写券包码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!form.value.isPermanent && (!dateRange.value || dateRange.value.length < 2)) {
|
||||||
|
ElMessage.warning('请选择有效期或勾选永久有效')
|
||||||
|
return
|
||||||
|
}
|
||||||
const data = { ...form.value }
|
const data = { ...form.value }
|
||||||
if (dateRange.value?.length === 2) {
|
delete data.isPermanent
|
||||||
|
if (form.value.isPermanent) {
|
||||||
|
data.validStart = new Date('2000-01-01')
|
||||||
|
data.validEnd = new Date('2099-12-31')
|
||||||
|
} else {
|
||||||
data.validStart = dateRange.value[0]
|
data.validStart = dateRange.value[0]
|
||||||
data.validEnd = dateRange.value[1]
|
data.validEnd = dateRange.value[1]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-card>
|
<el-card>
|
||||||
<template #header>优惠券发放记录</template>
|
<template #header>
|
||||||
<!-- 筛选栏 -->
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||||
<el-form :inline="true" :model="filters" style="margin-bottom:16px">
|
<span>优惠券发放记录</span>
|
||||||
<el-form-item label="用户手机号">
|
<div style="display:flex;gap:8px;align-items:center">
|
||||||
<el-input v-model="filters.userPhone" placeholder="手机号" clearable @clear="fetchData" />
|
<el-input v-model="filters.userPhone" placeholder="用户手机号/UID" clearable style="width:160px" @clear="fetchData" @keyup.enter="fetchData" />
|
||||||
</el-form-item>
|
<el-select v-model="filters.storeId" placeholder="全部门店" clearable style="width:140px" @change="fetchData">
|
||||||
<el-form-item label="门店">
|
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
||||||
<el-select v-model="filters.storeId" placeholder="全部门店" clearable @change="fetchData">
|
</el-select>
|
||||||
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
<el-select v-model="filters.status" placeholder="全部状态" clearable style="width:120px" @change="fetchData">
|
||||||
</el-select>
|
<el-option label="未使用" value="unused" />
|
||||||
</el-form-item>
|
<el-option label="已使用" value="used" />
|
||||||
<el-form-item label="状态">
|
<el-option label="已过期" value="expired" />
|
||||||
<el-select v-model="filters.status" placeholder="全部状态" clearable @change="fetchData">
|
</el-select>
|
||||||
<el-option label="未使用" value="unused" />
|
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||||
<el-option label="已使用" value="used" />
|
</div>
|
||||||
<el-option label="已过期" value="expired" />
|
</div>
|
||||||
</el-select>
|
</template>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-table :data="list" v-loading="loading">
|
<el-table :data="list" v-loading="loading">
|
||||||
<el-table-column prop="id" label="ID" width="60" />
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
<el-table-column prop="code" label="券码" width="120" />
|
<el-table-column prop="code" label="券码" width="120" />
|
||||||
<el-table-column prop="templateName" label="优惠券名称" />
|
<el-table-column prop="templateName" label="优惠券名称" />
|
||||||
|
<el-table-column prop="userUid" label="用户UID" width="90" />
|
||||||
<el-table-column prop="userPhone" label="用户手机号" width="130" />
|
<el-table-column prop="userPhone" label="用户手机号" width="130" />
|
||||||
<el-table-column prop="storeName" label="门店" />
|
<el-table-column prop="storeName" label="门店" />
|
||||||
<el-table-column label="状态" width="90">
|
<el-table-column label="状态" width="90">
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,14 @@ onMounted(fetchData)
|
||||||
.store-uploader:hover {
|
.store-uploader:hover {
|
||||||
border-color: #409eff;
|
border-color: #409eff;
|
||||||
}
|
}
|
||||||
|
.store-uploader :deep(.el-upload) {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.uploader-icon {
|
.uploader-icon {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: #8c939d;
|
color: #8c939d;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { isLoggedIn } from '@/utils/auth'
|
import { isLoggedIn, getRole } from '@/utils/auth'
|
||||||
|
|
||||||
// 需要登录才能访问的页面路径
|
// 需要登录才能访问的页面路径
|
||||||
const AUTH_PAGES = [
|
const AUTH_PAGES = [
|
||||||
|
|
@ -21,6 +21,10 @@
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
onLaunch: function() {
|
||||||
console.log('App Launch')
|
console.log('App Launch')
|
||||||
|
// 已登录的商户自动跳转商户端
|
||||||
|
if (isLoggedIn() && getRole() === 'merchant') {
|
||||||
|
uni.reLaunch({ url: '/pages/merchant/index' })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
console.log('App Show')
|
console.log('App Show')
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,11 @@ export function getRecords() {
|
||||||
export function getMerchantRecords() {
|
export function getMerchantRecords() {
|
||||||
return get('/api/verify/merchant-records')
|
return get('/api/verify/merchant-records')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据券码查询优惠券信息(核销前预览)
|
||||||
|
* @param {string} code - 券码
|
||||||
|
*/
|
||||||
|
export function previewCoupon(code) {
|
||||||
|
return get('/api/verify/preview', { code })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<view class="dialog-title" v-if="title">{{ title }}</view>
|
<view class="dialog-title" v-if="title">{{ title }}</view>
|
||||||
<view class="dialog-message">{{ message }}</view>
|
<view class="dialog-message">{{ message }}</view>
|
||||||
<view class="dialog-actions">
|
<view class="dialog-actions">
|
||||||
<button class="btn-cancel" @click="cancel">{{ cancelText }}</button>
|
<button v-if="cancelText" class="btn-cancel" @click="cancel">{{ cancelText }}</button>
|
||||||
<button class="btn-confirm" @click="confirm">{{ confirmText }}</button>
|
<button class="btn-confirm" @click="confirm">{{ confirmText }}</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="coupon-card">
|
<view class="coupon-card">
|
||||||
<view class="coupon-info">
|
<!-- 背景图 -->
|
||||||
<view class="coupon-name">{{ coupon.name }}</view>
|
<image class="coupon-card-bg" src="/static/item_bg2.png" mode="scaleToFill" />
|
||||||
<view class="coupon-store">适用门店:{{ coupon.storeName }}</view>
|
<!-- 左侧券类型 -->
|
||||||
<view class="coupon-code">券码:{{ coupon.code }}</view>
|
<view class="coupon-left">
|
||||||
<view class="coupon-expire">有效期:{{ coupon.validStart }} ~ {{ coupon.validEnd }}</view>
|
<template v-if="coupon.couponType === 'discount'">
|
||||||
|
<text class="coupon-price">¥{{ coupon.discountAmount }}</text>
|
||||||
|
<text class="coupon-type-label">折扣券</text>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<text class="coupon-price">免费</text>
|
||||||
|
<text class="coupon-type-label">洗车券</text>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<view class="coupon-action">
|
<!-- 右侧信息 -->
|
||||||
<!-- 平台券:显示二维码按钮 -->
|
<view class="coupon-right">
|
||||||
<button
|
<text class="coupon-name">{{ coupon.storeName }}</text>
|
||||||
v-if="coupon.source === 'platform'"
|
<text class="coupon-info">券码:{{ coupon.code }}</text>
|
||||||
class="btn btn-primary"
|
<text class="coupon-info">有效期:{{ formatValidity(coupon) }}</text>
|
||||||
size="mini"
|
<!-- 操作按钮 -->
|
||||||
@click="$emit('showQrcode', coupon)"
|
<view class="coupon-bottom">
|
||||||
>二维码</button>
|
<view v-if="coupon.source === 'ygl'" class="ygl-tip-text">
|
||||||
<!-- 驿公里券:显示跳转按钮 -->
|
<text class="coupon-info">需在驿公里洗车小程序中使用</text>
|
||||||
<view v-else-if="coupon.source === 'ygl'" class="ygl-action">
|
</view>
|
||||||
<text class="ygl-tip">需在驿公里使用</text>
|
<view
|
||||||
<button
|
v-if="coupon.source === 'platform'"
|
||||||
class="btn btn-ygl"
|
class="action-btn"
|
||||||
size="mini"
|
@click.stop="$emit('showQrcode', coupon)"
|
||||||
@click="$emit('gotoYgl', coupon)"
|
>
|
||||||
>去使用</button>
|
<text class="action-btn-text">二维码</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-else-if="coupon.source === 'ygl'"
|
||||||
|
class="action-btn"
|
||||||
|
@click.stop="$emit('gotoYgl', coupon)"
|
||||||
|
>
|
||||||
|
<text class="action-btn-text">去使用</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -31,66 +46,105 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'CouponCard',
|
name: 'CouponCard',
|
||||||
props: {
|
props: {
|
||||||
coupon: {
|
coupon: { type: Object, required: true }
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['showQrcode', 'gotoYgl']
|
emits: ['showQrcode', 'gotoYgl'],
|
||||||
|
methods: {
|
||||||
|
formatValidity(coupon) {
|
||||||
|
if (!coupon.validStart && !coupon.validEnd) return '-'
|
||||||
|
const endYear = new Date(coupon.validEnd).getFullYear()
|
||||||
|
if (endYear >= 2099) return '永久有效'
|
||||||
|
const fmt = (d) => d ? d.substring(0, 10) : ''
|
||||||
|
return `${fmt(coupon.validStart)}至${fmt(coupon.validEnd)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.coupon-card {
|
.coupon-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
.coupon-info {
|
.coupon-card-bg {
|
||||||
flex: 1;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.coupon-name {
|
/* 左侧标签 */
|
||||||
font-size: 32rpx;
|
.coupon-left {
|
||||||
font-weight: bold;
|
width: 160rpx;
|
||||||
color: #333;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
.coupon-store,
|
|
||||||
.coupon-code,
|
|
||||||
.coupon-expire {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 4rpx;
|
|
||||||
}
|
|
||||||
.coupon-action {
|
|
||||||
margin-left: 20rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
border-radius: 8rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
.btn-primary {
|
|
||||||
background: #007aff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.btn-ygl {
|
|
||||||
background: #ff9500;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.ygl-action {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24rpx 0 24rpx 20rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.ygl-tip {
|
.coupon-price {
|
||||||
font-size: 20rpx;
|
font-size: 65rpx;
|
||||||
color: #999;
|
font-weight: bold;
|
||||||
|
color: #E8C7AA;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.coupon-type-label {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #E8C7AA;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
}
|
||||||
|
/* 右侧信息 */
|
||||||
|
.coupon-right {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24rpx 24rpx 20rpx;
|
||||||
|
display: flex;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.coupon-name {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FFFFFF;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.coupon-info {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.coupon-bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
.ygl-tip-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.action-btn {
|
||||||
|
background: #FFF8E8;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 10rpx 28rpx;
|
||||||
|
}
|
||||||
|
.action-btn:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.action-btn-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #E8C7AA;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name" : "黄岩停车积分",
|
"name" : "黄岩智惠停",
|
||||||
"appid" : "__UNI__F1854F8",
|
"appid" : "__UNI__F1854F8",
|
||||||
"description" : "黄岩停车积分兑换微信小程序",
|
"description" : "黄岩停车积分兑换微信小程序",
|
||||||
"versionName" : "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="coupons-page">
|
<view class="coupons-page">
|
||||||
<NavBar title="我的优惠券" />
|
<NavBar title="我的优惠券" />
|
||||||
<!-- 状态标签页 -->
|
<!-- 状态标签页 - 胶囊样式 -->
|
||||||
<view class="tabs">
|
<view class="tabs">
|
||||||
<view
|
<view
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
|
|
@ -21,7 +21,20 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 筛选栏 -->
|
<!-- 筛选栏 -->
|
||||||
<FilterBar :filters="filters" @change="onFilterChange" />
|
<view class="filter-row">
|
||||||
|
<picker :range="storeOptions" range-key="label" @change="onStoreChange">
|
||||||
|
<view class="filter-item">
|
||||||
|
<text class="filter-text">适用门店:{{ currentStoreName }}</text>
|
||||||
|
<text class="filter-arrow">∨</text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<picker :range="typeOptions" range-key="label" @change="onTypeChange">
|
||||||
|
<view class="filter-item">
|
||||||
|
<text class="filter-text">类型:{{ currentTypeName }}</text>
|
||||||
|
<text class="filter-arrow">∨</text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 优惠券列表 -->
|
<!-- 优惠券列表 -->
|
||||||
<view class="coupon-list">
|
<view class="coupon-list">
|
||||||
|
|
@ -49,37 +62,26 @@
|
||||||
import { getStores } from '@/api/store'
|
import { getStores } from '@/api/store'
|
||||||
import CouponCard from '@/components/CouponCard.vue'
|
import CouponCard from '@/components/CouponCard.vue'
|
||||||
import QrcodePopup from '@/components/QrcodePopup.vue'
|
import QrcodePopup from '@/components/QrcodePopup.vue'
|
||||||
import FilterBar from '@/components/FilterBar.vue'
|
|
||||||
import NavBar from '@/components/NavBar.vue'
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { CouponCard, QrcodePopup, FilterBar, NavBar },
|
components: { CouponCard, QrcodePopup, NavBar },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
currentStatus: 'unused',
|
currentStatus: 'unused',
|
||||||
currentStoreId: '',
|
currentStoreId: '',
|
||||||
|
currentStoreName: '全部',
|
||||||
currentType: '',
|
currentType: '',
|
||||||
|
currentTypeName: '全部',
|
||||||
coupons: [],
|
coupons: [],
|
||||||
qrcodeVisible: false,
|
qrcodeVisible: false,
|
||||||
currentCoupon: {},
|
currentCoupon: {},
|
||||||
filters: [
|
storeOptions: [{ label: '全部', value: '' }],
|
||||||
{
|
typeOptions: [
|
||||||
label: '门店',
|
{ label: '全部', value: '' },
|
||||||
key: 'storeId',
|
{ label: '免费券', value: 'free' },
|
||||||
options: [{ label: '全部门店', value: '' }],
|
{ label: '抵扣券', value: 'discount' }
|
||||||
selectedIndex: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '类型',
|
|
||||||
key: 'type',
|
|
||||||
options: [
|
|
||||||
{ label: '全部类型', value: '' },
|
|
||||||
{ label: '免费券', value: 'free' },
|
|
||||||
{ label: '抵扣券', value: 'discount' }
|
|
||||||
],
|
|
||||||
selectedIndex: 0
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -93,8 +95,8 @@
|
||||||
try {
|
try {
|
||||||
const res = await getStores()
|
const res = await getStores()
|
||||||
const stores = res.data || res || []
|
const stores = res.data || res || []
|
||||||
this.filters[0].options = [
|
this.storeOptions = [
|
||||||
{ label: '全部门店', value: '' },
|
{ label: '全部', value: '' },
|
||||||
...stores.map(s => ({ label: s.name, value: s.id }))
|
...stores.map(s => ({ label: s.name, value: s.id }))
|
||||||
]
|
]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -124,14 +126,21 @@
|
||||||
this.loadCoupons()
|
this.loadCoupons()
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 筛选变更 */
|
/** 门店筛选变更 */
|
||||||
onFilterChange({ key, value, filterIndex, selectedIndex }) {
|
onStoreChange(e) {
|
||||||
if (key === 'storeId') {
|
const idx = e.detail.value
|
||||||
this.currentStoreId = value
|
const opt = this.storeOptions[idx]
|
||||||
} else if (key === 'type') {
|
this.currentStoreId = opt.value
|
||||||
this.currentType = value
|
this.currentStoreName = opt.label
|
||||||
}
|
this.loadCoupons()
|
||||||
this.filters[filterIndex].selectedIndex = selectedIndex
|
},
|
||||||
|
|
||||||
|
/** 类型筛选变更 */
|
||||||
|
onTypeChange(e) {
|
||||||
|
const idx = e.detail.value
|
||||||
|
const opt = this.typeOptions[idx]
|
||||||
|
this.currentType = opt.value
|
||||||
|
this.currentTypeName = opt.label
|
||||||
this.loadCoupons()
|
this.loadCoupons()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -146,7 +155,7 @@
|
||||||
uni.navigateToMiniProgram({
|
uni.navigateToMiniProgram({
|
||||||
appId: 'wx8c943e2e64e04284',
|
appId: 'wx8c943e2e64e04284',
|
||||||
fail: () => {
|
fail: () => {
|
||||||
uni.showToast({ title: '跳转驿公里失败', icon: 'none' })
|
uni.showToast({ title: '跳转驿公里洗车小程序失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -156,33 +165,57 @@
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.coupons-page {
|
.coupons-page {
|
||||||
padding: 20rpx;
|
padding: 20rpx 24rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
/* 胶囊标签 */
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #fff;
|
gap: 16rpx;
|
||||||
border-radius: 12rpx;
|
margin-bottom: 20rpx;
|
||||||
margin-bottom: 16rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.tab-item {
|
.tab-item {
|
||||||
flex: 1;
|
padding: 12rpx 32rpx;
|
||||||
text-align: center;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
border: 2rpx solid #eee;
|
||||||
}
|
}
|
||||||
.tab-item.active {
|
.tab-item.active {
|
||||||
color: #007aff;
|
color: #C88A00;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 4rpx solid #007aff;
|
background: #FFF8E1;
|
||||||
|
border-color: #FFCC00;
|
||||||
}
|
}
|
||||||
|
/* 筛选行 */
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
.filter-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
.filter-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.filter-arrow {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
/* 列表 */
|
||||||
.coupon-list {
|
.coupon-list {
|
||||||
padding-bottom: 20rpx;
|
padding-bottom: 20rpx;
|
||||||
}
|
}
|
||||||
.empty-tip {
|
.empty-tip {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60rpx 0;
|
padding: 100rpx 0;
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<text class="coupon-info">券码:{{ coupon.code }}</text>
|
<text class="coupon-info">券码:{{ coupon.code }}</text>
|
||||||
<text
|
<text
|
||||||
class="coupon-info">有效期:{{ formatDate(coupon.exchangedAt) }}至{{ formatDate(coupon.expiredAt) }}</text>
|
class="coupon-info">有效期:{{ formatDate(coupon.exchangedAt) }}至{{ formatDate(coupon.expiredAt) }}</text>
|
||||||
<text class="coupon-info" v-if="coupon.source === 'ygl'">使用方式:请在驿公里App中使用</text>
|
<text class="coupon-info" v-if="coupon.source === 'ygl'">使用方式:请在驿公里洗车小程序中使用</text>
|
||||||
<view class="coupon-bottom">
|
<view class="coupon-bottom">
|
||||||
<view v-if="coupon.source === 'platform'" class="action-btn" @click="openQrcode(coupon)">
|
<view v-if="coupon.source === 'platform'" class="action-btn" @click="openQrcode(coupon)">
|
||||||
<text class="action-btn-text">二维码</text>
|
<text class="action-btn-text">二维码</text>
|
||||||
|
|
@ -113,7 +113,7 @@
|
||||||
appId: 'wx8c943e2e64e04284',
|
appId: 'wx8c943e2e64e04284',
|
||||||
fail: () => {
|
fail: () => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '跳转驿公里失败',
|
title: '跳转驿公里洗车小程序失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,14 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-radius: 48rpx;
|
border-radius: 48rpx;
|
||||||
border: none;
|
border: none;
|
||||||
|
outline: none;
|
||||||
margin-bottom: 28rpx;
|
margin-bottom: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-login::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-login[disabled] {
|
.btn-login[disabled] {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
uni.reLaunch({ url: '/pages/merchant/index' })
|
uni.reLaunch({ url: '/pages/merchant/index' })
|
||||||
} else {
|
} else {
|
||||||
// 用户跳转首页
|
// 用户跳转首页
|
||||||
uni.switchTab({ url: '/pages/home/index' })
|
uni.switchTab({ url: '/pages/index/index' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,15 @@
|
||||||
<text class="record-arrow">›</text>
|
<text class="record-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 退出登录 -->
|
||||||
|
<view class="record-entry logout-entry" @click="handleLogout">
|
||||||
|
<view style="display:flex;align-items:center">
|
||||||
|
<image src="/static/login_out.png" class="logout-icon" />
|
||||||
|
<text class="record-label">退出登录</text>
|
||||||
|
</view>
|
||||||
|
<text class="record-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 券码输入弹窗 -->
|
<!-- 券码输入弹窗 -->
|
||||||
<view class="dialog-mask" v-if="showCodeInput" @click="closeCodeInput">
|
<view class="dialog-mask" v-if="showCodeInput" @click="closeCodeInput">
|
||||||
<view class="code-input-dialog" @click.stop>
|
<view class="code-input-dialog" @click.stop>
|
||||||
|
|
@ -73,8 +82,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { scanVerify, codeVerify } from '@/api/verify'
|
import { scanVerify, codeVerify, previewCoupon } from '@/api/verify'
|
||||||
import { getUserInfo, setLoginInfo, getToken } from '@/utils/auth'
|
import { getUserInfo, setLoginInfo, getToken, clearAuth } from '@/utils/auth'
|
||||||
import { checkMerchant } from '@/api/auth'
|
import { checkMerchant } from '@/api/auth'
|
||||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||||
import NavBar from '@/components/NavBar.vue'
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
|
@ -134,17 +143,13 @@
|
||||||
uni.scanCode({
|
uni.scanCode({
|
||||||
onlyFromCamera: true,
|
onlyFromCamera: true,
|
||||||
scanType: ['qrCode'],
|
scanType: ['qrCode'],
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
const code = res.result
|
const code = res.result
|
||||||
if (!code) {
|
if (!code) {
|
||||||
this.showResult(false, '未识别到有效二维码')
|
this.showResult(false, '未识别到有效二维码')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 弹出核销确认
|
await this.previewAndConfirm(code, 'scan')
|
||||||
this.pendingCode = code
|
|
||||||
this.pendingType = 'scan'
|
|
||||||
this.confirmMessage = `确认核销券码:${code}?`
|
|
||||||
this.confirmVisible = true
|
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
// 用户拒绝相机权限或取消扫码
|
// 用户拒绝相机权限或取消扫码
|
||||||
|
|
@ -162,7 +167,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 提交券码 */
|
/** 提交券码 */
|
||||||
submitCode() {
|
async submitCode() {
|
||||||
const code = this.inputCode.trim()
|
const code = this.inputCode.trim()
|
||||||
if (!code) {
|
if (!code) {
|
||||||
uni.showToast({ title: '请输入券码', icon: 'none' })
|
uni.showToast({ title: '请输入券码', icon: 'none' })
|
||||||
|
|
@ -173,10 +178,27 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.showCodeInput = false
|
this.showCodeInput = false
|
||||||
this.pendingCode = code
|
await this.previewAndConfirm(code, 'code')
|
||||||
this.pendingType = 'code'
|
},
|
||||||
this.confirmMessage = `确认核销券码:${code}?`
|
|
||||||
this.confirmVisible = true
|
/** 查询券码信息并弹出确认 */
|
||||||
|
async previewAndConfirm(code, type) {
|
||||||
|
uni.showLoading({ title: '查询中...' })
|
||||||
|
try {
|
||||||
|
const res = await previewCoupon(code)
|
||||||
|
const info = res.data || res || {}
|
||||||
|
const validEnd = info.validEnd ? info.validEnd.substring(0, 10) : '未知'
|
||||||
|
const isPermanent = info.validEnd && new Date(info.validEnd).getFullYear() >= 2099
|
||||||
|
this.pendingCode = code
|
||||||
|
this.pendingType = type
|
||||||
|
this.confirmMessage = `优惠券名称:${info.couponName || '未知'}\n券码:${code}\n有效期至:${isPermanent ? '永久有效' : validEnd}\n\n确认核销该优惠券?`
|
||||||
|
this.confirmVisible = true
|
||||||
|
} catch (err) {
|
||||||
|
const msg = (err && err.message) || '查询券码失败'
|
||||||
|
this.showResult(false, msg)
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 执行核销 */
|
/** 执行核销 */
|
||||||
|
|
@ -213,6 +235,20 @@
|
||||||
/** 跳转核销记录页 */
|
/** 跳转核销记录页 */
|
||||||
gotoRecords() {
|
gotoRecords() {
|
||||||
uni.navigateTo({ url: '/pages/merchant/records' })
|
uni.navigateTo({ url: '/pages/merchant/records' })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 退出登录 */
|
||||||
|
handleLogout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
clearAuth()
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +324,14 @@
|
||||||
font-size: 36rpx;
|
font-size: 36rpx;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
.logout-entry {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
.logout-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
/* 券码输入弹窗 */
|
/* 券码输入弹窗 */
|
||||||
.dialog-mask {
|
.dialog-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,18 @@
|
||||||
<text class="menu-text">隐私政策</text>
|
<text class="menu-text">隐私政策</text>
|
||||||
<text class="arrow-right">›</text>
|
<text class="arrow-right">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="isLogin && userInfo.isMerchant" class="menu-item" @click="goRoleSelect">
|
||||||
|
<image class="menu-icon" src="/static/ic_pitch.png" mode="aspectFit" />
|
||||||
|
<text class="menu-text">切换身份</text>
|
||||||
|
<text class="arrow-right">›</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="isLogin" class="menu-item" @click="showLogoutConfirm">
|
||||||
|
<image class="menu-icon" src="/static/login_out.png" mode="aspectFit" />
|
||||||
|
<text class="menu-text">退出登录</text>
|
||||||
|
<text class="arrow-right">›</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 退出登录 -->
|
|
||||||
<button v-if="isLogin" class="btn-logout" @click="showLogoutConfirm">退出登录</button>
|
|
||||||
|
|
||||||
<!-- 退出确认弹窗 -->
|
<!-- 退出确认弹窗 -->
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
:visible="logoutVisible"
|
:visible="logoutVisible"
|
||||||
|
|
@ -132,6 +139,11 @@
|
||||||
uni.navigateTo({ url: `/pages/mine/agreement?type=${type}` })
|
uni.navigateTo({ url: `/pages/mine/agreement?type=${type}` })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 切换身份 */
|
||||||
|
goRoleSelect() {
|
||||||
|
uni.navigateTo({ url: '/pages/login/role-select' })
|
||||||
|
},
|
||||||
|
|
||||||
/** 显示退出确认弹窗 */
|
/** 显示退出确认弹窗 */
|
||||||
showLogoutConfirm() {
|
showLogoutConfirm() {
|
||||||
this.logoutVisible = true
|
this.logoutVisible = true
|
||||||
|
|
@ -250,13 +262,6 @@
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.btn-logout {
|
|
||||||
background: #fff;
|
|
||||||
color: #ff3b30;
|
|
||||||
font-size: 30rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
margin-top: 20rpx;
|
|
||||||
}
|
|
||||||
.contact-btn {
|
.contact-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@
|
||||||
<view class="coupon-right">
|
<view class="coupon-right">
|
||||||
<text class="coupon-name">{{ item.name }}</text>
|
<text class="coupon-name">{{ item.name }}</text>
|
||||||
<text
|
<text
|
||||||
class="coupon-info">有效期:{{ formatDate(item.validStart) }}至{{ formatDate(item.validEnd) }}</text>
|
class="coupon-info">有效期:{{ isPermanent(item) ? '永久有效' : formatDate(item.validStart) + '至' + formatDate(item.validEnd) }}</text>
|
||||||
<text class="coupon-info">剩余数量:{{ item.remainingCount }} 张</text>
|
<text class="coupon-info">剩余数量:{{ item.remainingCount }} 张</text>
|
||||||
<text class="coupon-info" v-if="item.source === 'ygl'">使用方式:请在驿公里App中使用</text>
|
<text class="coupon-info" v-if="item.source === 'ygl'">使用方式:请在驿公里洗车小程序中使用</text>
|
||||||
<view class="coupon-bottom">
|
<view class="coupon-bottom">
|
||||||
<view class="exchange-btn" @click="onExchange(item)"
|
<view class="exchange-btn" @click="onExchange(item)"
|
||||||
:class="{ disabled: item.remainingCount <= 0 }">
|
:class="{ disabled: item.remainingCount <= 0 }">
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
@cancel="confirmVisible = false" />
|
@cancel="confirmVisible = false" />
|
||||||
|
|
||||||
<!-- 驿公里券提示弹窗 -->
|
<!-- 驿公里券提示弹窗 -->
|
||||||
<ConfirmDialog :visible="yglTipVisible" title="温馨提示" message="该优惠券为驿公里券,兑换后需在驿公里APP/小程序中使用,确定兑换吗?"
|
<ConfirmDialog :visible="yglTipVisible" title="温馨提示" message="该优惠券为驿公里券,兑换后需在驿公里洗车小程序中使用,确定兑换吗?"
|
||||||
@confirm="doExchangeYgl" @cancel="yglTipVisible = false" />
|
@confirm="doExchangeYgl" @cancel="yglTipVisible = false" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -119,13 +119,20 @@
|
||||||
confirmMessage: '',
|
confirmMessage: '',
|
||||||
yglTipVisible: false,
|
yglTipVisible: false,
|
||||||
currentItem: null,
|
currentItem: null,
|
||||||
|
selectedStoreId: null,
|
||||||
pointsBgUrl: ''
|
pointsBgUrl: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredList() {
|
filteredList() {
|
||||||
if (!this.currentStoreId) return this.availableList
|
if (!this.currentStoreId) return this.availableList
|
||||||
return this.availableList.filter(item => item.storeId == this.currentStoreId)
|
// 筛选包含该门店的优惠券
|
||||||
|
return this.availableList.filter(item => {
|
||||||
|
if (item.stores && item.stores.length) {
|
||||||
|
return item.stores.some(s => s.id == this.currentStoreId)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
|
|
@ -136,6 +143,10 @@
|
||||||
if (!d) return ''
|
if (!d) return ''
|
||||||
return d.substring(0, 10)
|
return d.substring(0, 10)
|
||||||
},
|
},
|
||||||
|
isPermanent(item) {
|
||||||
|
if (!item.validEnd) return false
|
||||||
|
return new Date(item.validEnd).getFullYear() >= 2099
|
||||||
|
},
|
||||||
async loadData() {
|
async loadData() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -197,6 +208,31 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.currentItem = item
|
this.currentItem = item
|
||||||
|
|
||||||
|
// 多门店时需要先选择门店
|
||||||
|
const stores = item.stores || []
|
||||||
|
if (stores.length === 0) {
|
||||||
|
uni.showToast({ title: '该优惠券未配置门店', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stores.length === 1) {
|
||||||
|
// 只有一个门店,直接使用
|
||||||
|
this.selectedStoreId = stores[0].id
|
||||||
|
this.showExchangeConfirm(item)
|
||||||
|
} else {
|
||||||
|
// 多个门店,弹出选择
|
||||||
|
const names = stores.map(s => s.name)
|
||||||
|
uni.showActionSheet({
|
||||||
|
itemList: names,
|
||||||
|
success: (res) => {
|
||||||
|
this.selectedStoreId = stores[res.tapIndex].id
|
||||||
|
this.showExchangeConfirm(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showExchangeConfirm(item) {
|
||||||
if (item.source === 'ygl') {
|
if (item.source === 'ygl') {
|
||||||
this.yglTipVisible = true
|
this.yglTipVisible = true
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -213,10 +249,11 @@
|
||||||
await this.executeExchange()
|
await this.executeExchange()
|
||||||
},
|
},
|
||||||
async executeExchange() {
|
async executeExchange() {
|
||||||
if (!this.currentItem) return
|
if (!this.currentItem || !this.selectedStoreId) return
|
||||||
try {
|
try {
|
||||||
await exchange({
|
await exchange({
|
||||||
templateId: this.currentItem.id
|
templateId: this.currentItem.id,
|
||||||
|
storeId: this.selectedStoreId
|
||||||
})
|
})
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '兑换成功',
|
title: '兑换成功',
|
||||||
|
|
|
||||||
BIN
miniapp/static/login_out.png
Normal file
BIN
miniapp/static/login_out.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 5.0 KiB |
|
|
@ -4,8 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 后台 API 基础地址,按环境配置
|
// 后台 API 基础地址,按环境配置
|
||||||
// const BASE_URL = 'http://localhost:5030'
|
const BASE_URL = 'http://localhost:5030'
|
||||||
const BASE_URL = 'https://api.pli.shhmkjgs.cn'
|
// const BASE_URL = 'https://api.pli.shhmkjgs.cn'
|
||||||
|
|
||||||
// 请求超时时间(毫秒)
|
// 请求超时时间(毫秒)
|
||||||
const TIMEOUT = 10000
|
const TIMEOUT = 10000
|
||||||
|
|
|
||||||
|
|
@ -176,20 +176,12 @@ public class AdminController : ControllerBase
|
||||||
store.Photo = request.Photo;
|
store.Photo = request.Photo;
|
||||||
store.Address = request.Address;
|
store.Address = request.Address;
|
||||||
store.Phone = request.Phone;
|
store.Phone = request.Phone;
|
||||||
|
store.Latitude = request.Latitude;
|
||||||
|
store.Longitude = request.Longitude;
|
||||||
store.Type = request.Type;
|
store.Type = request.Type;
|
||||||
store.IsActive = request.IsActive;
|
store.IsActive = request.IsActive;
|
||||||
store.UpdatedAt = DateTime.UtcNow;
|
store.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
if (addressChanged && !string.IsNullOrEmpty(request.Address))
|
|
||||||
{
|
|
||||||
var location = await _geocodingService.GeocodeAsync(request.Address);
|
|
||||||
if (location is not null)
|
|
||||||
{
|
|
||||||
store.Latitude = location.Latitude;
|
|
||||||
store.Longitude = location.Longitude;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
return Ok(ApiResponse<Store>.Ok(store));
|
return Ok(ApiResponse<Store>.Ok(store));
|
||||||
}
|
}
|
||||||
|
|
@ -292,13 +284,25 @@ public class AdminController : ControllerBase
|
||||||
|
|
||||||
/// <summary>获取优惠券模板列表</summary>
|
/// <summary>获取优惠券模板列表</summary>
|
||||||
[HttpGet("coupon-templates")]
|
[HttpGet("coupon-templates")]
|
||||||
public async Task<ActionResult<ApiResponse<List<CouponTemplate>>>> GetCouponTemplates()
|
public async Task<ActionResult<ApiResponse<List<object>>>> GetCouponTemplates()
|
||||||
{
|
{
|
||||||
var templates = await _db.CouponTemplates
|
var templates = await _db.CouponTemplates
|
||||||
.Include(t => t.Store)
|
.Include(t => t.TemplateStores).ThenInclude(ts => ts.Store)
|
||||||
.OrderByDescending(t => t.CreatedAt)
|
.OrderByDescending(t => t.CreatedAt)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return Ok(ApiResponse<List<CouponTemplate>>.Ok(templates));
|
|
||||||
|
// 返回包含门店列表的响应
|
||||||
|
var result = templates.Select(t => new
|
||||||
|
{
|
||||||
|
t.Id, t.Name, t.CouponType, t.DiscountAmount,
|
||||||
|
t.ValidStart, t.ValidEnd, t.TotalCount, t.RemainingCount,
|
||||||
|
t.PointsCost, t.Source, t.YglTicketPackageCode, t.IsActive,
|
||||||
|
t.CreatedAt, t.UpdatedAt,
|
||||||
|
StoreIds = t.TemplateStores.Select(ts => ts.StoreId).ToList(),
|
||||||
|
StoreNames = string.Join("、", t.TemplateStores.Select(ts => ts.Store.Name))
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Ok(ApiResponse<List<object>>.Ok(result.Cast<object>().ToList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>创建优惠券模板</summary>
|
/// <summary>创建优惠券模板</summary>
|
||||||
|
|
@ -310,11 +314,10 @@ public class AdminController : ControllerBase
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
CouponType = request.CouponType,
|
CouponType = request.CouponType,
|
||||||
DiscountAmount = request.DiscountAmount,
|
DiscountAmount = request.DiscountAmount,
|
||||||
StoreId = request.StoreId,
|
|
||||||
ValidStart = request.ValidStart,
|
ValidStart = request.ValidStart,
|
||||||
ValidEnd = request.ValidEnd,
|
ValidEnd = request.ValidEnd,
|
||||||
TotalCount = request.TotalCount,
|
TotalCount = request.TotalCount,
|
||||||
RemainingCount = request.TotalCount, // 初始剩余=总数
|
RemainingCount = request.TotalCount,
|
||||||
PointsCost = request.PointsCost,
|
PointsCost = request.PointsCost,
|
||||||
Source = request.Source,
|
Source = request.Source,
|
||||||
YglTicketPackageCode = request.YglTicketPackageCode,
|
YglTicketPackageCode = request.YglTicketPackageCode,
|
||||||
|
|
@ -322,6 +325,18 @@ public class AdminController : ControllerBase
|
||||||
};
|
};
|
||||||
_db.CouponTemplates.Add(template);
|
_db.CouponTemplates.Add(template);
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// 添加门店关联
|
||||||
|
foreach (var storeId in request.StoreIds)
|
||||||
|
{
|
||||||
|
_db.CouponTemplateStores.Add(new CouponTemplateStore
|
||||||
|
{
|
||||||
|
CouponTemplateId = template.Id,
|
||||||
|
StoreId = storeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
return Ok(ApiResponse<CouponTemplate>.Ok(template));
|
return Ok(ApiResponse<CouponTemplate>.Ok(template));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,7 +344,9 @@ public class AdminController : ControllerBase
|
||||||
[HttpPut("coupon-templates/{id}")]
|
[HttpPut("coupon-templates/{id}")]
|
||||||
public async Task<ActionResult<ApiResponse<CouponTemplate>>> UpdateCouponTemplate(int id, [FromBody] AdminCouponTemplateRequest request)
|
public async Task<ActionResult<ApiResponse<CouponTemplate>>> UpdateCouponTemplate(int id, [FromBody] AdminCouponTemplateRequest request)
|
||||||
{
|
{
|
||||||
var template = await _db.CouponTemplates.FindAsync(id);
|
var template = await _db.CouponTemplates
|
||||||
|
.Include(t => t.TemplateStores)
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == id);
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return NotFound(ApiResponse<CouponTemplate>.Fail("优惠券模板不存在"));
|
return NotFound(ApiResponse<CouponTemplate>.Fail("优惠券模板不存在"));
|
||||||
|
|
||||||
|
|
@ -338,7 +355,6 @@ public class AdminController : ControllerBase
|
||||||
template.Name = request.Name;
|
template.Name = request.Name;
|
||||||
template.CouponType = request.CouponType;
|
template.CouponType = request.CouponType;
|
||||||
template.DiscountAmount = request.DiscountAmount;
|
template.DiscountAmount = request.DiscountAmount;
|
||||||
template.StoreId = request.StoreId;
|
|
||||||
template.ValidStart = request.ValidStart;
|
template.ValidStart = request.ValidStart;
|
||||||
template.ValidEnd = request.ValidEnd;
|
template.ValidEnd = request.ValidEnd;
|
||||||
template.TotalCount = request.TotalCount;
|
template.TotalCount = request.TotalCount;
|
||||||
|
|
@ -348,6 +364,18 @@ public class AdminController : ControllerBase
|
||||||
template.YglTicketPackageCode = request.YglTicketPackageCode;
|
template.YglTicketPackageCode = request.YglTicketPackageCode;
|
||||||
template.IsActive = request.IsActive;
|
template.IsActive = request.IsActive;
|
||||||
template.UpdatedAt = DateTime.UtcNow;
|
template.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// 更新门店关联:先删后加
|
||||||
|
_db.CouponTemplateStores.RemoveRange(template.TemplateStores);
|
||||||
|
foreach (var storeId in request.StoreIds)
|
||||||
|
{
|
||||||
|
_db.CouponTemplateStores.Add(new CouponTemplateStore
|
||||||
|
{
|
||||||
|
CouponTemplateId = template.Id,
|
||||||
|
StoreId = storeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
return Ok(ApiResponse<CouponTemplate>.Ok(template));
|
return Ok(ApiResponse<CouponTemplate>.Ok(template));
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +490,7 @@ public class AdminController : ControllerBase
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(userPhone))
|
if (!string.IsNullOrEmpty(userPhone))
|
||||||
query = query.Where(c => c.User.Phone.Contains(userPhone));
|
query = query.Where(c => c.User.Phone.Contains(userPhone) || c.User.Uid.Contains(userPhone));
|
||||||
if (storeId.HasValue)
|
if (storeId.HasValue)
|
||||||
query = query.Where(c => c.StoreId == storeId.Value);
|
query = query.Where(c => c.StoreId == storeId.Value);
|
||||||
if (!string.IsNullOrEmpty(status))
|
if (!string.IsNullOrEmpty(status))
|
||||||
|
|
@ -475,6 +503,7 @@ public class AdminController : ControllerBase
|
||||||
Id = c.Id,
|
Id = c.Id,
|
||||||
Code = c.Code,
|
Code = c.Code,
|
||||||
TemplateName = c.Template.Name,
|
TemplateName = c.Template.Name,
|
||||||
|
UserUid = c.User.Uid,
|
||||||
UserPhone = c.User.Phone,
|
UserPhone = c.User.Phone,
|
||||||
StoreName = c.Store.Name,
|
StoreName = c.Store.Name,
|
||||||
Status = c.Status,
|
Status = c.Status,
|
||||||
|
|
@ -556,7 +585,22 @@ public class AdminController : ControllerBase
|
||||||
|
|
||||||
if (request.Nickname != null) user.Nickname = request.Nickname;
|
if (request.Nickname != null) user.Nickname = request.Nickname;
|
||||||
if (request.Phone != null) user.Phone = request.Phone;
|
if (request.Phone != null) user.Phone = request.Phone;
|
||||||
if (request.Points.HasValue) user.Points = request.Points.Value;
|
|
||||||
|
// 积分变更时创建积分记录
|
||||||
|
if (request.Points.HasValue && request.Points.Value != user.Points)
|
||||||
|
{
|
||||||
|
var delta = request.Points.Value - user.Points;
|
||||||
|
_db.PointsRecords.Add(new Domain.Entities.PointsRecord
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
Amount = delta,
|
||||||
|
Type = delta > 0 ? "income" : "expense",
|
||||||
|
Reason = "管理员调整积分",
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
user.Points = request.Points.Value;
|
||||||
|
}
|
||||||
|
|
||||||
user.UpdatedAt = DateTime.UtcNow;
|
user.UpdatedAt = DateTime.UtcNow;
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,11 @@ public class CouponController : ControllerBase
|
||||||
Name = t.Name,
|
Name = t.Name,
|
||||||
CouponType = t.CouponType,
|
CouponType = t.CouponType,
|
||||||
DiscountAmount = t.DiscountAmount,
|
DiscountAmount = t.DiscountAmount,
|
||||||
StoreName = t.Store.Name,
|
Stores = t.TemplateStores.Select(ts => new CouponStoreInfo
|
||||||
StoreId = t.StoreId,
|
{
|
||||||
|
Id = ts.Store.Id,
|
||||||
|
Name = ts.Store.Name
|
||||||
|
}).ToList(),
|
||||||
ValidStart = t.ValidStart,
|
ValidStart = t.ValidStart,
|
||||||
ValidEnd = t.ValidEnd,
|
ValidEnd = t.ValidEnd,
|
||||||
RemainingCount = t.RemainingCount,
|
RemainingCount = t.RemainingCount,
|
||||||
|
|
@ -58,7 +61,7 @@ public class CouponController : ControllerBase
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var coupon = await _couponService.ExchangeAsync(userId, request.TemplateId);
|
var coupon = await _couponService.ExchangeAsync(userId, request.TemplateId, request.StoreId);
|
||||||
var response = new UserCouponResponse
|
var response = new UserCouponResponse
|
||||||
{
|
{
|
||||||
Id = coupon.Id,
|
Id = coupon.Id,
|
||||||
|
|
@ -70,6 +73,8 @@ public class CouponController : ControllerBase
|
||||||
StoreId = coupon.StoreId,
|
StoreId = coupon.StoreId,
|
||||||
Status = coupon.Status,
|
Status = coupon.Status,
|
||||||
Source = coupon.Source,
|
Source = coupon.Source,
|
||||||
|
ValidStart = coupon.Template?.ValidStart,
|
||||||
|
ValidEnd = coupon.Template?.ValidEnd,
|
||||||
ExchangedAt = coupon.ExchangedAt,
|
ExchangedAt = coupon.ExchangedAt,
|
||||||
UsedAt = coupon.UsedAt,
|
UsedAt = coupon.UsedAt,
|
||||||
ExpiredAt = coupon.ExpiredAt
|
ExpiredAt = coupon.ExpiredAt
|
||||||
|
|
@ -107,6 +112,8 @@ public class CouponController : ControllerBase
|
||||||
StoreId = c.StoreId,
|
StoreId = c.StoreId,
|
||||||
Status = c.Status,
|
Status = c.Status,
|
||||||
Source = c.Source,
|
Source = c.Source,
|
||||||
|
ValidStart = c.Template?.ValidStart,
|
||||||
|
ValidEnd = c.Template?.ValidEnd,
|
||||||
ExchangedAt = c.ExchangedAt,
|
ExchangedAt = c.ExchangedAt,
|
||||||
UsedAt = c.UsedAt,
|
UsedAt = c.UsedAt,
|
||||||
ExpiredAt = c.ExpiredAt
|
ExpiredAt = c.ExpiredAt
|
||||||
|
|
@ -136,6 +143,8 @@ public class CouponController : ControllerBase
|
||||||
StoreId = c.StoreId,
|
StoreId = c.StoreId,
|
||||||
Status = c.Status,
|
Status = c.Status,
|
||||||
Source = c.Source,
|
Source = c.Source,
|
||||||
|
ValidStart = c.Template?.ValidStart,
|
||||||
|
ValidEnd = c.Template?.ValidEnd,
|
||||||
ExchangedAt = c.ExchangedAt,
|
ExchangedAt = c.ExchangedAt,
|
||||||
UsedAt = c.UsedAt,
|
UsedAt = c.UsedAt,
|
||||||
ExpiredAt = c.ExpiredAt
|
ExpiredAt = c.ExpiredAt
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using HuangyanParking.Api.Models;
|
using HuangyanParking.Api.Models;
|
||||||
using HuangyanParking.Domain.Services;
|
using HuangyanParking.Domain.Services;
|
||||||
|
using HuangyanParking.Infrastructure.Data;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace HuangyanParking.Api.Controllers;
|
namespace HuangyanParking.Api.Controllers;
|
||||||
|
|
||||||
|
|
@ -15,10 +17,41 @@ namespace HuangyanParking.Api.Controllers;
|
||||||
public class VerifyController : ControllerBase
|
public class VerifyController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IVerifyService _verifyService;
|
private readonly IVerifyService _verifyService;
|
||||||
|
private readonly AppDbContext _db;
|
||||||
|
|
||||||
public VerifyController(IVerifyService verifyService)
|
public VerifyController(IVerifyService verifyService, AppDbContext db)
|
||||||
{
|
{
|
||||||
_verifyService = verifyService;
|
_verifyService = verifyService;
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据券码查询优惠券信息(核销前预览)
|
||||||
|
/// GET /api/verify/preview?code=xxx
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("preview")]
|
||||||
|
public async Task<ActionResult<ApiResponse<CouponPreviewResponse>>> Preview([FromQuery] string code)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(code))
|
||||||
|
return BadRequest(ApiResponse<CouponPreviewResponse>.Fail("券码不能为空"));
|
||||||
|
|
||||||
|
var coupon = await _db.Coupons
|
||||||
|
.Include(c => c.Template)
|
||||||
|
.Include(c => c.Store)
|
||||||
|
.FirstOrDefaultAsync(c => c.Code == code);
|
||||||
|
|
||||||
|
if (coupon == null)
|
||||||
|
return NotFound(ApiResponse<CouponPreviewResponse>.Fail("未找到该券码对应的优惠券"));
|
||||||
|
|
||||||
|
return Ok(ApiResponse<CouponPreviewResponse>.Ok(new CouponPreviewResponse
|
||||||
|
{
|
||||||
|
CouponName = coupon.Template?.Name ?? string.Empty,
|
||||||
|
CouponCode = coupon.Code,
|
||||||
|
CouponType = coupon.Template?.CouponType ?? string.Empty,
|
||||||
|
DiscountAmount = coupon.Template?.DiscountAmount ?? 0,
|
||||||
|
StoreName = coupon.Store?.Name ?? string.Empty,
|
||||||
|
ValidEnd = coupon.Template?.ValidEnd
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,8 @@ public class AdminCouponTemplateRequest
|
||||||
/// <summary>抵扣金额(抵扣券)</summary>
|
/// <summary>抵扣金额(抵扣券)</summary>
|
||||||
public decimal DiscountAmount { get; set; }
|
public decimal DiscountAmount { get; set; }
|
||||||
|
|
||||||
/// <summary>适用门店ID</summary>
|
/// <summary>适用门店ID列表(多选)</summary>
|
||||||
public int StoreId { get; set; }
|
public List<int> StoreIds { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>有效期开始</summary>
|
/// <summary>有效期开始</summary>
|
||||||
public DateTime ValidStart { get; set; }
|
public DateTime ValidStart { get; set; }
|
||||||
|
|
@ -179,6 +179,7 @@ public class AdminCouponResponse
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string TemplateName { get; set; } = string.Empty;
|
public string TemplateName { get; set; } = string.Empty;
|
||||||
|
public string UserUid { get; set; } = string.Empty;
|
||||||
public string UserPhone { get; set; } = string.Empty;
|
public string UserPhone { get; set; } = string.Empty;
|
||||||
public string StoreName { get; set; } = string.Empty;
|
public string StoreName { get; set; } = string.Empty;
|
||||||
public string Status { get; set; } = string.Empty;
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ public class ExchangeCouponRequest
|
||||||
{
|
{
|
||||||
/// <summary>优惠券模板ID</summary>
|
/// <summary>优惠券模板ID</summary>
|
||||||
public int TemplateId { get; set; }
|
public int TemplateId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>选择的门店ID</summary>
|
||||||
|
public int StoreId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -18,8 +21,8 @@ public class AvailableCouponResponse
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string CouponType { get; set; } = string.Empty;
|
public string CouponType { get; set; } = string.Empty;
|
||||||
public decimal DiscountAmount { get; set; }
|
public decimal DiscountAmount { get; set; }
|
||||||
public string StoreName { get; set; } = string.Empty;
|
/// <summary>适用门店列表</summary>
|
||||||
public int StoreId { get; set; }
|
public List<CouponStoreInfo> Stores { get; set; } = [];
|
||||||
public DateTime ValidStart { get; set; }
|
public DateTime ValidStart { get; set; }
|
||||||
public DateTime ValidEnd { get; set; }
|
public DateTime ValidEnd { get; set; }
|
||||||
public int RemainingCount { get; set; }
|
public int RemainingCount { get; set; }
|
||||||
|
|
@ -27,6 +30,15 @@ public class AvailableCouponResponse
|
||||||
public string Source { get; set; } = string.Empty;
|
public string Source { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 优惠券关联门店信息
|
||||||
|
/// </summary>
|
||||||
|
public class CouponStoreInfo
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户优惠券响应
|
/// 用户优惠券响应
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -41,6 +53,8 @@ public class UserCouponResponse
|
||||||
public int StoreId { get; set; }
|
public int StoreId { get; set; }
|
||||||
public string Status { get; set; } = string.Empty;
|
public string Status { get; set; } = string.Empty;
|
||||||
public string Source { get; set; } = string.Empty;
|
public string Source { get; set; } = string.Empty;
|
||||||
|
public DateTime? ValidStart { get; set; }
|
||||||
|
public DateTime? ValidEnd { get; set; }
|
||||||
public DateTime ExchangedAt { get; set; }
|
public DateTime ExchangedAt { get; set; }
|
||||||
public DateTime? UsedAt { get; set; }
|
public DateTime? UsedAt { get; set; }
|
||||||
public DateTime ExpiredAt { get; set; }
|
public DateTime ExpiredAt { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,16 @@ public class VerifyRecordResponse
|
||||||
public string? UserPhone { get; set; }
|
public string? UserPhone { get; set; }
|
||||||
public DateTime VerifiedAt { get; set; }
|
public DateTime VerifiedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 券码查询响应(核销前预览)
|
||||||
|
/// </summary>
|
||||||
|
public class CouponPreviewResponse
|
||||||
|
{
|
||||||
|
public string CouponName { get; set; } = string.Empty;
|
||||||
|
public string CouponCode { get; set; } = string.Empty;
|
||||||
|
public string CouponType { get; set; } = string.Empty;
|
||||||
|
public decimal DiscountAmount { get; set; }
|
||||||
|
public string StoreName { get; set; } = string.Empty;
|
||||||
|
public DateTime? ValidEnd { get; set; }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,77 @@ using (var scope = app.Services.CreateScope())
|
||||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
db.Database.EnsureCreated();
|
db.Database.EnsureCreated();
|
||||||
|
|
||||||
|
// 数据库结构升级:优惠券模板支持多门店
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 创建中间表(如果不存在)
|
||||||
|
await db.Database.ExecuteSqlRawAsync(@"
|
||||||
|
IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'coupon_template_stores')
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE coupon_template_stores (
|
||||||
|
CouponTemplateId int NOT NULL,
|
||||||
|
StoreId int NOT NULL,
|
||||||
|
CONSTRAINT PK_coupon_template_stores PRIMARY KEY (CouponTemplateId, StoreId),
|
||||||
|
CONSTRAINT FK_coupon_template_stores_coupon_templates FOREIGN KEY (CouponTemplateId) REFERENCES coupon_templates(Id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT FK_coupon_template_stores_stores FOREIGN KEY (StoreId) REFERENCES stores(Id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
-- 从旧的 StoreId 列迁移数据到中间表
|
||||||
|
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'coupon_templates' AND COLUMN_NAME = 'StoreId')
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO coupon_template_stores (CouponTemplateId, StoreId)
|
||||||
|
SELECT Id, StoreId FROM coupon_templates WHERE StoreId IS NOT NULL AND StoreId > 0;
|
||||||
|
END
|
||||||
|
END");
|
||||||
|
|
||||||
|
// 删除旧的外键和列(如果存在)
|
||||||
|
await db.Database.ExecuteSqlRawAsync(@"
|
||||||
|
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'coupon_templates' AND COLUMN_NAME = 'StoreId')
|
||||||
|
BEGIN
|
||||||
|
-- 删除外键约束
|
||||||
|
DECLARE @fkName NVARCHAR(200);
|
||||||
|
SELECT @fkName = fk.name FROM sys.foreign_keys fk
|
||||||
|
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
|
||||||
|
JOIN sys.columns c ON fkc.parent_column_id = c.column_id AND fkc.parent_object_id = c.object_id
|
||||||
|
WHERE OBJECT_NAME(fk.parent_object_id) = 'coupon_templates' AND c.name = 'StoreId';
|
||||||
|
IF @fkName IS NOT NULL
|
||||||
|
EXEC('ALTER TABLE coupon_templates DROP CONSTRAINT ' + @fkName);
|
||||||
|
-- 删除索引(如果有)
|
||||||
|
DECLARE @ixName NVARCHAR(200);
|
||||||
|
SELECT @ixName = i.name FROM sys.indexes i
|
||||||
|
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
||||||
|
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
||||||
|
WHERE OBJECT_NAME(i.object_id) = 'coupon_templates' AND c.name = 'StoreId' AND i.is_primary_key = 0;
|
||||||
|
IF @ixName IS NOT NULL
|
||||||
|
EXEC('DROP INDEX ' + @ixName + ' ON coupon_templates');
|
||||||
|
-- 删除列
|
||||||
|
ALTER TABLE coupon_templates DROP COLUMN StoreId;
|
||||||
|
END");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "数据库结构升级时出现异常(可忽略)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除MinSpend字段(如果存在)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await db.Database.ExecuteSqlRawAsync(@"
|
||||||
|
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'coupon_templates' AND COLUMN_NAME = 'MinSpend')
|
||||||
|
BEGIN
|
||||||
|
DECLARE @dfName NVARCHAR(200);
|
||||||
|
SELECT @dfName = d.name FROM sys.default_constraints d
|
||||||
|
JOIN sys.columns c ON d.parent_column_id = c.column_id AND d.parent_object_id = c.object_id
|
||||||
|
WHERE OBJECT_NAME(d.parent_object_id) = 'coupon_templates' AND c.name = 'MinSpend';
|
||||||
|
IF @dfName IS NOT NULL
|
||||||
|
EXEC('ALTER TABLE coupon_templates DROP CONSTRAINT ' + @dfName);
|
||||||
|
ALTER TABLE coupon_templates DROP COLUMN MinSpend;
|
||||||
|
END");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "删除MinSpend字段时出现异常(可忽略)");
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化默认管理员账号(如果不存在)
|
// 初始化默认管理员账号(如果不存在)
|
||||||
if (!db.Admins.Any())
|
if (!db.Admins.Any())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@ public class CouponTemplate
|
||||||
/// <summary>抵扣金额(抵扣券)</summary>
|
/// <summary>抵扣金额(抵扣券)</summary>
|
||||||
public decimal DiscountAmount { get; set; }
|
public decimal DiscountAmount { get; set; }
|
||||||
|
|
||||||
/// <summary>适用门店</summary>
|
|
||||||
public int StoreId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>有效期开始</summary>
|
/// <summary>有效期开始</summary>
|
||||||
public DateTime ValidStart { get; set; }
|
public DateTime ValidStart { get; set; }
|
||||||
|
|
||||||
|
|
@ -46,7 +43,20 @@ public class CouponTemplate
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
// 导航属性
|
// 导航属性 - 多对多门店关联
|
||||||
public Store Store { get; set; } = null!;
|
public ICollection<CouponTemplateStore> TemplateStores { get; set; } = [];
|
||||||
public ICollection<Coupon> Coupons { get; set; } = [];
|
public ICollection<Coupon> Coupons { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 优惠券模板-门店 多对多中间表
|
||||||
|
/// </summary>
|
||||||
|
public class CouponTemplateStore
|
||||||
|
{
|
||||||
|
public int CouponTemplateId { get; set; }
|
||||||
|
public int StoreId { get; set; }
|
||||||
|
|
||||||
|
// 导航属性
|
||||||
|
public CouponTemplate CouponTemplate { get; set; } = null!;
|
||||||
|
public Store Store { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,6 @@ public class Store
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
// 导航属性
|
// 导航属性
|
||||||
public ICollection<CouponTemplate> CouponTemplates { get; set; } = [];
|
public ICollection<CouponTemplateStore> TemplateStores { get; set; } = [];
|
||||||
public ICollection<Coupon> Coupons { get; set; } = [];
|
public ICollection<Coupon> Coupons { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ public interface ICouponService
|
||||||
Task<List<CouponTemplate>> GetAvailableTemplatesAsync();
|
Task<List<CouponTemplate>> GetAvailableTemplatesAsync();
|
||||||
|
|
||||||
/// <summary>兑换优惠券(积分校验+扣减+券码生成+驿公里API调用)</summary>
|
/// <summary>兑换优惠券(积分校验+扣减+券码生成+驿公里API调用)</summary>
|
||||||
Task<Coupon> ExchangeAsync(int userId, int templateId);
|
Task<Coupon> ExchangeAsync(int userId, int templateId, int storeId);
|
||||||
|
|
||||||
/// <summary>获取用户优惠券列表(支持状态/门店/类型筛选)</summary>
|
/// <summary>获取用户优惠券列表(支持状态/门店/类型筛选)</summary>
|
||||||
Task<List<Coupon>> GetUserCouponsAsync(int userId, string? status = null, int? storeId = null, string? couponType = null);
|
Task<List<Coupon>> GetUserCouponsAsync(int userId, string? status = null, int? storeId = null, string? couponType = null);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ public class AppDbContext : DbContext
|
||||||
public DbSet<User> Users => Set<User>();
|
public DbSet<User> Users => Set<User>();
|
||||||
public DbSet<Store> Stores => Set<Store>();
|
public DbSet<Store> Stores => Set<Store>();
|
||||||
public DbSet<CouponTemplate> CouponTemplates => Set<CouponTemplate>();
|
public DbSet<CouponTemplate> CouponTemplates => Set<CouponTemplate>();
|
||||||
|
public DbSet<CouponTemplateStore> CouponTemplateStores => Set<CouponTemplateStore>();
|
||||||
public DbSet<Coupon> Coupons => Set<Coupon>();
|
public DbSet<Coupon> Coupons => Set<Coupon>();
|
||||||
public DbSet<PointsRecord> PointsRecords => Set<PointsRecord>();
|
public DbSet<PointsRecord> PointsRecords => Set<PointsRecord>();
|
||||||
public DbSet<VerifyRecord> VerifyRecords => Set<VerifyRecord>();
|
public DbSet<VerifyRecord> VerifyRecords => Set<VerifyRecord>();
|
||||||
|
|
@ -64,7 +65,15 @@ public class AppDbContext : DbContext
|
||||||
entity.Property(e => e.DiscountAmount).HasPrecision(10, 2);
|
entity.Property(e => e.DiscountAmount).HasPrecision(10, 2);
|
||||||
entity.Property(e => e.Source).HasMaxLength(20).IsRequired();
|
entity.Property(e => e.Source).HasMaxLength(20).IsRequired();
|
||||||
entity.Property(e => e.YglTicketPackageCode).HasMaxLength(100);
|
entity.Property(e => e.YglTicketPackageCode).HasMaxLength(100);
|
||||||
entity.HasOne(e => e.Store).WithMany(s => s.CouponTemplates).HasForeignKey(e => e.StoreId).OnDelete(DeleteBehavior.Restrict);
|
});
|
||||||
|
|
||||||
|
// CouponTemplateStore 多对多中间表配置
|
||||||
|
modelBuilder.Entity<CouponTemplateStore>(entity =>
|
||||||
|
{
|
||||||
|
entity.ToTable("coupon_template_stores");
|
||||||
|
entity.HasKey(e => new { e.CouponTemplateId, e.StoreId });
|
||||||
|
entity.HasOne(e => e.CouponTemplate).WithMany(t => t.TemplateStores).HasForeignKey(e => e.CouponTemplateId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
entity.HasOne(e => e.Store).WithMany(s => s.TemplateStores).HasForeignKey(e => e.StoreId).OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Coupon 配置
|
// Coupon 配置
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public class CouponService : ICouponService
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
return await _db.CouponTemplates
|
return await _db.CouponTemplates
|
||||||
.Include(t => t.Store)
|
.Include(t => t.TemplateStores).ThenInclude(ts => ts.Store)
|
||||||
.Where(t => t.IsActive
|
.Where(t => t.IsActive
|
||||||
&& t.RemainingCount > 0
|
&& t.RemainingCount > 0
|
||||||
&& t.ValidStart <= now
|
&& t.ValidStart <= now
|
||||||
|
|
@ -44,7 +44,7 @@ public class CouponService : ICouponService
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>兑换优惠券</summary>
|
/// <summary>兑换优惠券</summary>
|
||||||
public async Task<Coupon> ExchangeAsync(int userId, int templateId)
|
public async Task<Coupon> ExchangeAsync(int userId, int templateId, int storeId)
|
||||||
{
|
{
|
||||||
// 使用事务确保一致性
|
// 使用事务确保一致性
|
||||||
await using var transaction = await _db.Database.BeginTransactionAsync();
|
await using var transaction = await _db.Database.BeginTransactionAsync();
|
||||||
|
|
@ -52,7 +52,7 @@ public class CouponService : ICouponService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var template = await _db.CouponTemplates
|
var template = await _db.CouponTemplates
|
||||||
.Include(t => t.Store)
|
.Include(t => t.TemplateStores)
|
||||||
.FirstOrDefaultAsync(t => t.Id == templateId)
|
.FirstOrDefaultAsync(t => t.Id == templateId)
|
||||||
?? throw new InvalidOperationException("优惠券模板不存在");
|
?? throw new InvalidOperationException("优惠券模板不存在");
|
||||||
|
|
||||||
|
|
@ -66,6 +66,10 @@ public class CouponService : ICouponService
|
||||||
if (now < template.ValidStart || now > template.ValidEnd)
|
if (now < template.ValidStart || now > template.ValidEnd)
|
||||||
throw new InvalidOperationException("优惠券不在有效期内");
|
throw new InvalidOperationException("优惠券不在有效期内");
|
||||||
|
|
||||||
|
// 校验门店是否属于该模板
|
||||||
|
if (!template.TemplateStores.Any(ts => ts.StoreId == storeId))
|
||||||
|
throw new InvalidOperationException("所选门店不适用于该优惠券");
|
||||||
|
|
||||||
// 校验积分
|
// 校验积分
|
||||||
var user = await _db.Users.FindAsync(userId)
|
var user = await _db.Users.FindAsync(userId)
|
||||||
?? throw new InvalidOperationException("用户不存在");
|
?? throw new InvalidOperationException("用户不存在");
|
||||||
|
|
@ -83,7 +87,7 @@ public class CouponService : ICouponService
|
||||||
Code = code,
|
Code = code,
|
||||||
TemplateId = templateId,
|
TemplateId = templateId,
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
StoreId = template.StoreId,
|
StoreId = storeId,
|
||||||
Source = template.Source,
|
Source = template.Source,
|
||||||
ExchangedAt = now,
|
ExchangedAt = now,
|
||||||
ExpiredAt = template.ValidEnd,
|
ExpiredAt = template.ValidEnd,
|
||||||
|
|
@ -120,7 +124,7 @@ public class CouponService : ICouponService
|
||||||
userId, template.PointsCost,
|
userId, template.PointsCost,
|
||||||
$"兑换优惠券:{template.Name}",
|
$"兑换优惠券:{template.Name}",
|
||||||
couponId: coupon.Id,
|
couponId: coupon.Id,
|
||||||
storeId: template.StoreId);
|
storeId: storeId);
|
||||||
}
|
}
|
||||||
await transaction.CommitAsync();
|
await transaction.CommitAsync();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using HuangyanParking.Domain.Entities;
|
using HuangyanParking.Domain.Entities;
|
||||||
|
|
@ -29,7 +31,8 @@ public class YglService : IYglService
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
};
|
};
|
||||||
|
|
||||||
public YglService(
|
public YglService(
|
||||||
|
|
@ -161,6 +164,8 @@ public class YglService : IYglService
|
||||||
{
|
{
|
||||||
// 加密请求体
|
// 加密请求体
|
||||||
var bodyJson = JsonSerializer.Serialize(body, JsonOptions);
|
var bodyJson = JsonSerializer.Serialize(body, JsonOptions);
|
||||||
|
_logger.LogInformation("驿公里API请求原文: Path={Path}, Body={Body}", path, bodyJson);
|
||||||
|
|
||||||
var encryptedBody = _crypto.EncryptDes(bodyJson, _desKey);
|
var encryptedBody = _crypto.EncryptDes(bodyJson, _desKey);
|
||||||
|
|
||||||
// 对原文签名
|
// 对原文签名
|
||||||
|
|
@ -177,14 +182,21 @@ public class YglService : IYglService
|
||||||
body = encryptedBody
|
body = encryptedBody
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}{path}", requestData, JsonOptions);
|
var requestJson = JsonSerializer.Serialize(requestData, JsonOptions);
|
||||||
|
_logger.LogInformation("驿公里API加密请求: {Request}", requestJson);
|
||||||
|
|
||||||
|
var content = new StringContent(requestJson, Encoding.UTF8, "application/json");
|
||||||
|
var response = await _httpClient.PostAsync($"{_baseUrl}{path}", content);
|
||||||
var responseJson = await response.Content.ReadAsStringAsync();
|
var responseJson = await response.Content.ReadAsStringAsync();
|
||||||
|
_logger.LogInformation("驿公里API响应: StatusCode={StatusCode}, Body={Body}",
|
||||||
|
response.StatusCode, responseJson);
|
||||||
|
|
||||||
var yglResponse = JsonSerializer.Deserialize<YglApiResponse<T>>(responseJson, JsonOptions);
|
var yglResponse = JsonSerializer.Deserialize<YglApiResponse<T>>(responseJson, JsonOptions);
|
||||||
|
|
||||||
if (yglResponse is null || !yglResponse.Success)
|
if (yglResponse is null || !yglResponse.Success)
|
||||||
{
|
{
|
||||||
_logger.LogError("驿公里API请求失败: Path={Path}, ErrorMsg={ErrorMsg}",
|
_logger.LogError("驿公里API请求失败: Path={Path}, Response={Response}",
|
||||||
path, yglResponse?.ErrorMsg);
|
path, responseJson);
|
||||||
throw new InvalidOperationException($"驿公里API请求失败: {yglResponse?.ErrorMsg}");
|
throw new InvalidOperationException($"驿公里API请求失败: {yglResponse?.ErrorMsg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user