bug修改
This commit is contained in:
parent
01ada8da2b
commit
0d793d3f75
|
|
@ -16,12 +16,15 @@
|
|||
<el-table-column label="抵扣金额" width="90">
|
||||
<template #default="{ row }">{{ row.couponType === 'discount' ? `¥${row.discountAmount}` : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用门店" width="120">
|
||||
<template #default="{ row }">{{ row.store?.name || '-' }}</template>
|
||||
<el-table-column label="适用门店" min-width="150">
|
||||
<template #default="{ row }">{{ row.storeNames || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="100">
|
||||
<template #default="{ row }">{{ row.remainingCount }}/{{ row.totalCount }}</template>
|
||||
</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 label="来源" width="90">
|
||||
<template #default="{ row }">{{ row.source === 'platform' ? '平台券' : '驿公里券' }}</template>
|
||||
|
|
@ -47,7 +50,7 @@
|
|||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<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-select>
|
||||
</el-form-item>
|
||||
|
|
@ -61,7 +64,10 @@
|
|||
<el-input-number v-model="form.discountAmount" :min="0" :precision="2" />
|
||||
</el-form-item>
|
||||
<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 label="总数量">
|
||||
<el-input-number v-model="form.totalCount" :min="1" />
|
||||
|
|
@ -75,7 +81,7 @@
|
|||
<el-option label="驿公里券" value="ygl" />
|
||||
</el-select>
|
||||
</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-form-item>
|
||||
<el-form-item label="状态">
|
||||
|
|
@ -91,7 +97,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getCouponTemplates, createCouponTemplate, updateCouponTemplate, deleteCouponTemplate, getStores } from '../api'
|
||||
|
||||
|
|
@ -104,9 +110,9 @@ const isEdit = ref(false)
|
|||
const editId = ref(null)
|
||||
|
||||
const defaultForm = () => ({
|
||||
name: '', couponType: 'free', discountAmount: 0, storeId: null,
|
||||
name: '', couponType: 'free', discountAmount: 0, storeIds: [],
|
||||
totalCount: 100, pointsCost: 0, source: 'platform',
|
||||
yglTicketPackageCode: '', isActive: true
|
||||
yglTicketPackageCode: '', isActive: true, isPermanent: false
|
||||
})
|
||||
const form = ref(defaultForm())
|
||||
const dateRange = ref([])
|
||||
|
|
@ -120,17 +126,32 @@ const fetchData = async () => {
|
|||
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) => {
|
||||
fetchStores()
|
||||
if (row) {
|
||||
isEdit.value = true
|
||||
editId.value = row.id
|
||||
const permanent = isPermanent(row)
|
||||
form.value = {
|
||||
name: row.name, couponType: row.couponType, discountAmount: row.discountAmount,
|
||||
storeId: row.storeId, totalCount: row.totalCount, pointsCost: row.pointsCost,
|
||||
source: row.source, yglTicketPackageCode: row.yglTicketPackageCode || '', isActive: row.isActive
|
||||
storeIds: row.storeIds || [], totalCount: row.totalCount, pointsCost: row.pointsCost,
|
||||
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 {
|
||||
isEdit.value = false
|
||||
editId.value = null
|
||||
|
|
@ -141,8 +162,24 @@ const openDialog = (row) => {
|
|||
}
|
||||
|
||||
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 }
|
||||
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.validEnd = dateRange.value[1]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,29 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card>
|
||||
<template #header>优惠券发放记录</template>
|
||||
<!-- 筛选栏 -->
|
||||
<el-form :inline="true" :model="filters" style="margin-bottom:16px">
|
||||
<el-form-item label="用户手机号">
|
||||
<el-input v-model="filters.userPhone" placeholder="手机号" clearable @clear="fetchData" />
|
||||
</el-form-item>
|
||||
<el-form-item label="门店">
|
||||
<el-select v-model="filters.storeId" placeholder="全部门店" clearable @change="fetchData">
|
||||
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filters.status" placeholder="全部状态" clearable @change="fetchData">
|
||||
<el-option label="未使用" value="unused" />
|
||||
<el-option label="已使用" value="used" />
|
||||
<el-option label="已过期" value="expired" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<span>优惠券发放记录</span>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<el-input v-model="filters.userPhone" placeholder="用户手机号/UID" clearable style="width:160px" @clear="fetchData" @keyup.enter="fetchData" />
|
||||
<el-select v-model="filters.storeId" placeholder="全部门店" clearable style="width:140px" @change="fetchData">
|
||||
<el-option v-for="s in stores" :key="s.id" :label="s.name" :value="s.id" />
|
||||
</el-select>
|
||||
<el-select v-model="filters.status" placeholder="全部状态" clearable style="width:120px" @change="fetchData">
|
||||
<el-option label="未使用" value="unused" />
|
||||
<el-option label="已使用" value="used" />
|
||||
<el-option label="已过期" value="expired" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="list" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column prop="code" label="券码" width="120" />
|
||||
<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="storeName" label="门店" />
|
||||
<el-table-column label="状态" width="90">
|
||||
|
|
|
|||
|
|
@ -209,6 +209,14 @@ onMounted(fetchData)
|
|||
.store-uploader:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.store-uploader :deep(.el-upload) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { isLoggedIn } from '@/utils/auth'
|
||||
import { isLoggedIn, getRole } from '@/utils/auth'
|
||||
|
||||
// 需要登录才能访问的页面路径
|
||||
const AUTH_PAGES = [
|
||||
|
|
@ -21,6 +21,10 @@
|
|||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
// 已登录的商户自动跳转商户端
|
||||
if (isLoggedIn() && getRole() === 'merchant') {
|
||||
uni.reLaunch({ url: '/pages/merchant/index' })
|
||||
}
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
|
|
|
|||
|
|
@ -34,3 +34,11 @@ export function getRecords() {
|
|||
export function getMerchantRecords() {
|
||||
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-message">{{ message }}</view>
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,42 @@
|
|||
<template>
|
||||
<view class="coupon-card">
|
||||
<view class="coupon-info">
|
||||
<view class="coupon-name">{{ coupon.name }}</view>
|
||||
<view class="coupon-store">适用门店:{{ coupon.storeName }}</view>
|
||||
<view class="coupon-code">券码:{{ coupon.code }}</view>
|
||||
<view class="coupon-expire">有效期:{{ coupon.validStart }} ~ {{ coupon.validEnd }}</view>
|
||||
<!-- 背景图 -->
|
||||
<image class="coupon-card-bg" src="/static/item_bg2.png" mode="scaleToFill" />
|
||||
<!-- 左侧券类型 -->
|
||||
<view class="coupon-left">
|
||||
<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 class="coupon-action">
|
||||
<!-- 平台券:显示二维码按钮 -->
|
||||
<button
|
||||
v-if="coupon.source === 'platform'"
|
||||
class="btn btn-primary"
|
||||
size="mini"
|
||||
@click="$emit('showQrcode', coupon)"
|
||||
>二维码</button>
|
||||
<!-- 驿公里券:显示跳转按钮 -->
|
||||
<view v-else-if="coupon.source === 'ygl'" class="ygl-action">
|
||||
<text class="ygl-tip">需在驿公里使用</text>
|
||||
<button
|
||||
class="btn btn-ygl"
|
||||
size="mini"
|
||||
@click="$emit('gotoYgl', coupon)"
|
||||
>去使用</button>
|
||||
<!-- 右侧信息 -->
|
||||
<view class="coupon-right">
|
||||
<text class="coupon-name">{{ coupon.storeName }}</text>
|
||||
<text class="coupon-info">券码:{{ coupon.code }}</text>
|
||||
<text class="coupon-info">有效期:{{ formatValidity(coupon) }}</text>
|
||||
<!-- 操作按钮 -->
|
||||
<view class="coupon-bottom">
|
||||
<view v-if="coupon.source === 'ygl'" class="ygl-tip-text">
|
||||
<text class="coupon-info">需在驿公里洗车小程序中使用</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="coupon.source === 'platform'"
|
||||
class="action-btn"
|
||||
@click.stop="$emit('showQrcode', coupon)"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -31,66 +46,105 @@
|
|||
export default {
|
||||
name: 'CouponCard',
|
||||
props: {
|
||||
coupon: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
coupon: { 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>
|
||||
|
||||
<style scoped>
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
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 {
|
||||
flex: 1;
|
||||
.coupon-card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
.coupon-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
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 {
|
||||
/* 左侧标签 */
|
||||
.coupon-left {
|
||||
width: 160rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 0 24rpx 20rpx;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.ygl-tip {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
.coupon-price {
|
||||
font-size: 65rpx;
|
||||
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;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name" : "黄岩停车积分",
|
||||
"name" : "黄岩智惠停",
|
||||
"appid" : "__UNI__F1854F8",
|
||||
"description" : "黄岩停车积分兑换微信小程序",
|
||||
"versionName" : "1.0.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<view class="coupons-page">
|
||||
<NavBar title="我的优惠券" />
|
||||
<!-- 状态标签页 -->
|
||||
<!-- 状态标签页 - 胶囊样式 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
|
|
@ -21,7 +21,20 @@
|
|||
</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">
|
||||
|
|
@ -49,37 +62,26 @@
|
|||
import { getStores } from '@/api/store'
|
||||
import CouponCard from '@/components/CouponCard.vue'
|
||||
import QrcodePopup from '@/components/QrcodePopup.vue'
|
||||
import FilterBar from '@/components/FilterBar.vue'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
|
||||
export default {
|
||||
components: { CouponCard, QrcodePopup, FilterBar, NavBar },
|
||||
components: { CouponCard, QrcodePopup, NavBar },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentStatus: 'unused',
|
||||
currentStoreId: '',
|
||||
currentStoreName: '全部',
|
||||
currentType: '',
|
||||
currentTypeName: '全部',
|
||||
coupons: [],
|
||||
qrcodeVisible: false,
|
||||
currentCoupon: {},
|
||||
filters: [
|
||||
{
|
||||
label: '门店',
|
||||
key: 'storeId',
|
||||
options: [{ label: '全部门店', value: '' }],
|
||||
selectedIndex: 0
|
||||
},
|
||||
{
|
||||
label: '类型',
|
||||
key: 'type',
|
||||
options: [
|
||||
{ label: '全部类型', value: '' },
|
||||
{ label: '免费券', value: 'free' },
|
||||
{ label: '抵扣券', value: 'discount' }
|
||||
],
|
||||
selectedIndex: 0
|
||||
}
|
||||
storeOptions: [{ label: '全部', value: '' }],
|
||||
typeOptions: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '免费券', value: 'free' },
|
||||
{ label: '抵扣券', value: 'discount' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -93,8 +95,8 @@
|
|||
try {
|
||||
const res = await getStores()
|
||||
const stores = res.data || res || []
|
||||
this.filters[0].options = [
|
||||
{ label: '全部门店', value: '' },
|
||||
this.storeOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
...stores.map(s => ({ label: s.name, value: s.id }))
|
||||
]
|
||||
} catch (err) {
|
||||
|
|
@ -124,14 +126,21 @@
|
|||
this.loadCoupons()
|
||||
},
|
||||
|
||||
/** 筛选变更 */
|
||||
onFilterChange({ key, value, filterIndex, selectedIndex }) {
|
||||
if (key === 'storeId') {
|
||||
this.currentStoreId = value
|
||||
} else if (key === 'type') {
|
||||
this.currentType = value
|
||||
}
|
||||
this.filters[filterIndex].selectedIndex = selectedIndex
|
||||
/** 门店筛选变更 */
|
||||
onStoreChange(e) {
|
||||
const idx = e.detail.value
|
||||
const opt = this.storeOptions[idx]
|
||||
this.currentStoreId = opt.value
|
||||
this.currentStoreName = opt.label
|
||||
this.loadCoupons()
|
||||
},
|
||||
|
||||
/** 类型筛选变更 */
|
||||
onTypeChange(e) {
|
||||
const idx = e.detail.value
|
||||
const opt = this.typeOptions[idx]
|
||||
this.currentType = opt.value
|
||||
this.currentTypeName = opt.label
|
||||
this.loadCoupons()
|
||||
},
|
||||
|
||||
|
|
@ -146,7 +155,7 @@
|
|||
uni.navigateToMiniProgram({
|
||||
appId: 'wx8c943e2e64e04284',
|
||||
fail: () => {
|
||||
uni.showToast({ title: '跳转驿公里失败', icon: 'none' })
|
||||
uni.showToast({ title: '跳转驿公里洗车小程序失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -156,33 +165,57 @@
|
|||
|
||||
<style scoped>
|
||||
.coupons-page {
|
||||
padding: 20rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
/* 胶囊标签 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
overflow: hidden;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
padding: 12rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
border-radius: 40rpx;
|
||||
border: 2rpx solid #eee;
|
||||
}
|
||||
.tab-item.active {
|
||||
color: #007aff;
|
||||
color: #C88A00;
|
||||
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 {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
padding: 100rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<text class="coupon-info">券码:{{ coupon.code }}</text>
|
||||
<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 v-if="coupon.source === 'platform'" class="action-btn" @click="openQrcode(coupon)">
|
||||
<text class="action-btn-text">二维码</text>
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
appId: 'wx8c943e2e64e04284',
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '跳转驿公里失败',
|
||||
title: '跳转驿公里洗车小程序失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,9 +165,14 @@
|
|||
font-weight: 500;
|
||||
border-radius: 48rpx;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.btn-login::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-login[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
uni.reLaunch({ url: '/pages/merchant/index' })
|
||||
} else {
|
||||
// 用户跳转首页
|
||||
uni.switchTab({ url: '/pages/home/index' })
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@
|
|||
<text class="record-arrow">›</text>
|
||||
</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="code-input-dialog" @click.stop>
|
||||
|
|
@ -73,8 +82,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { scanVerify, codeVerify } from '@/api/verify'
|
||||
import { getUserInfo, setLoginInfo, getToken } from '@/utils/auth'
|
||||
import { scanVerify, codeVerify, previewCoupon } from '@/api/verify'
|
||||
import { getUserInfo, setLoginInfo, getToken, clearAuth } from '@/utils/auth'
|
||||
import { checkMerchant } from '@/api/auth'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
|
|
@ -134,17 +143,13 @@
|
|||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
scanType: ['qrCode'],
|
||||
success: (res) => {
|
||||
success: async (res) => {
|
||||
const code = res.result
|
||||
if (!code) {
|
||||
this.showResult(false, '未识别到有效二维码')
|
||||
return
|
||||
}
|
||||
// 弹出核销确认
|
||||
this.pendingCode = code
|
||||
this.pendingType = 'scan'
|
||||
this.confirmMessage = `确认核销券码:${code}?`
|
||||
this.confirmVisible = true
|
||||
await this.previewAndConfirm(code, 'scan')
|
||||
},
|
||||
fail: (err) => {
|
||||
// 用户拒绝相机权限或取消扫码
|
||||
|
|
@ -162,7 +167,7 @@
|
|||
},
|
||||
|
||||
/** 提交券码 */
|
||||
submitCode() {
|
||||
async submitCode() {
|
||||
const code = this.inputCode.trim()
|
||||
if (!code) {
|
||||
uni.showToast({ title: '请输入券码', icon: 'none' })
|
||||
|
|
@ -173,10 +178,27 @@
|
|||
return
|
||||
}
|
||||
this.showCodeInput = false
|
||||
this.pendingCode = code
|
||||
this.pendingType = 'code'
|
||||
this.confirmMessage = `确认核销券码:${code}?`
|
||||
this.confirmVisible = true
|
||||
await this.previewAndConfirm(code, 'code')
|
||||
},
|
||||
|
||||
/** 查询券码信息并弹出确认 */
|
||||
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() {
|
||||
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;
|
||||
color: #ccc;
|
||||
}
|
||||
.logout-entry {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.logout-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
/* 券码输入弹窗 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -48,11 +48,18 @@
|
|||
<text class="menu-text">隐私政策</text>
|
||||
<text class="arrow-right">›</text>
|
||||
</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>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<button v-if="isLogin" class="btn-logout" @click="showLogoutConfirm">退出登录</button>
|
||||
|
||||
<!-- 退出确认弹窗 -->
|
||||
<ConfirmDialog
|
||||
:visible="logoutVisible"
|
||||
|
|
@ -132,6 +139,11 @@
|
|||
uni.navigateTo({ url: `/pages/mine/agreement?type=${type}` })
|
||||
},
|
||||
|
||||
/** 切换身份 */
|
||||
goRoleSelect() {
|
||||
uni.navigateTo({ url: '/pages/login/role-select' })
|
||||
},
|
||||
|
||||
/** 显示退出确认弹窗 */
|
||||
showLogoutConfirm() {
|
||||
this.logoutVisible = true
|
||||
|
|
@ -250,13 +262,6 @@
|
|||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
.btn-logout {
|
||||
background: #fff;
|
||||
color: #ff3b30;
|
||||
font-size: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.contact-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@
|
|||
<view class="coupon-right">
|
||||
<text class="coupon-name">{{ item.name }}</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" v-if="item.source === 'ygl'">使用方式:请在驿公里App中使用</text>
|
||||
<text class="coupon-info" v-if="item.source === 'ygl'">使用方式:请在驿公里洗车小程序中使用</text>
|
||||
<view class="coupon-bottom">
|
||||
<view class="exchange-btn" @click="onExchange(item)"
|
||||
:class="{ disabled: item.remainingCount <= 0 }">
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
@cancel="confirmVisible = false" />
|
||||
|
||||
<!-- 驿公里券提示弹窗 -->
|
||||
<ConfirmDialog :visible="yglTipVisible" title="温馨提示" message="该优惠券为驿公里券,兑换后需在驿公里APP/小程序中使用,确定兑换吗?"
|
||||
<ConfirmDialog :visible="yglTipVisible" title="温馨提示" message="该优惠券为驿公里券,兑换后需在驿公里洗车小程序中使用,确定兑换吗?"
|
||||
@confirm="doExchangeYgl" @cancel="yglTipVisible = false" />
|
||||
</view>
|
||||
</template>
|
||||
|
|
@ -119,13 +119,20 @@
|
|||
confirmMessage: '',
|
||||
yglTipVisible: false,
|
||||
currentItem: null,
|
||||
selectedStoreId: null,
|
||||
pointsBgUrl: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredList() {
|
||||
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() {
|
||||
|
|
@ -136,6 +143,10 @@
|
|||
if (!d) return ''
|
||||
return d.substring(0, 10)
|
||||
},
|
||||
isPermanent(item) {
|
||||
if (!item.validEnd) return false
|
||||
return new Date(item.validEnd).getFullYear() >= 2099
|
||||
},
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
|
|
@ -197,6 +208,31 @@
|
|||
return
|
||||
}
|
||||
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') {
|
||||
this.yglTipVisible = true
|
||||
} else {
|
||||
|
|
@ -213,10 +249,11 @@
|
|||
await this.executeExchange()
|
||||
},
|
||||
async executeExchange() {
|
||||
if (!this.currentItem) return
|
||||
if (!this.currentItem || !this.selectedStoreId) return
|
||||
try {
|
||||
await exchange({
|
||||
templateId: this.currentItem.id
|
||||
templateId: this.currentItem.id,
|
||||
storeId: this.selectedStoreId
|
||||
})
|
||||
uni.showToast({
|
||||
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 基础地址,按环境配置
|
||||
// const BASE_URL = 'http://localhost:5030'
|
||||
const BASE_URL = 'https://api.pli.shhmkjgs.cn'
|
||||
const BASE_URL = 'http://localhost:5030'
|
||||
// const BASE_URL = 'https://api.pli.shhmkjgs.cn'
|
||||
|
||||
// 请求超时时间(毫秒)
|
||||
const TIMEOUT = 10000
|
||||
|
|
|
|||
|
|
@ -176,20 +176,12 @@ public class AdminController : ControllerBase
|
|||
store.Photo = request.Photo;
|
||||
store.Address = request.Address;
|
||||
store.Phone = request.Phone;
|
||||
store.Latitude = request.Latitude;
|
||||
store.Longitude = request.Longitude;
|
||||
store.Type = request.Type;
|
||||
store.IsActive = request.IsActive;
|
||||
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();
|
||||
return Ok(ApiResponse<Store>.Ok(store));
|
||||
}
|
||||
|
|
@ -292,13 +284,25 @@ public class AdminController : ControllerBase
|
|||
|
||||
/// <summary>获取优惠券模板列表</summary>
|
||||
[HttpGet("coupon-templates")]
|
||||
public async Task<ActionResult<ApiResponse<List<CouponTemplate>>>> GetCouponTemplates()
|
||||
public async Task<ActionResult<ApiResponse<List<object>>>> GetCouponTemplates()
|
||||
{
|
||||
var templates = await _db.CouponTemplates
|
||||
.Include(t => t.Store)
|
||||
.Include(t => t.TemplateStores).ThenInclude(ts => ts.Store)
|
||||
.OrderByDescending(t => t.CreatedAt)
|
||||
.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>
|
||||
|
|
@ -310,11 +314,10 @@ public class AdminController : ControllerBase
|
|||
Name = request.Name,
|
||||
CouponType = request.CouponType,
|
||||
DiscountAmount = request.DiscountAmount,
|
||||
StoreId = request.StoreId,
|
||||
ValidStart = request.ValidStart,
|
||||
ValidEnd = request.ValidEnd,
|
||||
TotalCount = request.TotalCount,
|
||||
RemainingCount = request.TotalCount, // 初始剩余=总数
|
||||
RemainingCount = request.TotalCount,
|
||||
PointsCost = request.PointsCost,
|
||||
Source = request.Source,
|
||||
YglTicketPackageCode = request.YglTicketPackageCode,
|
||||
|
|
@ -322,6 +325,18 @@ public class AdminController : ControllerBase
|
|||
};
|
||||
_db.CouponTemplates.Add(template);
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +344,9 @@ public class AdminController : ControllerBase
|
|||
[HttpPut("coupon-templates/{id}")]
|
||||
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)
|
||||
return NotFound(ApiResponse<CouponTemplate>.Fail("优惠券模板不存在"));
|
||||
|
||||
|
|
@ -338,7 +355,6 @@ public class AdminController : ControllerBase
|
|||
template.Name = request.Name;
|
||||
template.CouponType = request.CouponType;
|
||||
template.DiscountAmount = request.DiscountAmount;
|
||||
template.StoreId = request.StoreId;
|
||||
template.ValidStart = request.ValidStart;
|
||||
template.ValidEnd = request.ValidEnd;
|
||||
template.TotalCount = request.TotalCount;
|
||||
|
|
@ -348,6 +364,18 @@ public class AdminController : ControllerBase
|
|||
template.YglTicketPackageCode = request.YglTicketPackageCode;
|
||||
template.IsActive = request.IsActive;
|
||||
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();
|
||||
return Ok(ApiResponse<CouponTemplate>.Ok(template));
|
||||
}
|
||||
|
|
@ -462,7 +490,7 @@ public class AdminController : ControllerBase
|
|||
.AsQueryable();
|
||||
|
||||
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)
|
||||
query = query.Where(c => c.StoreId == storeId.Value);
|
||||
if (!string.IsNullOrEmpty(status))
|
||||
|
|
@ -475,6 +503,7 @@ public class AdminController : ControllerBase
|
|||
Id = c.Id,
|
||||
Code = c.Code,
|
||||
TemplateName = c.Template.Name,
|
||||
UserUid = c.User.Uid,
|
||||
UserPhone = c.User.Phone,
|
||||
StoreName = c.Store.Name,
|
||||
Status = c.Status,
|
||||
|
|
@ -556,7 +585,22 @@ public class AdminController : ControllerBase
|
|||
|
||||
if (request.Nickname != null) user.Nickname = request.Nickname;
|
||||
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;
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,11 @@ public class CouponController : ControllerBase
|
|||
Name = t.Name,
|
||||
CouponType = t.CouponType,
|
||||
DiscountAmount = t.DiscountAmount,
|
||||
StoreName = t.Store.Name,
|
||||
StoreId = t.StoreId,
|
||||
Stores = t.TemplateStores.Select(ts => new CouponStoreInfo
|
||||
{
|
||||
Id = ts.Store.Id,
|
||||
Name = ts.Store.Name
|
||||
}).ToList(),
|
||||
ValidStart = t.ValidStart,
|
||||
ValidEnd = t.ValidEnd,
|
||||
RemainingCount = t.RemainingCount,
|
||||
|
|
@ -58,7 +61,7 @@ public class CouponController : ControllerBase
|
|||
|
||||
try
|
||||
{
|
||||
var coupon = await _couponService.ExchangeAsync(userId, request.TemplateId);
|
||||
var coupon = await _couponService.ExchangeAsync(userId, request.TemplateId, request.StoreId);
|
||||
var response = new UserCouponResponse
|
||||
{
|
||||
Id = coupon.Id,
|
||||
|
|
@ -70,6 +73,8 @@ public class CouponController : ControllerBase
|
|||
StoreId = coupon.StoreId,
|
||||
Status = coupon.Status,
|
||||
Source = coupon.Source,
|
||||
ValidStart = coupon.Template?.ValidStart,
|
||||
ValidEnd = coupon.Template?.ValidEnd,
|
||||
ExchangedAt = coupon.ExchangedAt,
|
||||
UsedAt = coupon.UsedAt,
|
||||
ExpiredAt = coupon.ExpiredAt
|
||||
|
|
@ -107,6 +112,8 @@ public class CouponController : ControllerBase
|
|||
StoreId = c.StoreId,
|
||||
Status = c.Status,
|
||||
Source = c.Source,
|
||||
ValidStart = c.Template?.ValidStart,
|
||||
ValidEnd = c.Template?.ValidEnd,
|
||||
ExchangedAt = c.ExchangedAt,
|
||||
UsedAt = c.UsedAt,
|
||||
ExpiredAt = c.ExpiredAt
|
||||
|
|
@ -136,6 +143,8 @@ public class CouponController : ControllerBase
|
|||
StoreId = c.StoreId,
|
||||
Status = c.Status,
|
||||
Source = c.Source,
|
||||
ValidStart = c.Template?.ValidStart,
|
||||
ValidEnd = c.Template?.ValidEnd,
|
||||
ExchangedAt = c.ExchangedAt,
|
||||
UsedAt = c.UsedAt,
|
||||
ExpiredAt = c.ExpiredAt
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System.Security.Claims;
|
||||
using HuangyanParking.Api.Models;
|
||||
using HuangyanParking.Domain.Services;
|
||||
using HuangyanParking.Infrastructure.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HuangyanParking.Api.Controllers;
|
||||
|
||||
|
|
@ -15,10 +17,41 @@ namespace HuangyanParking.Api.Controllers;
|
|||
public class VerifyController : ControllerBase
|
||||
{
|
||||
private readonly IVerifyService _verifyService;
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public VerifyController(IVerifyService verifyService)
|
||||
public VerifyController(IVerifyService verifyService, AppDbContext db)
|
||||
{
|
||||
_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>
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ public class AdminCouponTemplateRequest
|
|||
/// <summary>抵扣金额(抵扣券)</summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>适用门店ID</summary>
|
||||
public int StoreId { get; set; }
|
||||
/// <summary>适用门店ID列表(多选)</summary>
|
||||
public List<int> StoreIds { get; set; } = [];
|
||||
|
||||
/// <summary>有效期开始</summary>
|
||||
public DateTime ValidStart { get; set; }
|
||||
|
|
@ -179,6 +179,7 @@ public class AdminCouponResponse
|
|||
public int Id { get; set; }
|
||||
public string Code { 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 StoreName { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ public class ExchangeCouponRequest
|
|||
{
|
||||
/// <summary>优惠券模板ID</summary>
|
||||
public int TemplateId { get; set; }
|
||||
|
||||
/// <summary>选择的门店ID</summary>
|
||||
public int StoreId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -18,8 +21,8 @@ public class AvailableCouponResponse
|
|||
public string Name { get; set; } = string.Empty;
|
||||
public string CouponType { get; set; } = string.Empty;
|
||||
public decimal DiscountAmount { get; set; }
|
||||
public string StoreName { get; set; } = string.Empty;
|
||||
public int StoreId { get; set; }
|
||||
/// <summary>适用门店列表</summary>
|
||||
public List<CouponStoreInfo> Stores { get; set; } = [];
|
||||
public DateTime ValidStart { get; set; }
|
||||
public DateTime ValidEnd { get; set; }
|
||||
public int RemainingCount { get; set; }
|
||||
|
|
@ -27,6 +30,15 @@ public class AvailableCouponResponse
|
|||
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>
|
||||
|
|
@ -41,6 +53,8 @@ public class UserCouponResponse
|
|||
public int StoreId { get; set; }
|
||||
public string Status { 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? UsedAt { get; set; }
|
||||
public DateTime ExpiredAt { get; set; }
|
||||
|
|
|
|||
|
|
@ -32,3 +32,16 @@ public class VerifyRecordResponse
|
|||
public string? UserPhone { 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>();
|
||||
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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ public class CouponTemplate
|
|||
/// <summary>抵扣金额(抵扣券)</summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>适用门店</summary>
|
||||
public int StoreId { get; set; }
|
||||
|
||||
/// <summary>有效期开始</summary>
|
||||
public DateTime ValidStart { get; set; }
|
||||
|
||||
|
|
@ -46,7 +43,20 @@ public class CouponTemplate
|
|||
public DateTime CreatedAt { 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; } = [];
|
||||
}
|
||||
|
||||
/// <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 ICollection<CouponTemplate> CouponTemplates { get; set; } = [];
|
||||
public ICollection<CouponTemplateStore> TemplateStores { get; set; } = [];
|
||||
public ICollection<Coupon> Coupons { get; set; } = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public interface ICouponService
|
|||
Task<List<CouponTemplate>> GetAvailableTemplatesAsync();
|
||||
|
||||
/// <summary>兑换优惠券(积分校验+扣减+券码生成+驿公里API调用)</summary>
|
||||
Task<Coupon> ExchangeAsync(int userId, int templateId);
|
||||
Task<Coupon> ExchangeAsync(int userId, int templateId, int storeId);
|
||||
|
||||
/// <summary>获取用户优惠券列表(支持状态/门店/类型筛选)</summary>
|
||||
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<Store> Stores => Set<Store>();
|
||||
public DbSet<CouponTemplate> CouponTemplates => Set<CouponTemplate>();
|
||||
public DbSet<CouponTemplateStore> CouponTemplateStores => Set<CouponTemplateStore>();
|
||||
public DbSet<Coupon> Coupons => Set<Coupon>();
|
||||
public DbSet<PointsRecord> PointsRecords => Set<PointsRecord>();
|
||||
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.Source).HasMaxLength(20).IsRequired();
|
||||
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 配置
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class CouponService : ICouponService
|
|||
{
|
||||
var now = DateTime.UtcNow;
|
||||
return await _db.CouponTemplates
|
||||
.Include(t => t.Store)
|
||||
.Include(t => t.TemplateStores).ThenInclude(ts => ts.Store)
|
||||
.Where(t => t.IsActive
|
||||
&& t.RemainingCount > 0
|
||||
&& t.ValidStart <= now
|
||||
|
|
@ -44,7 +44,7 @@ public class CouponService : ICouponService
|
|||
}
|
||||
|
||||
/// <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();
|
||||
|
|
@ -52,7 +52,7 @@ public class CouponService : ICouponService
|
|||
try
|
||||
{
|
||||
var template = await _db.CouponTemplates
|
||||
.Include(t => t.Store)
|
||||
.Include(t => t.TemplateStores)
|
||||
.FirstOrDefaultAsync(t => t.Id == templateId)
|
||||
?? throw new InvalidOperationException("优惠券模板不存在");
|
||||
|
||||
|
|
@ -66,6 +66,10 @@ public class CouponService : ICouponService
|
|||
if (now < template.ValidStart || now > template.ValidEnd)
|
||||
throw new InvalidOperationException("优惠券不在有效期内");
|
||||
|
||||
// 校验门店是否属于该模板
|
||||
if (!template.TemplateStores.Any(ts => ts.StoreId == storeId))
|
||||
throw new InvalidOperationException("所选门店不适用于该优惠券");
|
||||
|
||||
// 校验积分
|
||||
var user = await _db.Users.FindAsync(userId)
|
||||
?? throw new InvalidOperationException("用户不存在");
|
||||
|
|
@ -83,7 +87,7 @@ public class CouponService : ICouponService
|
|||
Code = code,
|
||||
TemplateId = templateId,
|
||||
UserId = userId,
|
||||
StoreId = template.StoreId,
|
||||
StoreId = storeId,
|
||||
Source = template.Source,
|
||||
ExchangedAt = now,
|
||||
ExpiredAt = template.ValidEnd,
|
||||
|
|
@ -120,7 +124,7 @@ public class CouponService : ICouponService
|
|||
userId, template.PointsCost,
|
||||
$"兑换优惠券:{template.Name}",
|
||||
couponId: coupon.Id,
|
||||
storeId: template.StoreId);
|
||||
storeId: storeId);
|
||||
}
|
||||
await transaction.CommitAsync();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using HuangyanParking.Domain.Entities;
|
||||
|
|
@ -29,7 +31,8 @@ public class YglService : IYglService
|
|||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
public YglService(
|
||||
|
|
@ -161,6 +164,8 @@ public class YglService : IYglService
|
|||
{
|
||||
// 加密请求体
|
||||
var bodyJson = JsonSerializer.Serialize(body, JsonOptions);
|
||||
_logger.LogInformation("驿公里API请求原文: Path={Path}, Body={Body}", path, bodyJson);
|
||||
|
||||
var encryptedBody = _crypto.EncryptDes(bodyJson, _desKey);
|
||||
|
||||
// 对原文签名
|
||||
|
|
@ -177,14 +182,21 @@ public class YglService : IYglService
|
|||
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();
|
||||
_logger.LogInformation("驿公里API响应: StatusCode={StatusCode}, Body={Body}",
|
||||
response.StatusCode, responseJson);
|
||||
|
||||
var yglResponse = JsonSerializer.Deserialize<YglApiResponse<T>>(responseJson, JsonOptions);
|
||||
|
||||
if (yglResponse is null || !yglResponse.Success)
|
||||
{
|
||||
_logger.LogError("驿公里API请求失败: Path={Path}, ErrorMsg={ErrorMsg}",
|
||||
path, yglResponse?.ErrorMsg);
|
||||
_logger.LogError("驿公里API请求失败: Path={Path}, Response={Response}",
|
||||
path, responseJson);
|
||||
throw new InvalidOperationException($"驿公里API请求失败: {yglResponse?.ErrorMsg}");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user