feat: 迭代
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001",
|
||||
"china-division": "^2.7.0",
|
||||
"pinia": "^2.2.0",
|
||||
"uview-plus": "^3.8.18",
|
||||
"vue": "^3.5.32",
|
||||
|
||||
@@ -29,11 +29,11 @@ export function refreshToken(refreshToken: string) {
|
||||
}
|
||||
|
||||
export function getUserProfile() {
|
||||
return get('/api/app/profile/profile');
|
||||
return get('/api/app/profile');
|
||||
}
|
||||
|
||||
export function updateUserProfile(data: { nickname?: string; avatar?: string }) {
|
||||
return post('/api/app/profile/profile', data);
|
||||
return post('/api/app/profile', data);
|
||||
}
|
||||
|
||||
export function uploadAvatar(filePath: string) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="region-selector" @tap="openSelector">
|
||||
<text v-if="displayText" class="region-text">{{ displayText }}</text>
|
||||
<text v-else class="region-placeholder">{{ placeholder }}</text>
|
||||
<text class="region-arrow">▾</text>
|
||||
<u-icon name="arrow-down" :size="20" color="#999" />
|
||||
</view>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
@@ -12,7 +12,9 @@
|
||||
<view :class="['popup-panel', { show: visible }]">
|
||||
<view class="panel-header">
|
||||
<text class="panel-title">选择地区</text>
|
||||
<text class="panel-close" @tap="closeSelector">✕</text>
|
||||
<view class="panel-close" @tap="closeSelector">
|
||||
<u-icon name="close" :size="32" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="panel-tabs">
|
||||
<view
|
||||
@@ -25,7 +27,7 @@
|
||||
<view v-if="activeTab === index" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view class="panel-content" scroll-y :scroll-top="0">
|
||||
<scroll-view class="panel-content" scroll-y>
|
||||
<view class="list-wrapper">
|
||||
<view
|
||||
v-for="item in currentList"
|
||||
@@ -34,7 +36,7 @@
|
||||
@tap="selectItem(item)"
|
||||
>
|
||||
<text class="item-name">{{ item.name }}</text>
|
||||
<text v-if="isItemSelected(item)" class="item-check">✓</text>
|
||||
<u-icon v-if="isItemSelected(item)" name="checkmark" :size="28" color="#667eea" />
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -43,6 +45,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { regionData } from '@/data/region';
|
||||
|
||||
interface RegionItem {
|
||||
code: string;
|
||||
@@ -76,9 +79,9 @@ const selectedCity = ref<RegionItem | null>(null);
|
||||
const selectedDistrict = ref<RegionItem | null>(null);
|
||||
|
||||
const tabs = computed(() => [
|
||||
{ label: selectedProvince.value?.name || '请选择' },
|
||||
{ label: selectedCity.value?.name || '请选择' },
|
||||
{ label: selectedDistrict.value?.name || '请选择' },
|
||||
{ label: selectedProvince.value?.name || '省' },
|
||||
{ label: selectedCity.value?.name || '市' },
|
||||
{ label: selectedDistrict.value?.name || '区' },
|
||||
]);
|
||||
|
||||
const displayText = computed(() => {
|
||||
@@ -89,137 +92,15 @@ const displayText = computed(() => {
|
||||
return parts.join(' ');
|
||||
});
|
||||
|
||||
const provinceList = ref<RegionItem[]>([
|
||||
{ code: '11', name: '北京市' },
|
||||
{ code: '12', name: '天津市' },
|
||||
{ code: '13', name: '河北省' },
|
||||
{ code: '14', name: '山西省' },
|
||||
{ code: '15', name: '内蒙古自治区' },
|
||||
{ code: '21', name: '辽宁省' },
|
||||
{ code: '22', name: '吉林省' },
|
||||
{ code: '23', name: '黑龙江省' },
|
||||
{ code: '31', name: '上海市' },
|
||||
{ code: '32', name: '江苏省' },
|
||||
{ code: '33', name: '浙江省' },
|
||||
{ code: '34', name: '安徽省' },
|
||||
{ code: '35', name: '福建省' },
|
||||
{ code: '36', name: '江西省' },
|
||||
{ code: '37', name: '山东省' },
|
||||
{ code: '41', name: '河南省' },
|
||||
{ code: '42', name: '湖北省' },
|
||||
{ code: '43', name: '湖南省' },
|
||||
{ code: '44', name: '广东省' },
|
||||
{ code: '45', name: '广西壮族自治区' },
|
||||
{ code: '46', name: '海南省' },
|
||||
{ code: '50', name: '重庆市' },
|
||||
{ code: '51', name: '四川省' },
|
||||
{ code: '52', name: '贵州省' },
|
||||
{ code: '53', name: '云南省' },
|
||||
{ code: '54', name: '西藏自治区' },
|
||||
{ code: '61', name: '陕西省' },
|
||||
{ code: '62', name: '甘肃省' },
|
||||
{ code: '63', name: '青海省' },
|
||||
{ code: '64', name: '宁夏回族自治区' },
|
||||
{ code: '65', name: '新疆维吾尔自治区' },
|
||||
{ code: '71', name: '台湾省' },
|
||||
{ code: '81', name: '香港特别行政区' },
|
||||
{ code: '82', name: '澳门特别行政区' },
|
||||
]);
|
||||
|
||||
const cityData: Record<string, RegionItem[]> = {
|
||||
'11': [{ code: '1101', name: '北京市' }],
|
||||
'12': [{ code: '1201', name: '天津市' }],
|
||||
'31': [{ code: '3101', name: '上海市' }],
|
||||
'50': [{ code: '5001', name: '重庆市' }],
|
||||
'32': [
|
||||
{ code: '3201', name: '南京市' },
|
||||
{ code: '3202', name: '无锡市' },
|
||||
{ code: '3203', name: '徐州市' },
|
||||
{ code: '3204', name: '常州市' },
|
||||
{ code: '3205', name: '苏州市' },
|
||||
{ code: '3206', name: '南通市' },
|
||||
{ code: '3207', name: '连云港市' },
|
||||
],
|
||||
'33': [
|
||||
{ code: '3301', name: '杭州市' },
|
||||
{ code: '3302', name: '宁波市' },
|
||||
{ code: '3303', name: '温州市' },
|
||||
{ code: '3304', name: '嘉兴市' },
|
||||
{ code: '3305', name: '湖州市' },
|
||||
{ code: '3306', name: '绍兴市' },
|
||||
{ code: '3307', name: '金华市' },
|
||||
],
|
||||
'44': [
|
||||
{ code: '4401', name: '广州市' },
|
||||
{ code: '4402', name: '韶关市' },
|
||||
{ code: '4403', name: '深圳市' },
|
||||
{ code: '4404', name: '珠海市' },
|
||||
{ code: '4405', name: '汕头市' },
|
||||
{ code: '4406', name: '佛山市' },
|
||||
{ code: '4407', name: '江门市' },
|
||||
],
|
||||
};
|
||||
|
||||
const districtData: Record<string, RegionItem[]> = {
|
||||
'1101': [
|
||||
{ code: '110101', name: '东城区' },
|
||||
{ code: '110102', name: '西城区' },
|
||||
{ code: '110105', name: '朝阳区' },
|
||||
{ code: '110106', name: '丰台区' },
|
||||
{ code: '110107', name: '石景山区' },
|
||||
{ code: '110108', name: '海淀区' },
|
||||
],
|
||||
'3101': [
|
||||
{ code: '310101', name: '黄浦区' },
|
||||
{ code: '310104', name: '徐汇区' },
|
||||
{ code: '310105', name: '长宁区' },
|
||||
{ code: '310106', name: '静安区' },
|
||||
{ code: '310107', name: '普陀区' },
|
||||
{ code: '310112', name: '闵行区' },
|
||||
],
|
||||
'3201': [
|
||||
{ code: '320102', name: '玄武区' },
|
||||
{ code: '320104', name: '秦淮区' },
|
||||
{ code: '320105', name: '建邺区' },
|
||||
{ code: '320106', name: '鼓楼区' },
|
||||
{ code: '320111', name: '浦口区' },
|
||||
],
|
||||
'3301': [
|
||||
{ code: '330102', name: '上城区' },
|
||||
{ code: '330103', name: '下城区' },
|
||||
{ code: '330104', name: '江干区' },
|
||||
{ code: '330105', name: '拱墅区' },
|
||||
{ code: '330106', name: '西湖区' },
|
||||
],
|
||||
'4401': [
|
||||
{ code: '440103', name: '荔湾区' },
|
||||
{ code: '440104', name: '越秀区' },
|
||||
{ code: '440105', name: '海珠区' },
|
||||
{ code: '440106', name: '天河区' },
|
||||
{ code: '440111', name: '白云区' },
|
||||
],
|
||||
'4403': [
|
||||
{ code: '440303', name: '罗湖区' },
|
||||
{ code: '440304', name: '福田区' },
|
||||
{ code: '440305', name: '南山区' },
|
||||
{ code: '440306', name: '宝安区' },
|
||||
{ code: '440307', name: '龙岗区' },
|
||||
],
|
||||
};
|
||||
|
||||
const currentList = computed(() => {
|
||||
if (activeTab.value === 0) {
|
||||
return provinceList.value;
|
||||
return regionData.provinces;
|
||||
} else if (activeTab.value === 1) {
|
||||
if (!selectedProvince.value) return [];
|
||||
return cityData[selectedProvince.value.code] || [
|
||||
{ code: selectedProvince.value.code + '01', name: selectedProvince.value.name.replace(/省$|自治区$|特别行政区$|壮族|维吾尔|回族/g, '') + '市辖区' },
|
||||
];
|
||||
return regionData.cities[selectedProvince.value.code] || [];
|
||||
} else {
|
||||
if (!selectedCity.value) return [];
|
||||
return districtData[selectedCity.value.code] || [
|
||||
{ code: selectedCity.value.code + '01', name: '市辖区' },
|
||||
];
|
||||
return regionData.districts[selectedCity.value.code] || [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -277,16 +158,16 @@ watch(
|
||||
() => [props.province, props.city, props.district],
|
||||
() => {
|
||||
if (props.province) {
|
||||
const province = provinceList.value.find((p) => p.name === props.province);
|
||||
const province = regionData.provinces.find((p) => p.name === props.province);
|
||||
if (province) {
|
||||
selectedProvince.value = province;
|
||||
if (props.city) {
|
||||
const cities = cityData[province.code] || [];
|
||||
const cities = regionData.cities[province.code] || [];
|
||||
const city = cities.find((c) => c.name === props.city);
|
||||
if (city) {
|
||||
selectedCity.value = city;
|
||||
if (props.district) {
|
||||
const districts = districtData[city.code] || [];
|
||||
const districts = regionData.districts[city.code] || [];
|
||||
const district = districts.find((d) => d.name === props.district);
|
||||
if (district) {
|
||||
selectedDistrict.value = district;
|
||||
@@ -305,10 +186,12 @@ watch(
|
||||
.region-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.region-text {
|
||||
@@ -323,12 +206,6 @@ watch(
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.region-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 遮罩 */
|
||||
.mask {
|
||||
position: fixed;
|
||||
@@ -374,8 +251,9 @@ watch(
|
||||
}
|
||||
|
||||
.panel-close {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
@@ -396,7 +274,7 @@ watch(
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #FF6B35;
|
||||
color: #667eea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -407,7 +285,7 @@ watch(
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background: #FF6B35;
|
||||
background: #667eea;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
@@ -427,7 +305,7 @@ watch(
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&.selected {
|
||||
background: #fff8f5;
|
||||
background: rgba(102, 126, 234, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,12 +315,7 @@ watch(
|
||||
}
|
||||
|
||||
.list-item.selected .item-name {
|
||||
color: #FF6B35;
|
||||
color: #667eea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item-check {
|
||||
font-size: 28rpx;
|
||||
color: #FF6B35;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 省市区数据
|
||||
import provinces from 'china-division/dist/provinces.json';
|
||||
import cities from 'china-division/dist/cities.json';
|
||||
import areas from 'china-division/dist/areas.json';
|
||||
|
||||
interface RegionItem {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// 转换省份数据
|
||||
const provinceList: RegionItem[] = provinces.map((p: any) => ({
|
||||
code: p.code,
|
||||
name: p.name,
|
||||
}));
|
||||
|
||||
// 转换城市数据 - 按省份code分组
|
||||
const cityData: Record<string, RegionItem[]> = {};
|
||||
cities.forEach((c: any) => {
|
||||
const provinceCode = c.provinceCode;
|
||||
if (!cityData[provinceCode]) {
|
||||
cityData[provinceCode] = [];
|
||||
}
|
||||
cityData[provinceCode].push({
|
||||
code: c.code,
|
||||
name: c.name,
|
||||
});
|
||||
});
|
||||
|
||||
// 转换区县数据 - 按城市code分组
|
||||
const districtData: Record<string, RegionItem[]> = {};
|
||||
areas.forEach((a: any) => {
|
||||
const cityCode = a.cityCode;
|
||||
if (!districtData[cityCode]) {
|
||||
districtData[cityCode] = [];
|
||||
}
|
||||
districtData[cityCode].push({
|
||||
code: a.code,
|
||||
name: a.name,
|
||||
});
|
||||
});
|
||||
|
||||
export const regionData = {
|
||||
provinces: provinceList,
|
||||
cities: cityData,
|
||||
districts: districtData,
|
||||
};
|
||||
@@ -2,7 +2,9 @@
|
||||
<view class="guest-page">
|
||||
<view v-if="loading" class="loading">加载中...</view>
|
||||
<view v-else-if="list.length === 0" class="empty">
|
||||
<view class="empty-icon">👤</view>
|
||||
<view class="empty-icon">
|
||||
<u-icon name="account" :size="120" color="#ccc" />
|
||||
</view>
|
||||
<view class="empty-text">暂无常住人</view>
|
||||
<view class="empty-tip">添加常住人,下次预订更便捷</view>
|
||||
</view>
|
||||
@@ -33,7 +35,7 @@
|
||||
</view>
|
||||
|
||||
<view class="add-btn" @tap="handleAdd">
|
||||
<text class="add-icon">+</text>
|
||||
<u-icon name="plus" :size="20" color="#fff" />
|
||||
<text class="add-text">添加常住人</text>
|
||||
</view>
|
||||
|
||||
@@ -42,7 +44,9 @@
|
||||
<view class="form-popup">
|
||||
<view class="form-header">
|
||||
<text class="form-title">{{ formData.id ? '编辑常住人' : '添加常住人' }}</text>
|
||||
<text class="form-close" @tap="closeForm">✕</text>
|
||||
<view class="form-close" @tap="closeForm">
|
||||
<u-icon name="close" :size="40" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-body">
|
||||
@@ -100,7 +104,7 @@
|
||||
<view class="form-item">
|
||||
<view class="form-checkbox" @tap="formData.isDefault = !formData.isDefault">
|
||||
<view class="checkbox" :class="{ checked: formData.isDefault }">
|
||||
<text v-if="formData.isDefault" class="checkbox-icon">✓</text>
|
||||
<u-icon v-if="formData.isDefault" name="checkbox-mark" :size="24" color="#fff" />
|
||||
</view>
|
||||
<text class="checkbox-label">设为默认常住人</text>
|
||||
</view>
|
||||
@@ -272,8 +276,9 @@ const handleDelete = (item: any) => {
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 24rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
@@ -369,9 +374,6 @@ const handleDelete = (item: any) => {
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.form-popup {
|
||||
background: #fff;
|
||||
@@ -396,8 +398,9 @@ const handleDelete = (item: any) => {
|
||||
}
|
||||
|
||||
.form-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-body {
|
||||
@@ -473,11 +476,6 @@ const handleDelete = (item: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: 28rpx;
|
||||
|
||||
@@ -2,58 +2,118 @@
|
||||
<view class="page-merchant">
|
||||
<!-- 未登录商家账号 -->
|
||||
<view v-if="!sellerStore.isSellerLoggedIn()" class="empty-state">
|
||||
<view class="empty-icon">
|
||||
<u-icon name="shop" :size="120" color="#667eea" />
|
||||
<view class="empty-bg">
|
||||
<view class="bg-circle circle-1"></view>
|
||||
<view class="bg-circle circle-2"></view>
|
||||
<view class="bg-circle circle-3"></view>
|
||||
</view>
|
||||
|
||||
<view class="empty-content">
|
||||
<text class="empty-title">开启您的事业</text>
|
||||
<text class="empty-desc">注册成为商家,轻松管理房源和订单</text>
|
||||
|
||||
<view class="feature-list">
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon">
|
||||
<u-icon name="checkmark" :size="20" color="#52c41a" />
|
||||
</view>
|
||||
<text class="feature-text">零门槛入驻,快速开店</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon">
|
||||
<u-icon name="checkmark" :size="20" color="#52c41a" />
|
||||
</view>
|
||||
<text class="feature-text">智能管理,高效运营</text>
|
||||
</view>
|
||||
<view class="feature-item">
|
||||
<view class="feature-icon">
|
||||
<u-icon name="checkmark" :size="20" color="#52c41a" />
|
||||
</view>
|
||||
<text class="feature-text">平台流量,订单无忧</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="primary-btn" @tap="goSellerRegister">
|
||||
<text class="btn-text">立即注册/登录</text>
|
||||
<u-icon name="arrow-right" :size="20" color="#fff" />
|
||||
</button>
|
||||
</view>
|
||||
<text class="empty-title">欢迎成为商家</text>
|
||||
<text class="empty-desc">注册商家账号,开启您的民宿事业</text>
|
||||
<button class="primary-btn" @tap="goSellerRegister">
|
||||
<text class="btn-text">立即注册/登录</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 已登录但未申请店铺 -->
|
||||
<view v-else-if="!sellerStore.hasMerchant()" class="create-shop-state">
|
||||
<view class="welcome-card">
|
||||
<view class="welcome-icon">
|
||||
<u-icon name="account-fill" :size="80" color="#fff" />
|
||||
</view>
|
||||
<text class="welcome-title">你好,{{ sellerStore.sellerInfo?.contactName }}</text>
|
||||
<text class="welcome-phone">{{ sellerStore.sellerInfo?.phone }}</text>
|
||||
</view>
|
||||
|
||||
<view class="create-card">
|
||||
<view class="create-icon">
|
||||
<u-icon name="home" :size="80" color="#667eea" />
|
||||
</view>
|
||||
<text class="create-title">创建您的店铺</text>
|
||||
<text class="create-desc">完成店铺信息填写,提交审核后即可开始营业</text>
|
||||
|
||||
<view class="create-steps">
|
||||
<view class="step-item">
|
||||
<view class="step-number">1</view>
|
||||
<text class="step-text">填写店铺信息</text>
|
||||
</view>
|
||||
<view class="step-divider"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-number">2</view>
|
||||
<text class="step-text">等待平台审核</text>
|
||||
</view>
|
||||
<view class="step-divider"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-number">3</view>
|
||||
<text class="step-text">开始营业</text>
|
||||
<view class="create-content">
|
||||
<view class="welcome-section">
|
||||
<view class="avatar-wrapper">
|
||||
<view class="avatar-circle">
|
||||
<u-icon name="account-fill" :size="60" color="#667eea" />
|
||||
</view>
|
||||
<view class="avatar-badge">
|
||||
<u-icon name="checkmark" :size="16" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
<text class="welcome-name">{{ sellerStore.sellerInfo?.contactName }}</text>
|
||||
<text class="welcome-phone">{{ sellerStore.sellerInfo?.phone }}</text>
|
||||
</view>
|
||||
|
||||
<button class="primary-btn large" @tap="goCreateShop">
|
||||
<text class="btn-text">立即创建店铺</text>
|
||||
<view class="create-main-card">
|
||||
<view class="card-header">
|
||||
<view class="header-icon-box">
|
||||
<u-icon name="home-fill" :size="48" color="#667eea" />
|
||||
</view>
|
||||
<view class="header-text">
|
||||
<text class="card-title">创建您的店铺</text>
|
||||
<text class="card-subtitle">开启民宿经营之旅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress-steps">
|
||||
<view class="progress-line"></view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">
|
||||
<text class="step-num">1</text>
|
||||
</view>
|
||||
<text class="step-label">填写信息</text>
|
||||
</view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">
|
||||
<text class="step-num">2</text>
|
||||
</view>
|
||||
<text class="step-label">等待审核</text>
|
||||
</view>
|
||||
<view class="step-item">
|
||||
<view class="step-circle">
|
||||
<text class="step-num">3</text>
|
||||
</view>
|
||||
<text class="step-label">开始营业</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="benefits-list">
|
||||
<view class="benefit-item">
|
||||
<u-icon name="checkmark-circle" :size="20" color="#52c41a" />
|
||||
<text class="benefit-text">免费入驻,无需押金</text>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<u-icon name="checkmark-circle" :size="20" color="#52c41a" />
|
||||
<text class="benefit-text">专业工具,轻松管理</text>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<u-icon name="checkmark-circle" :size="20" color="#52c41a" />
|
||||
<text class="benefit-text">平台推广,客源无忧</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="create-shop-btn" @tap="goCreateShop">
|
||||
<text class="btn-text">立即创建店铺</text>
|
||||
<u-icon name="arrow-right" :size="20" color="#fff" />
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="logout-btn" @tap="handleLogoutSeller">
|
||||
<text class="logout-text">退出商家账号</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="text-btn" @tap="handleLogoutSeller">
|
||||
<text class="text-btn-label">退出商家账号</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 已有店铺:加载中 -->
|
||||
@@ -339,174 +399,438 @@ function navigateTo(url: string) {
|
||||
|
||||
/* ========== 空状态 ========== */
|
||||
.empty-state {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 0 48rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
.empty-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bg-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
|
||||
&.circle-1 {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
top: -100rpx;
|
||||
right: -100rpx;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
&.circle-2 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background: linear-gradient(135deg, #f093fb, #f5576c);
|
||||
bottom: 100rpx;
|
||||
left: -80rpx;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
&.circle-3 {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
top: 50%;
|
||||
right: -50rpx;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30rpx) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-2xl;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-20rpx); }
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: $font-2xl;
|
||||
font-size: 48rpx;
|
||||
font-weight: $font-bold;
|
||||
color: $text-primary;
|
||||
margin-bottom: $spacing-md;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: $font-base;
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
margin-bottom: $spacing-3xl;
|
||||
margin-bottom: 64rpx;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
width: 100%;
|
||||
margin-bottom: 64rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10rpx);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateX(8rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: rgba(82, 196, 26, 0.1);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-primary;
|
||||
font-weight: $font-medium;
|
||||
}
|
||||
|
||||
/* ========== 创建店铺状态 ========== */
|
||||
.create-shop-state {
|
||||
padding: $spacing-2xl;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 48rpx 32rpx;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
background: linear-gradient(135deg, $primary-color, $primary-400);
|
||||
border-radius: $radius-xl;
|
||||
padding: $spacing-3xl $spacing-2xl;
|
||||
.create-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-2xl;
|
||||
box-shadow: $shadow-lg;
|
||||
padding: 48rpx 32rpx;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05));
|
||||
border-radius: 24rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4rpx;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: $font-xl;
|
||||
.avatar-badge {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background: linear-gradient(135deg, #52c41a, #73d13d);
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.welcome-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: $font-bold;
|
||||
color: #fff;
|
||||
margin-bottom: $spacing-xs;
|
||||
color: $text-primary;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.welcome-phone {
|
||||
font-size: $font-base;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 26rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.create-card {
|
||||
background: $bg-card;
|
||||
border-radius: $radius-xl;
|
||||
padding: $spacing-3xl $spacing-2xl;
|
||||
margin-bottom: $spacing-xl;
|
||||
box-shadow: $shadow-sm;
|
||||
.create-main-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.create-icon {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
padding-bottom: 32rpx;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.header-icon-box {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.create-title {
|
||||
font-size: $font-xl;
|
||||
.header-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: $font-bold;
|
||||
color: $text-primary;
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
.create-desc {
|
||||
font-size: $font-base;
|
||||
.card-subtitle {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
margin-bottom: $spacing-3xl;
|
||||
}
|
||||
|
||||
.create-steps {
|
||||
.progress-steps {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: $spacing-3xl;
|
||||
padding: 0 $spacing-md;
|
||||
margin-bottom: 40rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
position: absolute;
|
||||
top: 28rpx;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
height: 2rpx;
|
||||
background: linear-gradient(90deg, #e8e8e8, #e8e8e8);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
.step-circle {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
background: linear-gradient(135deg, $primary-color, $primary-400);
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-size: $font-lg;
|
||||
font-weight: $font-bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-sm;
|
||||
box-shadow: 0 4rpx 12rpx rgba($primary-color, 0.3);
|
||||
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: $font-xs;
|
||||
.step-num {
|
||||
font-size: 24rpx;
|
||||
font-weight: $font-bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-divider {
|
||||
width: 60rpx;
|
||||
height: 2rpx;
|
||||
background: $border-base;
|
||||
margin: 0 $spacing-xs;
|
||||
.benefits-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
padding: 32rpx;
|
||||
background: linear-gradient(135deg, rgba(82, 196, 26, 0.03), rgba(115, 209, 61, 0.03));
|
||||
border-radius: 16rpx;
|
||||
border: 1rpx solid rgba(82, 196, 26, 0.1);
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.benefit-text {
|
||||
font-size: 26rpx;
|
||||
color: $text-primary;
|
||||
font-weight: $font-medium;
|
||||
}
|
||||
|
||||
.create-shop-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 12rpx 40rpx rgba(102, 126, 234, 0.4);
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: transparent;
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
/* ========== 按钮样式 ========== */
|
||||
.primary-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, $primary-color, $primary-400);
|
||||
border-radius: $radius-round;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba($primary-color, 0.3);
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 12rpx 40rpx rgba($primary-color, 0.4);
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&.large {
|
||||
height: 96rpx;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba($primary-color, 0.2);
|
||||
box-shadow: 0 8rpx 24rpx rgba($primary-color, 0.3);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
height: 96rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,9 +1183,7 @@ function navigateTo(url: string) {
|
||||
gap: $spacing-lg;
|
||||
|
||||
.menu-card:nth-child(5) {
|
||||
grid-column: 1 / -1;
|
||||
max-width: 50%;
|
||||
margin: 0 auto;
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
:class="['type-item', { active: form.paymentChannel === type.value }]"
|
||||
@tap="form.paymentChannel = type.value"
|
||||
>
|
||||
<u-icon :name="type.icon" :size="28" :color="form.paymentChannel === type.value ? '#FF6B35' : '#999'" />
|
||||
<text class="type-text">{{ type.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -68,27 +68,27 @@ export function confirmPlatformWithdrawal(id: number, transactionNo: string) {
|
||||
}
|
||||
|
||||
export function approveUserWithdrawal(id: number) {
|
||||
return request.put(`/api/admin/finance/withdrawals/users/${id}/approve`);
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/approve?type=user`);
|
||||
}
|
||||
|
||||
export function rejectUserWithdrawal(id: number, reason: string) {
|
||||
return request.put(`/api/admin/finance/withdrawals/users/${id}/reject`, { rejectReason: reason });
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/reject?type=user`, { rejectReason: reason });
|
||||
}
|
||||
|
||||
export function confirmUserWithdrawal(id: number, transactionNo: string) {
|
||||
return request.put(`/api/admin/finance/withdrawals/users/${id}/confirm`, { paymentNo: transactionNo });
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/confirm?type=user`, { paymentNo: transactionNo });
|
||||
}
|
||||
|
||||
export function approveMerchantWithdrawal(id: number) {
|
||||
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/approve`);
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/approve?type=merchant`);
|
||||
}
|
||||
|
||||
export function rejectMerchantWithdrawal(id: number, reason: string) {
|
||||
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/reject`, { rejectReason: reason });
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/reject?type=merchant`, { rejectReason: reason });
|
||||
}
|
||||
|
||||
export function confirmMerchantWithdrawal(id: number, transactionNo: string) {
|
||||
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/confirm`, { paymentNo: transactionNo });
|
||||
return request.put(`/api/admin/finance/withdrawals/${id}/confirm?type=merchant`, { paymentNo: transactionNo });
|
||||
}
|
||||
|
||||
// 平台银行卡管理
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Card, Descriptions, Tag, Button, Space, Tabs, Table, Statistic, Row, Co
|
||||
import { ArrowLeftOutlined, ShopOutlined, PhoneOutlined, EnvironmentOutlined, StarOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getMerchantDetail, approveMerchant, rejectMerchant, freezeMerchant, unfreezeMerchant } from '@/api/admin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const statusMap: Record<string, { color: string; label: string }> = {
|
||||
pending: { color: 'gold', label: '待审核' },
|
||||
@@ -74,7 +75,7 @@ const MerchantDetail: React.FC = () => {
|
||||
{ title: '价格', dataIndex: 'price', width: 100, render: (v) => `¥${v}` },
|
||||
{ title: '面积', dataIndex: 'area', width: 100, render: (v) => `${v}㎡` },
|
||||
{ title: '状态', dataIndex: 'status', width: 100, render: (s) => <Tag color={s === 'available' ? 'green' : 'default'}>{s === 'available' ? '可租' : '已租'}</Tag> },
|
||||
{ title: '发布时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '发布时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
];
|
||||
|
||||
const orderColumns: ColumnsType<any> = [
|
||||
@@ -83,7 +84,7 @@ const MerchantDetail: React.FC = () => {
|
||||
{ title: '租客', dataIndex: 'userName', width: 120 },
|
||||
{ title: '金额', dataIndex: 'totalAmount', width: 100, render: (v) => `¥${v}` },
|
||||
{ title: '状态', dataIndex: 'status', width: 120, render: (s) => <Tag>{s}</Tag> },
|
||||
{ title: '下单时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '下单时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
];
|
||||
|
||||
const reviewColumns: ColumnsType<any> = [
|
||||
@@ -91,7 +92,7 @@ const MerchantDetail: React.FC = () => {
|
||||
{ title: '评价人', dataIndex: 'userName', width: 120 },
|
||||
{ title: '评分', dataIndex: 'rating', width: 100, render: (v) => <><StarOutlined style={{ color: '#faad14' }} /> {v}</> },
|
||||
{ title: '评价内容', dataIndex: 'content', ellipsis: true },
|
||||
{ title: '评价时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '评价时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -137,8 +138,8 @@ const MerchantDetail: React.FC = () => {
|
||||
<Descriptions.Item label="详细地址">{merchant.address || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="营业时间">{merchant.businessHours || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="店铺简介" span={2}>{merchant.description || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="入驻时间">{merchant.createdAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">{merchant.updatedAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="入驻时间">{merchant.createdAt ? dayjs(merchant.createdAt).format('YYYY-MM-DD HH:mm') : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="更新时间">{merchant.updatedAt ? dayjs(merchant.updatedAt).format('YYYY-MM-DD HH:mm') : '-'}</Descriptions.Item>
|
||||
{merchant.status === 'rejected' && (
|
||||
<Descriptions.Item label="拒绝原因" span={2}>
|
||||
<span style={{ color: '#ff4d4f' }}>{merchant.rejectReason}</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Table, Tag, Button, Space, Select, Modal, Input, message, Popconfirm } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getMerchantList, approveMerchant, rejectMerchant, freezeMerchant, unfreezeMerchant } from '@/api/admin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -80,7 +81,7 @@ const MerchantList: React.FC = () => {
|
||||
title: '状态', dataIndex: 'status', width: 100,
|
||||
render: (s) => <Tag color={statusMap[s]?.color}>{statusMap[s]?.label || s}</Tag>,
|
||||
},
|
||||
{ title: '入驻时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '入驻时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
{
|
||||
title: '操作', width: 300, fixed: 'right',
|
||||
render: (_, r) => (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Table, Tag, Select, Space, Input, Button } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getOrderList } from '@/api/admin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -50,7 +51,7 @@ const OrderList: React.FC = () => {
|
||||
title: '状态', dataIndex: 'status', width: 100,
|
||||
render: (s) => <Tag color={statusMap[s]?.color}>{statusMap[s]?.label || s}</Tag>,
|
||||
},
|
||||
{ title: '下单时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '下单时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
{
|
||||
title: '操作', key: 'action', width: 100, fixed: 'right',
|
||||
render: (_, record) => (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Table, Button, Space, Tag, Modal, Input, Select, message, Image } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getAdminRoomList, approveRoom, rejectRoom } from '@/api/room';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const typeLabels: Record<string, string> = { hotel: '酒店', homestay: '民宿', apartment: '公寓', hostel: '青旅' };
|
||||
const auditStatusMap: Record<string, { label: string; color: string }> = {
|
||||
@@ -74,7 +75,7 @@ const RoomAudit: React.FC = () => {
|
||||
title: '拒绝原因', dataIndex: 'auditRejectReason', width: 160, ellipsis: true,
|
||||
render: (v) => v || '-',
|
||||
},
|
||||
{ title: '提交时间', dataIndex: 'createdAt', width: 170 },
|
||||
{ title: '提交时间', dataIndex: 'createdAt', width: 170, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
{
|
||||
title: '操作', width: 180, fixed: 'right',
|
||||
render: (_, r) => (
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Table, Tag, Button, Space, Select, Input, Popconfirm, message } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getUserList, freezeUser, unfreezeUser } from '@/api/admin';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Search } = Input;
|
||||
@@ -63,7 +64,7 @@ const UserList: React.FC = () => {
|
||||
title: '状态', dataIndex: 'status', width: 100,
|
||||
render: (s) => <Tag color={statusMap[s]?.color}>{statusMap[s]?.label || s}</Tag>,
|
||||
},
|
||||
{ title: '注册时间', dataIndex: 'createdAt', width: 180 },
|
||||
{ title: '注册时间', dataIndex: 'createdAt', width: 180, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' },
|
||||
{
|
||||
title: '操作', width: 150, fixed: 'right',
|
||||
render: (_, r) => (
|
||||
|
||||
@@ -12,7 +12,7 @@ interface PlatformAccount {
|
||||
frozen_balance: number;
|
||||
total_income: number;
|
||||
total_expense: number;
|
||||
total_service_fee: number;
|
||||
total_service_fee?: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -45,6 +45,7 @@ const PlatformWallet: React.FC = () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getPlatformAccounts({});
|
||||
console.log('Platform account data:', res.data);
|
||||
if (res.data && res.data.length > 0) {
|
||||
setAccount(res.data[0]);
|
||||
}
|
||||
@@ -112,6 +113,9 @@ const PlatformWallet: React.FC = () => {
|
||||
// 可提现金额 = 钱包余额 - 冻结余额
|
||||
const withdrawableAmount = account ? Number(account.balance) - Number(account.frozen_balance) : 0;
|
||||
|
||||
// 服务费收入 = 总收入 - 总支出(如果后端没有返回 total_service_fee)
|
||||
const serviceFeeIncome = account ? (account.total_service_fee ?? (Number(account.total_income) - Number(account.total_expense))) : 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ marginBottom: 24 }}>平台钱包</h2>
|
||||
@@ -167,7 +171,7 @@ const PlatformWallet: React.FC = () => {
|
||||
<Card>
|
||||
<Statistic
|
||||
title="服务费收入"
|
||||
value={account.total_service_fee}
|
||||
value={serviceFeeIncome}
|
||||
precision={2}
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#722ed1', fontSize: 32 }}
|
||||
@@ -251,7 +255,7 @@ const PlatformWallet: React.FC = () => {
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="服务费收入">
|
||||
<span style={{ color: '#722ed1', fontSize: 16, fontWeight: 'bold' }}>{formatMoney(account.total_service_fee)}</span>
|
||||
<span style={{ color: '#722ed1', fontSize: 16, fontWeight: 'bold' }}>{formatMoney(serviceFeeIncome)}</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="其他收入">
|
||||
<span style={{ color: '#13c2c2' }}>{formatMoney(0)}</span>
|
||||
|
||||
@@ -81,7 +81,7 @@ const PlatformWithdrawals: React.FC = () => {
|
||||
const handleApprove = async (record: PlatformWithdrawal) => {
|
||||
Modal.confirm({
|
||||
title: '确认审核通过',
|
||||
content: `确定要审核通过提现申请 ${record.withdrawal_no} 吗?`,
|
||||
content: `确定要审核通过提现申请 ${record.withdrawNo} 吗?`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
|
||||
@@ -83,16 +83,9 @@ const Withdrawals: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewDetail = async (id: number) => {
|
||||
try {
|
||||
// TODO: 实现提现详情接口
|
||||
message.info('提现详情功能待实现');
|
||||
// const res = await getWithdrawalDetail(id);
|
||||
// setCurrentDetail(res.data);
|
||||
// setDetailVisible(true);
|
||||
} catch (error) {
|
||||
message.error('获取提现详情失败');
|
||||
}
|
||||
const handleViewDetail = (record: Withdrawal) => {
|
||||
setCurrentDetail(record);
|
||||
setDetailVisible(true);
|
||||
};
|
||||
|
||||
const handleApprove = async (id: number) => {
|
||||
@@ -240,7 +233,7 @@ const Withdrawals: React.FC = () => {
|
||||
fixed: 'right',
|
||||
render: (_, record: Withdrawal) => (
|
||||
<Space>
|
||||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleViewDetail(record.id)}>
|
||||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>
|
||||
详情
|
||||
</Button>
|
||||
{record.status === 'pending' && (
|
||||
@@ -331,19 +324,25 @@ const Withdrawals: React.FC = () => {
|
||||
<Descriptions.Item label="申请人">
|
||||
{currentDetail.userName || currentDetail.merchantName} (ID: {currentDetail.userId || currentDetail.merchantId})
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="申请时间">{currentDetail.createdAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="提现金额">¥{currentDetail.amount.toFixed(2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="手续费">¥{currentDetail.fee.toFixed(2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="提现金额">
|
||||
<span style={{ fontSize: 16, fontWeight: 'bold' }}>¥{Number(currentDetail.amount).toFixed(2)}</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="手续费">¥{Number(currentDetail.fee || 0).toFixed(2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="到账金额">
|
||||
<span style={{ fontWeight: 'bold', color: '#52c41a' }}>
|
||||
¥{currentDetail.actualAmount.toFixed(2)}
|
||||
<span style={{ fontWeight: 'bold', color: '#52c41a', fontSize: 16 }}>
|
||||
¥{Number(currentDetail.actualAmount).toFixed(2)}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="开户银行">{currentDetail.bankName}</Descriptions.Item>
|
||||
<Descriptions.Item label="银行账号">{currentDetail.bankAccount}</Descriptions.Item>
|
||||
<Descriptions.Item label="账户名称">{currentDetail.accountName}</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">
|
||||
<Tag color={currentDetail.status === 'completed' ? 'green' : 'orange'}>
|
||||
<Tag color={
|
||||
currentDetail.status === 'completed' ? 'green' :
|
||||
currentDetail.status === 'approved' ? 'blue' :
|
||||
currentDetail.status === 'rejected' ? 'red' :
|
||||
currentDetail.status === 'pending' ? 'orange' : 'default'
|
||||
}>
|
||||
{currentDetail.status === 'pending' && '待审核'}
|
||||
{currentDetail.status === 'approved' && '已通过'}
|
||||
{currentDetail.status === 'rejected' && '已拒绝'}
|
||||
@@ -351,11 +350,12 @@ const Withdrawals: React.FC = () => {
|
||||
{currentDetail.status === 'cancelled' && '已取消'}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="申请时间">{formatDateTime(currentDetail.createdAt)}</Descriptions.Item>
|
||||
{currentDetail.processedAt && (
|
||||
<Descriptions.Item label="处理时间">{currentDetail.processedAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="处理时间">{formatDateTime(currentDetail.processedAt)}</Descriptions.Item>
|
||||
)}
|
||||
{currentDetail.paidAt && (
|
||||
<Descriptions.Item label="打款时间">{currentDetail.paidAt}</Descriptions.Item>
|
||||
<Descriptions.Item label="打款时间">{formatDateTime(currentDetail.paidAt)}</Descriptions.Item>
|
||||
)}
|
||||
{currentDetail.transactionNo && (
|
||||
<Descriptions.Item label="交易单号">{currentDetail.transactionNo}</Descriptions.Item>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# 周结算接口修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
调用 `/api/admin/finance/settlements/execute-weekly` 接口时,即使没有实际执行结算,也会返回成功消息 `"周结算任务已执行完成"`,导致用户误以为结算成功。
|
||||
|
||||
## 问题原因
|
||||
|
||||
1. **Controller 层**:没有捕获 Service 层抛出的异常,也没有返回详细的执行结果
|
||||
2. **Service 层**:在以下情况会抛出 `BadRequestException`:
|
||||
- 该周期已经结算过
|
||||
- 没有需要结算的订单
|
||||
- 所有订单都已结算
|
||||
|
||||
这些异常导致接口返回 400 错误,但前端可能没有正确处理,或者异常被中间件捕获后返回了成功状态。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. Controller 层改进
|
||||
|
||||
**修改前:**
|
||||
```typescript
|
||||
@Post('execute-weekly')
|
||||
async executeWeeklySettlement() {
|
||||
await this.settlementService.handleWeeklySettlement();
|
||||
return { message: '周结算任务已执行完成' };
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```typescript
|
||||
@Post('execute-weekly')
|
||||
async executeWeeklySettlement() {
|
||||
const result = await this.settlementService.handleWeeklySettlement();
|
||||
return {
|
||||
message: '周结算任务已执行完成',
|
||||
...result
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service 层改进
|
||||
|
||||
将抛出异常改为返回明确的结果对象:
|
||||
|
||||
**修改前:**
|
||||
```typescript
|
||||
if (existingSettlements > 0) {
|
||||
throw new BadRequestException(`该周期已经结算过,无法重复结算`);
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```typescript
|
||||
if (existingSettlements > 0) {
|
||||
this.logger.warn(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,跳过`);
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: `该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 返回值结构
|
||||
|
||||
### 成功执行结算
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 5,
|
||||
"failCount": 0,
|
||||
"totalOrders": 120,
|
||||
"skipped": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 跳过结算(已结算过)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 0,
|
||||
"failCount": 0,
|
||||
"totalOrders": 0,
|
||||
"skipped": true,
|
||||
"reason": "该周期 2026-05-19 ~ 2026-05-25 已经结算过"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 跳过结算(无订单)
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "周结算任务已执行完成",
|
||||
"successCount": 0,
|
||||
"failCount": 0,
|
||||
"totalOrders": 0,
|
||||
"skipped": true,
|
||||
"reason": "该周期内没有需要结算的订单"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 前端处理建议
|
||||
|
||||
前端应该根据返回的 `skipped` 字段判断是否真正执行了结算:
|
||||
|
||||
```typescript
|
||||
const response = await executeWeeklySettlement();
|
||||
|
||||
if (response.skipped) {
|
||||
// 显示警告信息
|
||||
message.warning(response.reason);
|
||||
} else if (response.successCount > 0) {
|
||||
// 显示成功信息
|
||||
message.success(`结算成功:${response.successCount} 个商家,共 ${response.totalOrders} 个订单`);
|
||||
|
||||
if (response.failCount > 0) {
|
||||
message.warning(`${response.failCount} 个商家结算失败,请查看日志`);
|
||||
}
|
||||
} else {
|
||||
message.info('没有需要结算的数据');
|
||||
}
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. **首次执行**:调用接口,应该返回 `skipped: false` 和实际的结算数据
|
||||
2. **重复执行**:再次调用接口,应该返回 `skipped: true` 和原因说明
|
||||
3. **无订单场景**:在没有已完成订单的情况下调用,应该返回相应的提示
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [settlement-admin.controller.ts](../src/modules/admin/finance/settlement-admin.controller.ts)
|
||||
- [settlement.service.ts](../src/modules/shared/finance/settlement.service.ts)
|
||||
@@ -9,8 +9,14 @@
|
||||
```
|
||||
finance/
|
||||
├── entities/
|
||||
│ ├── account.entity.ts # 账户实体
|
||||
│ ├── transaction.entity.ts # 交易流水实体
|
||||
│ ├── user-account.entity.ts # 用户账户实体
|
||||
│ ├── merchant-account.entity.ts # 商家账户实体
|
||||
│ ├── platform-account.entity.ts # 平台账户实体
|
||||
│ ├── system-account.entity.ts # 系统总账户实体
|
||||
│ ├── user-transaction.entity.ts # 用户交易流水实体
|
||||
│ ├── merchant-transaction.entity.ts # 商家交易流水实体
|
||||
│ ├── platform-transaction.entity.ts # 平台交易流水实体
|
||||
│ ├── system-transaction.entity.ts # 系统总账户交易流水实体
|
||||
│ ├── settlement.entity.ts # 结算单实体
|
||||
│ ├── settlement-item.entity.ts # 结算明细实体
|
||||
│ ├── user-withdrawal.entity.ts # 用户提现实体
|
||||
@@ -203,8 +209,14 @@ finance/
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| `accounts` | 账户表(用户/商家/平台) |
|
||||
| `transactions` | 交易流水表(复式记账) |
|
||||
| `system_accounts` | 系统总账户表 |
|
||||
| `system_transactions` | 系统总账户交易流水表 |
|
||||
| `platform_accounts` | 平台账户表 |
|
||||
| `platform_transactions` | 平台交易流水表 |
|
||||
| `merchant_accounts` | 商家账户表 |
|
||||
| `merchant_transactions` | 商家交易流水表 |
|
||||
| `user_accounts` | 用户账户表 |
|
||||
| `user_transactions` | 用户交易流水表 |
|
||||
| `settlements` | 结算单表 |
|
||||
| `settlement_items` | 结算明细表 |
|
||||
| `user_withdrawals` | 用户提现表 |
|
||||
@@ -216,12 +228,13 @@ finance/
|
||||
|
||||
## 技术特性
|
||||
|
||||
1. **复式记账**: 每笔转账生成两条交易流水(支出+收入)
|
||||
1. **分表设计**: 账户和交易流水按角色分表(用户/商家/平台/系统)
|
||||
2. **乐观锁**: 账户余额更新使用版本号防止并发问题
|
||||
3. **事务保证**: 所有涉及金额变动的操作都在事务中执行
|
||||
4. **冻结机制**: 提现时先冻结余额,审核通过后扣减
|
||||
5. **自动对账**: 每日自动检查账户余额和交易流水一致性
|
||||
6. **定时结算**: 每周自动生成商家结算单
|
||||
7. **资金守恒**: 系统总账户 = 商家账户 + 用户账户 + 平台账户
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index, VersionColumn } from 'typeorm';
|
||||
|
||||
@Entity('accounts')
|
||||
@Index(['account_type', 'owner_id'], { unique: true })
|
||||
export class Account {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['user', 'merchant', 'platform'],
|
||||
comment: '账户类型'
|
||||
})
|
||||
@Index()
|
||||
account_type: 'user' | 'merchant' | 'platform';
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '所有者ID(user_id/merchant_id/platform固定为0)' })
|
||||
owner_id: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '可用余额' })
|
||||
balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '冻结余额(提现中、退款中)' })
|
||||
frozen_balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入' })
|
||||
total_income: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出' })
|
||||
total_expense: number;
|
||||
|
||||
@VersionColumn({ comment: '乐观锁版本号' })
|
||||
version: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['active', 'frozen', 'closed'],
|
||||
default: 'active',
|
||||
comment: '状态'
|
||||
})
|
||||
@Index()
|
||||
status: 'active' | 'frozen' | 'closed';
|
||||
|
||||
@CreateDateColumn({ comment: '创建时间' })
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn({ comment: '更新时间' })
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-activity.entity.ts
|
||||
export { MktActivity as InviteActivity } from './mkt-activity.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-cashback.entity.ts
|
||||
export { MktCashback as InviteCashback } from './mkt-cashback.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 mkt-invitation.entity.ts
|
||||
export { MktInvitation as InviteRecord } from './mkt-invitation.entity';
|
||||
@@ -1,2 +0,0 @@
|
||||
// 别名映射到 platform-config.entity.ts
|
||||
export { PlatformConfig as SystemConfig } from './platform-config.entity';
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm';
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, unique: true, comment: '交易流水号(全局唯一)' })
|
||||
transaction_no: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '账户ID' })
|
||||
@Index()
|
||||
account_id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['user', 'merchant', 'platform'],
|
||||
comment: '账户类型'
|
||||
})
|
||||
account_type: 'user' | 'merchant' | 'platform';
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, comment: '账户所有者ID' })
|
||||
owner_id: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['income', 'expense'],
|
||||
comment: '方向:income-收入/expense-支出'
|
||||
})
|
||||
direction: 'income' | 'expense';
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '金额(正数)' })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '交易前余额' })
|
||||
balance_before: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '交易后余额' })
|
||||
balance_after: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: '交易类型' })
|
||||
@Index()
|
||||
transaction_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: '业务类型:order/refund/settlement/cashback/withdraw' })
|
||||
business_type: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, nullable: true, comment: '业务ID(订单ID/提现ID等)' })
|
||||
business_id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, nullable: true, comment: '业务单号(订单号/提现单号等)' })
|
||||
business_no: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, nullable: true, comment: '对方账户ID(复式记账关联)' })
|
||||
related_account_id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 500, nullable: true, comment: '备注' })
|
||||
remark: string;
|
||||
|
||||
@CreateDateColumn({ comment: '创建时间' })
|
||||
@Index()
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
@Index(['business_type', 'business_id'])
|
||||
export class TransactionIndex {}
|
||||
@@ -1,57 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { CouponService } from '@/modules/shared/coupon/coupon.service';
|
||||
import { JwtAuthGuard, RolesGuard } from '@/common';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { CreateCouponDto, UpdateCouponDto, QueryCouponDto } from './dto/coupon.dto';
|
||||
|
||||
@ApiTags('优惠券管理(管理员)')
|
||||
@Controller('admin/coupons')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ApiBearerAuth()
|
||||
export class CouponController {
|
||||
constructor(private readonly couponService: CouponService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建优惠券' })
|
||||
async create(@Body() dto: CreateCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.create(dto, user.id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '更新优惠券' })
|
||||
async update(@Param('id') id: number, @Body() dto: UpdateCouponDto) {
|
||||
return this.couponService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除优惠券' })
|
||||
async delete(@Param('id') id: number) {
|
||||
await this.couponService.delete(id);
|
||||
return { message: '删除成功' };
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '查询优惠券列表' })
|
||||
async findAll(@Query() dto: QueryCouponDto) {
|
||||
return this.couponService.findAll(dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取优惠券详情' })
|
||||
async findOne(@Param('id') id: number) {
|
||||
return this.couponService.findOne(id);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CouponModule } from '@/modules/shared/coupon/coupon.module';
|
||||
import { CouponController } from './coupon.controller';
|
||||
import { CouponAdminController } from './coupon-admin.controller';
|
||||
|
||||
@Module({
|
||||
imports: [CouponModule],
|
||||
controllers: [CouponController],
|
||||
controllers: [CouponAdminController],
|
||||
})
|
||||
export class AdminCouponModule {}
|
||||
|
||||
@@ -33,12 +33,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getPlatformAccountDetail(id);
|
||||
}
|
||||
|
||||
@Get('platform/:id/balance')
|
||||
@ApiOperation({ summary: '查询平台账户余额' })
|
||||
async getPlatformAccountBalance(@Param('id') id: number) {
|
||||
return this.accountService.getPlatformAccountBalance(id);
|
||||
}
|
||||
|
||||
// ==================== 用户账户管理 ====================
|
||||
|
||||
@Get('users')
|
||||
@@ -47,12 +41,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getUserAccounts(dto);
|
||||
}
|
||||
|
||||
@Get('users/:userId')
|
||||
@ApiOperation({ summary: '查询用户账户详情' })
|
||||
async getUserAccountDetail(@Param('userId') userId: number) {
|
||||
return this.accountService.getUserAccountDetail(userId);
|
||||
}
|
||||
|
||||
@Get('users/summary')
|
||||
@ApiOperation({ summary: '用户账户汇总统计' })
|
||||
async getUserAccountsSummary() {
|
||||
@@ -67,12 +55,6 @@ export class AccountAdminController {
|
||||
return this.accountService.getMerchantAccounts(dto);
|
||||
}
|
||||
|
||||
@Get('merchants/:merchantId')
|
||||
@ApiOperation({ summary: '查询商家账户详情' })
|
||||
async getMerchantAccountDetail(@Param('merchantId') merchantId: number) {
|
||||
return this.accountService.getMerchantAccountDetail(merchantId);
|
||||
}
|
||||
|
||||
@Get('merchants/summary')
|
||||
@ApiOperation({ summary: '商家账户汇总统计' })
|
||||
async getMerchantAccountsSummary() {
|
||||
|
||||
@@ -89,8 +89,11 @@ export class SettlementAdminController {
|
||||
@Post('execute-weekly')
|
||||
@ApiOperation({ summary: '手动执行周结算(所有商家)' })
|
||||
async executeWeeklySettlement() {
|
||||
await this.settlementService.handleWeeklySettlement();
|
||||
return { message: '周结算任务已执行完成' };
|
||||
const result = await this.settlementService.handleWeeklySettlement();
|
||||
return {
|
||||
message: '周结算任务已执行完成',
|
||||
...result
|
||||
};
|
||||
}
|
||||
|
||||
@Put(':id/approve')
|
||||
|
||||
@@ -40,13 +40,6 @@ export class TransactionAdminController {
|
||||
});
|
||||
}
|
||||
|
||||
@Get('platform/:id')
|
||||
@ApiOperation({ summary: '查询平台交易详情' })
|
||||
async getPlatformTransactionDetail(@Param('id') id: number) {
|
||||
// TODO: 实现根据ID查询交易详情
|
||||
return { message: '功能开发中' };
|
||||
}
|
||||
|
||||
@Get('platform/export')
|
||||
@ApiOperation({ summary: '导出平台交易流水' })
|
||||
async exportPlatformTransactions(@Query() dto: QueryPlatformTransactionDto) {
|
||||
@@ -71,24 +64,6 @@ export class TransactionAdminController {
|
||||
});
|
||||
}
|
||||
|
||||
@Get('users/:userId')
|
||||
@ApiOperation({ summary: '查询指定用户交易流水' })
|
||||
async getUserTransactionsByUserId(
|
||||
@Param('userId') userId: number,
|
||||
@Query() dto: QueryUserTransactionDto,
|
||||
) {
|
||||
return this.transactionService.getUserTransactions({
|
||||
userId,
|
||||
direction: dto.direction,
|
||||
transactionType: dto.transactionType,
|
||||
businessType: dto.businessType,
|
||||
startDate: dto.startDate,
|
||||
endDate: dto.endDate,
|
||||
page: dto.page,
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 商家交易流水 ====================
|
||||
|
||||
@Get('merchants')
|
||||
@@ -105,22 +80,4 @@ export class TransactionAdminController {
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('merchants/:merchantId')
|
||||
@ApiOperation({ summary: '查询指定商家交易流水' })
|
||||
async getMerchantTransactionsByMerchantId(
|
||||
@Param('merchantId') merchantId: number,
|
||||
@Query() dto: QueryMerchantTransactionDto,
|
||||
) {
|
||||
return this.transactionService.getMerchantTransactions({
|
||||
merchantId,
|
||||
direction: dto.direction,
|
||||
transactionType: dto.transactionType,
|
||||
businessType: dto.businessType,
|
||||
startDate: dto.startDate,
|
||||
endDate: dto.endDate,
|
||||
page: dto.page,
|
||||
pageSize: dto.pageSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Query,
|
||||
UseGuards,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { CouponService } from './coupon.service';
|
||||
import { JwtAuthGuard } from '@/common';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { ReceiveCouponDto, QueryUserCouponDto, QueryCouponDto } from './dto/coupon.dto';
|
||||
|
||||
@ApiTags('优惠券(用户)')
|
||||
@Controller('app/coupons')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
export class CouponController {
|
||||
constructor(private readonly couponService: CouponService) {}
|
||||
|
||||
@Get('available')
|
||||
@ApiOperation({ summary: '查询可领取的优惠券' })
|
||||
async findAvailable(@Query() dto: QueryCouponDto) {
|
||||
// 只返回active状态的优惠券
|
||||
return this.couponService.findAll({ ...dto, status: 'active' });
|
||||
}
|
||||
|
||||
@Post('receive')
|
||||
@ApiOperation({ summary: '领取优惠券' })
|
||||
async receive(@Body() dto: ReceiveCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.receive(user.sub, dto.couponId);
|
||||
}
|
||||
|
||||
@Get('my')
|
||||
@ApiOperation({ summary: '查询我的优惠券' })
|
||||
async findMyCoupons(@Query() dto: QueryUserCouponDto, @CurrentUser() user: any) {
|
||||
return this.couponService.findUserCoupons(user.id, dto);
|
||||
}
|
||||
|
||||
@Get('usable/:orderId')
|
||||
@ApiOperation({ summary: '查询订单可用优惠券' })
|
||||
async findUsableCoupons(
|
||||
@Param('orderId') orderId: number,
|
||||
@Query('orderAmount') orderAmount: number,
|
||||
@Query('merchantId') merchantId: number,
|
||||
@Query('roomId') roomId: number,
|
||||
@CurrentUser() user: any,
|
||||
) {
|
||||
return this.couponService.findAvailableCoupons(
|
||||
user.id,
|
||||
orderAmount,
|
||||
merchantId,
|
||||
roomId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CouponController } from './coupon.controller';
|
||||
import { CouponUserController } from './coupon-user.controller';
|
||||
import { CouponService } from './coupon.service';
|
||||
import { Coupon } from '@/entities/coupon.entity';
|
||||
import { UserCoupon } from '@/entities/user-coupon.entity';
|
||||
@@ -9,7 +9,7 @@ import { UserCoupon } from '@/entities/user-coupon.entity';
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Coupon, UserCoupon]),
|
||||
],
|
||||
controllers: [CouponController],
|
||||
controllers: [CouponUserController],
|
||||
providers: [CouponService],
|
||||
exports: [CouponService],
|
||||
})
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Body,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { UserService } from './user.service';
|
||||
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import {
|
||||
UpdateProfileDto,
|
||||
ChangePasswordDto,
|
||||
VerifyIdentityDto,
|
||||
} from './dto/user.dto';
|
||||
|
||||
@ApiTags('用户')
|
||||
@Controller('app')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
export class UserUserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@Get('profile')
|
||||
@ApiOperation({ summary: '获取个人信息' })
|
||||
async getProfile(@CurrentUser('sub') userId: number) {
|
||||
return this.userService.findById(userId);
|
||||
}
|
||||
|
||||
@Post('profile')
|
||||
@ApiOperation({ summary: '更新个人信息' })
|
||||
async updateProfile(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
) {
|
||||
return this.userService.updateProfile(userId, dto);
|
||||
}
|
||||
|
||||
@Post('avatar')
|
||||
@ApiOperation({ summary: '上传头像' })
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadAvatar(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
return this.userService.uploadAvatar(userId, file);
|
||||
}
|
||||
|
||||
@Put('profile')
|
||||
@ApiOperation({ summary: '更新个人信息(旧接口,保留兼容)' })
|
||||
async updateProfilePut(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: UpdateProfileDto,
|
||||
) {
|
||||
return this.userService.updateProfile(userId, dto);
|
||||
}
|
||||
|
||||
@Put('password')
|
||||
@ApiOperation({ summary: '修改密码' })
|
||||
async changePassword(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: ChangePasswordDto,
|
||||
) {
|
||||
return this.userService.changePassword(userId, dto);
|
||||
}
|
||||
|
||||
@Post('verify')
|
||||
@ApiOperation({ summary: '实名认证' })
|
||||
async verifyIdentity(
|
||||
@CurrentUser('sub') userId: number,
|
||||
@Body() dto: VerifyIdentityDto,
|
||||
) {
|
||||
return this.userService.verifyIdentity(userId, dto);
|
||||
}
|
||||
|
||||
@Get('verify/status')
|
||||
@ApiOperation({ summary: '获取实名认证状态' })
|
||||
async getVerifyStatus(@CurrentUser('sub') userId: number) {
|
||||
return this.userService.getVerifyStatus(userId);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantFinanceService } from './finance.service';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { QueryTransactionDto } from './dto/finance.dto';
|
||||
|
||||
@ApiTags('商家财务管理')
|
||||
@@ -19,11 +19,11 @@ import { QueryTransactionDto } from './dto/finance.dto';
|
||||
export class MerchantFinanceController {
|
||||
constructor(
|
||||
private readonly financeService: MerchantFinanceService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
private async getMerchantId(sellerId: number): Promise<number> {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return merchant.id;
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import { WithdrawalMerchantController } from './withdrawal-merchant.controller';
|
||||
import { TransactionSellerController } from './transaction-seller.controller';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
import { FinanceModule } from '@/modules/shared/finance/finance.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([MerchantAccount, MerchantTransaction]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
forwardRef(() => FinanceModule),
|
||||
],
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Put,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantService } from './merchant.service';
|
||||
import { JwtAuthGuard, RolesGuard } from '@/common';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { QueryMerchantDto } from './dto/merchant.dto';
|
||||
|
||||
@ApiTags('商家管理(管理员)')
|
||||
@Controller('admin/merchants')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ApiBearerAuth()
|
||||
export class MerchantAdminController {
|
||||
constructor(private readonly merchantService: MerchantService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取商家列表' })
|
||||
async findAll(@Query() query: QueryMerchantDto) {
|
||||
return this.merchantService.findAll(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取商家详情' })
|
||||
async findById(@Param('id') id: number) {
|
||||
return this.merchantService.findById(id);
|
||||
}
|
||||
|
||||
@Put(':id/approve')
|
||||
@ApiOperation({ summary: '审核通过' })
|
||||
async approve(@Param('id') id: number) {
|
||||
return this.merchantService.approve(id);
|
||||
}
|
||||
|
||||
@Put(':id/reject')
|
||||
@ApiOperation({ summary: '审核拒绝' })
|
||||
async reject(@Param('id') id: number, @Body('reason') reason: string) {
|
||||
return this.merchantService.reject(id, reason);
|
||||
}
|
||||
|
||||
@Put(':id/freeze')
|
||||
@ApiOperation({ summary: '冻结店铺' })
|
||||
async freeze(@Param('id') id: number) {
|
||||
return this.merchantService.freeze(id);
|
||||
}
|
||||
|
||||
@Put(':id/unfreeze')
|
||||
@ApiOperation({ summary: '解冻店铺' })
|
||||
async unfreeze(@Param('id') id: number) {
|
||||
return this.merchantService.unfreeze(id);
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,14 @@ import { Review } from '@/entities/review.entity';
|
||||
import { MerchantService } from './merchant.service';
|
||||
import { StatisticsService } from './statistics.service';
|
||||
import { MerchantSellerController } from './merchant-seller.controller';
|
||||
import { MerchantAdminController } from './merchant-admin.controller';
|
||||
import { MerchantAuthModule } from './auth/auth.module';
|
||||
import { MerchantProfileModule } from './profile/profile.module';
|
||||
import { MerchantRoomModule } from './room/room.module';
|
||||
import { MerchantRoomCalendarModule } from './room-calendar/room-calendar.module';
|
||||
import { MerchantOrderModule } from './order/order.module';
|
||||
import { MerchantReviewModule } from './review/review.module';
|
||||
import { MerchantFinanceModule } from './finance/finance.module';
|
||||
import { MerchantStatisticsModule } from './statistics/statistics.module';
|
||||
import { MerchantProfileModule } from './profile/profile.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -33,7 +32,6 @@ import { MerchantStatisticsModule } from './statistics/statistics.module';
|
||||
],
|
||||
controllers: [
|
||||
MerchantSellerController,
|
||||
MerchantAdminController,
|
||||
],
|
||||
providers: [MerchantService, StatisticsService],
|
||||
exports: [MerchantService, StatisticsService],
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantOrderService } from './order.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { QueryOrderDto } from './dto/order.dto';
|
||||
|
||||
@ApiTags('订单管理(商家)')
|
||||
@@ -23,7 +23,7 @@ import { QueryOrderDto } from './dto/order.dto';
|
||||
export class MerchantOrderController {
|
||||
constructor(
|
||||
private readonly orderService: MerchantOrderService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -32,7 +32,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query() query: QueryOrderDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.findByMerchant(merchant.id, query);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
@@ -58,7 +58,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Body('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
const order = await this.orderService.findOne(orderNo);
|
||||
if (order.merchantId !== merchant.id) {
|
||||
@@ -73,7 +73,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.confirm(merchant.id, orderNo);
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export class MerchantOrderController {
|
||||
@Param('orderNo') orderNo: string,
|
||||
@Body('reason') reason: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.reject(merchant.id, orderNo, reason);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkin(merchant.id, orderNo);
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export class MerchantOrderController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('orderNo') orderNo: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.orderService.checkout(merchant.id, orderNo);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import { MerchantOrderService } from './order.service';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { Room } from '@/entities/room.entity';
|
||||
import { RoomCalendar } from '@/entities/room-calendar.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
import { UserActivityModule } from '@/modules/app/activity/activity.module';
|
||||
import { FinanceModule } from '@/modules/shared/finance/finance.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Order, Room, RoomCalendar]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
forwardRef(() => UserActivityModule),
|
||||
forwardRef(() => FinanceModule),
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantReviewService } from './review.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
|
||||
@ApiTags('评价管理(商家)')
|
||||
@Controller('merchant/reviews')
|
||||
@@ -18,7 +18,7 @@ import { MerchantProfileService } from '../profile/profile.service';
|
||||
export class MerchantReviewController {
|
||||
constructor(
|
||||
private readonly reviewService: MerchantReviewService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -29,7 +29,7 @@ export class MerchantReviewController {
|
||||
@Query('limit') limit: string = '10',
|
||||
@Query('roomId') roomId?: string,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
|
||||
return this.reviewService.getSellerReviews(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantReviewController } from './review.controller';
|
||||
import { MerchantReviewService } from './review.service';
|
||||
import { Review } from '@/entities/review.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Review]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantReviewController],
|
||||
providers: [MerchantReviewService],
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MerchantRoomService } from './room.service';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
import { CreateRoomDto, UpdateRoomDto, QueryRoomDto } from './dto/room.dto';
|
||||
|
||||
@ApiTags('房源管理(商家)')
|
||||
@@ -24,7 +24,7 @@ import { CreateRoomDto, UpdateRoomDto, QueryRoomDto } from './dto/room.dto';
|
||||
export class MerchantRoomController {
|
||||
constructor(
|
||||
private readonly roomService: MerchantRoomService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@@ -33,7 +33,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query() query: QueryRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.findByMerchant(Number(merchant.id), query);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.findByIdAndMerchant(
|
||||
Number(id),
|
||||
@@ -58,7 +58,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Body() dto: CreateRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.create(Number(merchant.id), dto);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export class MerchantRoomController {
|
||||
@Param('id') id: number,
|
||||
@Body() dto: UpdateRoomDto,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.update(Number(id), Number(merchant.id), dto);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export class MerchantRoomController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Param('id') id: number,
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.roomService.remove(Number(id), Number(merchant.id));
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantRoomController } from './room.controller';
|
||||
import { MerchantRoomService } from './room.service';
|
||||
import { Room } from '@/entities/room.entity';
|
||||
import { RoomCalendar } from '@/entities/room-calendar.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Room, RoomCalendar]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantRoomController],
|
||||
providers: [MerchantRoomService],
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '@/common/guards/roles.guard';
|
||||
import { Roles } from '@/common/decorators/roles.decorator';
|
||||
import { CurrentUser } from '@/common/decorators/current-user.decorator';
|
||||
import { StatisticsService } from './statistics.service';
|
||||
|
||||
@ApiTags('商家统计')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('merchant')
|
||||
@Controller('merchant/statistics')
|
||||
export class StatisticsSellerController {
|
||||
constructor(private readonly statisticsService: StatisticsService) {}
|
||||
|
||||
@Get('overview')
|
||||
@ApiOperation({ summary: '获取数据概览' })
|
||||
async getOverview(@CurrentUser() user: any) {
|
||||
return this.statisticsService.getOverview(user.merchantId);
|
||||
}
|
||||
|
||||
@Get('income-trend')
|
||||
@ApiOperation({ summary: '获取收入趋势' })
|
||||
async getIncomeTrend(
|
||||
@CurrentUser() user: any,
|
||||
@Query('type') type: 'day' | 'week' | 'month' = 'day',
|
||||
) {
|
||||
return this.statisticsService.getIncomeTrend(user.merchantId, type);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { SellerJwtAuthGuard } from '@/common/guards/seller-jwt-auth.guard';
|
||||
import { CurrentSeller } from '@/common/decorators/current-seller.decorator';
|
||||
import { MerchantStatisticsService } from './statistics.service';
|
||||
import { MerchantProfileService } from '../profile/profile.service';
|
||||
import { MerchantService } from '../merchant.service';
|
||||
|
||||
@ApiTags('商家统计')
|
||||
@ApiBearerAuth()
|
||||
@@ -12,13 +12,13 @@ import { MerchantProfileService } from '../profile/profile.service';
|
||||
export class MerchantStatisticsController {
|
||||
constructor(
|
||||
private readonly statisticsService: MerchantStatisticsService,
|
||||
private readonly profileService: MerchantProfileService,
|
||||
private readonly merchantService: MerchantService,
|
||||
) {}
|
||||
|
||||
@Get('overview')
|
||||
@ApiOperation({ summary: '获取数据概览' })
|
||||
async getOverview(@CurrentSeller('sub') sellerId: number) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.statisticsService.getOverview(merchant.id);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export class MerchantStatisticsController {
|
||||
@CurrentSeller('sub') sellerId: number,
|
||||
@Query('type') type: 'day' | 'week' | 'month' = 'day',
|
||||
) {
|
||||
const merchant = await this.profileService.findBySellerId(sellerId);
|
||||
const merchant = await this.merchantService.findBySellerId(sellerId);
|
||||
if (!merchant) throw new NotFoundException('店铺不存在');
|
||||
return this.statisticsService.getIncomeTrend(merchant.id, type);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MerchantStatisticsController } from './statistics.controller';
|
||||
import { MerchantStatisticsService } from './statistics.service';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { MerchantProfileModule } from '../profile/profile.module';
|
||||
import { MerchantModule } from '../merchant.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Order]),
|
||||
MerchantProfileModule,
|
||||
forwardRef(() => MerchantModule),
|
||||
],
|
||||
controllers: [MerchantStatisticsController],
|
||||
providers: [MerchantStatisticsService],
|
||||
|
||||
@@ -318,6 +318,9 @@ export class AccountService {
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
// 先确保商家账户存在(如果不存在会自动创建)
|
||||
await this.getMerchantAccount(merchantId);
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
@@ -48,7 +48,13 @@ export class SettlementService {
|
||||
|
||||
if (existingSettlements > 0) {
|
||||
this.logger.warn(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,跳过`);
|
||||
throw new BadRequestException(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,无法重复结算`);
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: `该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过`
|
||||
};
|
||||
}
|
||||
|
||||
// 查询所有已完成且截止到上周末的订单
|
||||
@@ -68,7 +74,13 @@ export class SettlementService {
|
||||
|
||||
if (allOrders.length === 0) {
|
||||
this.logger.log('没有需要结算的订单');
|
||||
throw new BadRequestException('该周期内没有需要结算的订单');
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: '该周期内没有需要结算的订单'
|
||||
};
|
||||
}
|
||||
|
||||
// 批量查询已结算的订单ID
|
||||
@@ -109,11 +121,18 @@ export class SettlementService {
|
||||
|
||||
if (Object.keys(ordersByMerchant).length === 0) {
|
||||
this.logger.log('没有需要结算的订单(所有订单都已结算)');
|
||||
throw new BadRequestException('没有需要结算的订单');
|
||||
return {
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
totalOrders: 0,
|
||||
skipped: true,
|
||||
reason: '没有需要结算的订单(所有订单都已结算)'
|
||||
};
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const errors: Array<{ merchantId: number; error: string }> = [];
|
||||
|
||||
for (const [merchantIdStr, merchantOrders] of Object.entries(ordersByMerchant)) {
|
||||
const merchantId = Number(merchantIdStr);
|
||||
@@ -123,13 +142,26 @@ export class SettlementService {
|
||||
this.logger.log(`商家 ${merchantId} 结算完成,订单数:${merchantOrders.length}`);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
this.logger.error(`商家 ${merchantId} 结算失败:${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorStack = error instanceof Error ? error.stack : '';
|
||||
this.logger.error(`商家 ${merchantId} 结算失败:${errorMessage}`);
|
||||
this.logger.error(`错误堆栈:${errorStack}`);
|
||||
failCount++;
|
||||
errors.push({
|
||||
merchantId,
|
||||
error: errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`周结算任务执行完成,成功:${successCount},失败:${failCount}`);
|
||||
return { successCount, failCount, totalOrders: allOrders.length - skippedCount };
|
||||
return {
|
||||
successCount,
|
||||
failCount,
|
||||
totalOrders: allOrders.length - skippedCount,
|
||||
skipped: false,
|
||||
errors: errors.length > 0 ? errors : undefined
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`周结算任务执行失败:${error.message}`);
|
||||
throw error;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MerchantWithdrawal } from '@/entities/merchant-withdrawal.entity';
|
||||
import { PlatformWithdrawal } from '@/entities/platform-withdrawal.entity';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { AccountService } from './account.service';
|
||||
import { TransactionService } from './transaction.service';
|
||||
|
||||
@@ -18,6 +19,8 @@ export class WithdrawalService {
|
||||
private merchantWithdrawalRepo: Repository<MerchantWithdrawal>,
|
||||
@InjectRepository(PlatformWithdrawal)
|
||||
private platformWithdrawalRepo: Repository<PlatformWithdrawal>,
|
||||
@InjectRepository(MerchantTransaction)
|
||||
private merchantTransactionRepo: Repository<MerchantTransaction>,
|
||||
private accountService: AccountService,
|
||||
private transactionService: TransactionService,
|
||||
private dataSource: DataSource,
|
||||
@@ -423,22 +426,51 @@ export class WithdrawalService {
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
// 扣减商家账户冻结金额
|
||||
const account = await queryRunner.manager.findOne(MerchantAccount, {
|
||||
where: { merchant_id: withdrawal.merchantId },
|
||||
lock: { mode: 'pessimistic_write' }
|
||||
});
|
||||
|
||||
// 扣减商家账户余额
|
||||
await this.accountService.deductMerchantBalance(
|
||||
withdrawal.merchantId,
|
||||
Number(withdrawal.actualAmount),
|
||||
transactionNo,
|
||||
'withdraw',
|
||||
withdrawal.id,
|
||||
withdrawal.withdrawNo,
|
||||
`商家提现 - ${withdrawal.bankName}`
|
||||
);
|
||||
if (!account) {
|
||||
throw new NotFoundException('商家账户不存在');
|
||||
}
|
||||
|
||||
const frozenBefore = Number(account.frozen_balance);
|
||||
const amount = Number(withdrawal.actualAmount);
|
||||
|
||||
if (frozenBefore < amount) {
|
||||
throw new BadRequestException('冻结金额不足');
|
||||
}
|
||||
|
||||
account.frozen_balance = frozenBefore - amount;
|
||||
account.total_withdraw = Number(account.total_withdraw) + amount;
|
||||
account.total_expense = Number(account.total_expense) + amount;
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
// 记录交易流水
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
const transaction = this.merchantTransactionRepo.create({
|
||||
transaction_no: transactionNo,
|
||||
merchant_id: withdrawal.merchantId,
|
||||
account_id: account.id,
|
||||
direction: 'expense',
|
||||
amount,
|
||||
balance_before: Number(account.balance),
|
||||
balance_after: Number(account.balance),
|
||||
transaction_type: '提现',
|
||||
business_type: 'withdraw',
|
||||
business_id: withdrawal.id,
|
||||
business_no: withdrawal.withdrawNo,
|
||||
remark: `商家提现 - ${withdrawal.bankName}`
|
||||
});
|
||||
await queryRunner.manager.save(transaction);
|
||||
|
||||
// 记录系统总账户提现
|
||||
await this.accountService.addSystemWithdrawal(
|
||||
Number(withdrawal.actualAmount),
|
||||
amount,
|
||||
transactionNo,
|
||||
'merchant_withdraw',
|
||||
withdrawal.id,
|
||||
@@ -611,7 +643,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -648,7 +680,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -680,7 +712,7 @@ export class WithdrawalService {
|
||||
const [list, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
return {
|
||||
list,
|
||||
items: list,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
|
||||
Reference in New Issue
Block a user