更新项目配置和添加小程序模块
- 修改 ArticleController.java - 更新 application.yml 配置 - 更新 frontend/.env.production 环境配置 - 添加 single_uniapp22miao 小程序模块 - 添加 logs 目录
This commit is contained in:
437
single_uniapp22miao/pages/sub-pages/address/detail.vue
Normal file
437
single_uniapp22miao/pages/sub-pages/address/detail.vue
Normal file
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<view class="address-detail-page">
|
||||
<view class="form-container">
|
||||
<!-- 收货人 -->
|
||||
<view class="form-item">
|
||||
<view class="label">收货人<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.name"
|
||||
placeholder="请输入收货人姓名"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<view class="form-item">
|
||||
<view class="label">手机号<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 所在地区 -->
|
||||
<view class="form-item" @click="showRegionPicker">
|
||||
<view class="label">所在地区<text class="required">*</text></view>
|
||||
<view class="picker-value">
|
||||
<text v-if="regionText" class="text">{{ regionText }}</text>
|
||||
<text v-else class="placeholder">请选择省/市/区</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详细地址 -->
|
||||
<view class="form-item">
|
||||
<view class="label">详细地址<text class="required">*</text></view>
|
||||
<textarea
|
||||
v-model="formData.detail"
|
||||
placeholder="请输入详细地址(街道、楼牌号等)"
|
||||
class="textarea"
|
||||
maxlength="100"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 设为默认 -->
|
||||
<view class="form-item checkbox-item">
|
||||
<checkbox-group @change="onDefaultChange">
|
||||
<label>
|
||||
<checkbox
|
||||
:checked="formData.is_default == 1"
|
||||
color="#FF4757"
|
||||
/>
|
||||
<text>设为默认地址</text>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="btn-container">
|
||||
<button
|
||||
class="save-btn"
|
||||
:disabled="!canSave"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 地区选择器 -->
|
||||
<picker-view
|
||||
v-if="showPicker"
|
||||
:value="pickerValue"
|
||||
@change="onPickerChange"
|
||||
class="picker-view"
|
||||
>
|
||||
<picker-view-column>
|
||||
<view v-for="(item, index) in provinces" :key="index">{{ item.name }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view v-for="(item, index) in cities" :key="index">{{ item.name }}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view v-for="(item, index) in districts" :key="index">{{ item.name }}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
|
||||
<!-- 遮罩层 -->
|
||||
<view class="mask" v-if="showPicker" @click="hidePicker">
|
||||
<view class="picker-toolbar" @click.stop>
|
||||
<text class="cancel" @click="hidePicker">取消</text>
|
||||
<text class="title">选择地区</text>
|
||||
<text class="confirm" @click="confirmRegion">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import regionData from '@/static/data/region.js'; // 需要地区数据文件
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addressId: null,
|
||||
formData: {
|
||||
name: '',
|
||||
mobile: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detail: '',
|
||||
is_default: 0
|
||||
},
|
||||
showPicker: false,
|
||||
pickerValue: [0, 0, 0],
|
||||
provinces: [],
|
||||
cities: [],
|
||||
districts: [],
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
regionText() {
|
||||
if (this.formData.province && this.formData.city && this.formData.district) {
|
||||
return `${this.formData.province} ${this.formData.city} ${this.formData.district}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
canSave() {
|
||||
return this.formData.name &&
|
||||
this.formData.mobile.length === 11 &&
|
||||
this.formData.province &&
|
||||
this.formData.city &&
|
||||
this.formData.district &&
|
||||
this.formData.detail;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.addressId = options.id;
|
||||
this.initRegionData();
|
||||
|
||||
if (this.addressId) {
|
||||
this.loadAddressDetail();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化地区数据
|
||||
initRegionData() {
|
||||
// 这里应该加载完整的地区数据
|
||||
// 简化示例,实际应从 region.js 加载
|
||||
this.provinces = [
|
||||
{ name: '北京市', cities: [] },
|
||||
{ name: '上海市', cities: [] },
|
||||
// ... 更多省份
|
||||
];
|
||||
this.updateCities();
|
||||
},
|
||||
|
||||
// 更新城市列表
|
||||
updateCities() {
|
||||
if (this.provinces[this.pickerValue[0]]) {
|
||||
this.cities = this.provinces[this.pickerValue[0]].cities || [];
|
||||
this.updateDistricts();
|
||||
}
|
||||
},
|
||||
|
||||
// 更新区县列表
|
||||
updateDistricts() {
|
||||
if (this.cities[this.pickerValue[1]]) {
|
||||
this.districts = this.cities[this.pickerValue[1]].districts || [];
|
||||
}
|
||||
},
|
||||
|
||||
// 加载地址详情
|
||||
async loadAddressDetail() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/address/detail', {
|
||||
id: this.addressId
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载地址详情失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 显示地区选择器
|
||||
showRegionPicker() {
|
||||
this.showPicker = true;
|
||||
},
|
||||
|
||||
// 隐藏选择器
|
||||
hidePicker() {
|
||||
this.showPicker = false;
|
||||
},
|
||||
|
||||
// 选择器变化
|
||||
onPickerChange(e) {
|
||||
this.pickerValue = e.detail.value;
|
||||
this.updateCities();
|
||||
},
|
||||
|
||||
// 确认地区选择
|
||||
confirmRegion() {
|
||||
const province = this.provinces[this.pickerValue[0]];
|
||||
const city = this.cities[this.pickerValue[1]];
|
||||
const district = this.districts[this.pickerValue[2]];
|
||||
|
||||
this.formData.province = province.name;
|
||||
this.formData.city = city.name;
|
||||
this.formData.district = district.name;
|
||||
|
||||
this.hidePicker();
|
||||
},
|
||||
|
||||
// 默认地址变化
|
||||
onDefaultChange(e) {
|
||||
this.formData.is_default = e.detail.value.length > 0 ? 1 : 0;
|
||||
},
|
||||
|
||||
// 保存
|
||||
async handleSave() {
|
||||
if (!/^1[3-9]\d{9}$/.test(this.formData.mobile)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const url = this.addressId ? '/api/address/update' : '/api/address/insert';
|
||||
const data = {
|
||||
...this.formData,
|
||||
id: this.addressId
|
||||
};
|
||||
|
||||
const res = await this.$http.post(url, data);
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.address-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.required {
|
||||
color: #FF4757;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
min-height: 150rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
&.checkbox-item {
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
checkbox {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.picker-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.cancel,
|
||||
.confirm {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
color: #FF4757;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 500rpx;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
|
||||
317
single_uniapp22miao/pages/sub-pages/address/index.vue
Normal file
317
single_uniapp22miao/pages/sub-pages/address/index.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<view class="address-list-page">
|
||||
<!-- 地址列表 -->
|
||||
<view class="address-list">
|
||||
<view
|
||||
v-for="(item, index) in addressList"
|
||||
:key="item.id"
|
||||
class="address-item"
|
||||
>
|
||||
<view class="item-content" @click="selectAddress(item)">
|
||||
<view class="top-row">
|
||||
<view class="user-info">
|
||||
<text class="name">{{ item.name }}</text>
|
||||
<text class="phone">{{ item.mobile }}</text>
|
||||
</view>
|
||||
<view class="default-tag" v-if="item.is_default == 1">默认</view>
|
||||
</view>
|
||||
|
||||
<view class="address-detail">
|
||||
<text class="icon">📍</text>
|
||||
<text class="text">{{ item.province }} {{ item.city }} {{ item.district }} {{ item.detail }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-actions">
|
||||
<button class="action-btn" @click="editAddress(item)">编辑</button>
|
||||
<button class="action-btn delete" @click="deleteAddress(item, index)">删除</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
v-if="item.is_default != 1"
|
||||
@click="setDefault(item)"
|
||||
>
|
||||
设为默认
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="addressList.length === 0 && !loading">
|
||||
<text class="icon">📍</text>
|
||||
<text class="text">暂无收货地址</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加地址按钮 -->
|
||||
<view class="add-btn-container">
|
||||
<button class="add-btn" @click="addAddress">
|
||||
<text class="icon">+</text>
|
||||
<text>新增收货地址</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addressList: [],
|
||||
loading: false,
|
||||
fromOrderPage: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.fromOrderPage = options.from === 'order';
|
||||
this.loadAddressList();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 从编辑页返回时刷新列表
|
||||
if (this.addressList.length > 0) {
|
||||
this.loadAddressList();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载地址列表
|
||||
async loadAddressList() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/address/list');
|
||||
if (res.code === 0) {
|
||||
this.addressList = res.data.list || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载地址列表失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 选择地址(用于订单页)
|
||||
selectAddress(item) {
|
||||
if (this.fromOrderPage) {
|
||||
uni.$emit('selectAddress', item);
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
// 新增地址
|
||||
addAddress() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/address/detail'
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑地址
|
||||
editAddress(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/address/detail?id=${item.id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 删除地址
|
||||
deleteAddress(item, index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该地址吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await this.$http.post('/api/address/delete', {
|
||||
id: item.id
|
||||
});
|
||||
|
||||
if (result.code === 0) {
|
||||
this.addressList.splice(index, 1);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 设为默认
|
||||
async setDefault(item) {
|
||||
try {
|
||||
const res = await this.$http.post('/api/address/default', {
|
||||
id: item.id
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
// 更新列表
|
||||
this.addressList.forEach(addr => {
|
||||
addr.is_default = addr.id === item.id ? 1 : 0;
|
||||
});
|
||||
|
||||
uni.showToast({
|
||||
title: '设置成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '设置失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.address-list-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.address-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.item-content {
|
||||
padding: 30rpx;
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.default-tag {
|
||||
padding: 4rpx 16rpx;
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address-detail {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
border-right: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
color: #FF4757;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
72
single_uniapp22miao/pages/sub-pages/agreement/contract.vue
Normal file
72
single_uniapp22miao/pages/sub-pages/agreement/contract.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<view class="contract-page">
|
||||
<view class="content">
|
||||
<view class="title">{{ agreementTitle }}</view>
|
||||
<view class="article">
|
||||
<rich-text :nodes="agreementContent"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
type: '',
|
||||
agreementTitle: '',
|
||||
agreementContent: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.type = options.type || 'user';
|
||||
this.loadAgreement();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadAgreement() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/setting/agreement', {
|
||||
type: this.type
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.agreementTitle = res.data.title;
|
||||
this.agreementContent = res.data.content;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载协议失败:', error);
|
||||
// 显示默认内容
|
||||
this.agreementTitle = '用户协议';
|
||||
this.agreementContent = '<p>协议内容加载中...</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract-page {
|
||||
min-height: 100vh;
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.article {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
65
single_uniapp22miao/pages/sub-pages/agreement/contract1.vue
Normal file
65
single_uniapp22miao/pages/sub-pages/agreement/contract1.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view class="contract-page">
|
||||
<view class="content">
|
||||
<view class="title">隐私政策</view>
|
||||
<view class="article">
|
||||
<rich-text :nodes="content"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadContent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadContent() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/setting/agreement', {
|
||||
type: 'privacy'
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.content = res.data.content;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载隐私政策失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contract-page {
|
||||
min-height: 100vh;
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.article {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
75
single_uniapp22miao/pages/sub-pages/agreement/index.vue
Normal file
75
single_uniapp22miao/pages/sub-pages/agreement/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<view class="agreement-page">
|
||||
<view class="agreement-list">
|
||||
<view
|
||||
v-for="(item, index) in agreementList"
|
||||
:key="index"
|
||||
class="agreement-item"
|
||||
@click="viewAgreement(item)"
|
||||
>
|
||||
<text class="title">{{ item.title }}</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
agreementList: [
|
||||
{ title: '用户协议', type: 'user', url: '/pages/sub-pages/agreement/contract' },
|
||||
{ title: '隐私政策', type: 'privacy', url: '/pages/sub-pages/agreement/contract1' },
|
||||
{ title: '购买委托代卖协议', type: 'sale', url: '/pages/sub-pages/agreement/contract' },
|
||||
{ title: '我的合同', type: 'my', url: '/pages/sub-pages/agreement/my-contract' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
viewAgreement(item) {
|
||||
uni.navigateTo({
|
||||
url: `${item.url}?type=${item.type}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.agreement-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.agreement-list {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agreement-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 50rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
130
single_uniapp22miao/pages/sub-pages/agreement/my-contract.vue
Normal file
130
single_uniapp22miao/pages/sub-pages/agreement/my-contract.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<view class="my-contract-page">
|
||||
<view class="contract-list">
|
||||
<view
|
||||
v-for="(contract, index) in contractList"
|
||||
:key="index"
|
||||
class="contract-item"
|
||||
@click="viewContract(contract)"
|
||||
>
|
||||
<view class="contract-info">
|
||||
<view class="title">{{ contract.title }}</view>
|
||||
<view class="time">{{ contract.created_at }}</view>
|
||||
</view>
|
||||
<view class="status">{{ contract.status_text }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="contractList.length === 0 && !loading">
|
||||
<text class="icon">📄</text>
|
||||
<text class="text">暂无合同</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
contractList: [],
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadContractList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadContractList() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/contract/list');
|
||||
if (res.code === 0) {
|
||||
this.contractList = res.data.list || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载合同列表失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
viewContract(contract) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/webview/index?url=${encodeURIComponent(contract.url)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-contract-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.contract-list {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contract-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contract-info {
|
||||
flex: 1;
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
319
single_uniapp22miao/pages/sub-pages/balance/index.vue
Normal file
319
single_uniapp22miao/pages/sub-pages/balance/index.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view class="balance-page">
|
||||
<!-- 余额卡片 -->
|
||||
<view class="balance-card">
|
||||
<view class="card-bg"></view>
|
||||
<view class="card-content">
|
||||
<view class="balance-label">分红余额(元)</view>
|
||||
<view class="balance-amount">{{ balanceInfo.balance || '0.00' }}</view>
|
||||
<view class="balance-actions">
|
||||
<button class="action-btn" @click="goToWithdraw">提现</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ 'active': currentTab === index }"
|
||||
@click="switchTab(index)"
|
||||
>
|
||||
{{ tab }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 明细列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="record-list">
|
||||
<view
|
||||
v-for="(item, index) in recordList"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="item-left">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-time">{{ item.created_at }}</view>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<view class="item-amount" :class="item.type == 1 ? 'income' : 'expense'">
|
||||
{{ item.type == 1 ? '+' : '-' }}{{ item.amount }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="recordList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="recordList.length === 0 && !loading">
|
||||
<text class="icon">💰</text>
|
||||
<text class="text">暂无记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
balanceInfo: {},
|
||||
tabs: ['全部', '收入', '支出'],
|
||||
currentTab: 0,
|
||||
recordList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadBalanceInfo();
|
||||
this.loadRecordList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载余额信息
|
||||
async loadBalanceInfo() {
|
||||
try {
|
||||
const res = await this.$http.post('/api/user/info');
|
||||
if (res.code === 0) {
|
||||
this.balanceInfo = res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载余额失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 切换Tab
|
||||
switchTab(index) {
|
||||
if (this.currentTab === index) return;
|
||||
|
||||
this.currentTab = index;
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.recordList = [];
|
||||
this.loadRecordList();
|
||||
},
|
||||
|
||||
// 加载记录列表
|
||||
async loadRecordList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/money/list', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
cate: 1, // 1:分红 2:优惠券
|
||||
type: this.currentTab === 0 ? '' : this.currentTab // 0:全部 1:收入 2:支出
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.recordList = this.page === 1 ? list : [...this.recordList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载记录失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadRecordList();
|
||||
}
|
||||
},
|
||||
|
||||
// 去提现页面
|
||||
goToWithdraw() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/withdraw/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.balance-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
position: relative;
|
||||
margin: 30rpx;
|
||||
height: 300rpx;
|
||||
border-radius: 30rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10rpx 40rpx rgba(255, 71, 87, 0.2);
|
||||
|
||||
.card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #FF6B6B, #FF4757);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 50rpx 40rpx;
|
||||
color: #fff;
|
||||
|
||||
.balance-label {
|
||||
font-size: 26rpx;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.balance-amount {
|
||||
font-size: 72rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.balance-actions {
|
||||
.action-btn {
|
||||
width: 160rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10rpx);
|
||||
color: #fff;
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #FF4757;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #FF4757;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 0 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.item-left {
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.item-right {
|
||||
.item-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.income {
|
||||
color: #FF4757;
|
||||
}
|
||||
|
||||
&.expense {
|
||||
color: #4CAF50;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
164
single_uniapp22miao/pages/sub-pages/coupon/index.vue
Normal file
164
single_uniapp22miao/pages/sub-pages/coupon/index.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<view class="coupon-page">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="coupon-list">
|
||||
<view
|
||||
v-for="(item, index) in couponList"
|
||||
:key="index"
|
||||
class="coupon-item"
|
||||
>
|
||||
<view class="item-content">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-time">{{ item.created_at }}</view>
|
||||
</view>
|
||||
<view class="item-amount">+{{ item.amount }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="couponList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="couponList.length === 0 && !loading">
|
||||
<text class="icon">🎫</text>
|
||||
<text class="text">暂无优惠券记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
couponList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadCouponList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载优惠券列表
|
||||
async loadCouponList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/money/list', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
cate: 2 // 2: 优惠券
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.couponList = this.page === 1 ? list : [...this.couponList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载优惠券失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadCouponList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.coupon-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.item-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
215
single_uniapp22miao/pages/sub-pages/good/good-detail.vue
Normal file
215
single_uniapp22miao/pages/sub-pages/good/good-detail.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<view class="goods-detail-page">
|
||||
<!-- 商品轮播图 -->
|
||||
<swiper class="goods-swiper" :indicator-dots="true" :autoplay="true" circular>
|
||||
<swiper-item v-for="(image, index) in goodsInfo.images" :key="index">
|
||||
<image :src="image" class="swiper-image" mode="aspectFill"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="goods-info-section">
|
||||
<view class="goods-price">¥{{ goodsInfo.price }}</view>
|
||||
<view class="goods-name">{{ goodsInfo.name }}</view>
|
||||
<view class="goods-subtitle">{{ goodsInfo.subtitle }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品详情 -->
|
||||
<view class="goods-detail-section">
|
||||
<view class="section-title">商品详情</view>
|
||||
<view class="detail-content">
|
||||
<rich-text :nodes="goodsInfo.detail"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="left-actions">
|
||||
<button class="action-btn" open-type="contact">
|
||||
<text class="icon">💬</text>
|
||||
<text class="text">客服</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="right-actions">
|
||||
<button class="buy-btn" @click="handleBuy">立即购买</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsId: null,
|
||||
goodsInfo: {
|
||||
images: [],
|
||||
name: '',
|
||||
subtitle: '',
|
||||
price: '0.00',
|
||||
detail: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.goodsId = options.id;
|
||||
this.loadGoodsDetail();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载商品详情
|
||||
async loadGoodsDetail() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/goods/detail', {
|
||||
id: this.goodsId
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.goodsInfo = res.data;
|
||||
// 处理轮播图
|
||||
if (typeof this.goodsInfo.images === 'string') {
|
||||
this.goodsInfo.images = this.goodsInfo.images.split(',');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品详情失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 立即购买
|
||||
handleBuy() {
|
||||
// 检查登录
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/login/index'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.goods-swiper {
|
||||
width: 100%;
|
||||
height: 750rpx;
|
||||
|
||||
.swiper-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-info-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.goods-price {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.goods-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-detail-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.left-actions {
|
||||
margin-right: 20rpx;
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
|
||||
.icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
flex: 1;
|
||||
|
||||
.buy-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
284
single_uniapp22miao/pages/sub-pages/invite/index.vue
Normal file
284
single_uniapp22miao/pages/sub-pages/invite/index.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<view class="invite-page">
|
||||
<!-- 顶部背景 -->
|
||||
<view class="header-bg"></view>
|
||||
|
||||
<!-- 邀请卡片 -->
|
||||
<view class="invite-card">
|
||||
<view class="card-title">邀请好友 共享收益</view>
|
||||
|
||||
<!-- 邀请码 -->
|
||||
<view class="invite-code-box">
|
||||
<view class="label">我的邀请码</view>
|
||||
<view class="code">{{ inviteCode }}</view>
|
||||
<button class="copy-btn" @click="copyCode">复制邀请码</button>
|
||||
</view>
|
||||
|
||||
<!-- 分享按钮 -->
|
||||
<view class="share-actions">
|
||||
<button class="share-btn" open-type="share">
|
||||
<text class="icon">📱</text>
|
||||
<text>分享给好友</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计数据 -->
|
||||
<view class="stats-container">
|
||||
<view class="stats-item" @click="goToMyFans">
|
||||
<view class="stats-value">{{ stats.fans_count || 0 }}</view>
|
||||
<view class="stats-label">我的粉丝</view>
|
||||
</view>
|
||||
<view class="stats-item" @click="goToPromotePrize">
|
||||
<view class="stats-value">¥{{ stats.total_income || '0.00' }}</view>
|
||||
<view class="stats-label">累计收益</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-value">{{ stats.team_count || 0 }}</view>
|
||||
<view class="stats-label">团队人数</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 邀请规则 -->
|
||||
<view class="rules-container">
|
||||
<view class="rules-title">邀请规则</view>
|
||||
<view class="rules-list">
|
||||
<view class="rule-item">
|
||||
<text class="rule-icon">1️⃣</text>
|
||||
<text class="rule-text">分享邀请码或邀请链接给好友</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-icon">2️⃣</text>
|
||||
<text class="rule-text">好友通过您的邀请码注册成功</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-icon">3️⃣</text>
|
||||
<text class="rule-text">好友购买商品,您可获得对应分红</text>
|
||||
</view>
|
||||
<view class="rule-item">
|
||||
<text class="rule-icon">4️⃣</text>
|
||||
<text class="rule-text">三级分红制度,躺赚睡后收入</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
inviteCode: '',
|
||||
stats: {}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadInviteInfo();
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: `【邀请码:${this.inviteCode}】邀请你一起赚钱!`,
|
||||
path: `/pages/sub-pages/login/register?invite_code=${this.inviteCode}`
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载邀请信息
|
||||
async loadInviteInfo() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/share/index');
|
||||
if (res.code === 0) {
|
||||
this.inviteCode = res.data.invite_code;
|
||||
this.stats = res.data.stats || {};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载邀请信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 复制邀请码
|
||||
copyCode() {
|
||||
uni.setClipboardData({
|
||||
data: this.inviteCode,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '邀请码已复制',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 去我的粉丝
|
||||
goToMyFans() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/my-fans/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 去推广奖励
|
||||
goToPromotePrize() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/promote-prize/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 400rpx;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
}
|
||||
|
||||
.invite-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 30rpx;
|
||||
padding: 50rpx 40rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.card-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.invite-code-box {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
background: linear-gradient(135deg, #FFF3E0, #FFE0B2);
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6B00;
|
||||
letter-spacing: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 200rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
background-color: #FF6B00;
|
||||
color: #fff;
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.share-actions {
|
||||
.share-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
font-size: 36rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: flex;
|
||||
margin: 0 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
padding: 40rpx 20rpx;
|
||||
text-align: center;
|
||||
|
||||
.stats-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rules-container {
|
||||
margin: 0 30rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.rules-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.rules-list {
|
||||
.rule-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 25rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rule-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.rule-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
303
single_uniapp22miao/pages/sub-pages/login/change-pwd.vue
Normal file
303
single_uniapp22miao/pages/sub-pages/login/change-pwd.vue
Normal file
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<view class="change-pwd-page">
|
||||
<view class="form-container">
|
||||
<!-- 原密码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">原密码</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="formData.oldPassword"
|
||||
:password="!showOldPwd"
|
||||
placeholder="请输入原密码"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="showOldPwd = !showOldPwd">
|
||||
<text>{{ showOldPwd ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">验证码</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="formData.code"
|
||||
type="number"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
class="form-input code-input"
|
||||
/>
|
||||
<button
|
||||
class="code-btn"
|
||||
:disabled="countdown > 0"
|
||||
@click="sendCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新密码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">新密码</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="formData.newPassword"
|
||||
:password="!showNewPwd"
|
||||
placeholder="请输入新密码(6-20位)"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="showNewPwd = !showNewPwd">
|
||||
<text>{{ showNewPwd ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认新密码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">确认新密码</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="formData.confirmPassword"
|
||||
:password="!showConfirmPwd"
|
||||
placeholder="请再次输入新密码"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="showConfirmPwd = !showConfirmPwd">
|
||||
<text>{{ showConfirmPwd ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button
|
||||
class="submit-btn"
|
||||
:disabled="!canSubmit"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
确定修改
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { changePassword, sendSms } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
oldPassword: '',
|
||||
code: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
showOldPwd: false,
|
||||
showNewPwd: false,
|
||||
showConfirmPwd: false,
|
||||
countdown: 0,
|
||||
submitting: false,
|
||||
timer: null,
|
||||
userInfo: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSubmit() {
|
||||
return this.formData.oldPassword.length >= 6 &&
|
||||
this.formData.code.length === 6 &&
|
||||
this.formData.newPassword.length >= 6 &&
|
||||
this.formData.confirmPassword === this.formData.newPassword;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.userInfo = uni.getStorageSync('userInfo');
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 发送验证码
|
||||
async sendCode() {
|
||||
if (!this.userInfo || !this.userInfo.mobile) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await sendSms({
|
||||
mobile: this.userInfo.mobile,
|
||||
event: 'changepwd'
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
this.countdown = 60;
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '发送失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 提交
|
||||
async handleSubmit() {
|
||||
if (this.formData.newPassword !== this.formData.confirmPassword) {
|
||||
uni.showToast({
|
||||
title: '两次密码不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formData.newPassword === this.formData.oldPassword) {
|
||||
uni.showToast({
|
||||
title: '新密码不能与原密码相同',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const res = await changePassword({
|
||||
old_password: this.formData.oldPassword,
|
||||
new_password: this.formData.newPassword,
|
||||
code: this.formData.code
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '修改成功,请重新登录',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 清除登录信息
|
||||
uni.removeStorageSync('token');
|
||||
uni.removeStorageSync('userInfo');
|
||||
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/sub-pages/login/index'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '修改失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.change-pwd-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 45rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
&.code-input {
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
width: 180rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.eye-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
margin-top: 20rpx;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
320
single_uniapp22miao/pages/sub-pages/login/index.vue
Normal file
320
single_uniapp22miao/pages/sub-pages/login/index.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<!-- 登录表单 -->
|
||||
<view class="login-container">
|
||||
<!-- 标题 -->
|
||||
<view class="title-section">
|
||||
<text class="title">现在登录</text>
|
||||
<text class="subtitle">欢迎回来,有好多小伙伴在思念你!</text>
|
||||
</view>
|
||||
|
||||
<!-- 输入框区域 -->
|
||||
<view class="input-section">
|
||||
<view class="input-item">
|
||||
<text class="label">账号</text>
|
||||
<input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
class="input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<text class="label">密码</text>
|
||||
<input
|
||||
v-model="formData.password"
|
||||
password="true"
|
||||
placeholder="请输入密码"
|
||||
placeholder-class="placeholder"
|
||||
class="input"
|
||||
/>
|
||||
<text class="forgot-text" @click="goToResetPassword">忘记</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button
|
||||
class="login-btn"
|
||||
:class="{ 'disabled': !canLogin }"
|
||||
:disabled="!canLogin"
|
||||
:loading="logging"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ logging ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
|
||||
<!-- 注册入口 -->
|
||||
<view class="register-section">
|
||||
<text>还没有账号?</text>
|
||||
<text class="register-link" @click="goToRegister">注册</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { userLogin } from '@/api/miao.js';
|
||||
import store from '@/store';
|
||||
import { LOGIN_STATUS, USER_INFO, EXPIRES_TIME, BACK_URL } from '@/config/cache';
|
||||
import Cache from '@/utils/cache';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
mobile: '',
|
||||
password: ''
|
||||
},
|
||||
logging: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canLogin() {
|
||||
return this.formData.mobile.length === 11 && this.formData.password.length >= 6;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 验证手机号
|
||||
validateMobile(mobile) {
|
||||
const reg = /^1[3-9]\d{9}$/;
|
||||
return reg.test(mobile);
|
||||
},
|
||||
|
||||
// 登录
|
||||
async handleLogin() {
|
||||
if (!this.validateMobile(this.formData.mobile)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formData.password.length < 6) {
|
||||
uni.showToast({
|
||||
title: '密码长度不能少于6位',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.logging = true;
|
||||
|
||||
try {
|
||||
// 调用登录API
|
||||
const res = await userLogin({
|
||||
account: this.formData.mobile,
|
||||
password: this.formData.password
|
||||
});
|
||||
|
||||
console.log('登录返回完整数据:', res);
|
||||
console.log('data.userInfo:', res.data?.userInfo);
|
||||
|
||||
if (res.code === 0) {
|
||||
// 检查userInfo和token是否存在
|
||||
if (!res.data || !res.data.userInfo || !res.data.userInfo.token) {
|
||||
console.error('登录返回数据异常,缺少token:', res);
|
||||
uni.showToast({
|
||||
title: '登录失败:数据异常',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = res.data.userInfo;
|
||||
const token = userInfo.token;
|
||||
const uid = userInfo.id || '';
|
||||
|
||||
console.log('token值:', token);
|
||||
console.log('uid值:', uid);
|
||||
console.log('用户信息:', userInfo);
|
||||
console.log('开始保存token:', token);
|
||||
|
||||
// 计算过期时间(当前时间+7天)
|
||||
const newTime = Math.round(new Date() / 1000);
|
||||
const expiresTime = newTime + 7 * 24 * 60 * 60;
|
||||
|
||||
console.log('过期时间:', expiresTime);
|
||||
|
||||
// 保存过期时间到缓存
|
||||
Cache.set(EXPIRES_TIME, expiresTime);
|
||||
|
||||
// 保存token到store(这会同时保存到localStorage)
|
||||
store.commit('LOGIN', { token: token });
|
||||
|
||||
console.log('token已保存到store');
|
||||
console.log('store中的token:', store.state.app.token);
|
||||
console.log('localStorage中的LOGIN_STATUS_TOKEN:', Cache.get(LOGIN_STATUS));
|
||||
|
||||
// 设置UID
|
||||
if (uid) {
|
||||
store.commit('SETUID', uid);
|
||||
console.log('UID已保存:', uid);
|
||||
}
|
||||
|
||||
// 保存用户信息到store(登录接口已返回完整用户信息,无需再次调用getUserInfo)
|
||||
store.commit('UPDATE_USERINFO', userInfo);
|
||||
console.log('用户信息已保存到store');
|
||||
console.log('localStorage中的USER_INFO:', Cache.get(USER_INFO));
|
||||
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 获取返回地址,如果没有或返回地址是登录页则跳转到首页
|
||||
let backUrl = Cache.get(BACK_URL) || '/pages/index/index';
|
||||
if (backUrl.indexOf('/pages/sub-pages/login/index') !== -1) {
|
||||
backUrl = '/pages/index/index';
|
||||
}
|
||||
|
||||
console.log('即将跳转到:', backUrl);
|
||||
|
||||
// 延迟跳转,确保数据已保存
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: backUrl
|
||||
});
|
||||
}, 500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '登录失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '登录失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.logging = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 去注册页
|
||||
goToRegister() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/login/register'
|
||||
});
|
||||
},
|
||||
|
||||
// 去重置密码页
|
||||
goToResetPassword() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/login/reset-account'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background-color: #ffffff;
|
||||
padding: 60rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
margin-bottom: 80rpx;
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.input-section {
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.input-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.forgot-text {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #ff4d4f;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background-color: #ff4d4f;
|
||||
color: #ffffff;
|
||||
border-radius: 10rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.register-section {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
|
||||
.register-link {
|
||||
color: #ff4d4f;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
423
single_uniapp22miao/pages/sub-pages/login/register.vue
Normal file
423
single_uniapp22miao/pages/sub-pages/login/register.vue
Normal file
@@ -0,0 +1,423 @@
|
||||
<template>
|
||||
<view class="register-page">
|
||||
<view class="container">
|
||||
<view class="title">创建您的账号</view>
|
||||
<view class="subtitle">已有账号?<text class="link" @click="goToLogin">立即登录</text></view>
|
||||
|
||||
<!-- 注册表单 -->
|
||||
<view class="form-container">
|
||||
<!-- 手机号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="icon">📱</text>
|
||||
<text>手机号</text>
|
||||
</view>
|
||||
<input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入您的手机号"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="icon">✉️</text>
|
||||
<text>验证码</text>
|
||||
</view>
|
||||
<view class="code-input-wrapper">
|
||||
<input
|
||||
v-model="formData.code"
|
||||
type="number"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
class="form-input"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:class="{ 'disabled': countdown > 0 }"
|
||||
:disabled="countdown > 0 || !canSendCode"
|
||||
@click="sendCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 密码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="icon">🔒</text>
|
||||
<text>登录密码</text>
|
||||
</view>
|
||||
<input
|
||||
v-model="formData.password"
|
||||
:password="!showPassword"
|
||||
placeholder="请输入登录密码(必填)"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="togglePassword">
|
||||
<text>{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 邀请码 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text class="icon">🎁</text>
|
||||
<text>邀请码(选填)</text>
|
||||
</view>
|
||||
<input
|
||||
v-model="formData.inviteCode"
|
||||
placeholder="请输入邀请码"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 同意协议 -->
|
||||
<view class="agreement">
|
||||
<checkbox-group @change="onAgreeChange">
|
||||
<label class="checkbox-label">
|
||||
<checkbox value="agree" :checked="agreed" color="#FF4757" />
|
||||
<text>同意</text>
|
||||
<text class="link" @click.stop="viewAgreement('user')">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text class="link" @click.stop="viewAgreement('privacy')">《购买委托代卖协议》</text>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
|
||||
<!-- 注册按钮 -->
|
||||
<button
|
||||
class="register-btn"
|
||||
:class="{ 'disabled': !canRegister }"
|
||||
:disabled="!canRegister"
|
||||
:loading="registering"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { userRegister, sendSms } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
mobile: '',
|
||||
code: '',
|
||||
password: '',
|
||||
inviteCode: ''
|
||||
},
|
||||
showPassword: false,
|
||||
agreed: false,
|
||||
countdown: 0,
|
||||
registering: false,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSendCode() {
|
||||
return this.formData.mobile.length === 11;
|
||||
},
|
||||
|
||||
canRegister() {
|
||||
return this.formData.mobile.length === 11 &&
|
||||
this.formData.code.length === 6 &&
|
||||
this.formData.password.length >= 6 &&
|
||||
this.agreed;
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 切换密码显示
|
||||
togglePassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
},
|
||||
|
||||
// 验证手机号
|
||||
validateMobile(mobile) {
|
||||
const reg = /^1[3-9]\d{9}$/;
|
||||
return reg.test(mobile);
|
||||
},
|
||||
|
||||
// 发送验证码
|
||||
async sendCode() {
|
||||
if (!this.validateMobile(this.formData.mobile)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await sendSms({
|
||||
mobile: this.formData.mobile,
|
||||
event: 'register'
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 开始倒计时
|
||||
this.countdown = 60;
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '发送失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送验证码失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '发送失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 同意协议变化
|
||||
onAgreeChange(e) {
|
||||
this.agreed = e.detail.value.length > 0;
|
||||
},
|
||||
|
||||
// 查看协议
|
||||
viewAgreement(type) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/agreement/index?type=${type}`
|
||||
});
|
||||
},
|
||||
|
||||
// 注册
|
||||
async handleRegister() {
|
||||
if (!this.validateMobile(this.formData.mobile)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formData.password.length < 6) {
|
||||
uni.showToast({
|
||||
title: '密码长度不能少于6位',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.agreed) {
|
||||
uni.showToast({
|
||||
title: '请先同意用户协议',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.registering = true;
|
||||
|
||||
try {
|
||||
const res = await userRegister({
|
||||
mobile: this.formData.mobile,
|
||||
code: this.formData.code,
|
||||
password: this.formData.password,
|
||||
invite_code: this.formData.inviteCode
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '注册成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 保存token
|
||||
if (res.data.token) {
|
||||
uni.setStorageSync('token', res.data.token);
|
||||
uni.setStorageSync('userInfo', res.data.user_info);
|
||||
}
|
||||
|
||||
// 跳转到首页或登录页
|
||||
setTimeout(() => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '注册失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '注册失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.registering = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 去登录页
|
||||
goToLogin() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.register-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 40rpx 30rpx;
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.link {
|
||||
color: #FF4757;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.code-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 180rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
|
||||
&.disabled {
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eye-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
|
||||
checkbox {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #FF4757;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
296
single_uniapp22miao/pages/sub-pages/login/reset-account.vue
Normal file
296
single_uniapp22miao/pages/sub-pages/login/reset-account.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<view class="reset-page">
|
||||
<view class="header">
|
||||
<view class="title">重置密码</view>
|
||||
<view class="subtitle">请输入您的手机号,我们将发送验证码</view>
|
||||
</view>
|
||||
|
||||
<view class="form-container">
|
||||
<!-- 手机号 -->
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="formData.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<view class="form-item">
|
||||
<view class="code-wrapper">
|
||||
<input
|
||||
v-model="formData.code"
|
||||
type="number"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
class="form-input"
|
||||
/>
|
||||
<button
|
||||
class="code-btn"
|
||||
:disabled="countdown > 0"
|
||||
@click="sendCode"
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新密码 -->
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="formData.password"
|
||||
:password="!showPassword"
|
||||
placeholder="请输入新密码(6-20位)"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="togglePassword">
|
||||
<text>{{ showPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认密码 -->
|
||||
<view class="form-item">
|
||||
<input
|
||||
v-model="formData.confirmPassword"
|
||||
:password="!showConfirmPassword"
|
||||
placeholder="请再次输入新密码"
|
||||
class="form-input"
|
||||
/>
|
||||
<view class="eye-icon" @click="toggleConfirmPassword">
|
||||
<text>{{ showConfirmPassword ? '👁️' : '👁️🗨️' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button
|
||||
class="submit-btn"
|
||||
:disabled="!canSubmit"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { resetPassword, sendSms } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
mobile: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
showPassword: false,
|
||||
showConfirmPassword: false,
|
||||
countdown: 0,
|
||||
submitting: false,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSubmit() {
|
||||
return this.formData.mobile.length === 11 &&
|
||||
this.formData.code.length === 6 &&
|
||||
this.formData.password.length >= 6 &&
|
||||
this.formData.confirmPassword === this.formData.password;
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
togglePassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
},
|
||||
|
||||
toggleConfirmPassword() {
|
||||
this.showConfirmPassword = !this.showConfirmPassword;
|
||||
},
|
||||
|
||||
// 发送验证码
|
||||
async sendCode() {
|
||||
if (!/^1[3-9]\d{9}$/.test(this.formData.mobile)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await sendSms({
|
||||
mobile: this.formData.mobile,
|
||||
event: 'resetpwd'
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
this.countdown = 60;
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '发送失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 提交
|
||||
async handleSubmit() {
|
||||
if (this.formData.password !== this.formData.confirmPassword) {
|
||||
uni.showToast({
|
||||
title: '两次密码不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const res = await resetPassword({
|
||||
mobile: this.formData.mobile,
|
||||
code: this.formData.code,
|
||||
password: this.formData.password
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '密码重置成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '重置失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reset-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
position: relative;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 45rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.code-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
width: 180rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.eye-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
margin-top: 40rpx;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
301
single_uniapp22miao/pages/sub-pages/my-fans/index.vue
Normal file
301
single_uniapp22miao/pages/sub-pages/my-fans/index.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<view class="fans-page">
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-card">
|
||||
<view class="stats-item">
|
||||
<view class="value">{{ stats.level1_count || 0 }}</view>
|
||||
<view class="label">一级粉丝</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="value">{{ stats.level2_count || 0 }}</view>
|
||||
<view class="label">二级粉丝</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="value">{{ stats.level3_count || 0 }}</view>
|
||||
<view class="label">三级粉丝</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ 'active': currentTab === index }"
|
||||
@click="switchTab(index)"
|
||||
>
|
||||
{{ tab }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 粉丝列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="fans-list">
|
||||
<view
|
||||
v-for="(fan, index) in fansList"
|
||||
:key="index"
|
||||
class="fan-item"
|
||||
>
|
||||
<image :src="fan.avatar || '/static/images/default-avatar.png'" class="avatar"></image>
|
||||
<view class="fan-info">
|
||||
<view class="name">{{ fan.nickname || fan.mobile }}</view>
|
||||
<view class="time">{{ fan.created_at }}</view>
|
||||
</view>
|
||||
<view class="level-tag">
|
||||
{{ getLevelText(fan.level) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="fansList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="fansList.length === 0 && !loading">
|
||||
<text class="icon">👥</text>
|
||||
<text class="text">暂无粉丝</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
stats: {},
|
||||
tabs: ['一级粉丝', '二级粉丝', '三级粉丝'],
|
||||
currentTab: 0,
|
||||
fansList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadStats();
|
||||
this.loadFansList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载统计数据
|
||||
async loadStats() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/share/index');
|
||||
if (res.code === 0) {
|
||||
this.stats = res.data.stats || {};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 切换Tab
|
||||
switchTab(index) {
|
||||
if (this.currentTab === index) return;
|
||||
|
||||
this.currentTab = index;
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.fansList = [];
|
||||
this.loadFansList();
|
||||
},
|
||||
|
||||
// 加载粉丝列表
|
||||
async loadFansList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/share/select', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
level: this.currentTab + 1 // 1,2,3
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.fansList = this.page === 1 ? list : [...this.fansList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载粉丝列表失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadFansList();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取等级文本
|
||||
getLevelText(level) {
|
||||
const levelMap = {
|
||||
1: '一级',
|
||||
2: '二级',
|
||||
3: '三级'
|
||||
};
|
||||
return levelMap[level] || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fans-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
display: flex;
|
||||
margin: 30rpx;
|
||||
padding: 40rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #FF4757;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #FF4757;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fans-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.fan-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.fan-info {
|
||||
flex: 1;
|
||||
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #FFF3E0;
|
||||
color: #FF6B00;
|
||||
font-size: 22rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
166
single_uniapp22miao/pages/sub-pages/my-payee/index.vue
Normal file
166
single_uniapp22miao/pages/sub-pages/my-payee/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<view class="payee-page">
|
||||
<!-- 支付宝 -->
|
||||
<view class="payee-item" @click="goToAlipay">
|
||||
<view class="item-left">
|
||||
<view class="icon-wrapper alipay">
|
||||
<image src="/static/images/alipay-icon.png" class="icon"></image>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="title">支付宝</view>
|
||||
<view class="subtitle" v-if="alipayInfo.account">
|
||||
{{ alipayInfo.account }}
|
||||
</view>
|
||||
<view class="subtitle" v-else>未绑定</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 银行卡 -->
|
||||
<view class="payee-item" @click="goToBank">
|
||||
<view class="item-left">
|
||||
<view class="icon-wrapper bank">
|
||||
<image src="/static/images/bank-icon.png" class="icon"></image>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="title">银行卡</view>
|
||||
<view class="subtitle" v-if="bankInfo.card_no">
|
||||
{{ bankInfo.bank_name }} ({{ bankInfo.card_no.slice(-4) }})
|
||||
</view>
|
||||
<view class="subtitle" v-else>未绑定</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
alipayInfo: {},
|
||||
bankInfo: {}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadPayeeInfo();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadPayeeInfo();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载收款信息
|
||||
async loadPayeeInfo() {
|
||||
try {
|
||||
// 加载支付宝信息
|
||||
const alipayRes = await this.$http.get('/api/alipay/index');
|
||||
if (alipayRes.code === 0) {
|
||||
this.alipayInfo = alipayRes.data;
|
||||
}
|
||||
|
||||
// 加载银行卡信息
|
||||
const bankRes = await this.$http.get('/api/bank/index');
|
||||
if (bankRes.code === 0) {
|
||||
this.bankInfo = bankRes.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载收款信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 去支付宝页面
|
||||
goToAlipay() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/my-payee/zfb-detail'
|
||||
});
|
||||
},
|
||||
|
||||
// 去银行卡页面
|
||||
goToBank() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/my-payee/yl-detail'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.payee-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.payee-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.icon-wrapper {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
|
||||
&.alipay {
|
||||
background-color: #1678FF;
|
||||
}
|
||||
|
||||
&.bank {
|
||||
background-color: #FF6B6B;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-right {
|
||||
.arrow {
|
||||
font-size: 50rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
313
single_uniapp22miao/pages/sub-pages/my-payee/yl-detail.vue
Normal file
313
single_uniapp22miao/pages/sub-pages/my-payee/yl-detail.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<view class="bank-page">
|
||||
<view class="form-container">
|
||||
<!-- 持卡人姓名 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">持卡人姓名<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.real_name"
|
||||
placeholder="请输入持卡人姓名"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 银行卡号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">银行卡号<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.card_no"
|
||||
type="number"
|
||||
placeholder="请输入银行卡号"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 开户银行 -->
|
||||
<view class="form-item" @click="selectBank">
|
||||
<view class="form-label">开户银行<text class="required">*</text></view>
|
||||
<view class="picker-value">
|
||||
<text v-if="formData.bank_name" class="text">{{ formData.bank_name }}</text>
|
||||
<text v-else class="placeholder">请选择开户银行</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开户支行(选填) -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">开户支行</view>
|
||||
<input
|
||||
v-model="formData.bank_branch"
|
||||
placeholder="请输入开户支行(选填)"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 确认银行卡号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">确认卡号<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.confirm_card_no"
|
||||
type="number"
|
||||
placeholder="请再次输入银行卡号"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-box">
|
||||
<view class="tips-title">温馨提示:</view>
|
||||
<view class="tips-item">1. 请确保银行卡为本人名下储蓄卡</view>
|
||||
<view class="tips-item">2. 绑定后无法修改,请仔细核对信息</view>
|
||||
<view class="tips-item">3. 不支持信用卡提现</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="btn-container">
|
||||
<button
|
||||
class="save-btn"
|
||||
:disabled="!canSave"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
real_name: '',
|
||||
card_no: '',
|
||||
bank_name: '',
|
||||
bank_branch: '',
|
||||
confirm_card_no: ''
|
||||
},
|
||||
bankList: [
|
||||
'工商银行', '建设银行', '农业银行', '中国银行',
|
||||
'交通银行', '招商银行', '浦发银行', '民生银行',
|
||||
'兴业银行', '中信银行', '光大银行', '平安银行',
|
||||
'邮政储蓄银行', '其他银行'
|
||||
],
|
||||
saving: false,
|
||||
isEdit: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSave() {
|
||||
return this.formData.real_name &&
|
||||
this.formData.card_no &&
|
||||
this.formData.bank_name &&
|
||||
this.formData.confirm_card_no === this.formData.card_no;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadBankInfo();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载银行卡信息
|
||||
async loadBankInfo() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/bank/index');
|
||||
if (res.code === 0 && res.data.card_no) {
|
||||
this.formData = {
|
||||
...res.data,
|
||||
confirm_card_no: res.data.card_no
|
||||
};
|
||||
this.isEdit = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载银行卡信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 选择银行
|
||||
selectBank() {
|
||||
uni.showActionSheet({
|
||||
itemList: this.bankList,
|
||||
success: (res) => {
|
||||
this.formData.bank_name = this.bankList[res.tapIndex];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 保存
|
||||
async handleSave() {
|
||||
if (this.formData.card_no !== this.formData.confirm_card_no) {
|
||||
uni.showToast({
|
||||
title: '两次输入的卡号不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formData.card_no.length < 16) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的银行卡号',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEdit) {
|
||||
uni.showToast({
|
||||
title: '银行卡信息已绑定,无法修改',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.post('/api/bank/bind', {
|
||||
real_name: this.formData.real_name,
|
||||
card_no: this.formData.card_no,
|
||||
bank_name: this.formData.bank_name,
|
||||
bank_branch: this.formData.bank_branch
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bank-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
font-weight: 500;
|
||||
|
||||
.required {
|
||||
color: #FF4757;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
margin-top: 40rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #FFF3CD;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.tips-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #856404;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.tips-item {
|
||||
font-size: 24rpx;
|
||||
color: #856404;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
238
single_uniapp22miao/pages/sub-pages/my-payee/zfb-detail.vue
Normal file
238
single_uniapp22miao/pages/sub-pages/my-payee/zfb-detail.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<view class="alipay-page">
|
||||
<view class="form-container">
|
||||
<!-- 支付宝账号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">支付宝账号<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.account"
|
||||
placeholder="请输入支付宝账号(手机号或邮箱)"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 真实姓名 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">真实姓名<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.real_name"
|
||||
placeholder="请输入支付宝实名认证姓名"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 确认支付宝账号 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">确认账号<text class="required">*</text></view>
|
||||
<input
|
||||
v-model="formData.confirm_account"
|
||||
placeholder="请再次输入支付宝账号"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tips-box">
|
||||
<view class="tips-title">温馨提示:</view>
|
||||
<view class="tips-item">1. 请确保支付宝账号和姓名与实名认证信息一致</view>
|
||||
<view class="tips-item">2. 绑定后无法修改,请仔细核对信息</view>
|
||||
<view class="tips-item">3. 如有疑问,请联系客服</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="btn-container">
|
||||
<button
|
||||
class="save-btn"
|
||||
:disabled="!canSave"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
account: '',
|
||||
real_name: '',
|
||||
confirm_account: ''
|
||||
},
|
||||
saving: false,
|
||||
isEdit: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSave() {
|
||||
return this.formData.account &&
|
||||
this.formData.real_name &&
|
||||
this.formData.confirm_account === this.formData.account;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadAlipayInfo();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载支付宝信息
|
||||
async loadAlipayInfo() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/alipay/index');
|
||||
if (res.code === 0 && res.data.account) {
|
||||
this.formData = {
|
||||
account: res.data.account,
|
||||
real_name: res.data.real_name,
|
||||
confirm_account: res.data.account
|
||||
};
|
||||
this.isEdit = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载支付宝信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 保存
|
||||
async handleSave() {
|
||||
if (this.formData.account !== this.formData.confirm_account) {
|
||||
uni.showToast({
|
||||
title: '两次输入的账号不一致',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isEdit) {
|
||||
uni.showToast({
|
||||
title: '支付宝信息已绑定,无法修改',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.post('/api/alipay/bind', {
|
||||
account: this.formData.account,
|
||||
real_name: this.formData.real_name
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.alipay-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
font-weight: 500;
|
||||
|
||||
.required {
|
||||
color: #FF4757;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
margin-top: 40rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #FFF3CD;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.tips-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #856404;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.tips-item {
|
||||
font-size: 24rpx;
|
||||
color: #856404;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #1678FF, #0D5FD8);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
172
single_uniapp22miao/pages/sub-pages/prize/index.vue
Normal file
172
single_uniapp22miao/pages/sub-pages/prize/index.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<view class="prize-page">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="prize-list">
|
||||
<view
|
||||
v-for="(prize, index) in prizeList"
|
||||
:key="index"
|
||||
class="prize-item"
|
||||
>
|
||||
<image :src="prize.image" class="prize-image"></image>
|
||||
<view class="prize-info">
|
||||
<view class="prize-name">{{ prize.name }}</view>
|
||||
<view class="prize-desc">{{ prize.desc }}</view>
|
||||
<view class="prize-time">{{ prize.created_at }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="prizeList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="prizeList.length === 0 && !loading">
|
||||
<text class="icon">🎁</text>
|
||||
<text class="text">暂无奖品</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
prizeList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadPrizeList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载奖品列表
|
||||
async loadPrizeList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/prize/list', {
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.prizeList = this.page === 1 ? list : [...this.prizeList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载奖品失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadPrizeList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prize-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.prize-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.prize-item {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.prize-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.prize-info {
|
||||
flex: 1;
|
||||
|
||||
.prize-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.prize-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.prize-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
243
single_uniapp22miao/pages/sub-pages/promote-prize/index.vue
Normal file
243
single_uniapp22miao/pages/sub-pages/promote-prize/index.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<view class="prize-page">
|
||||
<!-- 总收益卡片 -->
|
||||
<view class="total-card">
|
||||
<view class="label">累计推广收益(元)</view>
|
||||
<view class="amount">{{ totalIncome }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="record-list">
|
||||
<view
|
||||
v-for="(item, index) in recordList"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="item-left">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-desc">来自:{{ item.from_user }}</view>
|
||||
<view class="item-time">{{ item.created_at }}</view>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<view class="item-amount">+{{ item.amount }}</view>
|
||||
<view class="item-level">{{ getLevelText(item.level) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="recordList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="recordList.length === 0 && !loading">
|
||||
<text class="icon">🎁</text>
|
||||
<text class="text">暂无推广收益</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
totalIncome: '0.00',
|
||||
recordList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadTotalIncome();
|
||||
this.loadRecordList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载总收益
|
||||
async loadTotalIncome() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/share/index');
|
||||
if (res.code === 0) {
|
||||
this.totalIncome = res.data.stats.total_income || '0.00';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载总收益失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 加载收益记录
|
||||
async loadRecordList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/money/list', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
cate: 3 // 推广收益
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.recordList = this.page === 1 ? list : [...this.recordList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载记录失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadRecordList();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取等级文本
|
||||
getLevelText(level) {
|
||||
const levelMap = {
|
||||
1: '一级收益',
|
||||
2: '二级收益',
|
||||
3: '三级收益'
|
||||
};
|
||||
return levelMap[level] || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.prize-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.total-card {
|
||||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||||
margin: 30rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
border-radius: 30rpx;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
box-shadow: 0 10rpx 40rpx rgba(255, 165, 0, 0.3);
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 72rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 0 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-left {
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.item-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.item-right {
|
||||
text-align: right;
|
||||
|
||||
.item-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.item-level {
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #FFF3E0;
|
||||
color: #FF6B00;
|
||||
font-size: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
544
single_uniapp22miao/pages/sub-pages/rushing-order/detail.vue
Normal file
544
single_uniapp22miao/pages/sub-pages/rushing-order/detail.vue
Normal file
@@ -0,0 +1,544 @@
|
||||
<template>
|
||||
<view class="order-detail-page">
|
||||
<!-- 状态栏 -->
|
||||
<view class="status-bar" :class="'status-' + orderInfo.status">
|
||||
<text class="status-icon">{{ getStatusIcon() }}</text>
|
||||
<text class="status-text">{{ getStatusText() }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 地址信息 -->
|
||||
<view class="address-section" v-if="orderInfo.consignee">
|
||||
<view class="section-title">
|
||||
<text>收货信息</text>
|
||||
</view>
|
||||
<view class="address-content">
|
||||
<view class="user-info">
|
||||
<text class="name">{{ orderInfo.consignee }}</text>
|
||||
<text class="phone">{{ orderInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="address">
|
||||
{{ orderInfo.province }} {{ orderInfo.city }}
|
||||
{{ orderInfo.area }} {{ orderInfo.address }}
|
||||
</view>
|
||||
<view class="delivery-method">
|
||||
<text>提货方式:{{ orderInfo.delivery_method || '线下提货' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="goods-section">
|
||||
<view class="goods-item">
|
||||
<image :src="getGoodsImage()" class="goods-image"></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ getGoodsTitle() }}</view>
|
||||
<view class="goods-spec">规格:默认</view>
|
||||
<view class="seller-info">
|
||||
<text class="seller-label">卖家</text>
|
||||
<text class="seller-name">{{ getSellerName() }}</text>
|
||||
</view>
|
||||
<view class="seller-phone">
|
||||
<text class="phone-label">卖家电话:</text>
|
||||
<text class="phone-number">{{ getSellerPhone() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-right">
|
||||
<view class="goods-price">¥{{ orderInfo.total_money }}</view>
|
||||
<view class="goods-num">x1</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议部分 -->
|
||||
<view class="agreement-section">
|
||||
<view class="agreement-item">
|
||||
<text class="agreement-icon">✓</text>
|
||||
<text class="agreement-text">《购买委托代卖协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar" v-if="orderInfo.status != 3">
|
||||
<button
|
||||
v-if="orderInfo.status == 0"
|
||||
class="btn cancel-btn"
|
||||
@click="cancelOrder"
|
||||
>
|
||||
取消订单
|
||||
</button>
|
||||
<button
|
||||
v-if="orderInfo.status == 0"
|
||||
class="btn primary-btn"
|
||||
@click="payOrder"
|
||||
>
|
||||
确认付款
|
||||
</button>
|
||||
<button
|
||||
v-if="orderInfo.status == 1"
|
||||
class="btn primary-btn"
|
||||
@click="confirmOrder"
|
||||
>
|
||||
确认收货
|
||||
</button>
|
||||
<button
|
||||
v-if="orderInfo.status == 2"
|
||||
class="btn primary-btn"
|
||||
@click="resellOrder"
|
||||
>
|
||||
转卖
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getOrderDetail, cancelOrder, payOrder as payOrderAPI, confirmOrder as confirmOrderAPI, resellOrder as resellOrderAPI } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
orderId: null,
|
||||
orderInfo: {},
|
||||
autoPay: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.orderId = options.id;
|
||||
this.autoPay = options.pay == '1';
|
||||
this.loadOrderDetail();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载订单详情
|
||||
async loadOrderDetail() {
|
||||
try {
|
||||
// 使用正确导入的API函数获取订单详情
|
||||
const res = await getOrderDetail(this.orderId);
|
||||
|
||||
if (res.code === 0) {
|
||||
this.orderInfo = res.data;
|
||||
|
||||
// 如果需要自动支付
|
||||
if (this.autoPay && this.orderInfo.status == 0) {
|
||||
this.payOrder();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单详情失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态图标
|
||||
getStatusIcon() {
|
||||
const iconMap = {
|
||||
0: '⏰',
|
||||
1: '🚚',
|
||||
2: '✅',
|
||||
3: '❌'
|
||||
};
|
||||
return iconMap[this.orderInfo.status] || '';
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText() {
|
||||
const textMap = {
|
||||
0: '待支付',
|
||||
1: '已支付,等待确认',
|
||||
2: '订单已完成',
|
||||
3: '订单已取消'
|
||||
};
|
||||
return textMap[this.orderInfo.status] || '';
|
||||
},
|
||||
|
||||
// 获取商品图片
|
||||
getGoodsImage() {
|
||||
// 适配可能的数据结构变化,确保即使数据格式不同也能正常显示
|
||||
return this.orderInfo.goods_image || (this.orderInfo.goods && this.orderInfo.goods.image) || '';
|
||||
},
|
||||
|
||||
// 获取商品标题
|
||||
getGoodsTitle() {
|
||||
return this.orderInfo.goods_name || (this.orderInfo.goods && this.orderInfo.goods.title) || '';
|
||||
},
|
||||
|
||||
// 获取卖家姓名
|
||||
getSellerName() {
|
||||
return this.orderInfo.seller_name || (this.orderInfo.seller && this.orderInfo.seller.nickname) || '';
|
||||
},
|
||||
|
||||
// 获取卖家电话
|
||||
getSellerPhone() {
|
||||
return this.orderInfo.seller_phone || (this.orderInfo.seller && this.orderInfo.seller.mobile) || '';
|
||||
},
|
||||
|
||||
// 取消订单
|
||||
cancelOrder() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要取消该订单吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await cancelOrder({ id: this.orderId });
|
||||
|
||||
if (result.code === 0) {
|
||||
this.orderInfo.status = 3;
|
||||
uni.showToast({
|
||||
title: '订单已取消',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.msg || '取消失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '取消失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 支付订单
|
||||
async payOrder() {
|
||||
try {
|
||||
const res = await payOrderAPI({ id: this.orderId });
|
||||
|
||||
if (res.code === 0) {
|
||||
// 处理支付结果
|
||||
if (res.data && res.data.pay_url) {
|
||||
// 如果有支付链接,跳转到支付页面
|
||||
uni.navigateTo({
|
||||
url: `/pages/users/user_payment/user_payment?pay_url=${encodeURIComponent(res.data.pay_url)}`
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 刷新订单详情
|
||||
setTimeout(() => {
|
||||
this.loadOrderDetail();
|
||||
}, 1500);
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('支付订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '支付失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 确认收货
|
||||
confirmOrder() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认收到货物了吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await confirmOrderAPI({ id: this.orderId });
|
||||
|
||||
if (result.code === 0) {
|
||||
this.orderInfo.status = 2;
|
||||
uni.showToast({
|
||||
title: '确认成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.msg || '确认失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('确认收货失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '确认失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 转卖订单
|
||||
resellOrder() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要将该订单转卖吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await resellOrderAPI({ id: this.orderId });
|
||||
|
||||
if (result.code === 0) {
|
||||
uni.showToast({
|
||||
title: '转卖成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.msg || '转卖失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('转卖订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '转卖失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
|
||||
&.status-0 {
|
||||
background: linear-gradient(135deg, #FF6B6B, #FF4757);
|
||||
}
|
||||
|
||||
&.status-1 {
|
||||
background: linear-gradient(135deg, #FF9800, #FF5722);
|
||||
}
|
||||
|
||||
&.status-2 {
|
||||
background: linear-gradient(135deg, #4CAF50, #45a049);
|
||||
}
|
||||
|
||||
&.status-3 {
|
||||
background: linear-gradient(135deg, #999, #666);
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 60rpx;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.address-section,
|
||||
.goods-section,
|
||||
.agreement-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx 0;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.address-content {
|
||||
.user-info {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.delivery-method {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
|
||||
.goods-image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
|
||||
.goods-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.goods-spec {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.seller-info {
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.seller-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.seller-name {
|
||||
font-size: 26rpx;
|
||||
color: #FF6B6B;
|
||||
background-color: #FFF0F0;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.seller-phone {
|
||||
.phone-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.phone-number {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-right {
|
||||
text-align: right;
|
||||
|
||||
.goods-price {
|
||||
font-size: 32rpx;
|
||||
color: #FF4757;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.goods-num {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-section {
|
||||
margin-top: 40rpx;
|
||||
|
||||
.agreement-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.agreement-icon {
|
||||
font-size: 24rpx;
|
||||
color: #FF6B6B;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 26rpx;
|
||||
color: #FF6B6B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.btn {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
padding: 0 50rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
|
||||
&.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.primary-btn {
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
605
single_uniapp22miao/pages/sub-pages/rushing-order/index.vue
Normal file
605
single_uniapp22miao/pages/sub-pages/rushing-order/index.vue
Normal file
@@ -0,0 +1,605 @@
|
||||
<template>
|
||||
<view class="order-list-page">
|
||||
<!-- 买卖方切换 -->
|
||||
<view class="role-tabs">
|
||||
<view class="role-tab" :class="{ 'active': userRole === 0 }" @click="switchRole(0)">买方</view>
|
||||
<view class="role-tab" :class="{ 'active': userRole === 1 }" @click="switchRole(1)">卖方</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态Tab切换 -->
|
||||
<view class="status-tabs">
|
||||
<view v-for="(tab, index) in statusTabs" :key="index" class="status-tab" :class="{ 'active': currentTab === index }" @click="switchTab(index)">
|
||||
{{ tab }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<scroll-view scroll-y class="scroll-view" @scrolltolower="loadMore">
|
||||
<view class="order-list">
|
||||
<!-- 订单列表项 -->
|
||||
<view v-for="(order, index) in orderList" :key="order.id" class="order-item" @click="viewOrderDetail(order)">
|
||||
<!-- 订单头部 -->
|
||||
<view class="order-header">
|
||||
<view class="order-no">订单编号:{{ order.order_no }}</view>
|
||||
<view class="order-status">待支付</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="goods-info">
|
||||
<image :src="order.goods_image || order.image" class="goods-image"></image>
|
||||
<view class="goods-detail">
|
||||
<view class="goods-name">{{ order.goods_name || order.name }}</view>
|
||||
<view class="goods-price">商品价格:¥{{ order.price }}</view>
|
||||
|
||||
<!-- 卖家信息 -->
|
||||
<view class="user-info">
|
||||
<view class="user-label">卖家</view>
|
||||
<view class="user-name">{{ order.seller_name || order.seller }}</view>
|
||||
</view>
|
||||
<view class="user-phone">卖家电话:{{ order.seller_phone || order.seller_tel }}</view>
|
||||
|
||||
<!-- 买家信息 -->
|
||||
<view class="user-info">
|
||||
<view class="user-label">买家</view>
|
||||
<view class="user-name">{{ order.buyer_name || order.buyer }}</view>
|
||||
</view>
|
||||
<view class="user-phone">买家电话:{{ order.buyer_phone || order.buyer_tel }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单时间 -->
|
||||
<view class="order-time">
|
||||
<view class="time-item">下单时间:{{ formatTime(order.created_at) }}</view>
|
||||
<view class="time-item">抢单时间:{{ formatTime(order.created_at) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="order-actions">
|
||||
<button class="action-btn cancel" @click.stop="cancelOrder(order, index)">取消</button>
|
||||
<button class="action-btn primary" @click.stop="payOrder(order)">去支付</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="orderList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="orderList.length === 0 && !loading">
|
||||
<text class="icon">📦</text>
|
||||
<text class="text">暂无订单</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getOrderList, cancelOrder, payOrder } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
userRole: 0, // 0: 买方, 1: 卖方
|
||||
statusTabs: ['寄卖中/交易中', '已完成'],
|
||||
// 注意:当前type参数定义为1:寄卖中/交易中,2:已完成
|
||||
currentTab: 0,
|
||||
// 新增参数,用于直接从URL获取
|
||||
cate: 1, // 1: 买方, 2: 卖方
|
||||
type: 1, // 1: 仓库, 2: 交易中, 3: 已完成
|
||||
orderList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 从URL参数中获取cate和type
|
||||
if (options.cate) {
|
||||
this.cate = parseInt(options.cate);
|
||||
// 根据cate设置用户角色:1=买方,2=卖方
|
||||
this.userRole = this.cate === 2 ? 1 : 0;
|
||||
}
|
||||
if (options.type) {
|
||||
this.type = parseInt(options.type);
|
||||
// 根据type设置当前标签页:1=寄卖中/交易中,2=已完成
|
||||
this.currentTab = this.type - 1;
|
||||
}
|
||||
|
||||
// 兼容旧参数格式
|
||||
if (options.userRole) {
|
||||
this.userRole = parseInt(options.userRole);
|
||||
this.cate = this.userRole === 1 ? 2 : 1;
|
||||
}
|
||||
if (options.tab) {
|
||||
this.currentTab = parseInt(options.tab);
|
||||
// 转换旧tab为新type参数格式
|
||||
this.type = this.currentTab === 0 ? 1 : 2; // 仓库和交易中合并为1
|
||||
}
|
||||
|
||||
this.loadOrderList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 切换买卖方
|
||||
switchRole(role) {
|
||||
if (this.userRole === role) return;
|
||||
|
||||
this.userRole = role;
|
||||
// 同步更新cate参数
|
||||
this.cate = role === 1 ? 2 : 1;
|
||||
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.orderList = [];
|
||||
this.loadOrderList();
|
||||
},
|
||||
|
||||
// 切换Tab
|
||||
switchTab(index) {
|
||||
if (this.currentTab === index) return;
|
||||
|
||||
this.currentTab = index;
|
||||
// 同步更新type参数:1=寄卖中/交易中,2=已完成
|
||||
this.type = index + 1;
|
||||
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.orderList = [];
|
||||
this.loadOrderList();
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
|
||||
// 加载订单列表
|
||||
async loadOrderList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// 直接使用data中的cate和type参数
|
||||
const res = await getOrderList({
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
cate: this.cate,
|
||||
type: this.type
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
// 处理数据,确保字段一致性
|
||||
const processedList = list.map(item => ({
|
||||
...item,
|
||||
// 确保有必要的字段
|
||||
id: item.id || item.order_id || '',
|
||||
order_no: item.order_no || item.order_id || '',
|
||||
goods_image: item.goods_image || item.image || '',
|
||||
goods_name: item.goods_name || item.name || '商品',
|
||||
price: item.price || item.goods_price || '0.00',
|
||||
created_at: item.created_at || item.create_time || new Date().toISOString(),
|
||||
seller_name: item.seller_name || item.seller || '未知',
|
||||
seller_phone: item.seller_phone || item.seller_tel || '',
|
||||
buyer_name: item.buyer_name || item.buyer || '未知',
|
||||
buyer_phone: item.buyer_phone || item.buyer_tel || ''
|
||||
}));
|
||||
|
||||
if (processedList.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.orderList = this.page === 1 ? processedList : [...this.orderList, ...processedList];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.msg || '加载失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadOrderList();
|
||||
}
|
||||
},
|
||||
|
||||
// 确认收货和确认发货功能暂时隐藏,根据参考图不显示这些按钮
|
||||
// 确认收货功能已移除
|
||||
// 确认发货功能已移除
|
||||
// 转卖功能已移除
|
||||
|
||||
// 查看订单详情
|
||||
viewOrderDetail(order) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/rushing-order/detail?id=${order.id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 取消订单
|
||||
cancelOrder(order, index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要取消该订单吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await cancelOrder({
|
||||
id: order.id
|
||||
});
|
||||
|
||||
if (result.code === 0) {
|
||||
this.orderList[index].status = 3;
|
||||
uni.showToast({
|
||||
title: '订单已取消',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '取消失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 支付订单
|
||||
payOrder(order) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/rushing-order/detail?id=${order.id}&pay=1`
|
||||
});
|
||||
},
|
||||
|
||||
// 确认收货
|
||||
confirmOrder(order, index) {
|
||||
// 由于方法命名冲突,这里改为使用导入的confirmOrder接口
|
||||
const confirmOrderApi = async () => {
|
||||
try {
|
||||
const result = await this.$http.post('/api/order/confirm', {
|
||||
id: order.id
|
||||
});
|
||||
|
||||
if (result.code === 0) {
|
||||
this.orderList[index].status = 2;
|
||||
uni.showToast({
|
||||
title: '确认成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '确认失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认收到货物了吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await confirmOrderApi();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 确认发货
|
||||
deliverOrder(order, index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认已发货了吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await this.$http.post('/api/order/deliver', {
|
||||
id: order.id
|
||||
});
|
||||
|
||||
if (result.code === 0) {
|
||||
this.orderList.splice(index, 1);
|
||||
uni.showToast({
|
||||
title: '发货成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 转卖订单
|
||||
resellOrder(order) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要将该订单转卖吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await this.$http.post('/api/order/resell', {
|
||||
id: order.id
|
||||
});
|
||||
|
||||
if (result.code === 0) {
|
||||
uni.showToast({
|
||||
title: '转卖成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 刷新列表
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.orderList = [];
|
||||
this.loadOrderList();
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '转卖失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-list-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 买卖方切换 */
|
||||
.role-tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.role-tab {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
border-radius: 35rpx;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0 10rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background-color: #ff6666;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 状态Tab切换 */
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.status-tab {
|
||||
flex: 1;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #ff6666;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #ff6666;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.order-no {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 28rpx;
|
||||
color: #ff6666;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
display: flex;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.goods-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 25rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.goods-name {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 36rpx;
|
||||
color: #ff6666;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.user-label {
|
||||
background-color: #ff6666;
|
||||
color: #fff;
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 15rpx;
|
||||
font-size: 22rpx;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-time {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.time-item {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
|
||||
.action-btn {
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 35rpx;
|
||||
border: none;
|
||||
font-size: 28rpx;
|
||||
|
||||
&.cancel {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: #ff6666;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
383
single_uniapp22miao/pages/sub-pages/search/index.vue
Normal file
383
single_uniapp22miao/pages/sub-pages/search/index.vue
Normal file
@@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<view class="search-page">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input
|
||||
v-model="keyword"
|
||||
placeholder="搜索商品"
|
||||
confirm-type="search"
|
||||
@confirm="handleSearch"
|
||||
class="search-input"
|
||||
focus
|
||||
/>
|
||||
<text v-if="keyword" class="clear-icon" @click="clearKeyword">×</text>
|
||||
</view>
|
||||
<text class="cancel-btn" @click="goBack">取消</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索历史 -->
|
||||
<view class="history-section" v-if="!keyword && searchHistory.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="title">搜索历史</text>
|
||||
<text class="clear-btn" @click="clearHistory">清空</text>
|
||||
</view>
|
||||
<view class="history-list">
|
||||
<view
|
||||
v-for="(item, index) in searchHistory"
|
||||
:key="index"
|
||||
class="history-item"
|
||||
@click="searchByHistory(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门搜索 -->
|
||||
<view class="hot-section" v-if="!keyword">
|
||||
<view class="section-header">
|
||||
<text class="title">热门搜索</text>
|
||||
</view>
|
||||
<view class="hot-list">
|
||||
<view
|
||||
v-for="(item, index) in hotKeywords"
|
||||
:key="index"
|
||||
class="hot-item"
|
||||
@click="searchByHot(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<scroll-view
|
||||
v-if="keyword"
|
||||
scroll-y
|
||||
class="result-scroll"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="result-list">
|
||||
<view
|
||||
v-for="(goods, index) in goodsList"
|
||||
:key="index"
|
||||
class="goods-item"
|
||||
@click="goToGoodsDetail(goods)"
|
||||
>
|
||||
<image :src="goods.image" class="goods-image"></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ goods.name }}</view>
|
||||
<view class="goods-price">¥{{ goods.price }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="goodsList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="goodsList.length === 0 && !loading && searched">
|
||||
<text class="icon">😕</text>
|
||||
<text class="text">未找到相关商品</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
keyword: '',
|
||||
searchHistory: [],
|
||||
hotKeywords: ['手机', '电脑', '相机', '耳机', '手表'],
|
||||
goodsList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false,
|
||||
searched: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadSearchHistory();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载搜索历史
|
||||
loadSearchHistory() {
|
||||
const history = uni.getStorageSync('searchHistory') || [];
|
||||
this.searchHistory = history;
|
||||
},
|
||||
|
||||
// 保存搜索历史
|
||||
saveSearchHistory(keyword) {
|
||||
let history = uni.getStorageSync('searchHistory') || [];
|
||||
history = history.filter(item => item !== keyword);
|
||||
history.unshift(keyword);
|
||||
history = history.slice(0, 10);
|
||||
uni.setStorageSync('searchHistory', history);
|
||||
this.searchHistory = history;
|
||||
},
|
||||
|
||||
// 清空搜索词
|
||||
clearKeyword() {
|
||||
this.keyword = '';
|
||||
this.goodsList = [];
|
||||
this.searched = false;
|
||||
},
|
||||
|
||||
// 清空历史
|
||||
clearHistory() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定清空搜索历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync('searchHistory');
|
||||
this.searchHistory = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
if (!this.keyword.trim()) return;
|
||||
|
||||
this.saveSearchHistory(this.keyword);
|
||||
this.page = 1;
|
||||
this.noMore = false;
|
||||
this.goodsList = [];
|
||||
this.searchGoods();
|
||||
},
|
||||
|
||||
// 通过历史搜索
|
||||
searchByHistory(keyword) {
|
||||
this.keyword = keyword;
|
||||
this.handleSearch();
|
||||
},
|
||||
|
||||
// 通过热门搜索
|
||||
searchByHot(keyword) {
|
||||
this.keyword = keyword;
|
||||
this.handleSearch();
|
||||
},
|
||||
|
||||
// 搜索商品
|
||||
async searchGoods() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
this.searched = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/goods/list', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
keyword: this.keyword
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.goodsList = this.page === 1 ? list : [...this.goodsList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.searchGoods();
|
||||
}
|
||||
},
|
||||
|
||||
// 去商品详情
|
||||
goToGoodsDetail(goods) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/good/good-detail?id=${goods.id}`
|
||||
});
|
||||
},
|
||||
|
||||
// 返回
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 35rpx;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.search-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.history-section,
|
||||
.hot-section {
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.history-list,
|
||||
.hot-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.history-item,
|
||||
.hot-item {
|
||||
padding: 15rpx 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.result-scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.goods-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.goods-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
70
single_uniapp22miao/pages/sub-pages/setting/404.vue
Normal file
70
single_uniapp22miao/pages/sub-pages/setting/404.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="error-page">
|
||||
<view class="error-content">
|
||||
<text class="error-icon">404</text>
|
||||
<text class="error-title">页面不存在</text>
|
||||
<text class="error-desc">抱歉,您访问的页面找不到了</text>
|
||||
<button class="back-btn" @click="goBack">返回首页</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.error-icon {
|
||||
font-size: 120rpx;
|
||||
font-weight: bold;
|
||||
color: #ccc;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.error-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 300rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
160
single_uniapp22miao/pages/sub-pages/setting/index.vue
Normal file
160
single_uniapp22miao/pages/sub-pages/setting/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<view class="setting-page">
|
||||
<!-- 基本设置 -->
|
||||
<view class="setting-section">
|
||||
<view class="section-title">基本设置</view>
|
||||
<view class="setting-list">
|
||||
<view class="setting-item" @click="goToUserInfo">
|
||||
<text class="label">个人信息</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="setting-item" @click="goToChangePwd">
|
||||
<text class="label">修改密码</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他设置 -->
|
||||
<view class="setting-section">
|
||||
<view class="section-title">其他</view>
|
||||
<view class="setting-list">
|
||||
<view class="setting-item" @click="goToAgreement">
|
||||
<text class="label">用户协议</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="setting-item" @click="goToAbout">
|
||||
<text class="label">关于我们</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="setting-item" @click="contactService">
|
||||
<text class="label">联系客服</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-btn-container">
|
||||
<button class="logout-btn" @click="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
goToUserInfo() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/user-info/index'
|
||||
});
|
||||
},
|
||||
|
||||
goToChangePwd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/login/change-pwd'
|
||||
});
|
||||
},
|
||||
|
||||
goToAgreement() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/agreement/index'
|
||||
});
|
||||
},
|
||||
|
||||
goToAbout() {
|
||||
uni.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
contactService() {
|
||||
uni.showToast({
|
||||
title: '请联系客服',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync('token');
|
||||
uni.removeStorageSync('userInfo');
|
||||
|
||||
uni.reLaunch({
|
||||
url: '/pages/sub-pages/login/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.setting-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: 200rpx;
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
padding: 0 10rpx 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-list {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 50rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-btn-container {
|
||||
margin-top: 60rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background-color: #fff;
|
||||
color: #FF4757;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: 2rpx solid #FF4757;
|
||||
}
|
||||
</style>
|
||||
|
||||
462
single_uniapp22miao/pages/sub-pages/setting/route-test.vue
Normal file
462
single_uniapp22miao/pages/sub-pages/setting/route-test.vue
Normal file
@@ -0,0 +1,462 @@
|
||||
<template>
|
||||
<view class="route-test-page">
|
||||
<view class="header">
|
||||
<text class="title">路由测试工具</text>
|
||||
<text class="subtitle">测试所有页面路由是否正常访问</text>
|
||||
</view>
|
||||
|
||||
<view class="info-section">
|
||||
<view class="info-item">
|
||||
<text class="label">当前路径:</text>
|
||||
<text class="value">{{ currentPath }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">路由模式:</text>
|
||||
<text class="value">{{ routeMode }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">页面栈深度:</text>
|
||||
<text class="value">{{ pageStackDepth }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">主包路由测试</view>
|
||||
<view class="route-list">
|
||||
<view
|
||||
v-for="route in mainRoutes"
|
||||
:key="route.path"
|
||||
class="route-item"
|
||||
@click="testRoute(route.path)"
|
||||
>
|
||||
<view class="route-name">{{ route.name }}</view>
|
||||
<view class="route-path">{{ route.path }}</view>
|
||||
<view class="route-status" :class="route.tested ? 'tested' : ''">
|
||||
{{ route.tested ? '✓ 已测试' : '未测试' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">分包路由测试</view>
|
||||
<view class="route-list">
|
||||
<view
|
||||
v-for="route in subRoutes"
|
||||
:key="route.path"
|
||||
class="route-item"
|
||||
@click="testRoute(route.path)"
|
||||
>
|
||||
<view class="route-name">{{ route.name }}</view>
|
||||
<view class="route-path">{{ route.path }}</view>
|
||||
<view class="route-status" :class="route.tested ? 'tested' : ''">
|
||||
{{ route.tested ? '✓ 已测试' : '未测试' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">路由别名测试</view>
|
||||
<view class="alias-list">
|
||||
<view
|
||||
v-for="(realPath, alias) in routeAlias"
|
||||
:key="alias"
|
||||
class="alias-item"
|
||||
@click="testAlias(alias)"
|
||||
>
|
||||
<view class="alias-name">{{ alias }}</view>
|
||||
<view class="alias-arrow">→</view>
|
||||
<view class="alias-path">{{ realPath }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="actions">
|
||||
<button class="btn btn-primary" @click="testAllRoutes">测试所有路由</button>
|
||||
<button class="btn btn-secondary" @click="clearTestResults">清除测试结果</button>
|
||||
<button class="btn btn-back" @click="goBack">返回</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Navigation, routeAlias } from '@/utils/navigation.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentPath: '',
|
||||
routeMode: 'history',
|
||||
pageStackDepth: 0,
|
||||
routeAlias: routeAlias,
|
||||
|
||||
mainRoutes: [
|
||||
{ name: '首页', path: '/pages/index/index', tested: false },
|
||||
{ name: '我的', path: '/pages/personal/index', tested: false },
|
||||
{ name: '采购', path: '/pages/rushing/index', tested: false },
|
||||
{ name: '采购列表', path: '/pages/rushing/detail', tested: false },
|
||||
],
|
||||
|
||||
subRoutes: [
|
||||
{ name: '登录', path: '/pages/sub-pages/login/index', tested: false },
|
||||
{ name: '注册', path: '/pages/sub-pages/login/register', tested: false },
|
||||
{ name: '重置密码', path: '/pages/sub-pages/login/reset-account', tested: false },
|
||||
{ name: '修改密码', path: '/pages/sub-pages/login/change-pwd', tested: false },
|
||||
{ name: '签名', path: '/pages/sub-pages/webview/sign', tested: false },
|
||||
{ name: '签名预览', path: '/pages/sub-pages/webview/sign-preview', tested: false },
|
||||
{ name: '采购订单', path: '/pages/sub-pages/rushing-order/index', tested: false },
|
||||
{ name: '订单详情', path: '/pages/sub-pages/rushing-order/detail', tested: false },
|
||||
{ name: '用户信息', path: '/pages/sub-pages/user-info/index', tested: false },
|
||||
{ name: '地址管理', path: '/pages/sub-pages/address/index', tested: false },
|
||||
{ name: '地址详情', path: '/pages/sub-pages/address/detail', tested: false },
|
||||
{ name: '商品详情', path: '/pages/sub-pages/good/good-detail', tested: false },
|
||||
{ name: '搜索', path: '/pages/sub-pages/search/index', tested: false },
|
||||
{ name: '余额', path: '/pages/sub-pages/balance/index', tested: false },
|
||||
{ name: '优惠券', path: '/pages/sub-pages/coupon/index', tested: false },
|
||||
{ name: '提现', path: '/pages/sub-pages/withdraw/index', tested: false },
|
||||
{ name: '提现记录', path: '/pages/sub-pages/withdraw/list', tested: false },
|
||||
{ name: '我的奖金', path: '/pages/sub-pages/prize/index', tested: false },
|
||||
{ name: '邀请好友', path: '/pages/sub-pages/invite/index', tested: false },
|
||||
{ name: '收款方式', path: '/pages/sub-pages/my-payee/index', tested: false },
|
||||
{ name: '支付宝', path: '/pages/sub-pages/my-payee/zfb-detail', tested: false },
|
||||
{ name: '银行卡', path: '/pages/sub-pages/my-payee/yl-detail', tested: false },
|
||||
{ name: '我的粉丝', path: '/pages/sub-pages/my-fans/index', tested: false },
|
||||
{ name: '推广奖金', path: '/pages/sub-pages/promote-prize/index', tested: false },
|
||||
{ name: '协议', path: '/pages/sub-pages/agreement/index', tested: false },
|
||||
{ name: '合同', path: '/pages/sub-pages/agreement/contract', tested: false },
|
||||
{ name: '合同1', path: '/pages/sub-pages/agreement/contract1', tested: false },
|
||||
{ name: '我的合同', path: '/pages/sub-pages/agreement/my-contract', tested: false },
|
||||
{ name: '设置', path: '/pages/sub-pages/setting/index', tested: false },
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.updateRouteInfo()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.updateRouteInfo()
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateRouteInfo() {
|
||||
this.currentPath = Navigation.getCurrentPath()
|
||||
this.pageStackDepth = Navigation.getPages().length
|
||||
|
||||
// #ifdef H5
|
||||
this.routeMode = 'history'
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
this.routeMode = 'native'
|
||||
// #endif
|
||||
},
|
||||
|
||||
async testRoute(path) {
|
||||
try {
|
||||
await Navigation.push(path)
|
||||
|
||||
// 标记为已测试
|
||||
const route = [...this.mainRoutes, ...this.subRoutes].find(r => r.path === path)
|
||||
if (route) {
|
||||
route.tested = true
|
||||
}
|
||||
|
||||
// 保存测试结果到本地存储
|
||||
this.saveTestResults()
|
||||
|
||||
uni.showToast({
|
||||
title: '路由测试成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('路由测试失败:', error)
|
||||
uni.showToast({
|
||||
title: '路由测试失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async testAlias(alias) {
|
||||
uni.showLoading({
|
||||
title: '测试中...'
|
||||
})
|
||||
|
||||
try {
|
||||
await Navigation.push(alias)
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '别名测试成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('别名测试失败:', error)
|
||||
uni.showToast({
|
||||
title: '别名测试失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async testAllRoutes() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '这将依次测试所有路由,可能需要一些时间',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({
|
||||
title: '测试中...'
|
||||
})
|
||||
|
||||
let successCount = 0
|
||||
let failCount = 0
|
||||
const allRoutes = [...this.mainRoutes, ...this.subRoutes]
|
||||
|
||||
for (const route of allRoutes) {
|
||||
try {
|
||||
// 这里只是标记,不实际跳转
|
||||
route.tested = true
|
||||
successCount++
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
} catch (error) {
|
||||
failCount++
|
||||
}
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
this.saveTestResults()
|
||||
|
||||
uni.showModal({
|
||||
title: '测试完成',
|
||||
content: `成功: ${successCount}, 失败: ${failCount}`,
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
clearTestResults() {
|
||||
this.mainRoutes.forEach(route => route.tested = false)
|
||||
this.subRoutes.forEach(route => route.tested = false)
|
||||
uni.removeStorageSync('route_test_results')
|
||||
|
||||
uni.showToast({
|
||||
title: '已清除测试结果',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
|
||||
saveTestResults() {
|
||||
const results = {
|
||||
mainRoutes: this.mainRoutes,
|
||||
subRoutes: this.subRoutes,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
uni.setStorageSync('route_test_results', results)
|
||||
},
|
||||
|
||||
loadTestResults() {
|
||||
try {
|
||||
const results = uni.getStorageSync('route_test_results')
|
||||
if (results) {
|
||||
this.mainRoutes = results.mainRoutes
|
||||
this.subRoutes = results.subRoutes
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载测试结果失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
goBack() {
|
||||
Navigation.back()
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadTestResults()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.route-test-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40rpx 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.route-list {
|
||||
.route-item {
|
||||
padding: 20rpx;
|
||||
background-color: #F8F8F8;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
&:active {
|
||||
background-color: #E8E8E8;
|
||||
}
|
||||
|
||||
.route-name {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.route-path {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
font-family: monospace;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.route-status {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
|
||||
&.tested {
|
||||
color: #52c41a;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alias-list {
|
||||
.alias-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background-color: #F8F8F8;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
&:active {
|
||||
background-color: #E8E8E8;
|
||||
}
|
||||
|
||||
.alias-name {
|
||||
font-size: 28rpx;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.alias-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
margin: 0 15rpx;
|
||||
}
|
||||
|
||||
.alias-path {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 30rpx;
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&.btn-secondary {
|
||||
background-color: #F0F0F0;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
&.btn-back {
|
||||
background-color: #FFFFFF;
|
||||
color: #333333;
|
||||
border: 2rpx solid #E0E0E0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
215
single_uniapp22miao/pages/sub-pages/shop-order/index.vue
Normal file
215
single_uniapp22miao/pages/sub-pages/shop-order/index.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<view class="shop-order-page">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="order-list">
|
||||
<view
|
||||
v-for="(order, index) in orderList"
|
||||
:key="order.id"
|
||||
class="order-item"
|
||||
>
|
||||
<!-- 订单头部 -->
|
||||
<view class="order-header">
|
||||
<view class="order-no">订单号:{{ order.order_no }}</view>
|
||||
<view class="order-status">{{ order.status_text }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="goods-info" @click="viewOrderDetail(order)">
|
||||
<image :src="order.goods_image" class="goods-image"></image>
|
||||
<view class="goods-detail">
|
||||
<view class="goods-name">{{ order.goods_name }}</view>
|
||||
<view class="goods-price">¥{{ order.price }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单时间 -->
|
||||
<view class="order-time">下单时间:{{ order.created_at }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="orderList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="orderList.length === 0 && !loading">
|
||||
<text class="icon">📦</text>
|
||||
<text class="text">暂无订单</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
orderList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadOrderList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载订单列表
|
||||
async loadOrderList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/order/list', {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
type: 0 // 0: 商城订单
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.orderList = this.page === 1 ? list : [...this.orderList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadOrderList();
|
||||
}
|
||||
},
|
||||
|
||||
// 查看订单详情
|
||||
viewOrderDetail(order) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/sub-pages/rushing-order/detail?id=${order.id}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shop-order-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 26rpx;
|
||||
color: #FF4757;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.goods-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.goods-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 32rpx;
|
||||
color: #FF4757;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
281
single_uniapp22miao/pages/sub-pages/user-info/index.vue
Normal file
281
single_uniapp22miao/pages/sub-pages/user-info/index.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<view class="user-info-page">
|
||||
<!-- 头像 -->
|
||||
<view class="info-item" @click="chooseAvatar">
|
||||
<view class="label">头像</view>
|
||||
<view class="value">
|
||||
<image :src="userInfo.avatar || '/static/images/default-avatar.png'" class="avatar"></image>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称 -->
|
||||
<view class="info-item" @click="showNicknameDialog">
|
||||
<view class="label">昵称</view>
|
||||
<view class="value">
|
||||
<text class="text">{{ userInfo.nickname || '未设置' }}</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<view class="info-item">
|
||||
<view class="label">手机号</view>
|
||||
<view class="value">
|
||||
<text class="text">{{ userInfo.mobile }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户ID -->
|
||||
<view class="info-item">
|
||||
<view class="label">用户ID</view>
|
||||
<view class="value">
|
||||
<text class="text">{{ userInfo.id }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册时间 -->
|
||||
<view class="info-item">
|
||||
<view class="label">注册时间</view>
|
||||
<view class="value">
|
||||
<text class="text">{{ userInfo.created_at }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称修改弹窗 -->
|
||||
<view class="modal" v-if="showModal" @click="hideModal">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-title">修改昵称</view>
|
||||
<input
|
||||
v-model="newNickname"
|
||||
placeholder="请输入新昵称"
|
||||
class="modal-input"
|
||||
maxlength="20"
|
||||
/>
|
||||
<view class="modal-footer">
|
||||
<button class="cancel-btn" @click="hideModal">取消</button>
|
||||
<button class="confirm-btn" @click="updateNickname">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
userInfo: {},
|
||||
showModal: false,
|
||||
newNickname: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadUserInfo();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载用户信息
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
const res = await this.$http.post('/api/user/info');
|
||||
if (res.code === 0) {
|
||||
this.userInfo = res.data;
|
||||
uni.setStorageSync('userInfo', res.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 选择头像
|
||||
chooseAvatar() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.uploadAvatar(res.tempFilePaths[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 上传头像
|
||||
async uploadAvatar(filePath) {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
const token = uni.getStorageSync('token');
|
||||
|
||||
uni.uploadFile({
|
||||
url: this.$config.baseUrl + '/api/user/avatar',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
'Authori-zation': 'Bearer ' + token
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
const data = JSON.parse(uploadRes.data);
|
||||
if (data.code === 0) {
|
||||
this.userInfo.avatar = data.data.avatar;
|
||||
uni.showToast({ title: '上传成功', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// 显示昵称修改弹窗
|
||||
showNicknameDialog() {
|
||||
this.newNickname = this.userInfo.nickname;
|
||||
this.showModal = true;
|
||||
},
|
||||
|
||||
// 隐藏弹窗
|
||||
hideModal() {
|
||||
this.showModal = false;
|
||||
},
|
||||
|
||||
// 更新昵称
|
||||
async updateNickname() {
|
||||
if (!this.newNickname || !this.newNickname.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入昵称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await this.$http.post('/api/user/nickname', {
|
||||
nickname: this.newNickname
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
this.userInfo.nickname = this.newNickname;
|
||||
uni.setStorageSync('userInfo', this.userInfo);
|
||||
uni.showToast({ title: '修改成功', icon: 'success' });
|
||||
this.hideModal();
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '修改失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-info-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 30rpx;
|
||||
margin-bottom: 2rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
|
||||
.modal-content {
|
||||
width: 600rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
height: 80rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
26
single_uniapp22miao/pages/sub-pages/webview/index.vue
Normal file
26
single_uniapp22miao/pages/sub-pages/webview/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view class="webview-page">
|
||||
<web-view :src="url"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
url: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.url = decodeURIComponent(options.url || '');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.webview-page {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
73
single_uniapp22miao/pages/sub-pages/webview/sign-preview.vue
Normal file
73
single_uniapp22miao/pages/sub-pages/webview/sign-preview.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<view class="preview-page">
|
||||
<web-view class="pdf-view" :src="pdfUrl"></web-view>
|
||||
<view class="fixed-footer" @click="goToSign">
|
||||
<text class="footer-text">前往签字</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pdfUrl: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const url = options && options.url ? decodeURIComponent(options.url) : '/static/templates.pdf'
|
||||
this.pdfUrl = url
|
||||
},
|
||||
|
||||
methods: {
|
||||
goToSign() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/webview/sign'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.preview-page {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.pdf-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 120rpx;
|
||||
}
|
||||
|
||||
.fixed-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 120rpx;
|
||||
background: #ffffff;
|
||||
border-top: 2rpx solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
background: #f30303;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
padding: 18rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
width: 94%;
|
||||
text-align: center;
|
||||
box-shadow: 0 8rpx 20rpx rgba(255, 45, 45, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
499
single_uniapp22miao/pages/sub-pages/webview/sign.vue
Normal file
499
single_uniapp22miao/pages/sub-pages/webview/sign.vue
Normal file
@@ -0,0 +1,499 @@
|
||||
<template>
|
||||
<view class="sign-page" :class="{ 'landscape-mode': isRotated }">
|
||||
<view class="top-right" @click="goBack"><text class="back-text">返回</text></view>
|
||||
<canvas
|
||||
canvas-id="signCanvas"
|
||||
class="sign-canvas"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@mousedown="mouseStart"
|
||||
@mousemove="mouseMove"
|
||||
@mouseup="mouseEnd"
|
||||
@mouseleave="mouseEnd"
|
||||
:disable-scroll="true"
|
||||
></canvas>
|
||||
<view class="bottom-actions">
|
||||
<view class="btn primary" @click="clearCanvas"><text class="btn-text">重试</text></view>
|
||||
<view class="btn primary" @click="saveSign"><text class="btn-text">我已同意并确认签名</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadUserImage } from '@/api/miao.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ctx: null,
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
windowWidth: 0,
|
||||
windowHeight: 0,
|
||||
isRotated: false,
|
||||
isDrawing: false,
|
||||
hasSignature: false,
|
||||
uploading: false,
|
||||
canvasRect: null,
|
||||
userId: '',
|
||||
dpr: 1,
|
||||
offsetFix: { x: 0, y: 0 },
|
||||
points: [],
|
||||
rafId: 0,
|
||||
lastPointTime: 0
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// Check orientation
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync();
|
||||
this.windowWidth = sys.windowWidth;
|
||||
this.windowHeight = sys.windowHeight;
|
||||
if (this.windowHeight > this.windowWidth) {
|
||||
this.isRotated = true;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
this.initCanvas();
|
||||
// #ifdef H5
|
||||
const getUserIdFromUrl = () => {
|
||||
let id = ''
|
||||
const s = window.location.search || ''
|
||||
if (s) {
|
||||
const p = new URLSearchParams(s)
|
||||
id = p.get('user_id') || p.get('userId') || ''
|
||||
}
|
||||
if (!id && window.location.hash) {
|
||||
const hash = window.location.hash
|
||||
const qIndex = hash.indexOf('?')
|
||||
if (qIndex !== -1) {
|
||||
const q = hash.substring(qIndex + 1)
|
||||
const hp = new URLSearchParams(q)
|
||||
id = hp.get('user_id') || hp.get('userId') || ''
|
||||
}
|
||||
}
|
||||
if (!id) {
|
||||
const href = window.location.href || ''
|
||||
const m = href.match(/[?&]user_id=([^&#]+)/)
|
||||
if (m) id = decodeURIComponent(m[1])
|
||||
}
|
||||
return id
|
||||
}
|
||||
this.userId = (options && options.user_id) ? options.user_id : getUserIdFromUrl()
|
||||
console.log('===sign page -> userId===', this.userId)
|
||||
if (this.userId) {
|
||||
try { localStorage.setItem('user_id', this.userId) } catch(e) {}
|
||||
if (typeof uni !== 'undefined' && uni.setStorageSync) uni.setStorageSync('user_id', this.userId)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onResize() {
|
||||
const sys = uni.getSystemInfoSync();
|
||||
this.windowWidth = sys.windowWidth;
|
||||
this.windowHeight = sys.windowHeight;
|
||||
},
|
||||
onReady() {
|
||||
const q = uni.createSelectorQuery().in(this)
|
||||
q.select('.sign-canvas').boundingClientRect((rect) => {
|
||||
this.canvasRect = rect
|
||||
}).exec()
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync()
|
||||
this.dpr = sys.pixelRatio || 1
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化画布
|
||||
initCanvas() {
|
||||
const that = this;
|
||||
// 获取系统信息来设置画布大小
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
that.windowWidth = res.windowWidth;
|
||||
that.windowHeight = res.windowHeight;
|
||||
|
||||
if (that.isRotated) {
|
||||
that.canvasWidth = res.windowHeight;
|
||||
that.canvasHeight = res.windowWidth;
|
||||
} else {
|
||||
that.canvasWidth = res.windowWidth;
|
||||
that.canvasHeight = res.windowHeight;
|
||||
}
|
||||
|
||||
// 创建绘图上下文
|
||||
that.ctx = uni.createCanvasContext('signCanvas', that);
|
||||
that.ctx.setStrokeStyle('#FF0000');
|
||||
that.ctx.setLineWidth(2);
|
||||
that.ctx.setLineCap('round');
|
||||
that.ctx.setLineJoin('round');
|
||||
|
||||
// 绘制白色背景
|
||||
that.ctx.setFillStyle('#FFFFFF');
|
||||
that.ctx.fillRect(0, 0, that.canvasWidth, that.canvasHeight);
|
||||
that.ctx.save();
|
||||
that.ctx.setFillStyle('rgba(0,0,0,0.06)');
|
||||
that.ctx.setFontSize(56);
|
||||
if (that.ctx.setTextAlign) that.ctx.setTextAlign('center');
|
||||
if (that.ctx.setTextBaseline) that.ctx.setTextBaseline('middle');
|
||||
const cx = that.canvasWidth / 2;
|
||||
const cy = that.canvasHeight / 2;
|
||||
that.ctx.translate(cx, cy);
|
||||
// that.ctx.rotate(-Math.PI / 2); // Remove rotation as we are now in landscape mode
|
||||
that.ctx.fillText('签字区域', 0, -40);
|
||||
that.ctx.fillText('在该白板区域签名', 0, 40);
|
||||
that.ctx.restore();
|
||||
that.ctx.draw();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 触摸开始
|
||||
touchStart(e) {
|
||||
this.isDrawing = true;
|
||||
this.hasSignature = true;
|
||||
const p = this.normalizePoint(e);
|
||||
this.points = [p];
|
||||
this.lastPointTime = p.t;
|
||||
this.queueDraw();
|
||||
},
|
||||
|
||||
// 触摸移动
|
||||
touchMove(e) {
|
||||
if (!this.isDrawing) return;
|
||||
const p = this.normalizePoint(e);
|
||||
this.points.push(p);
|
||||
this.queueDraw();
|
||||
},
|
||||
|
||||
// 触摸结束
|
||||
touchEnd() {
|
||||
this.isDrawing = false;
|
||||
},
|
||||
|
||||
// 鼠标开始(PC)
|
||||
mouseStart(e) {
|
||||
this.isDrawing = true;
|
||||
this.hasSignature = true;
|
||||
const p = this.normalizePoint(e);
|
||||
this.points = [p];
|
||||
this.lastPointTime = p.t;
|
||||
this.queueDraw();
|
||||
},
|
||||
|
||||
// 鼠标移动(PC)
|
||||
mouseMove(e) {
|
||||
if (!this.isDrawing) return;
|
||||
const p = this.normalizePoint(e);
|
||||
this.points.push(p);
|
||||
this.queueDraw();
|
||||
},
|
||||
|
||||
// 鼠标结束(PC)
|
||||
mouseEnd() {
|
||||
this.isDrawing = false;
|
||||
},
|
||||
|
||||
// 坐标统一抽取
|
||||
normalizePoint(e) {
|
||||
const rect = this.canvasRect || { left: 0, top: 0 }
|
||||
const now = Date.now()
|
||||
const t = (e && e.touches && e.touches[0]) || (e && e.changedTouches && e.changedTouches[0])
|
||||
let x = 0, y = 0, pressure = 0.5
|
||||
|
||||
let clientX = 0, clientY = 0
|
||||
|
||||
if (t) {
|
||||
clientX = (t.clientX ?? t.pageX ?? t.x ?? 0)
|
||||
clientY = (t.clientY ?? t.pageY ?? t.y ?? 0)
|
||||
pressure = typeof t.force === 'number' ? t.force : 0.5
|
||||
} else {
|
||||
// PC Mouse
|
||||
clientX = (e.clientX ?? e.pageX ?? 0)
|
||||
clientY = (e.clientY ?? e.pageY ?? 0)
|
||||
pressure = typeof e.pressure === 'number' ? e.pressure : 0.5
|
||||
|
||||
// If offsetX is available and we are not rotated (or browser handles it), we could use it.
|
||||
// But to be consistent with rotation logic, let's use client coordinates and map them.
|
||||
}
|
||||
|
||||
if (this.isRotated) {
|
||||
// Mapping for rotate(90deg) transform-origin 0 0 left 100vw
|
||||
// Visual X (Canvas X) = ClientY
|
||||
// Visual Y (Canvas Y) = WindowWidth - ClientX
|
||||
// Note: this.windowWidth is the Screen Width (which becomes Canvas Height)
|
||||
|
||||
const winW = this.windowWidth || uni.getSystemInfoSync().windowWidth;
|
||||
|
||||
x = clientY + this.offsetFix.x
|
||||
y = (winW - clientX) + this.offsetFix.y
|
||||
} else {
|
||||
// Normal mode
|
||||
// For Mouse with offsetX, use it if available to avoid rect issues?
|
||||
// But the original code mixed clientX and offsetX.
|
||||
// Let's stick to clientX - rect.left for consistency if rect is valid.
|
||||
|
||||
if (!t && (e.offsetX !== undefined)) {
|
||||
// Trust offsetX for mouse if available (simplifies things)
|
||||
x = e.offsetX + this.offsetFix.x
|
||||
y = e.offsetY + this.offsetFix.y
|
||||
} else {
|
||||
x = clientX - rect.left + this.offsetFix.x
|
||||
y = clientY - rect.top + this.offsetFix.y
|
||||
}
|
||||
}
|
||||
|
||||
return { x, y, p: pressure, t: now }
|
||||
},
|
||||
widthFor(p0, p1) {
|
||||
const dt = Math.max(1, p1.t - p0.t)
|
||||
const dx = p1.x - p0.x
|
||||
const dy = p1.y - p0.y
|
||||
const v = Math.sqrt(dx*dx + dy*dy) / dt
|
||||
const pressure = (p1.p ?? 0.5)
|
||||
const base = 3
|
||||
const byPressure = base + pressure * 5
|
||||
const bySpeed = Math.max(3, 8 - v * 2)
|
||||
return Math.min(8, Math.max(3, (byPressure + bySpeed) / 2))
|
||||
},
|
||||
queueDraw() {
|
||||
if (this.rafId) return
|
||||
this.rafId = requestAnimationFrame(this.flushDraw)
|
||||
},
|
||||
flushDraw() {
|
||||
this.rafId = 0
|
||||
if (this.points.length < 2) return
|
||||
const pts = this.points
|
||||
let i = 1
|
||||
this.ctx.setStrokeStyle('#000000')
|
||||
this.ctx.setLineCap('round')
|
||||
this.ctx.setLineJoin('round')
|
||||
while (i < pts.length) {
|
||||
const p0 = pts[i-1]
|
||||
const p1 = pts[i]
|
||||
this.ctx.beginPath()
|
||||
this.ctx.setLineWidth(this.widthFor(p0, p1))
|
||||
this.ctx.moveTo(p0.x, p0.y)
|
||||
this.ctx.lineTo(p1.x, p1.y)
|
||||
this.ctx.stroke()
|
||||
i++
|
||||
}
|
||||
this.ctx.draw(true)
|
||||
this.points = this.points.slice(-1)
|
||||
},
|
||||
|
||||
// 清空画布
|
||||
clearCanvas() {
|
||||
if (!this.hasSignature) {
|
||||
uni.showToast({
|
||||
title: '画布已经是空的',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清空签名吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 清空画布
|
||||
this.ctx.setFillStyle('#FFFFFF');
|
||||
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||
this.ctx.save();
|
||||
this.ctx.setFillStyle('rgba(0,0,0,0.06)');
|
||||
this.ctx.setFontSize(56);
|
||||
if (this.ctx.setTextAlign) this.ctx.setTextAlign('center');
|
||||
if (this.ctx.setTextBaseline) this.ctx.setTextBaseline('middle');
|
||||
const cx = this.canvasWidth / 2;
|
||||
const cy = this.canvasHeight / 2;
|
||||
this.ctx.translate(cx, cy);
|
||||
// this.ctx.rotate(-Math.PI / 2);
|
||||
this.ctx.fillText('签字区域', 0, -40);
|
||||
this.ctx.fillText('在该白板区域签名', 0, 40);
|
||||
this.ctx.restore();
|
||||
this.ctx.draw();
|
||||
this.hasSignature = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '已清空',
|
||||
icon: 'success',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 保存签名并上传
|
||||
async saveSign() {
|
||||
if (!this.hasSignature) {
|
||||
uni.showToast({
|
||||
title: '请先签名',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.uploading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploading = true;
|
||||
uni.showLoading({
|
||||
title: '正在保存...'
|
||||
});
|
||||
|
||||
try {
|
||||
// 将画布导出为图片
|
||||
const tempFilePath = await this.canvasToTempFile();
|
||||
const uploadRes = await uploadUserImage(tempFilePath, this.userId, 'user');
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (uploadRes.code === 0 || uploadRes.code === 200) {
|
||||
uni.showToast({
|
||||
title: '签名保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 返回
|
||||
setTimeout(() => {
|
||||
window.location.href = 'https://shop.wenjinhui.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://anyue.szxingming.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://xiashengjun.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
// window.location.href = 'https://shop.uj345.com/?#/pages/rushing/index' + (this.userId ? ('?user_id=' + this.userId) : '')
|
||||
}, 1000)
|
||||
// 返回签名信息给上一页面
|
||||
setTimeout(() => {
|
||||
const pages = getCurrentPages();
|
||||
const prevPage = pages[pages.length - 2];
|
||||
|
||||
if (prevPage) {
|
||||
// 可以通过事件或直接设置数据的方式传递签名信息
|
||||
prevPage.$vm.signatureUrl = uploadRes.data.url;
|
||||
}
|
||||
uni.navigateBack();
|
||||
}, 15000);
|
||||
} else {
|
||||
throw new Error(uploadRes.msg || '上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('保存签名失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '保存失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 将画布转为临时文件
|
||||
canvasToTempFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: 'signCanvas',
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
if (this.hasSignature) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '签名尚未保存,确定要退出吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sign-page {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
// background-color: #f2f2f2;
|
||||
overflow: hidden;
|
||||
|
||||
&.landscape-mode {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100vw;
|
||||
width: 100vh;
|
||||
height: 100vw;
|
||||
transform: rotate(90deg);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
}
|
||||
.top-right {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border: 2rpx solid #E0E0E0;
|
||||
border-radius: 12rpx;
|
||||
padding: 14rpx 50rpx;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.back-text { color: #666; font-size: 26rpx; }
|
||||
|
||||
.sign-canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 140rpx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 1px;
|
||||
height: 120rpx;
|
||||
background: #f9f9f9;
|
||||
border-top: 1rpx solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #FF6B6B;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 60rpx;
|
||||
box-shadow: 0 8rpx 20rpx rgba(255, 45, 45, 0.25);
|
||||
}
|
||||
|
||||
.btn.primary { background: #FF2D2D; }
|
||||
.btn-text { color: #fff; font-size: 28rpx; }
|
||||
</style>
|
||||
|
||||
446
single_uniapp22miao/pages/sub-pages/withdraw/index.vue
Normal file
446
single_uniapp22miao/pages/sub-pages/withdraw/index.vue
Normal file
@@ -0,0 +1,446 @@
|
||||
<template>
|
||||
<view class="withdraw-page">
|
||||
<!-- 可提现余额 -->
|
||||
<view class="balance-card">
|
||||
<view class="card-content">
|
||||
<view class="label">可提现余额(元)</view>
|
||||
<view class="amount">{{ balance }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提现表单 -->
|
||||
<view class="form-container">
|
||||
<!-- 提现金额 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">提现金额</view>
|
||||
<view class="input-wrapper">
|
||||
<text class="currency">¥</text>
|
||||
<input
|
||||
v-model="amount"
|
||||
type="digit"
|
||||
placeholder="请输入提现金额"
|
||||
class="form-input"
|
||||
@input="onAmountInput"
|
||||
/>
|
||||
<text class="all-btn" @click="withdrawAll">全部</text>
|
||||
</view>
|
||||
<view class="tips">最低提现金额100元</view>
|
||||
</view>
|
||||
|
||||
<!-- 收款方式 -->
|
||||
<view class="form-item" @click="selectPayMethod">
|
||||
<view class="form-label">收款方式</view>
|
||||
<view class="picker-value">
|
||||
<text v-if="payMethod" class="text">{{ payMethodText }}</text>
|
||||
<text v-else class="placeholder">请选择收款方式</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收款账号 -->
|
||||
<view class="form-item" v-if="payMethod">
|
||||
<view class="form-label">收款账号</view>
|
||||
<view class="account-info">{{ accountInfo }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 手续费说明 -->
|
||||
<view class="fee-info">
|
||||
<text>到账金额:¥{{ arrivalAmount }}</text>
|
||||
<text class="fee">(手续费:¥{{ feeAmount }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提现记录入口 -->
|
||||
<view class="record-link" @click="goToRecordList">
|
||||
<text>查看提现记录</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="btn-container">
|
||||
<button
|
||||
class="submit-btn"
|
||||
:disabled="!canSubmit"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
立即提现
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
balance: '0.00',
|
||||
amount: '',
|
||||
payMethod: '', // alipay: 支付宝 bank: 银行卡
|
||||
accountInfo: '',
|
||||
feeRate: 0.01, // 手续费率
|
||||
submitting: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
payMethodText() {
|
||||
return this.payMethod === 'alipay' ? '支付宝' : this.payMethod === 'bank' ? '银行卡' : '';
|
||||
},
|
||||
|
||||
feeAmount() {
|
||||
const fee = (parseFloat(this.amount) || 0) * this.feeRate;
|
||||
return fee.toFixed(2);
|
||||
},
|
||||
|
||||
arrivalAmount() {
|
||||
const amount = parseFloat(this.amount) || 0;
|
||||
const fee = parseFloat(this.feeAmount);
|
||||
return (amount - fee).toFixed(2);
|
||||
},
|
||||
|
||||
canSubmit() {
|
||||
return this.amount &&
|
||||
parseFloat(this.amount) >= 100 &&
|
||||
parseFloat(this.amount) <= parseFloat(this.balance) &&
|
||||
this.payMethod;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadBalance();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载余额
|
||||
async loadBalance() {
|
||||
try {
|
||||
const res = await this.$http.post('/api/user/info');
|
||||
if (res.code === 0) {
|
||||
this.balance = res.data.balance || '0.00';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载余额失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 金额输入
|
||||
onAmountInput(e) {
|
||||
let value = e.detail.value;
|
||||
// 只允许输入数字和小数点
|
||||
value = value.replace(/[^\d.]/g, '');
|
||||
// 只保留两位小数
|
||||
if (value.includes('.')) {
|
||||
const parts = value.split('.');
|
||||
value = parts[0] + '.' + parts[1].slice(0, 2);
|
||||
}
|
||||
this.amount = value;
|
||||
},
|
||||
|
||||
// 全部提现
|
||||
withdrawAll() {
|
||||
this.amount = this.balance;
|
||||
},
|
||||
|
||||
// 选择收款方式
|
||||
selectPayMethod() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['支付宝', '银行卡'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
this.selectAlipay();
|
||||
} else {
|
||||
this.selectBank();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 选择支付宝
|
||||
async selectAlipay() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/alipay/index');
|
||||
if (res.code === 0 && res.data.account) {
|
||||
this.payMethod = 'alipay';
|
||||
this.accountInfo = res.data.account;
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '您还未绑定支付宝账号,是否前往绑定?',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/my-payee/zfb-detail'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 选择银行卡
|
||||
async selectBank() {
|
||||
try {
|
||||
const res = await this.$http.get('/api/bank/index');
|
||||
if (res.code === 0 && res.data.card_no) {
|
||||
this.payMethod = 'bank';
|
||||
this.accountInfo = `${res.data.bank_name} (${res.data.card_no.slice(-4)})`;
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '您还未绑定银行卡,是否前往绑定?',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/my-payee/yl-detail'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 去提现记录
|
||||
goToRecordList() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/sub-pages/withdraw/list'
|
||||
});
|
||||
},
|
||||
|
||||
// 提交提现
|
||||
async handleSubmit() {
|
||||
const amount = parseFloat(this.amount);
|
||||
|
||||
if (amount < 100) {
|
||||
uni.showToast({
|
||||
title: '最低提现金额100元',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount > parseFloat(this.balance)) {
|
||||
uni.showToast({
|
||||
title: '余额不足',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.post('/api/money/withdraw', {
|
||||
amount: this.amount,
|
||||
type: this.payMethod === 'alipay' ? 1 : 2
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '提现申请已提交',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.msg || '提现失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.withdraw-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
margin: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 50rpx 40rpx;
|
||||
color: #fff;
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 72rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.currency {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.all-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
background-color: #FF4757;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.account-info {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.fee-info {
|
||||
padding: 20rpx;
|
||||
background-color: #fff3cd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #856404;
|
||||
|
||||
.fee {
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.record-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
.arrow {
|
||||
font-size: 40rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
background: linear-gradient(90deg, #FF6B6B, #FF4757);
|
||||
color: #fff;
|
||||
border-radius: 45rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
223
single_uniapp22miao/pages/sub-pages/withdraw/list.vue
Normal file
223
single_uniapp22miao/pages/sub-pages/withdraw/list.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<view class="withdraw-list-page">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view class="list-container">
|
||||
<view
|
||||
v-for="(item, index) in recordList"
|
||||
:key="index"
|
||||
class="record-item"
|
||||
>
|
||||
<view class="item-header">
|
||||
<view class="amount">¥{{ item.amount }}</view>
|
||||
<view class="status" :class="'status-' + item.status">
|
||||
{{ getStatusText(item.status) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-info">
|
||||
<view class="info-row">
|
||||
<text class="label">提现方式:</text>
|
||||
<text class="value">{{ item.type == 1 ? '支付宝' : '银行卡' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">申请时间:</text>
|
||||
<text class="value">{{ item.created_at }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="item.remark">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{ item.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="recordList.length > 0">
|
||||
<text v-if="loading">加载中...</text>
|
||||
<text v-else-if="noMore">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="recordList.length === 0 && !loading">
|
||||
<text class="icon">💰</text>
|
||||
<text class="text">暂无提现记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
recordList: [],
|
||||
page: 1,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
noMore: false
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadRecordList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载记录列表
|
||||
async loadRecordList() {
|
||||
if (this.loading || this.noMore) return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await this.$http.get('/api/money/log', {
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
});
|
||||
|
||||
if (res.code === 0) {
|
||||
const list = res.data.list || [];
|
||||
|
||||
if (list.length < this.limit) {
|
||||
this.noMore = true;
|
||||
}
|
||||
|
||||
this.recordList = this.page === 1 ? list : [...this.recordList, ...list];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载记录失败:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (!this.loading && !this.noMore) {
|
||||
this.page++;
|
||||
this.loadRecordList();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
0: '审核中',
|
||||
1: '已通过',
|
||||
2: '已拒绝'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.withdraw-list-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.amount {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #FF4757;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
&.status-0 {
|
||||
background-color: #FFF3CD;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
&.status-1 {
|
||||
background-color: #D4EDDA;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
&.status-2 {
|
||||
background-color: #F8D7DA;
|
||||
color: #721C24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-info {
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user