feat: 迭代
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
VITE_H5_API_BASE_URL=http://localhost:3000
|
||||
|
||||
# 小程序开发环境接口(真机调试使用局域网IP)
|
||||
VITE_MP_API_BASE_URL=http://192.168.0.111:3000
|
||||
VITE_MP_API_BASE_URL=http://localhost:3000
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface Withdrawal {
|
||||
accountType: 'alipay' | 'wechat';
|
||||
accountName: string;
|
||||
accountNumber: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'rejected';
|
||||
status: 'pending' | 'approved' | 'rejected' | 'paid';
|
||||
remark?: string;
|
||||
processedAt?: string;
|
||||
createdAt: string;
|
||||
@@ -49,10 +49,18 @@ export const walletApi = {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}) {
|
||||
return request<{ items: Withdrawal[]; total: number }>({
|
||||
url: '/api/app/finance/withdrawals',
|
||||
// 构建查询字符串
|
||||
const query = new URLSearchParams();
|
||||
if (params?.status) query.append('status', params.status);
|
||||
if (params?.page) query.append('page', params.page.toString());
|
||||
if (params?.pageSize) query.append('pageSize', params.pageSize.toString());
|
||||
|
||||
const queryString = query.toString();
|
||||
const url = queryString ? `/api/app/finance/withdrawals?${queryString}` : '/api/app/finance/withdrawals';
|
||||
|
||||
return request<{ list: Withdrawal[]; total: number }>({
|
||||
url,
|
||||
method: 'GET',
|
||||
data: params,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- 定位城市 -->
|
||||
<view class="locate-section">
|
||||
<view class="locate-label">
|
||||
<text class="locate-icon">📍</text>
|
||||
<u-icon name="map-fill" :size="20" color="#FF6B35" />
|
||||
<text class="locate-text">当前定位</text>
|
||||
</view>
|
||||
<view class="locate-city" @tap="selectLocatedCity">
|
||||
@@ -34,8 +34,13 @@
|
||||
</view>
|
||||
|
||||
<!-- 城市列表 -->
|
||||
<scroll-view scroll-y class="city-list">
|
||||
<view v-for="group in cityGroups" :key="group.letter" class="city-group">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="city-list"
|
||||
:scroll-top="scrollTop"
|
||||
scroll-with-animation
|
||||
>
|
||||
<view v-for="(group, index) in cityGroups" :key="group.letter" class="city-group" :data-index="index">
|
||||
<text class="group-letter">{{ group.letter }}</text>
|
||||
<view
|
||||
v-for="city in group.cities"
|
||||
@@ -47,12 +52,24 @@
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 字母索引 -->
|
||||
<view class="letter-index">
|
||||
<view
|
||||
v-for="group in cityGroups"
|
||||
:key="group.letter"
|
||||
:class="['letter-item', { active: currentLetter === group.letter }]"
|
||||
@tap="scrollToLetter(group.letter)"
|
||||
>
|
||||
{{ group.letter }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
city?: string;
|
||||
@@ -65,6 +82,15 @@ const emit = defineEmits<{
|
||||
const visible = ref(false);
|
||||
const selectedCity = ref(props.city || '上海');
|
||||
const locatedCity = ref('');
|
||||
const scrollTop = ref(0);
|
||||
const currentLetter = ref('');
|
||||
|
||||
// 监听 props.city 变化
|
||||
watch(() => props.city, (newCity) => {
|
||||
if (newCity) {
|
||||
selectedCity.value = newCity;
|
||||
}
|
||||
});
|
||||
|
||||
const hotCities = [
|
||||
'上海', '北京', '广州', '深圳',
|
||||
@@ -73,30 +99,33 @@ const hotCities = [
|
||||
];
|
||||
|
||||
const cityGroups = [
|
||||
{ letter: 'A', cities: ['安庆', '安阳', '鞍山', '安康'] },
|
||||
{ letter: 'B', cities: ['北京', '保定', '包头', '蚌埠', '宝鸡', '滨州'] },
|
||||
{ letter: 'C', cities: ['成都', '重庆', '长沙', '长春', '常州', '沧州', '承德'] },
|
||||
{ letter: 'D', cities: ['大连', '东莞', '大庆', '大同', '德州', '东营'] },
|
||||
{ letter: 'F', cities: ['福州', '佛山', '抚顺', '阜阳'] },
|
||||
{ letter: 'G', cities: ['广州', '贵阳', '桂林', '赣州'] },
|
||||
{ letter: 'H', cities: ['杭州', '哈尔滨', '合肥', '海口', '呼和浩特', '惠州', '邯郸'] },
|
||||
{ letter: 'J', cities: ['济南', '吉林', '嘉兴', '金华', '济宁', '荆州', '九江'] },
|
||||
{ letter: 'K', cities: ['昆明', '开封', '喀什'] },
|
||||
{ letter: 'L', cities: ['兰州', '洛阳', '连云港', '临沂', '柳州', '泸州'] },
|
||||
{ letter: 'M', cities: ['绵阳', '牡丹江', '茂名'] },
|
||||
{ letter: 'N', cities: ['南京', '南昌', '南宁', '宁波', '南通', '南阳'] },
|
||||
{ letter: 'P', cities: ['平顶山', '濮阳', '莆田'] },
|
||||
{ letter: 'Q', cities: ['青岛', '秦皇岛', '泉州', '齐齐哈尔'] },
|
||||
{ letter: 'S', cities: ['上海', '深圳', '沈阳', '石家庄', '苏州', '汕头', '绍兴', '三亚'] },
|
||||
{ letter: 'T', cities: ['天津', '太原', '唐山', '台州', '泰安'] },
|
||||
{ letter: 'W', cities: ['武汉', '无锡', '温州', '乌鲁木齐', '潍坊', '威海', '芜湖'] },
|
||||
{ letter: 'X', cities: ['西安', '厦门', '徐州', '襄阳', '咸阳', '新乡'] },
|
||||
{ letter: 'Y', cities: ['烟台', '扬州', '银川', '宜昌', '盐城', '岳阳'] },
|
||||
{ letter: 'Z', cities: ['郑州', '珠海', '淄博', '中山', '株洲', '漳州'] },
|
||||
{ letter: 'A', cities: ['阿坝', '阿克苏', '阿拉善', '阿勒泰', '安康', '安庆', '安顺', '安阳', '鞍山'] },
|
||||
{ letter: 'B', cities: ['白城', '白山', '白银', '百色', '蚌埠', '包头', '宝鸡', '保定', '保山', '北海', '北京', '本溪', '毕节', '滨州', '博尔塔拉'] },
|
||||
{ letter: 'C', cities: ['沧州', '昌都', '昌吉', '长春', '长沙', '长治', '常德', '常州', '巢湖', '朝阳', '潮州', '郴州', '成都', '承德', '池州', '赤峰', '崇左', '滁州', '楚雄'] },
|
||||
{ letter: 'D', cities: ['达州', '大连', '大庆', '大同', '大兴安岭', '丹东', '德宏', '德阳', '德州', '迪庆', '定西', '东莞', '东营', '鄂尔多斯', '鄂州', '恩施'] },
|
||||
{ letter: 'E', cities: ['鄂尔多斯', '鄂州', '恩施'] },
|
||||
{ letter: 'F', cities: ['防城港', '佛山', '福州', '抚顺', '抚州', '阜新', '阜阳'] },
|
||||
{ letter: 'G', cities: ['甘南', '甘孜', '赣州', '固原', '广安', '广元', '广州', '贵港', '贵阳', '桂林', '果洛'] },
|
||||
{ letter: 'H', cities: ['哈尔滨', '哈密', '海北', '海东', '海口', '海南', '海西', '邯郸', '汉中', '杭州', '毫州', '合肥', '河池', '河源', '菏泽', '贺州', '鹤壁', '鹤岗', '黑河', '衡水', '衡阳', '红河', '呼和浩特', '呼伦贝尔', '湖州', '葫芦岛', '怀化', '淮安', '淮北', '淮南', '黄冈', '黄南', '黄山', '黄石', '惠州'] },
|
||||
{ letter: 'J', cities: ['鸡西', '吉安', '吉林', '济南', '济宁', '济源', '佳木斯', '嘉兴', '嘉峪关', '江门', '焦作', '揭阳', '金昌', '金华', '锦州', '晋城', '晋中', '荆门', '荆州', '景德镇', '九江', '酒泉'] },
|
||||
{ letter: 'K', cities: ['开封', '喀什', '克拉玛依', '克孜勒苏', '昆明'] },
|
||||
{ letter: 'L', cities: ['来宾', '莱芜', '兰州', '廊坊', '乐山', '丽江', '丽水', '连云港', '凉山', '辽阳', '辽源', '聊城', '临沧', '临汾', '临夏', '临沂', '林芝', '柳州', '六安', '六盘水', '龙岩', '陇南', '娄底', '泸州', '吕梁', '洛阳', '漯河', '泸州'] },
|
||||
{ letter: 'M', cities: ['马鞍山', '茂名', '眉山', '梅州', '绵阳', '牡丹江'] },
|
||||
{ letter: 'N', cities: ['那曲', '南昌', '南充', '南京', '南宁', '南平', '南通', '南阳', '内江', '宁波', '宁德', '怒江'] },
|
||||
{ letter: 'P', cities: ['盘锦', '攀枝花', '平顶山', '平凉', '萍乡', '莆田', '濮阳', '普洱'] },
|
||||
{ letter: 'Q', cities: ['七台河', '齐齐哈尔', '黔东南', '黔南', '黔西南', '钦州', '秦皇岛', '青岛', '清远', '庆阳', '曲靖', '衢州', '泉州'] },
|
||||
{ letter: 'R', cities: ['日喀则', '日照', '荣成'] },
|
||||
{ letter: 'S', cities: ['三门峡', '三明', '三亚', '山南', '汕头', '汕尾', '商洛', '商丘', '上海', '上饶', '韶关', '邵阳', '绍兴', '深圳', '神农架', '沈阳', '十堰', '石家庄', '石嘴山', '双鸭山', '朔州', '四平', '松原', '苏州', '宿迁', '宿州', '绥化', '随州', '遂宁'] },
|
||||
{ letter: 'T', cities: ['台州', '太原', '泰安', '泰州', '唐山', '天津', '天水', '铁岭', '通化', '通辽', '铜川', '铜陵', '铜仁', '吐鲁番', '图木舒克'] },
|
||||
{ letter: 'W', cities: ['威海', '潍坊', '渭南', '温州', '文山', '乌海', '乌兰察布', '乌鲁木齐', '无锡', '吴忠', '芜湖', '梧州', '武汉', '武威'] },
|
||||
{ letter: 'X', cities: ['西安', '西宁', '西双版纳', '锡林郭勒', '厦门', '咸宁', '咸阳', '湘潭', '湘西', '襄阳', '孝感', '忻州', '新乡', '新余', '信阳', '兴安', '邢台', '徐州', '许昌', '宣城', 'xuancheng'] },
|
||||
{ letter: 'Y', cities: ['雅安', '烟台', '延安', '延边', '盐城', '扬州', '阳江', '阳泉', '伊春', '伊犁', '宜宾', '宜昌', '宜春', '益阳', '银川', '鹰潭', '营口', '永州', '榆林', '玉林', '玉树', '玉溪', '岳阳', '云浮', '运城'] },
|
||||
{ letter: 'Z', cities: ['枣庄', '湛江', '张家界', '张家口', '张掖', '漳州', '昭通', '肇庆', '镇江', '郑州', '中山', '中卫', '舟山', '周口', '株洲', '珠海', '驻马店', '资阳', '淄博', '自贡', '遵义'] },
|
||||
];
|
||||
|
||||
function open() {
|
||||
visible.value = true;
|
||||
selectedCity.value = props.city || '上海';
|
||||
}
|
||||
|
||||
function close() {
|
||||
@@ -110,16 +139,52 @@ function selectCity(city: string) {
|
||||
}
|
||||
|
||||
function selectLocatedCity() {
|
||||
if (locatedCity.value && locatedCity.value !== '定位失败') {
|
||||
if (locatedCity.value && locatedCity.value !== '定位失败' && locatedCity.value !== '定位中...') {
|
||||
selectCity(locatedCity.value);
|
||||
}
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: () => {
|
||||
locatedCity.value = '上海';
|
||||
function scrollToLetter(letter: string) {
|
||||
currentLetter.value = letter;
|
||||
|
||||
// 计算目标字母的索引
|
||||
const index = cityGroups.findIndex(group => group.letter === letter);
|
||||
if (index === -1) return;
|
||||
|
||||
// 计算滚动位置
|
||||
// 每个分组的高度 = 标题高度(约60rpx) + 城市数量 * 城市项高度(约80rpx)
|
||||
let scrollPosition = 0;
|
||||
for (let i = 0; i < index; i++) {
|
||||
const group = cityGroups[i];
|
||||
// 标题高度 + 城市列表高度
|
||||
scrollPosition += 60 + (group.cities.length * 80);
|
||||
}
|
||||
|
||||
// 转换 rpx 到 px (假设 1rpx = 0.5px,实际根据设备不同)
|
||||
scrollTop.value = scrollPosition * 0.5;
|
||||
|
||||
console.log(`滚动到字母 ${letter}, 索引 ${index}, 位置 ${scrollTop.value}px`);
|
||||
}
|
||||
|
||||
// 逆地理编码:将经纬度转换为城市名(调用后端接口)
|
||||
function reverseGeocode(latitude: number, longitude: number) {
|
||||
uni.request({
|
||||
url: `${import.meta.env?.VITE_MP_API_BASE_URL || 'http://localhost:3000'}/api/app/location/geocode`,
|
||||
method: 'GET',
|
||||
data: {
|
||||
latitude,
|
||||
longitude,
|
||||
},
|
||||
success: (res: any) => {
|
||||
if (res.statusCode === 200 && res.data?.data?.city) {
|
||||
const city = res.data.data.city;
|
||||
locatedCity.value = city;
|
||||
|
||||
// 自动更新首页城市
|
||||
emit('change', city);
|
||||
} else {
|
||||
locatedCity.value = '定位失败';
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
locatedCity.value = '定位失败';
|
||||
@@ -127,6 +192,36 @@ function getLocation() {
|
||||
});
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
locatedCity.value = '定位中...';
|
||||
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
// 获取到经纬度后,调用后端接口进行逆地理编码
|
||||
reverseGeocode(res.latitude, res.longitude);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('定位失败:', err);
|
||||
locatedCity.value = '定位失败';
|
||||
|
||||
// 提示用户授权
|
||||
if (err.errMsg && err.errMsg.includes('auth')) {
|
||||
uni.showModal({
|
||||
title: '需要定位权限',
|
||||
content: '请在设置中开启定位权限以获取当前城市',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLocation();
|
||||
});
|
||||
@@ -155,6 +250,8 @@ defineExpose({ open, close });
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.picker-header {
|
||||
@@ -183,18 +280,16 @@ defineExpose({ open, close });
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.locate-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.locate-icon {
|
||||
font-size: 28rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.locate-text {
|
||||
@@ -206,12 +301,17 @@ defineExpose({ open, close });
|
||||
background: #fff1eb;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
flex: 0 0 auto;
|
||||
max-width: 400rpx;
|
||||
}
|
||||
|
||||
.locate-city .city-text {
|
||||
font-size: 28rpx;
|
||||
color: #FF6B35;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hot-section {
|
||||
@@ -250,6 +350,7 @@ defineExpose({ open, close });
|
||||
.city-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.city-group {
|
||||
@@ -275,4 +376,45 @@ defineExpose({ open, close });
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.letter-index {
|
||||
position: absolute;
|
||||
right: 8rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rpx;
|
||||
z-index: 10;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 20rpx;
|
||||
padding: 8rpx 4rpx;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.letter-item {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
padding: 2rpx 6rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
min-width: 32rpx;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: #FF6B35;
|
||||
background: #fff1eb;
|
||||
border-radius: 4rpx;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #FF6B35;
|
||||
background: #fff1eb;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,15 @@
|
||||
"usingComponents": true,
|
||||
"optimization": {
|
||||
"subPackages": true
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "您的位置信息将用于为您推荐附近的酒店民宿"
|
||||
}
|
||||
},
|
||||
"requiredPrivateInfos": [
|
||||
"getLocation"
|
||||
]
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true,
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<text class="city-name">{{ searchParams.city }}</text>
|
||||
<u-icon name="arrow-down" :size="16" color="#fff" />
|
||||
</view>
|
||||
<view class="header-icon">
|
||||
<!-- <view class="header-icon">
|
||||
<u-icon name="bell" :size="22" color="#fff" />
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<view class="header-title">
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { getCashbackRecords } from '@/api/user/invite';
|
||||
import { formatRelativeDate } from '@/utils/date';
|
||||
|
||||
const records = ref<any[]>([]);
|
||||
const page = ref(1);
|
||||
@@ -234,23 +235,7 @@ function changeTime(value: string) {
|
||||
currentTime.value = value;
|
||||
}
|
||||
|
||||
function formatTime(dateStr: string) {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - d.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (days === 0) {
|
||||
return `今天 ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
} else if (days === 1) {
|
||||
return `昨天 ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
return `${d.getMonth() + 1}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
const formatTime = formatRelativeDate;
|
||||
|
||||
function formatOrderNo(orderNo: string) {
|
||||
if (!orderNo) return '';
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { getInviteRecords } from '@/api/user/invite';
|
||||
import { formatRelativeTime } from '@/utils/date';
|
||||
|
||||
const records = ref<any[]>([]);
|
||||
const page = ref(1);
|
||||
@@ -208,28 +209,7 @@ function changeFilter(value: string) {
|
||||
console.log('筛选后记录数:', filteredRecords.value.length);
|
||||
}
|
||||
|
||||
function formatTime(dateStr: string) {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - d.getTime();
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (days === 0) {
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
if (hours === 0) {
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
return minutes <= 0 ? '刚刚' : `${minutes}分钟前`;
|
||||
}
|
||||
return `${hours}小时前`;
|
||||
} else if (days === 1) {
|
||||
return '昨天';
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
const formatTime = formatRelativeTime;
|
||||
|
||||
function getOrderBadgeClass(count: number) {
|
||||
if (count === 0) return 'badge-gray';
|
||||
|
||||
@@ -149,9 +149,6 @@
|
||||
<view class="room-content">
|
||||
<view class="room-header">
|
||||
<text class="room-name">{{ room.name }}</text>
|
||||
<view class="room-type-tag">
|
||||
<text class="type-text">{{ room.type }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="room-specs">
|
||||
@@ -1082,18 +1079,6 @@ onMounted(() => {
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.room-type-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #F0F7FF;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.type-text {
|
||||
font-size: 20rpx;
|
||||
color: #4A90E2;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.room-specs {
|
||||
display: flex;
|
||||
|
||||
@@ -278,6 +278,7 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { getOrderDetail, cancelOrder, payOrder } from '@/api/user/order';
|
||||
import { createReview, checkOrderReviewed } from '@/api/user/review';
|
||||
import { formatDate, formatDateTime } from '@/utils/date';
|
||||
|
||||
const paymentMethodLabels: Record<string, string> = {
|
||||
wechat: '微信支付',
|
||||
@@ -379,18 +380,6 @@ function goRoomDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatDateTime(dateStr: string): string {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function maskIdCard(idCard: string): string {
|
||||
if (!idCard || idCard.length < 8) return idCard;
|
||||
return idCard.substring(0, 6) + '********' + idCard.substring(idCard.length - 4);
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import * as orderApi from '@/api/user/order';
|
||||
import { formatDateShort } from '@/utils/date';
|
||||
|
||||
interface Tab {
|
||||
label: string;
|
||||
@@ -228,11 +229,7 @@ function getStatusText(status: string): string {
|
||||
// 格式化日期范围
|
||||
function formatDateRange(checkIn: string, checkOut: string): string {
|
||||
if (!checkIn || !checkOut) return '';
|
||||
const formatDate = (dateStr: string) => {
|
||||
const d = new Date(dateStr);
|
||||
return `${d.getMonth() + 1}/${d.getDate()}`;
|
||||
};
|
||||
return `${formatDate(checkIn)} - ${formatDate(checkOut)}`;
|
||||
return `${formatDateShort(checkIn)} - ${formatDateShort(checkOut)}`;
|
||||
}
|
||||
|
||||
// 获取操作按钮
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { getSellerOrders, confirmOrder, rejectOrder, checkinOrder } from '@/api/seller/order';
|
||||
import LoadingState from '@/components/base/LoadingState.vue';
|
||||
import { formatDateShort } from '@/utils/date';
|
||||
|
||||
const tabs = [
|
||||
{ label: '全部', value: '', badge: 0 },
|
||||
@@ -319,9 +320,7 @@ function getStatusText(status: string): string {
|
||||
|
||||
function formatDateRange(checkIn: string, checkOut: string): string {
|
||||
if (!checkIn || !checkOut) return '日期待定';
|
||||
const inDate = checkIn.split('T')[0].substring(5).replace('-', '/');
|
||||
const outDate = checkOut.split('T')[0].substring(5).replace('-', '/');
|
||||
return `${inDate} - ${outDate}`;
|
||||
return `${formatDateShort(checkIn)} - ${formatDateShort(checkOut)}`;
|
||||
}
|
||||
|
||||
function formatTime(time: string): string {
|
||||
@@ -338,11 +337,7 @@ function formatTime(time: string): string {
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
if (days < 7) return `${days}天前`;
|
||||
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hour = date.getHours().toString().padStart(2, '0');
|
||||
const minute = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${month}/${day} ${hour}:${minute}`;
|
||||
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function getEmptyText(): string {
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { financeApi } from '@/api/seller/finance';
|
||||
import { formatDate, formatDateTime } from '@/utils/date';
|
||||
|
||||
const loading = ref(true);
|
||||
const settlement = ref<any>(null);
|
||||
@@ -163,18 +164,6 @@ function formatPeriod(startDate: string, endDate: string): string {
|
||||
return `${start.getFullYear()}/${start.getMonth() + 1}/${start.getDate()} - ${end.getMonth() + 1}/${end.getDate()}`;
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatDateTime(dateStr: string): string {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatMoney(value: number | string): string {
|
||||
const num = Number(value || 0);
|
||||
return num.toFixed(2);
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { financeApi } from '@/api/seller/finance';
|
||||
import { formatRelativeDate } from '@/utils/date';
|
||||
|
||||
const loading = ref(true);
|
||||
const transactions = ref<any[]>([]);
|
||||
@@ -203,21 +204,7 @@ function formatMoney(value: number | string): string {
|
||||
return num.toFixed(2);
|
||||
}
|
||||
|
||||
function formatTime(time: string): string {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const day = 24 * 60 * 60 * 1000;
|
||||
|
||||
if (diff < day && date.getDate() === now.getDate()) {
|
||||
return `今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
} else if (diff < 2 * day && date.getDate() === now.getDate() - 1) {
|
||||
return `昨天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
} else {
|
||||
return `${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
}
|
||||
const formatTime = formatRelativeDate;
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
|
||||
@@ -35,29 +35,45 @@
|
||||
class="withdrawal-item"
|
||||
@tap="goDetail(item.id)"
|
||||
>
|
||||
<view class="item-header">
|
||||
<view class="header-left">
|
||||
<view class="account-info">
|
||||
<u-icon :name="getAccountIcon(item.accountType)" :size="20" color="#FF6B35" />
|
||||
<text class="account-text">{{ getAccountLabel(item.accountType) }}</text>
|
||||
</view>
|
||||
<!-- 顶部:金额和状态 -->
|
||||
<view class="item-top">
|
||||
<view class="amount-section">
|
||||
<text class="amount-label">提现金额</text>
|
||||
<text class="amount-value">¥{{ formatMoney(item.amount) }}</text>
|
||||
</view>
|
||||
<view class="top-right">
|
||||
<view :class="['status-badge', `status-${item.status}`]">
|
||||
<text class="status-text">{{ getStatusLabel(item.status) }}</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" :size="16" color="#CCCCCC" />
|
||||
</view>
|
||||
<u-icon name="arrow-right" :size="16" color="#CCCCCC" />
|
||||
</view>
|
||||
|
||||
<view class="item-amount">
|
||||
<text class="amount-value">¥{{ formatMoney(item.amount) }}</text>
|
||||
<!-- 中间:账户信息 -->
|
||||
<view class="item-middle">
|
||||
<view v-if="item.accountType" class="info-row">
|
||||
<text class="info-label">提现方式</text>
|
||||
<view class="info-value-row">
|
||||
<u-icon v-if="getAccountIcon(item.accountType)" :name="getAccountIcon(item.accountType)" :size="16" color="#FF6B35" />
|
||||
<text class="info-value">{{ getAccountLabel(item.accountType) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.accountName || item.accountNumber" class="info-row">
|
||||
<text class="info-label">账户信息</text>
|
||||
<text class="info-value">{{ item.accountName }} {{ maskAccount(item.accountNumber) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请时间</text>
|
||||
<text class="info-value">{{ formatTime(item.createdAt) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-info">
|
||||
<text class="info-text">{{ item.accountName }} {{ maskAccount(item.accountNumber) }}</text>
|
||||
<text class="info-time">{{ formatTime(item.createdAt) }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="item.remark" class="item-remark">
|
||||
<!-- 底部:备注 -->
|
||||
<view v-if="item.remark" class="item-bottom">
|
||||
<view class="remark-label">
|
||||
<u-icon name="info-circle" :size="14" color="#FF6B35" />
|
||||
<text class="remark-label-text">备注</text>
|
||||
</view>
|
||||
<text class="remark-text">{{ item.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -95,6 +111,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { financeApi } from '@/api/seller/finance';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
|
||||
const loading = ref(true);
|
||||
const withdrawals = ref<any[]>([]);
|
||||
@@ -106,8 +123,8 @@ const hasMore = ref(true);
|
||||
const statusList = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待审核', value: 'pending' },
|
||||
{ label: '处理中', value: 'processing' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已通过', value: 'approved' },
|
||||
{ label: '已打款', value: 'paid' },
|
||||
{ label: '已拒绝', value: 'rejected' },
|
||||
];
|
||||
|
||||
@@ -134,7 +151,7 @@ async function fetchWithdrawals(reset = true) {
|
||||
}
|
||||
|
||||
const res = await financeApi.getWithdrawals(params);
|
||||
const newData = res.data.items || [];
|
||||
const newData = res.data.list || [];
|
||||
|
||||
if (reset) {
|
||||
withdrawals.value = newData;
|
||||
@@ -160,8 +177,8 @@ function loadMore() {
|
||||
function getStatusLabel(status: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: '待审核',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
approved: '已通过',
|
||||
paid: '已打款',
|
||||
rejected: '已拒绝',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
@@ -176,13 +193,13 @@ function getAccountLabel(type: string): string {
|
||||
return typeMap[type] || type;
|
||||
}
|
||||
|
||||
function getAccountIcon(type: string): string {
|
||||
function getAccountIcon(type: string): string | null {
|
||||
const iconMap: Record<string, string> = {
|
||||
alipay: 'pay-circle',
|
||||
wechat: 'weixin',
|
||||
bank: 'credit-card',
|
||||
};
|
||||
return iconMap[type] || 'help-circle';
|
||||
return iconMap[type] || null;
|
||||
}
|
||||
|
||||
function maskAccount(account: string): string {
|
||||
@@ -197,11 +214,7 @@ function formatMoney(value: number | string): string {
|
||||
return num.toFixed(2);
|
||||
}
|
||||
|
||||
function formatTime(time: string): string {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
const formatTime = formatDateTime;
|
||||
|
||||
function goDetail(id: number) {
|
||||
// 提现详情页面可以后续添加
|
||||
@@ -307,76 +320,86 @@ function goBack() {
|
||||
|
||||
.withdrawal-item {
|
||||
margin: 0 24rpx 16rpx;
|
||||
padding: 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.item-header {
|
||||
/* 顶部区域 */
|
||||
.item-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
background: linear-gradient(135deg, #FFF8F5 0%, #FFFFFF 100%);
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
.amount-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
.amount-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #FF6B35;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.account-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.status-pending {
|
||||
background: #FFF7E6;
|
||||
background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
|
||||
}
|
||||
|
||||
&.status-processing {
|
||||
background: #E6F7FF;
|
||||
&.status-approved {
|
||||
background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
|
||||
}
|
||||
|
||||
&.status-completed {
|
||||
background: #F6FFED;
|
||||
&.status-paid {
|
||||
background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
|
||||
}
|
||||
|
||||
&.status-rejected {
|
||||
background: #FFF1F0;
|
||||
background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
.status-pending & {
|
||||
color: #FAAD14;
|
||||
color: #FA8C16;
|
||||
}
|
||||
|
||||
.status-processing & {
|
||||
.status-approved & {
|
||||
color: #1890FF;
|
||||
}
|
||||
|
||||
.status-completed & {
|
||||
.status-paid & {
|
||||
color: #52C41A;
|
||||
}
|
||||
|
||||
@@ -385,45 +408,63 @@ function goBack() {
|
||||
}
|
||||
}
|
||||
|
||||
.item-amount {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
/* 中间区域 */
|
||||
.item-middle {
|
||||
padding: 20rpx 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12rpx;
|
||||
border-top: 1rpx solid #F0F0F0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
.info-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 26rpx;
|
||||
color: #1A1A1A;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 底部区域 */
|
||||
.item-bottom {
|
||||
padding: 16rpx 24rpx 20rpx;
|
||||
background: #FFFBF5;
|
||||
border-top: 1rpx solid #FFE7BA;
|
||||
}
|
||||
|
||||
.remark-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.remark-label-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.info-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-remark {
|
||||
margin-top: 12rpx;
|
||||
padding: 12rpx;
|
||||
background: #FFF7E6;
|
||||
border-radius: 8rpx;
|
||||
color: #FF6B35;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
font-size: 24rpx;
|
||||
color: #FAAD14;
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { verifyIdentity, getVerifyStatus } from '@/api/user/auth';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
|
||||
const verifyStatus = ref<any>({
|
||||
isVerified: false,
|
||||
@@ -151,11 +152,8 @@ function maskName(name: string) {
|
||||
return name[0] + '*'.repeat(name.length - 1);
|
||||
}
|
||||
|
||||
function formatDate(date: string | null) {
|
||||
if (!date) return '';
|
||||
const d = new Date(date);
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
}
|
||||
const formatDate = formatDateTime;
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -25,28 +25,42 @@
|
||||
:key="item.id"
|
||||
class="withdrawal-item"
|
||||
>
|
||||
<view class="item-header">
|
||||
<view class="header-left">
|
||||
<view class="account-info">
|
||||
<u-icon :name="getAccountIcon(item.accountType)" :size="20" color="#FF6B35" />
|
||||
<text class="account-text">{{ getAccountLabel(item.accountType) }}</text>
|
||||
</view>
|
||||
<view :class="['status-badge', `status-${item.status}`]">
|
||||
<text class="status-text">{{ getStatusLabel(item.status) }}</text>
|
||||
</view>
|
||||
<!-- 顶部:金额和状态 -->
|
||||
<view class="item-top">
|
||||
<view class="amount-section">
|
||||
<text class="amount-label">提现金额</text>
|
||||
<text class="amount-value">¥{{ formatMoney(item.amount) }}</text>
|
||||
</view>
|
||||
<view :class="['status-badge', `status-${item.status}`]">
|
||||
<text class="status-text">{{ getStatusLabel(item.status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-amount">
|
||||
<text class="amount-value">¥{{ formatMoney(item.amount) }}</text>
|
||||
<!-- 中间:账户信息 -->
|
||||
<view class="item-middle">
|
||||
<view v-if="item.accountType" class="info-row">
|
||||
<text class="info-label">提现方式</text>
|
||||
<view class="info-value-row">
|
||||
<u-icon v-if="getAccountIcon(item.accountType)" :name="getAccountIcon(item.accountType)" :size="16" color="#FF6B35" />
|
||||
<text class="info-value">{{ getAccountLabel(item.accountType) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.accountName || item.accountNumber" class="info-row">
|
||||
<text class="info-label">账户信息</text>
|
||||
<text class="info-value">{{ item.accountName }} {{ maskAccount(item.accountNumber) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">申请时间</text>
|
||||
<text class="info-value">{{ formatTime(item.createdAt) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-info">
|
||||
<text class="info-text">{{ item.accountName }} {{ maskAccount(item.accountNumber) }}</text>
|
||||
<text class="info-time">{{ formatTime(item.createdAt) }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="item.remark" class="item-remark">
|
||||
<!-- 底部:备注 -->
|
||||
<view v-if="item.remark" class="item-bottom">
|
||||
<view class="remark-label">
|
||||
<u-icon name="info-circle" :size="14" color="#FF6B35" />
|
||||
<text class="remark-label-text">备注</text>
|
||||
</view>
|
||||
<text class="remark-text">{{ item.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -84,6 +98,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { walletApi } from '@/api/user/wallet';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
|
||||
const loading = ref(true);
|
||||
const withdrawals = ref<any[]>([]);
|
||||
@@ -95,8 +110,8 @@ const hasMore = ref(true);
|
||||
const statusList = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待审核', value: 'pending' },
|
||||
{ label: '处理中', value: 'processing' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已通过', value: 'approved' },
|
||||
{ label: '已打款', value: 'paid' },
|
||||
{ label: '已拒绝', value: 'rejected' },
|
||||
];
|
||||
|
||||
@@ -123,7 +138,7 @@ async function fetchWithdrawals(reset = true) {
|
||||
}
|
||||
|
||||
const res = await walletApi.getWithdrawals(params);
|
||||
const newData = res.data.items || [];
|
||||
const newData = res.data.list || [];
|
||||
|
||||
if (reset) {
|
||||
withdrawals.value = newData;
|
||||
@@ -149,8 +164,8 @@ function loadMore() {
|
||||
function getStatusLabel(status: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
pending: '待审核',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
approved: '已通过',
|
||||
paid: '已打款',
|
||||
rejected: '已拒绝',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
@@ -164,12 +179,12 @@ function getAccountLabel(type: string): string {
|
||||
return typeMap[type] || type;
|
||||
}
|
||||
|
||||
function getAccountIcon(type: string): string {
|
||||
function getAccountIcon(type: string): string | null {
|
||||
const iconMap: Record<string, string> = {
|
||||
alipay: 'pay-circle',
|
||||
wechat: 'weixin',
|
||||
};
|
||||
return iconMap[type] || 'help-circle';
|
||||
return iconMap[type] || null;
|
||||
}
|
||||
|
||||
function maskAccount(account: string): string {
|
||||
@@ -184,11 +199,7 @@ function formatMoney(value: number | string): string {
|
||||
return num.toFixed(2);
|
||||
}
|
||||
|
||||
function formatTime(time: string): string {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
const formatTime = formatDateTime;
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
@@ -252,70 +263,74 @@ function goBack() {
|
||||
|
||||
.withdrawal-item {
|
||||
margin: 0 24rpx 16rpx;
|
||||
padding: 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.item-header {
|
||||
/* 顶部区域 */
|
||||
.item-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
background: linear-gradient(135deg, #FFF8F5 0%, #FFFFFF 100%);
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
.amount-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.account-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
.amount-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #FF6B35;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.status-pending {
|
||||
background: #FFF7E6;
|
||||
background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
|
||||
}
|
||||
|
||||
&.status-processing {
|
||||
background: #E6F7FF;
|
||||
&.status-approved {
|
||||
background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
|
||||
}
|
||||
|
||||
&.status-completed {
|
||||
background: #F6FFED;
|
||||
&.status-paid {
|
||||
background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
|
||||
}
|
||||
|
||||
&.status-rejected {
|
||||
background: #FFF1F0;
|
||||
background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
.status-pending & {
|
||||
color: #FAAD14;
|
||||
color: #FA8C16;
|
||||
}
|
||||
|
||||
.status-processing & {
|
||||
.status-approved & {
|
||||
color: #1890FF;
|
||||
}
|
||||
|
||||
.status-completed & {
|
||||
.status-paid & {
|
||||
color: #52C41A;
|
||||
}
|
||||
|
||||
@@ -324,45 +339,63 @@ function goBack() {
|
||||
}
|
||||
}
|
||||
|
||||
.item-amount {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
/* 中间区域 */
|
||||
.item-middle {
|
||||
padding: 20rpx 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12rpx;
|
||||
border-top: 1rpx solid #F0F0F0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
.info-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 26rpx;
|
||||
color: #1A1A1A;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 底部区域 */
|
||||
.item-bottom {
|
||||
padding: 16rpx 24rpx 20rpx;
|
||||
background: #FFFBF5;
|
||||
border-top: 1rpx solid #FFE7BA;
|
||||
}
|
||||
|
||||
.remark-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.remark-label-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.info-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-remark {
|
||||
margin-top: 12rpx;
|
||||
padding: 12rpx;
|
||||
background: #FFF7E6;
|
||||
border-radius: 8rpx;
|
||||
color: #FF6B35;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
font-size: 24rpx;
|
||||
color: #FAAD14;
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
|
||||
@@ -5,13 +5,43 @@
|
||||
/**
|
||||
* 格式化日期为 YYYY-MM-DD
|
||||
*/
|
||||
export function formatDate(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
export function formatDate(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间为 YYYY-MM-DD HH:mm
|
||||
*/
|
||||
export function formatDateTime(dateTime: Date | string): string {
|
||||
if (!dateTime) return '';
|
||||
const d = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
const hours = String(d.getHours()).padStart(2, '0');
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间为 YYYY-MM-DD HH:mm:ss
|
||||
*/
|
||||
export function formatDateTimeFull(dateTime: Date | string): string {
|
||||
if (!dateTime) return '';
|
||||
const d = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
const hours = String(d.getHours()).padStart(2, '0');
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期为 M月D日
|
||||
*/
|
||||
@@ -20,6 +50,15 @@ export function formatDateShort(date: Date | string): string {
|
||||
return `${d.getMonth() + 1}月${d.getDate()}日`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期范围为 M月D日 - M月D日
|
||||
*/
|
||||
export function formatDateRange(startDate: Date | string, endDate: Date | string): string {
|
||||
const start = typeof startDate === 'string' ? new Date(startDate) : startDate;
|
||||
const end = typeof endDate === 'string' ? new Date(endDate) : endDate;
|
||||
return `${formatDateShort(start)} - ${formatDateShort(end)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日期描述(今天、明天、后天、周X)
|
||||
*/
|
||||
@@ -64,3 +103,51 @@ export function getDefaultDates(): { today: string; tomorrow: string } {
|
||||
tomorrow: formatDate(tomorrow),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今天的日期字符串 YYYY-MM-DD
|
||||
*/
|
||||
export function getTodayString(): string {
|
||||
return formatDate(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化相对时间(刚刚、X分钟前、X小时前、X天前)
|
||||
*/
|
||||
export function formatRelativeTime(dateTime: Date | string): string {
|
||||
if (!dateTime) return '';
|
||||
const d = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - d.getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 1) return '刚刚';
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
if (days < 7) return `${days}天前`;
|
||||
|
||||
return formatDateTime(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化相对时间(今天、昨天、日期)
|
||||
*/
|
||||
export function formatRelativeDate(dateTime: Date | string): string {
|
||||
if (!dateTime) return '';
|
||||
const d = typeof dateTime === 'string' ? new Date(dateTime) : dateTime;
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - d.getTime();
|
||||
const day = 24 * 60 * 60 * 1000;
|
||||
|
||||
const timeStr = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
if (diff < day && d.getDate() === now.getDate()) {
|
||||
return `今天 ${timeStr}`;
|
||||
} else if (diff < 2 * day && d.getDate() === now.getDate() - 1) {
|
||||
return `昨天 ${timeStr}`;
|
||||
} else {
|
||||
return `${d.getMonth() + 1}-${d.getDate()} ${timeStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
function getBaseURL(): string {
|
||||
// 判断当前运行平台
|
||||
// #ifdef H5
|
||||
return 'http://localhost:3000';
|
||||
return import.meta.env?.VITE_H5_API_BASE_URL as string || 'http://localhost:3000';
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
return 'http://192.168.0.111:3000';
|
||||
return import.meta.env?.VITE_MP_API_BASE_URL as string || 'http://localhost:3000';
|
||||
// #endif
|
||||
|
||||
// 其他平台默认值
|
||||
return 'http://192.168.0.111:3000';
|
||||
return import.meta.env?.VITE_MP_API_BASE_URL as string || 'http://localhost:3000';
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
|
||||
Reference in New Issue
Block a user