This commit is contained in:
2025-10-24 17:12:18 +08:00
commit 9dead7a890
2004 changed files with 298646 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<script setup>
// http.getCartCount();
onShow(() => {
// #ifndef MP-WEIXIN
uni.hideTabBar({
animation: false,
});
// #endif
});
</script>
<style lang="scss" scoped>
@import "uview-plus/index.scss";
@import "./app.css";
/* 隐藏头部 */
uni-page-head {
display: none;
}
/* 轮播图指示点 */
uni-swiper .uni-swiper-dots-horizontal {
bottom: 20px !important;
}
</style>

View File

@@ -0,0 +1,27 @@
// 添加繁忙时间
export const addBusyHour = (param: object) =>
http.request({
url: "/p/merchantPage/busyHour",
method: "POST",
data: param,
});
export const getMyScheduleList = () =>
http.request({
url: "/p/merchantPage/getMyScheduleList",
method: "GET",
});
export const editBusyHour = (param: object) =>
http.request({
url: "/p/merchantPage/updateBusyHour",
method: "POST",
data: param,
});
export const delBusyHour = (param: object) =>
http.request({
url: "/p/merchantPage/deleteBusyHour",
method: "GET",
data: param,
});

View File

@@ -0,0 +1,24 @@
export const getPackageData = () =>
http.request({
url: "/p/merchantPage/packageData",
method: "GET",
});
export const getOrderManageData = () =>
http.request({
url: "/p/merchantPage/orderManageData",
method: "GET",
});
export const getManageData = () =>
http.request({
url: "/p/merchantPage/merchantData",
method: "GET",
});
export const getTel = () =>{
return http.request({
url: "/merchantNotLogin/getTel",
method: "GET",
})
}

View File

@@ -0,0 +1,30 @@
import http from '@/utils/http'
// 获取消息列表
export const getMessageList = () =>
http.request({
url: '/p/message/list',
method: 'GET',
data: {}
})
// 获取消息类型未读数
export const getMessageTypeCount = () =>
http.request({
url: '/p/message/typeCount',
method: 'GET',
})
// 标记消息为已读
export const markMessageAsRead = (messageId: string) =>
http.request({
url: `/p/message/read/${messageId}`,
method: 'PUT',
})
// 标记某类型消息全部已读
export const markMessageTypeAsRead = (type: number) =>
http.request({
url: `/p/message/read/type/${type}`,
method: 'PUT',
})

View File

@@ -0,0 +1,98 @@
export const getMerchantOrderList = (param: object) =>
http.request({
url: "/p/merchantPage/getMerchantOrderList",
method: "GET",
data: param,
});
export const updateOrder = (param: object) =>
http.request({
url: "/p/order/updateOrder",
method: "POST",
data: param,
});
// 添加抢单接口
export const grabOrders = (param: object) =>
http.request({
url: "/p/order/grabOrders",
method: "POST",
data: param,
});
// 获取用户订单列表
export const getUserOrderList = (params: {
current: number;
size: number;
status: string | null;
}) =>
http.request({
url: "/p/order/getUserOrderList",
method: "GET",
data: params,
});
export const getOrderDetail = (params: object) =>
http.request({
url: "/p/order/orderDetail",
method: "GET",
data: params,
});
// 取消订单
export const userCancelOrder = (orderNumber: string) =>
http.request({
url: "/p/order/userCancelOrder",
method: "POST",
data: {
orderNumber,
},
});
// 支付订单
export const payNotify = (orderSn: string) =>
http.request({
url: "/p/order/payNotify",
method: "POST",
data: {
orderSn,
},
});
// 确认收货
export const confirmReceipt = (orderNumber: string) =>
http.request({
url: `/p/myOrder/receipt/${orderNumber}`,
method: "PUT",
});
// 删除订单
export const deleteOrder = (orderNumber: string) =>
http.request({
url: `/p/myOrder/${orderNumber}`,
method: "DELETE",
});
export const obligationPay = (params: object) =>
http.request({
url: `/p/order/obligationPay`,
method: "POST",
data: params,
});
export const onFinish = (params: object) =>
http.request({
url: `/p/order/updateUserOrder`,
method: "POST",
data: params,
});
// 添加商户取消订单接口
export const merchantCancelOrder = (param: object) =>
http.request({
url: '/p/order/merchantCancelOrder',
method: 'POST',
data: param,
});

View File

@@ -0,0 +1,19 @@
export const getMyProjectList = () =>
http.request({
url: "/p/merchantPage/myProjectList",
method: "GET",
});
export const addRelation = (param: object) =>
http.request({
url: "/p/merchantPage/addRelation",
method: "POST",
data: param,
});
export const delRelation = (param: object) =>
http.request({
url: "/p/merchantPage/delRelation",
method: "POST",
data: param,
});

View File

@@ -0,0 +1,22 @@
// 获取标签列表
export const getLabelList = (params: object = {}) =>
http.request({
url: "/merchant/getLabelList",
method: "GET",
data: params,
});
// 获取预约人员分页接口
export const getPageMerchant = (params: object = {}) =>
http.request({
url: "/merchantNotLogin/pageMerchant",
method: "GET",
data: params,
});
export const getMerchantInfo = (params: object = {}) =>
http.request({
url: "/merchant/getMerchantInfo",
method: "GET",
data: params,
});

View File

@@ -0,0 +1,20 @@
export const orderConfirm = (param: object) =>
http.request({
url: "/p/order/confirm",
method: "POST",
data: param,
});
export const getScheduleList = (params: object = {}) =>
http.request({
url: "/merchant/getScheduleList",
method: "GET",
data: params,
});
export const orderSubmit = (param: object) =>
http.request({
url: "/p/order/submit",
method: "POST",
data: param,
});

View File

@@ -0,0 +1,12 @@
import http from '@/utils/http'
// 手机号登录
export const phoneLogin = (userMobile: string) => {
return http.request({
url: '/wxcore/testtMerchantLogin',
method: 'POST',
data: {
userMobile
}
})
}

View File

@@ -0,0 +1,61 @@
import http from "@/utils/http";
// 更新用户头像
export const updateUserAvatar = (params: Object = {}) =>
http.request({
url: "/p/user/updateAvatar",
method: "POST",
data: params,
});
// 更新用户昵称
export const updateNickname = (nickName: string) =>
http.request({
url: "/p/user/updateNickname",
method: "POST",
data: {
nickName,
},
});
// 更新用户手机号
export const updatePhone = (params: object) =>
http.request({
url: "/p/user/updatePhone",
method: "POST",
data: {
phone: params.phone,
},
});
// 同时更新手机号和昵称
export const updatePhoneAndNickName = (params: {
phone: string;
nickName: string;
}) =>
http.request({
url: "/p/user/updatePhoneAndNickName",
method: "POST",
data: params,
});
// 设置用户信息
export const setUserInfo = (params: object) =>
http.request({
url: "/p/user/setUserInfo",
method: "PUT",
data: params,
});
export const getImages = () =>
http.request({
url: "/p/user/getImages",
method: "GET",
});
export const activation = (params: object) =>
http.request({
url: "/p/user/activation",
method: "POST",
data: params,
});

View File

@@ -0,0 +1,154 @@
export const getPackageData = () =>
http.request({
url: "/p/merchantPage/packageData",
method: "GET",
});
export const getOrderManageData = () =>
http.request({
url: "/p/merchantPage/orderManageData",
method: "GET",
});
export const getManageData = () =>
http.request({
url: "/p/merchantPage/merchantData",
method: "GET",
});
export const updateProdSwitch = () =>
http.request({
url: "/merchantNotLogin/getUserShow",
method: "GET"
});
export const getWeChatOpenId = (param: object) =>
http.request({
url: "/wx/getWeChatMerchantOpenId",
method: "POST",
data: param,
});
export const testLogin = (params: object) =>
http.request({
url: "/wxcore/merchantLogin",
method: "POST",
data: params,
});
export const wxLogin = (params: object) =>
http.request({
url: "/wxcore/wxMerchantLogin",
method: "POST",
data: params,
});
export const logOut = () =>
http.request({
url: "/logOut",
method: "POST",
});
export const getUserInfo = () => {
return http.request({
url: "/p/user/merchantUserInfo",
method: "GET",
});
};
export const getOrderCount = () => {
return http.request({
url: "/p/myOrder/orderCount",
method: "GET",
});
};
export const getCollectionCount = () => {
return http.request({
url: "/p/collection/count",
method: "GET",
});
};
export const uploadFile = (filePath: string) => {
const apiBaseUrl = http.baseUrl || "http://localhost:8086";
const uploadUrl = apiBaseUrl + "/file/upload";
return new Promise((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath: filePath,
name: "file",
header: {
Authorization: "Bearer " + uni.getStorageSync("Token"),
},
success: (uploadRes) => {
try {
const data = JSON.parse(uploadRes.data);
if (data.success && data.data && data.data.fileUrl) {
resolve(data.data.fileUrl);
} else {
reject(new Error(data.msg || "上传失败"));
}
} catch (e) {
reject(new Error("解析上传响应失败"));
}
},
fail: (err) => {
reject(new Error(err.errMsg || "上传失败"));
},
});
});
};
export const updateUserAvatar = (avatarUrl: string) => {
return http.request({
url: "/p/user/updateAvatar",
method: "POST",
data: {
avatarUrl,
},
});
};
export const updatePhoneAndNickName = (phone: string, nickName: string) => {
return http.request({
url: "/p/user/updatePhoneAndNickName",
method: "POST",
data: {
phone,
nickName,
},
});
};
export const updatePhone = (phone: string) => {
return http.request({
url: "/p/user/updatePhone",
method: "POST",
data: {
phone,
},
});
};
export const updateNickname = (nickName: string) => {
return http.request({
url: "/p/user/updateNickname",
method: "POST",
data: {
nickName,
},
});
};
export const setUserInfo = (avatarUrl: string, nickName: string) => {
return http.request({
url: "/p/user/setUserInfo",
method: "PUT",
data: {
avatarUrl,
nickName,
},
});
};

View File

@@ -0,0 +1,19 @@
export const getWithdrawalList = (param: object) =>
http.request({
url: "/p/user/withdrawal/list",
method: "GET",
data: param,
});
export const getMyWallet = () =>
http.request({
url: "/p/merchantPage/myWallet",
method: "GET",
});
export const applyWithdrawal = (param: object) =>
http.request({
url: "/p/user/withdrawal/apply",
method: "POST",
data: param,
});

107
pxdj-merch-uni/src/app.css Normal file
View File

@@ -0,0 +1,107 @@
/**app.wxss**/
.container {
height: 100%;
box-sizing: border-box;
color: #333;
}
.price{
display: inline-block;
color: #eb2444;
padding-bottom:10rpx;
padding-left:10rpx;
}
/* 价格数字显示不同大小 */
.symbol {
font-size: 24rpx;
}
.big-num {
font-size: 32rpx;
}
.small-num {
font-size: 24rpx;
}
/*
*改变checkbox样式
*自定义样式
*/
/* reg */
uni-checkbox-group {
width: 100% !important;
}
uni-checkbox-group uni-label{
width: 33% !important;
display: inline-flex;
margin-bottom: 20rpx;
}
/*checkbox 选项框大小 */
uni-checkbox .uni-checkbox-input{
width: 38rpx !important;
height: 38rpx !important;
border-radius: 50%!important;
}
/*checkbox选中后样式 */
uni-checkbox .uni-checkbox-input.uni-checkbox-input-checked{
background: #e43130;
border: 1px solid transparent !important;
}
/*checkbox选中后图标样式 */
uni-checkbox .uni-checkbox-input.uni-checkbox-input-checked::before{
display: inline-block;
width: 20rpx;
height: 20rpx;
line-height: 20rpx;
text-align: center;
font-size: 18rpx;
color: #fff;
background: transparent;
transform: translate(-60%, -50%) scale(1);
-webkit-transform: translate(-60%, -50%) scale(1);
}
/*
*改变radio样式
*自定义样式
*/
/* 未选中的 背景样式 */
uni-radio .uni-radio-input{
height: 36rpx;
width: 36rpx;
border-radius: 50%;
background: transparent;
box-sizing: border-box;
}
/* 选中后的 背景样式 */
uni-radio .uni-radio-input.uni-radio-input-checked{
border: none !important;
background: #e43130 !important;
}
/* 选中后的 对勾样式 */
uni-radio .uni-radio-input.uni-radio-input-checked::before{
border-radius: 50%;
width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
font-size: 20rpx;
color:#fff;
background: #e43130;
border-radius: 50%;
transform: translate(-50%, -50%) scale(1);
-webkit-transform: translate(-50%, -50%) scale(1);
}
/* 底部按钮兼容 iPhone X以上 */
@media screen and (width: 375px) and (height: 812px){
.container {
padding-bottom: 70px;
}
}
@media screen and (width: 414px) and (height: 736px){
.container {
padding-bottom: 70px;
}
}

View File

@@ -0,0 +1,79 @@
<template>
<image
v-if="!isError && imgPath"
:src="imgPath"
:style="imgStyle"
:class="classList"
:mode="imgMode"
@error="imgError"
@load="imgLoad"
@tap="handleTap"
/>
<image
v-else
:src="defaultImgPath"
:style="imgStyle"
:class="classList"
@tap="handleTap"
/>
</template>
<script setup>
const props = defineProps({
src: {
type: String,
default: ''
},
imgMode: {
type: String,
default: 'scaleToFill'
},
classList: {
type: Array,
default: () => {
return []
}
},
imgStyle: {
type: Object,
default: () => {
return {}
}
},
// 默认失败图片类型, false为默认图片, true为默认头像图片
defaultImgType: {
type: Boolean,
default: false
}
})
const imgPath = computed(() => {
return util.checkFileUrl(props.src)
})
const defaultImgPath = computed(() => {
if (props.defaultImgType) return '/static/images/icon/head04.png'
return '/static/images/icon/def.png'
})
const emit = defineEmits(['imgError', 'imgLoad', 'handleTap'])
const isError = ref(false)
const imgError = () => {
isError.value = true
emit('imgError')
}
const imgLoad = (e) => {
emit('imgLoad', e)
}
const handleTap = () => {
emit('handleTap')
}
</script>
<style scoped>
image {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<up-datetime-picker
:hasInput="hasInput"
ref="datetimePickerRef"
v-model:show="showPicker"
:closeOnClickOverlay="props.closeOnClickOverlay"
:modelValue="modelValue"
@update:modelValue="updateValue"
:mode="mode"
:filter="props.filter"
@confirm="onConfirm"
@cancel="onCancel"
@close="onClose"
></up-datetime-picker>
</template>
<script setup>
const props = defineProps({
hasInput: {
type: Boolean,
default: true
},
show: {
type: Boolean,
default: false
},
modelValue: {
type: [Number, String, Date],
default: null
},
filter: {
type: Function,
default: null
},
closeOnClickOverlay: {
type: Boolean,
default: true
},
mode: {
type: String,
default: 'datetime'
}
});
const datetimePickerRef = ref();
const showPicker = computed({
get() {
return props.show;
},
set(val) {
emit('update:show', val);
}
});
const emit = defineEmits([
'update:show',
'update:modelValue',
'confirm',
'cancel',
'close'
]);
const updateValue = (val) => {
emit('update:modelValue', val);
};
const formatter = (type, value) => {
if (type === 'year') {
return `${value}`;
}
if (type === 'month') {
return `${value}`;
}
if (type === 'day') {
return `${value}`;
}
return value;
};
const onConfirm = () => {
showPicker.value = false;
emit('confirm', props.modelValue);
};
const onCancel = () => {
showPicker.value = false;
emit('cancel');
};
const onClose = () => {
showPicker.value = false;
emit('close');
};
onReady(() => {
// 微信小程序需要用此写法
datetimePickerRef.value.setFormatter(formatter);
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,119 @@
<template>
<view class="navbar">
<view class="fixed-content">
<!-- 状态栏高度 -->
<view :style="{ height: geStatusBarHeight + 'px' }"></view>
<!-- 导航栏高度 -->
<view class="bar-content" :style="{ height: getNavBarHeight() + 'px' }">
<slot>
<view class="bar-defalut">
<up-icon
v-if="showBack"
class="nav-left"
@tap="navigateBack()"
name="arrow-left"
color="#000"
size="22"
></up-icon>
<view class="nav-title">{{ title }}</view>
</view>
</slot>
</view>
</view>
<!-- 占位高度状态栏高度+导航栏高度父组件就不需要计算导航栏高度 -->
<view
:style="{ height: geStatusBarHeight + getNavBarHeight() + 'px' }"
></view>
</view>
<view
:style="{
height: geStatusBarHeight + getNavBarHeight() + 'px',
width: '100vw',
}"
></view>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
default: () => {
return "";
},
},
showBack: {
type: Boolean,
default: () => {
return true;
},
},
});
// 获取状态栏高度
function geStatusBarHeight() {
return uni.getSystemInfoSync()["statusBarHeight"];
}
// 获取导航栏高度
function getNavBarHeight() {
let navbarHeight = 0;
// #ifdef MP-WEIXIN
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// 导航栏高度 = 胶囊高度 + 上间距 + 下间距 + 微调 menuButtonInfo.top - uni.getSystemInfoSync()['statusBarHeight'] = 上间距)
navbarHeight =
menuButtonInfo.height +
(menuButtonInfo.top - uni.getSystemInfoSync()["statusBarHeight"]) * 2 +
2;
// #endif
// #ifdef APP-PLUS || H5
navbarHeight = 44;
// #endif
return navbarHeight;
}
function navigateBack() {
uni.navigateBack();
}
</script>
<style lang="scss" scoped>
.navbar {
.fixed-content {
position: fixed;
top: 0;
left: 0;
width: 100%;
box-sizing: border-box;
z-index: 1996;
.bar-content {
width: 100%;
.bar-defalut {
--content-padding-tr: 24rpx;
position: relative;
display: flex;
box-sizing: border-box;
padding: 0 var(--content-padding-tr);
align-items: center;
width: 100%;
height: 100%;
.nav-left {
width: 22px;
height: 22px;
position: absolute;
z-index: 2;
top: 50%;
transform: translateY(-50%);
}
.nav-title {
color: #000;
font-weight: bold;
font-size: 16px;
width: calc(100% - var(--content-padding-tr) * 2);
position: absolute;
top: 50%;
transform: translateY(-50%);
text-align: center;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
.popup-content {
padding: 32rpx 24rpx;
.popup-content-header {
width: 100%;
display: flex;
align-items: flex-end;
justify-content: space-between;
.popup-title {
font-size: 32rpx;
line-height: 32rpx;
font-weight: 500;
}
.close-icon {
font-size: 28rpx;
color: #00000055;
line-height: 28rpx;
}
}
.popup-content-body {
width: 100%;
margin-bottom: 24rpx;
.date-list {
margin: 40rpx 0 26rpx 0;
width: 100%;
display: flex;
}
.time-scroll {
height: 400rpx;
::-webkit-scrollbar {
display: none;
}
.time-list {
width: 100%;
display: flex;
flex-wrap: wrap;
}
}
.date-item {
width: 165rpx;
height: 60rpx;
line-height: 56rpx;
box-sizing: border-box;
border: 2rpx solid #d1d1d140;
margin-right: 14rpx;
margin-bottom: 18rpx;
background-color: #d1d1d140;
color: #000000f2;
font-size: 24rpx;
text-align: center;
border-radius: 0 24rpx 0 24rpx;
-webkit-border-radius: 0 24rpx 0 24rpx;
-moz-border-radius: 0 24rpx 0 24rpx;
-ms-border-radius: 0 24rpx 0 24rpx;
-o-border-radius: 0 24rpx 0 24rpx;
&.date-item-active {
border-color: #f96012;
background-color: #fff6ef;
color: #f96012;
}
&.date-item-disable {
color: #99999994;
background-color: #d1d1d18f;
border-color: #d1d1d18f;
}
&:nth-child(4n) {
margin-right: 0rpx;
}
}
}
}

View File

@@ -0,0 +1,230 @@
<template>
<up-popup
zIndex="999"
round="18rpx"
v-model:show="showPopup"
mode="bottom"
@close="close"
@open="open"
>
<view class="popup-content">
<view class="popup-content-header">
<view class="popup-title">选择服务时间</view>
<view class="close-icon" @tap="close"> 取消 </view>
</view>
<view class="popup-content-body">
<view class="date-list">
<view
v-for="(item, index) in dateList"
:key="index"
:class="{ 'date-item-active': dateActive == index }"
class="date-item"
@tap="clickDate(index)"
>
{{ item.label }}
</view>
</view>
<scroll-view scroll-y="true" class="time-scroll">
<view class="time-list">
<template
v-if="dateList.length && dateList[dateActive].timeList.length"
>
<view
v-for="(item, index) in dateList[dateActive].timeList"
:key="index"
class="date-item"
:class="{
'date-item-active': timeActive == item,
'date-item-disable': item.disable,
}"
@tap="clickTime(item)"
>
{{ item.label }}
</view>
</template>
</view>
</scroll-view>
</view>
<up-button @click="confirm" color="#F96012" shape="circle"
>确定</up-button
>
</view>
</up-popup>
</template>
<script setup>
const props = defineProps({
show: {
type: Boolean,
default: false,
},
disableList: {
type: Array,
default: () => {
return [];
},
},
});
const dateList = ref([]);
const timeActive = ref(null);
const dateActive = ref(0);
const emit = defineEmits(["update:show", "close", "open", "confirm"]);
const showPopup = computed({
get() {
return props.show;
},
set(val) {
emit("update:show", val);
},
});
const getDate = () => {
function __formatLocalDate(date) {
return `${(date.getMonth() + 1).toString().padStart(2, "0")}-${date
.getDate()
.toString()
.padStart(2, "0")}`;
}
const today = new Date();
const todayStr = `今天 ${__formatLocalDate(today)}`;
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const tomorrowStr = `明天 ${__formatLocalDate(tomorrow)}`;
dateList.value = [
{ label: todayStr, timeList: [] },
{ label: tomorrowStr, timeList: [] },
];
};
const confirm = () => {
console.log("confirm");
if (timeActive.value) {
emit("confirm", timeActive.value);
close();
} else {
uni.showToast({
title: "请选择时间",
icon: "none",
});
}
};
// 点击是时间按钮
const clickTime = (item) => {
if (item.disable) {
uni.showToast({
title: "该时间段已被预约",
icon: "none",
});
return;
}
timeActive.value = item;
console.log(item);
};
const clickDate = (index) => {
dateActive.value = index;
timeActive.value = null;
};
const close = () => {
showPopup.value = false;
emit("close");
};
const generateTimeSlots = () => {
// 创建当前时间对象
const now = new Date();
// 创建结束时间明天24点
const endDate = new Date(now);
endDate.setDate(now.getDate() + 1);
endDate.setHours(24, 0, 0, 0);
// 时间格式化函数
const formatTime = (date) => {
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
return `${hours}:${minutes}`;
};
const formatValueTime = (date) => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 时间是否在范围内
const isInRange = (date) => {
for (let i = 0; i < props.disableList.length; i++) {
const startTime = new Date(props.disableList[i].startTime);
const endTime = new Date(props.disableList[i].endTime);
if (date >= startTime && date <= endTime) {
return false;
}
}
return true;
};
// 调整起始时间为下一个整点或半点
const start = new Date(now);
const currentMinutes = start.getMinutes();
const remainder = currentMinutes % 30;
start.setMinutes(currentMinutes + (remainder ? 30 - remainder : 0));
start.setSeconds(0, 0);
// 生成时间点数组
while (start < endDate) {
if (start.getDate() === now.getDate()) {
dateList.value[0].timeList.push({
label: formatTime(start),
value: formatValueTime(start),
disable: !isInRange(start),
});
} else {
dateList.value[1].timeList.push({
label: formatTime(start),
value: formatValueTime(start),
disable: !isInRange(start),
});
}
start.setMinutes(start.getMinutes() + 30);
}
};
const getTime = () => {
//获取从现在开始到明天24点的半点和整点的时间
generateTimeSlots();
};
const open = () => {
console.log(props.disableList);
getDate();
getTime();
console.log(dateList.value);
if (dateList.value.length && dateList.value[0].timeList.length) {
timeActive.value = null;
dateActive.value = 0;
}
emit("open");
};
</script>
<style lang="scss" scoped>
@import "./order-date-popup.scss";
</style>

View File

@@ -0,0 +1,67 @@
.prod-items {
width: 43%;
background: #fff;
margin-bottom: 40rpx;
box-sizing: border-box;
.hot-imagecont {
border-radius: 8rpx;
text-align: center;
font-size: 0;
}
.hot-text {
margin-top: 20rpx;
.prod-info {
font-size: 20rpx;
color: #777;
padding: 0 20rpx;
margin-top: 8rpx;
}
.prod-text-info {
position: relative;
height: 50rpx;
line-height: 70rpx;
font-family: Arial;
.price {
color: #eb2444;
padding-left: 20rpx;
}
}
}
}
prod {
&:nth-child(2n-1) {
.prod-items {
padding: 20rpx 10rpx 10rpx 20rpx;
}
}
&:nth-child(2n) {
.prod-items {
padding: 20rpx 20rpx 10rpx 10rpx;
}
}
}
.hot-imagecont {
.hotsaleimg {
width: 100%;
height: 345rpx;
}
}
.hot-text {
.hotprod-text {
height: 76rpx;
font-size: 28rpx;
display: -webkit-box;
word-break: break-all;
padding: 0 20rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: #000;
}
}
.deadline-price {
font-size: 22rpx;
margin-right: 5rpx;
}

View File

@@ -0,0 +1,73 @@
<template>
<view
class="prod-items"
:data-prodid="item.prodId"
@tap="toProdPage"
>
<view class="hot-imagecont">
<image
:src="item.pic"
class="hotsaleimg"
/>
</view>
<view class="hot-text">
<view class="hotprod-text">
{{ item.prodName }}
</view>
<view
v-if="sts===6"
class="prod-info"
>
{{ item.prodCommNumber }}评价 {{ item.positiveRating }}%好评
</view>
<view class="prod-text-info">
<view class="price">
<text
v-if="sts===2"
class="deadline-price"
>
限时价
</text>
<text class="symbol">
</text>
<text class="big-num">
{{ wxs.parsePrice(item.price)[0] }}
</text>
<text class="small-num">
.{{ wxs.parsePrice(item.price)[1] }}
</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
const wxs = number()
// eslint-disable-next-line no-unused-vars
const props = defineProps({
item: {
type: Object,
default: () => {
return null
}
},
sts: {
type: Number,
default: () => {
return 2
}
}
})
const toProdPage = (e) => {
const prodid = e.currentTarget.dataset.prodid
uni.navigateTo({
url: '/pages/prod/prod?prodid=' + prodid
})
}
</script>
<style scoped lang="scss">
@import './production.scss';
</style>

View File

@@ -0,0 +1,135 @@
.project-detail {
display: flex;
width: 100%;
align-items: center;
.project-image {
width: 128rpx;
height: 128rpx;
border-radius: 16rpx;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
flex-shrink: 0;
margin-right: 24rpx;
}
.project-detail-content {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
.project-detail-line {
display: flex;
width: 100%;
justify-content: space-between;
&.top {
align-items: flex-start;
.project-price {
color: #ff4b10f2;
font-style: normal;
font-weight: 500;
.symbol {
font-size: 28rpx;
line-height: 28rpx;
}
.num {
font-size: 44rpx;
line-height: 44rpx;
}
}
.project-more {
display: flex;
align-items: center;
color: #0000008c;
font-size: 20rpx;
font-style: normal;
font-weight: 400;
line-height: 20rpx; /* 100% */
.text {
margin-right: 2rpx;
}
}
}
&.bottom {
align-items: flex-end;
.project-specs {
width: 0;
flex: 1;
display: flex;
align-items: center;
color: #000000f2;
font-family: "PingFang SC";
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: 24rpx; /* 100% */
.specs-value {
max-width: calc(100% - 92rpx - 26rpx - 72rpx);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.specs-space {
font-size: 16rpx;
line-height: 18rpx; /* 100% */
stroke-width: 0.5px;
color: #00000059;
margin: 0 8rpx;
}
.specs-time {
width: 92rpx;
text-align: start;
}
}
.project-number {
flex-shrink: 0;
width: 154rpx;
margin-left: 24rpx;
.number-btn {
width: 44rpx;
height: 44rpx;
box-sizing: border-box;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
background-color: #f96012;
&.disable {
border: 1rpx solid #cdcdcd;
background-color: #fff;
}
}
.input {
flex: 1;
width: 0;
text-align: center;
font-size: 28rpx;
}
}
}
}
.project-tip {
height: 36rpx;
flex-shrink: 0;
align-self: baseline; //取消flex100%
line-height: 36rpx;
padding: 0 10rpx;
margin-top: 8rpx;
background: #fff4f2;
color: #ff6a4d;
font-size: 20rpx;
font-style: normal;
font-weight: 400;
border-radius: 24rpx;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
}
}
}

View File

@@ -0,0 +1,77 @@
<template>
<view class="project-detail">
<image class="project-image" :src="project.pic" />
<view class="project-detail-content">
<view class="project-detail-line top">
<view class="project-price">
<text class="symbol"></text>
<text class="num">{{ project.price }}</text>
</view>
<view v-if="showMore" class="project-more">
<text class="text">项目详情</text>
<up-icon name="arrow-right" color="#00000066" size="20rpx"></up-icon>
</view>
</view>
<view class="project-tip">祛湿通络激活能量</view>
<view class="project-detail-line bottom">
<view class="project-specs">
<view class="specs-label">已选</view>
<view class="specs-value">{{ project.name }} </view>
<view class="specs-space">|</view>
<view class="specs-time">{{ project.duration }}分钟</view>
</view>
<view class="project-number">
<up-number-box v-model="number">
<template #minus>
<view class="number-btn" :class="{ disable: number <= 1 }">
<up-icon
name="minus"
:color="number <= 1 ? '#CDCDCD' : '#fff'"
size="14"
></up-icon>
</view>
</template>
<template #input>
<text class="input">{{ number }}</text>
</template>
<template #plus>
<view class="number-btn">
<up-icon name="plus" color="#FFFFFF" size="14"></up-icon>
</view>
</template>
</up-number-box>
</view>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
projectNumber: {
type: Boolean,
default: false,
},
project: {
type: Object,
default: () => {},
},
showMore: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["update:projectNumber"]);
const number = computed({
get() {
return props.projectNumber;
},
set(val) {
emit("update:projectNumber", val);
},
});
</script>
<style lang="scss" scoped>
@import "./project-cart.scss";
</style>

View File

@@ -0,0 +1,169 @@
.popup-content {
position: relative;
width: 100%;
box-sizing: border-box;
padding: 40rpx 24rpx 0 24rpx;
background: linear-gradient(143deg, #fbecdf 7.23%, #fff 100.56%);
border-radius: 18rpx;
-webkit-border-radius: 18rpx;
-moz-border-radius: 18rpx;
-ms-border-radius: 18rpx;
-o-border-radius: 18rpx;
.close-icon {
position: absolute;
right: 24rpx;
top: 40rpx;
}
.popup-content-header {
width: 100%;
display: flex;
.avatar {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
flex-shrink: 0;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
margin-right: 24rpx;
}
.user-info {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
justify-content: center;
.user-name {
color: #000000f2;
font-family: "PingFang SC";
font-size: 36rpx;
line-height: 36rpx; /* 100% */
font-style: normal;
font-weight: 400;
margin-bottom: 16rpx;
}
.user-tip {
width: 100%;
color: #00000059;
font-family: "PingFang SC";
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: 24rpx; /* 100% */
// 超出显示省略号
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.popup-content-body {
margin-top: 24rpx;
width: 100%;
box-sizing: border-box;
padding: 40rpx 24rpx 30rpx 24rpx;
background-color: #fff;
border-radius: 16rpx;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
.project-title {
color: rgba(0, 0, 0, 0.95);
font-family: "PingFang SC";
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 28rpx; /* 100% */
}
.project-list {
display: flex;
flex-wrap: wrap;
margin-top: 28rpx;
margin-bottom: 58rpx;
.project-item {
box-sizing: border-box;
position: relative;
overflow: hidden;
height: 56rpx;
line-height: 56rpx;
text-align: center;
padding: 0 16rpx;
color: #0000008c;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
background: #d1d1d140;
border-radius: 8rpx;
margin-right: 24rpx;
-webkit-border-radius: 8rpx;
-moz-border-radius: 8rpx;
-ms-border-radius: 8rpx;
-o-border-radius: 8rpx;
&:first-child {
padding-left: 28rpx;
&.project-item-active {
padding-left: 26rpx;
}
}
&.project-item-active {
border: 2rpx solid #f96012;
background: #fff6ef;
color: #f96012;
line-height: 52rpx;
padding: 0 14rpx;
.seckill-icon {
top: -22rpx;
left: -24rpx;
}
}
.seckill-icon {
position: absolute;
width: 68rpx;
height: 66rpx;
top: -20rpx;
left: -22rpx;
}
}
}
}
.project-content-footer {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
bottom: 0;
left: 0;
background-color: #fff;
box-sizing: border-box;
padding: 34rpx 24rpx 12rpx 24rpx;
.all-price {
display: flex;
align-items: flex-end;
.all-tabel {
color: #000000f2;
font-size: 28rpx;
line-height: 28rpx; /* 100% */
}
.symbol {
color: #ff4b10f2;
font-size: 28rpx;
line-height: 28rpx;
}
.num {
color: #ff4b10f2;
font-size: 44rpx;
font-weight: 500;
line-height: 44rpx;
}
}
.project-btn {
width: 104px;
height: 38px;
flex-shrink: 0;
}
}
}

View File

@@ -0,0 +1,203 @@
<template>
<up-popup
round="18rpx"
v-model:show="showPopup"
mode="bottom"
@close="close"
@open="open"
>
<view class="popup-content">
<up-icon
@tap="close"
class="close-icon"
name="close"
size="32rpx"
></up-icon>
<view class="popup-content-header">
<image class="avatar" :src="project.pic" />
<view class="user-info">
<view class="user-name">{{ project.name }}</view>
<view class="user-tip">{{ project.brief }}</view>
</view>
</view>
<view class="popup-content-body">
<view class="project-title">全部项目</view>
<view class="project-list">
<view
v-for="(item, index) in project.projects"
:key="index"
:class="{ 'project-item-active': active == index }"
class="project-item"
@tap="onProjectChange(index)"
>
<image
v-if="index == 0"
class="seckill-icon"
src="@/static/images/icon/reservation/seckill.png"
></image>
<view class="project-item-title">{{ item.name }}</view>
</view>
</view>
<!-- <view class="project-detail">
<image class="project-image" :src="activeProject.pic" />
<view class="project-detail-content">
<view class="project-detail-line top">
<view class="project-price">
<text class="symbol"></text>
<text class="num">{{ activeProject.price }}</text>
</view>
<view class="project-more">
<text class="text">项目详情</text>
<up-icon
name="arrow-right"
color="#00000066"
size="20rpx"
></up-icon>
</view>
</view>
<view class="project-tip">祛湿通络激活能量</view>
<view class="project-detail-line bottom">
<view class="project-specs">
<view class="specs-label">已选</view>
<view class="specs-value">{{ activeProject.name }} </view>
<view class="specs-space">|</view>
<view class="specs-time">{{ activeProject.duration }}分钟</view>
</view>
<view class="project-number">
<up-number-box v-model="projectNumber">
<template #minus>
<view
class="number-btn"
:class="{ disable: projectNumber <= 1 }"
>
<up-icon
name="minus"
:color="projectNumber <= 1 ? '#CDCDCD' : '#fff'"
size="14"
></up-icon>
</view>
</template>
<template #input>
<text class="input">{{ projectNumber }}</text>
</template>
<template #plus>
<view class="number-btn">
<up-icon name="plus" color="#FFFFFF" size="14"></up-icon>
</view>
</template>
</up-number-box>
</view>
</view>
</view>
</view> -->
<project-cart
v-model:projectNumber="projectNumber"
:project="activeProject"
></project-cart>
</view>
<view style="width: 100%; height: 146rpx"> </view>
<view class="project-content-footer">
<view class="all-price">
<view class="all-tabel">合计</view>
<text class="symbol"></text>
<text class="num">{{ projectNumber * activeProject.price }}</text>
</view>
<up-button
:custom-style="{
margin: '0',
width: '208rpx',
height: '76rpx',
borderRadius: '42rpx',
background: '#F96012',
border: 'none',
color: '#FFFFFF',
fontSize: '28rpx',
lineHeight: '76rpx',
}"
:disabled="projectNumber <= 0"
@click="confirm"
>
立即预约
</up-button>
</view>
</view>
</up-popup>
</template>
<script setup>
import useOrderStore from "@/stores/order/useOrderStore";
import { nextTick } from "vue";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
project: {
type: Object,
default: () => {},
},
});
const emit = defineEmits(["update:show", "close", "open"]);
const orderStore = useOrderStore();
const showPopup = computed({
get() {
return props.show;
},
set(val) {
emit("update:show", val);
},
});
const projectNumber = ref(1);
const active = ref(0);
const activeProject = computed(() => {
return props.project.projects[active.value];
});
const onProjectChange = (index) => {
projectNumber.value = 1;
active.value = index;
};
const projectList = ref([
{
title: "通络培元",
},
{
title: "泰式SPA",
},
{
title: "中式推拿",
},
]);
const confirm = () => {
orderStore.setOrderInfo({
prodCount: projectNumber.value,
merchantId: props.project.id,
projectId: activeProject.value.id,
});
nextTick(() => {
uni.navigateTo({
url: "/pages/submit-order/submit-order",
});
});
};
const close = () => {
showPopup.value = false;
emit("close");
};
const open = () => {
emit("open");
};
</script>
<style lang="scss" scoped>
@import "./project-popup.scss";
</style>

View File

@@ -0,0 +1,207 @@
.sort-item {
margin-bottom: 16rpx;
background-color: #fff;
border-radius: 24rpx;
display: flex;
width: 100%;
box-sizing: border-box;
padding: 20rpx;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
.sort-item__left {
display: flex;
flex-direction: column;
align-items: center;
width: 124rpx;
margin-right: 24rpx;
.img-box {
width: 124rpx;
height: 124rpx;
image {
border-radius: 50%;
width: 100%;
height: 100%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
}
}
.sort-stata {
margin-top: 8rpx;
width: 112rpx;
text-align: center;
height: 34rpx;
line-height: 34rpx; /* 100% */
flex-shrink: 0;
border-radius: 16rpx;
background: #0dc086;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
color: rgba(255, 255, 255, 0.95);
font-family: "PingFang SC";
font-size: 24rpx;
font-style: normal;
font-weight: 400;
}
}
.sort-item__right {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
.sort-item__right__header {
margin-top: 24rpx;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.sort-title {
display: flex;
align-items: center;
.sort-name {
color: rgba(0, 0, 0, 0.95);
font-family: "PingFang SC";
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 28rpx; /* 100% */
margin-right: 16rpx;
}
.sort-tab {
height: 28rpx;
padding: 0 18rpx;
line-height: 28rpx;
flex-shrink: 0;
border-radius: 8px;
background: #0dc086;
color: rgba(255, 255, 255, 0.95);
font-family: "PingFang SC";
font-size: 16rpx;
font-style: normal;
font-weight: 400;
}
}
.sort-time {
display: flex;
width: 184rpx;
height: 28rpx;
line-height: 28rpx;
font-family: "PingFang SC";
font-size: 16rpx;
font-style: normal;
font-weight: 400;
border-radius: 40rpx;
-webkit-border-radius: 40rpx;
-moz-border-radius: 40rpx;
-ms-border-radius: 40rpx;
-o-border-radius: 40rpx;
overflow: hidden;
.sort-time-label {
width: 50%;
text-align: center;
background-color: #492d19;
color: #e1cdbff2;
}
.sort-time-text {
width: 50%;
text-align: center;
background-color: #e6c8a9;
color: #492d19f2;
}
}
}
.sort-item__right-rate {
margin-top: 16rpx;
display: flex;
align-items: center;
.rate-num {
margin-left: 8rpx;
color: #f8651e;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 10px; /* 100% */
}
}
.sort-item__right__footer {
display: flex;
justify-content: space-between;
align-items: center;
.sort-info {
display: flex;
flex-direction: column;
.sort-info-line {
display: flex;
margin-top: 18rpx;
.sort-sale,
.sort-distance {
display: flex;
}
.sort-sale {
margin-right: 24rpx;
}
.info-label {
color: rgba(0, 0, 0, 0.75);
font-family: "PingFang SC";
font-size: 20rpx;
font-style: normal;
font-weight: 400;
line-height: 20rpx; /* 100% */
margin-right: 8rpx;
}
.info-value {
color: rgba(13, 192, 134, 0.95);
font-family: "PingFang SC";
font-size: 20rpx;
font-style: normal;
font-weight: 400;
line-height: 20rpx; /* 100% */
}
.info-love,
.info-comment {
display: flex;
align-items: center;
color: rgba(0, 0, 0, 0.55);
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 10px; /* 100% */
}
image {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
text {
margin-right: 24rpx;
}
}
}
.sort-btn {
padding: 0 18rpx;
height: 48rpx;
line-height: 48rpx;
text-align: center;
flex-shrink: 0;
border-radius: 40rpx;
background: #fa610f;
color: rgba(255, 223, 197, 0.95);
font-family: "PingFang SC";
font-size: 24rpx;
-webkit-border-radius: 40rpx;
-moz-border-radius: 40rpx;
-ms-border-radius: 40rpx;
-o-border-radius: 40rpx;
}
}
}
}

View File

@@ -0,0 +1,74 @@
<template>
<view class="sort-item">
<view class="sort-item__left">
<view class="img-box">
<image :src="shop.pic" />
</view>
<view class="sort-stata"> 可服务 </view>
</view>
<view class="sort-item__right">
<view class="sort-item__right__header">
<view class="sort-title">
<view class="sort-name">{{ shop.name }}</view>
<view class="sort-tab">极速达</view>
</view>
<view class="sort-time">
<view class="sort-time-label">最早可约</view>
<view class="sort-time-text">{{
wxs.getDateDiff(shop.nearestHalfHour)
}}</view>
</view>
</view>
<view class="sort-item__right-rate">
<up-rate size="12" gutter="1" v-model="shop.score" readonly></up-rate>
<view class="rate-num">{{ shop.score }}</view>
</view>
<view class="sort-item__right__footer">
<view class="sort-info">
<view class="sort-info-line">
<view class="sort-sale">
<view class="info-label">已服务</view>
<view class="info-value">{{ shop.orderNum }}</view>
</view>
<view v-if="shop.distance" class="sort-distance">
<view class="info-label">距您</view>
<view class="info-value">{{ shop.distance }}km</view>
</view>
</view>
<view class="sort-info-line">
<view class="info-love">
<image src="@/static/images/icon/index/love.png" />
<text>{{ shop.collectNum }}</text>
</view>
<view class="info-comment">
<image src="@/static/images/icon/index/message.png" />
<text>{{ shop.evaluateNum }}</text>
</view>
</view>
</view>
<view @click="toReserva" class="sort-btn">立即预约</view>
</view>
</view>
</view>
</template>
<script setup>
const wxs = number();
const props = defineProps({
shop: {
type: Object,
default: () => {
return null;
},
},
});
const emit = defineEmits(["onReserva"]);
const toReserva = () => {
emit("onReserva", props.shop);
};
</script>
<style lang="scss" scoped>
@import "./shop-cart.scss";
</style>

View File

@@ -0,0 +1,108 @@
<template>
<view class="tabbar">
<up-tabbar
:value="tabbarActive"
:fixed="true"
activeColor="#000000f2"
inactiveColor="#0000008c"
:placeholder="true"
:safeAreaInsetBottom="false"
>
<template v-for="(item, index) in tabbarItems">
<u-tabbar-item
v-if="!item.isSpecial"
:key="index"
:icon="getTabbarIcon(item, index)"
:text="item.text"
@click="handleTabbarItemClick(item, index)"
></u-tabbar-item>
<view
v-else
@click="handleTabbarItemClick(item, index)"
class="tabbar-reservation"
>
<image
class="tabbar-reservation-icon"
:src="getTabbarIcon(item, index)"
mode="widthFix"
></image>
</view>
</template>
</up-tabbar>
</view>
</template>
<script setup>
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
const tabbarItems = [
{
pagePath: "/pages/index/index",
text: "首页",
iconPath: "/static/images/tabbar/indexpage.png",
selectedIconPath: "/static/images/tabbar/indexpage-sel.png",
},
{
pagePath: "/pages/order-list/order-list",
text: "订单",
iconPath: "/static/images/tabbar/userpage.png",
selectedIconPath: "/static/images/tabbar/userpage-sel.png",
},
];
const tabbarActive = computed(() => {
if (tabbarItems[userStore.activeTab].isSpecial) {
return -1;
} else {
let active = 0;
for (let i = 0; i < userStore.activeTab; i++) {
if (tabbarItems[i].isSpecial) {
continue;
}
active++;
}
return active;
}
});
//点击tabbar按钮
const handleTabbarItemClick = (item, index) => {
if (userStore.activeTab !== index) {
userStore.setActive(index);
const path = item.pagePath;
uni.redirectTo({
url: path,
});
}
};
//图标的切换
const getTabbarIcon = (item, index) => {
return userStore.activeTab === index ? item.selectedIconPath : item.iconPath;
};
</script>
<script>
export default { options: { styleIsolation: "shared" } };
</script>
<style lang="scss" scoped>
.tabbar-reservation {
width: 80rpx;
height: 80rpx;
margin: 0 24rpx;
.tabbar-reservation-icon {
width: 100%;
height: 100%;
}
}
.tabbar ::v-deep .u-tabbar__content {
--iphone-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
--iphone-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
padding-bottom: var(--iphone-bottom) !important;
}
</style>

View File

@@ -0,0 +1,18 @@
import { createSSRApp } from "vue";
import pinia from "@/stores";
import App from "./App.vue";
import useUserStore from '@/stores/user/useUserStore'
import uviewPlus from "uview-plus";
export function createApp() {
const app = createSSRApp(App).use(pinia);
app.use(uviewPlus);
app.mixin({
onShow() {
const userStore = useUserStore()
userStore.updateProdSwitch()
}
})
return {
app,
};
}

View File

@@ -0,0 +1,101 @@
{
"name" : "",
"appid" : "__UNI__2CF44C6",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"compatible" : {
"ignoreVersion" : true
},
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {
"Camera" : {},
"LivePusher" : {}
},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
}
}
},
/* */
"mp-weixin" : {
"appid" : "wx0998717562e54fd1",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "你的位置信息将用于地址信息新增、修改以及获取附近门店"
},
"scope.userFuzzyLocation": {
"desc": "你的位置信息将用于完成服务时验证服务位置"
}
},
"requiredPrivateInfos" : [ "getFuzzyLocation", "chooseLocation" ]
},
"h5" : {
"title" : "",
"domain" : "https://mini-h5.mall4j.com",
"router" : {
"mode" : "history"
},
"uniStatistics" : {
"enable" : false
},
"optimization" : {
"treeShaking" : {
"enable" : false
}
},
"template" : "index.html",
"devServer" : {
"disableHostCheck" : true,
"port" : 80
},
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : ""
}
}
}
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}

View File

@@ -0,0 +1,211 @@
{
"easycom": {
"custom": {
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue",
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
}
},
"pages": [
{
"path": "pages/index/index",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#FADFC4",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "品香到家"
}
},
{
"path": "pages/user/user",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/accountLogin/accountLogin"
},
{
"path": "pages/order-list/order-list",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "订单中心"
}
},
{
"path": "pages/order-detail/order-detail",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "订单详情"
}
},
{
"path": "pages/agreement/user-agreement",
"style": {
"navigationBarTitleText": "用户协议",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/agreement/privacy-policy",
"style": {
"navigationBarTitleText": "隐私政策",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/my-project/my-project",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的项目"
}
},
{
"path": "pages/editAddress/editAddress",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "设置地址"
}
},
{
"path": "pages/setting/setting",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#FADFC4",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "设置"
}
},
{
"path": "pages/busy-hour/busy-hour",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "忙时时间"
}
},
{
"path": "pages/busy-detail/busy-detail",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "时间设置"
}
},
{
"path": "pages/my-wallet/my-wallet",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#fff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "pages/setting/setting-detail/setting-name",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#161922",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"navigationBarTitleText": "修改名字"
}
},
{
"path": "pages/setting/setting-detail/setting-phone",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#161922",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"navigationBarTitleText": "修改电话"
}
},
{
"path": "pages/setting/setting-detail/brief-introduction",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#161922",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"navigationBarTitleText": "修改简介"
}
},
{
"path": "pages/setting/setting-detail/setting-location",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": true,
"navigationBarTitleText": "选择地址"
}
},
{
"path": "pages/setting/setting-detail/my-album",
"style": {
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#161922",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的相册"
}
},
{
"path": "pages/login/login"
},
{
"path": "pages/register/register"
}
],
"sitemapLocation": "sitemap.json",
"globalStyle": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#0000008c",
"selectedColor": "#000000f2",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/images/tabbar/indexpage.png",
"selectedIconPath": "static/images/tabbar/indexpage-sel.png"
},
{
"pagePath": "pages/order-list/order-list",
"text": "订单",
"iconPath": "static/images/tabbar/userpage.png",
"selectedIconPath": "static/images/tabbar/userpage-sel.png"
}
]
},
"subPackages": []
}

View File

@@ -0,0 +1,113 @@
.con {
background: #fff;
height: 100%;
margin-top: 50px;
}
image {
display: block;
width: 150rpx;
height: 150rpx;
margin: auto;
border-radius: 50%;
width: 150rpx;
height: 150rpx;
margin-bottom: 8%;
}
.login-form {
width: 90%;
margin: 0 auto;
margin-bottom: 20%;
}
.footer-btn {
margin-bottom: 30rpx;
width: 90%;
text-align: center;
color: #fff;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
}
.authorized-btn {
background-color: #0ab906;
border: 1rpx solid #0ab906;
}
.to-idx-btn {
background-color: #eeeeee;
color: #333;
}
.form-title {
width: 100%;
margin-bottom: 50rpx;
font-size: 32rpx;
text-align: center;
color: #00a0e9;
margin-bottom: 50rpx;
font-size: 32rpx;
}
.item {
display: block;
margin-bottom: 30rpx;
margin-bottom: 30rpx;
}
.account {
display: flex;
background: #f8f8f8;
padding: 15rpx;
box-sizing: border-box;
font-size: 26rpx;
align-items: center;
input {
padding-left: 20rpx;
width: 75%;
padding-left: 20rpx;
}
}
button {
&::after {
border: 0 !important;
}
}
.operate {
display: flex;
justify-content: space-between;
align-items: center;
}
.to-register {
font-size: 28rpx;
color: #00aaff;
font-size: 28rpx;
}
.error {
.error-text {
display: block;
width: 100%;
font-size: 28rpx;
color: #e43130;
text-align: left;
margin-top: 10rpx;
font-size: 28rpx;
margin-top: 10rpx;
.warning-icon {
display: inline-block;
color: #fff;
width: 26rpx;
height: 26rpx;
line-height: 26rpx;
background: #e43130;
border-radius: 50%;
text-align: center;
margin-right: 12rpx;
font-size: 22rpx;
width: 26rpx;
height: 26rpx;
line-height: 26rpx;
margin-right: 12rpx;
font-size: 22rpx;
}
}
}

View File

@@ -0,0 +1,177 @@
<template>
<view class="con">
<image src="@/static/logo.png" />
<!-- 登录 -->
<view class="login-form">
<view :class="['item', errorTips == 1 ? 'error' : '']">
<view class="account">
<text class="input-item"> 账号 </text>
<input
type="text"
data-type="account"
placeholder-class="inp-palcehoder"
placeholder="请输入用户名"
@input="getInputVal"
/>
</view>
<view v-if="errorTips == 1" class="error-text">
<text class="warning-icon"> ! </text>
请输入账号
</view>
</view>
<!-- <view :class="['item', errorTips == 2 ? 'error' : '']">
<view class="account">
<text class="input-item"> 密码 </text>
<input
type="password"
data-type="password"
placeholder-class="inp-palcehoder"
placeholder="请输入密码"
@input="getInputVal"
/>
</view>
<view v-if="errorTips == 2" class="error-text">
<text class="warning-icon"> ! </text>
请输入密码
</view>
</view> -->
<!-- <view class="operate">
<view class="to-register" @tap="toRegitser">
还没有账号
<text>去注册></text>
</view>
</view> -->
</view>
<view>
<button class="authorized-btn footer-btn" @tap="login">登录</button>
<!-- #ifdef H5 -->
<button class="authorized-btn footer-btn" @tap="toPhoneLogin">
手机号登录
</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button class="authorized-btn footer-btn" @tap="toWxlogin">
手机号一键登录
</button>
<!-- #endif -->
<button class="to-idx-btn footer-btn" @tap="toIndex">回到首页</button>
</view>
</view>
</template>
<script setup>
import { encrypt } from '@/utils/crypto.js';
import { testLogin } from '@/api/user/user';
import http from '@/utils/http';
import useUserStore from '@/stores/user/useUserStore';
/**
* 生命周期函数--监听页面显示
*/
onShow(() => {
// 头部导航标题
uni.setNavigationBarTitle({
title: '用户登录'
});
});
const userStore = useUserStore();
const principal = ref(''); // 账号
const errorTips = ref(0); // 错误提示
watch(
() => principal.value,
() => {
errorTips.value = 0;
}
);
// const credentials = ref(''); // 密码
/**
* 输入框的值
*/
const getInputVal = (e) => {
const type = e.currentTarget.dataset.type;
if (type == 'account') {
principal.value = e.detail.value;
}
// else if (type == 'password') {
// credentials.value = e.detail.value;
// }
};
/**
* 登录
*/
const login = () => {
if (principal.value.length == 0) {
errorTips.value = 1;
}
// else if (credentials.value.length == 0) {
// errorTips.value = 2;
// }
else {
errorTips.value = 0;
const params = {
userMobile: principal.value,
// passWord: encrypt(credentials.value)
};
testLogin(params)
.then(({ data }) => {
// 保存token到pinia store
userStore.token = data.accessToken;
http.loginSuccess(data, () => {
// 获取用户信息
userStore.getUserInfo().then(() => {
uni.showToast({
title: '登录成功',
icon: 'none',
complete: () => {
setTimeout(() => {
wx.switchTab({
url: '/pages/index/index'
});
}, 1000);
}
});
});
});
});
}
};
const toWxlogin = () => {
uni.navigateTo({
url: '/pages/login/login'
});
};
const toPhoneLogin = () => {
uni.navigateTo({
url: '/pages/login/phoneLogin'
});
};
/**
* 去注册
*/
const toRegitser = () => {
uni.navigateTo({
url: '/pages/register/register'
});
};
/**
* 回到首页
*/
const toIndex = () => {
wx.switchTab({
url: '/pages/index/index'
});
};
</script>
<style scoped lang="scss">
@import "./accountLogin.scss";
</style>

View File

@@ -0,0 +1,178 @@
<template>
<view class="container">
<view class="header">
<view class="title">隐私政策</view>
</view>
<view class="content">
<view class="section">
<view class="section-content">
<view class="paragraph">本隐私政策旨在向您说明我们如何收集使用存储和共享您的个人信息以及您享有的相关权利请您仔细阅读并充分理解本政策的全部内容</view>
</view>
</view>
<view class="section">
<view class="section-title">1. 我们收集的信息</view>
<view class="section-content">
<view class="paragraph">1.1 您提供给我们的信息包括但不限于您在注册账号使用我们的服务时提供的个人身份信息联系方式交易信息等</view>
<view class="paragraph">1.2 我们在您使用服务过程中获取的信息包括设备信息日志信息位置信息交易信息等</view>
<view class="paragraph">1.3 我们从第三方获取的您的信息在您授权的情况下我们可能从第三方获取您的个人信息</view>
</view>
</view>
<view class="section">
<view class="section-title">2. 我们如何使用收集的信息</view>
<view class="section-content">
<view class="paragraph">2.1 向您提供服务我们使用收集的信息来提供维护和改进我们的服务并开发新的服务</view>
<view class="paragraph">2.2 安全保障我们使用收集的信息来验证身份防范和打击欺诈等非法行为</view>
<view class="paragraph">2.3 向您推送消息我们可能使用您的联系方式向您发送服务相关的通知</view>
<view class="paragraph">2.4 改进我们的服务我们使用收集的信息来了解您如何使用我们的服务以改进现有服务和开发新服务</view>
</view>
</view>
<view class="section">
<view class="section-title">3. 信息的共享与披露</view>
<view class="section-content">
<view class="paragraph">3.1 经您授权或同意的情况下我们可能与第三方共享您的个人信息</view>
<view class="paragraph">3.2 在法律法规要求或政府部门依法要求的情况下我们可能会披露您的个人信息</view>
<view class="paragraph">3.3 为了向您提供服务我们可能会与我们的合作伙伴共享必要的信息在这种情况下我们会要求合作伙伴按照我们的隐私政策和安全要求处理您的信息</view>
</view>
</view>
<view class="section">
<view class="section-title">4. 信息的保护</view>
<view class="section-content">
<view class="paragraph">4.1 我们采取严格的安全措施来保护您的个人信息不被未经授权的访问使用或泄露</view>
<view class="paragraph">4.2 我们仅在为您提供服务所必需的期限内保留您的个人信息</view>
<view class="paragraph">4.3 尽管我们采取了上述措施保护您的个人信息但请注意没有任何安全措施是绝对安全的</view>
</view>
</view>
<view class="section">
<view class="section-title">5. 您的权利</view>
<view class="section-content">
<view class="paragraph">5.1 访问权您有权访问您提供给我们的个人信息</view>
<view class="paragraph">5.2 更正权您可以要求更正您认为不准确的个人信息</view>
<view class="paragraph">5.3 删除权在某些情况下您有权要求删除您的个人信息</view>
<view class="paragraph">5.4 撤回同意权对于基于您同意而收集和使用的个人信息您有权随时撤回您的同意</view>
</view>
</view>
<view class="section">
<view class="section-title">6. Cookie和同类技术</view>
<view class="section-content">
<view class="paragraph">6.1 我们使用Cookie和同类技术来提供保护和改进我们的服务</view>
<view class="paragraph">6.2 您可以通过浏览器设置拒绝或管理Cookie但这可能会影响您使用我们服务的某些功能</view>
</view>
</view>
<view class="section">
<view class="section-title">7. 未成年人保护</view>
<view class="section-content">
<view class="paragraph">7.1 我们非常重视对未成年人个人信息的保护</view>
<view class="paragraph">7.2 若您是未满18周岁的未成年人在使用我们的服务前应在您的父母或监护人的指导下阅读本隐私政策并征得您父母或监护人的同意</view>
</view>
</view>
<view class="section">
<view class="section-title">8. 隐私政策的变更</view>
<view class="section-content">
<view class="paragraph">8.1 我们可能会不时更新本隐私政策</view>
<view class="paragraph">8.2 当本隐私政策发生实质性变更时我们会通过网站公告或其他方式通知您</view>
<view class="paragraph">8.3 若您不同意修改后的隐私政策您可以停止使用我们的服务若您继续使用我们的服务则视为您接受修改后的隐私政策</view>
</view>
</view>
<!-- <view class="section">
<view class="section-title">9. 联系我们</view>
<view class="section-content">
<view class="paragraph">如果您对本隐私政策有任何疑问意见或建议您可以通过以下方式与我们联系</view>
<view class="paragraph">电子邮件privacy@example.com</view>
<view class="paragraph">电话400-XXX-XXXX</view>
<view class="paragraph">地址XX省XX市XX区XX路XX号</view>
</view>
</view>-->
</view>
<view class="bottom-btn">
<button class="btn" @tap="goBack">我已阅读并同意</button>
</view>
</view>
</template>
<script setup>
const goBack = () => {
uni.navigateBack({
delta: 1
});
};
</script>
<style lang="scss" scoped>
.container {
padding: 40rpx 30rpx;
padding-bottom: 160rpx;
}
.header {
padding: 30rpx 0;
text-align: center;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.content {
margin-top: 30rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.section-content {
padding-left: 20rpx;
}
.paragraph {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 16rpx;
text-align: justify;
}
.bottom-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 30rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background: #07c160;
color: #fff;
font-size: 32rpx;
border-radius: 45rpx;
border: none;
}
.btn:active {
opacity: 0.8;
}
</style>

View File

@@ -0,0 +1,163 @@
<template>
<view class="container">
<view class="header">
<view class="title">用户协议</view>
</view>
<view class="content">
<view class="section">
<view class="section-title">1. 协议的接受与修改</view>
<view class="section-content">
<view class="paragraph">1.1 欢迎您使用我们的服务本协议是您与本平台之间关于使用本平台服务所订立的协议</view>
<view class="paragraph">1.2 当您注册成为本平台用户时您已经阅读理解并接受本协议的全部条款并同意遵守本平台的相关规定</view>
<view class="paragraph">1.3 本平台有权在必要时修改本协议条款您可以在相关页面中查阅最新版本的协议条款</view>
<view class="paragraph">1.4 本协议条款变更后如果您继续使用本平台服务即视为您已接受修改后的协议</view>
</view>
</view>
<view class="section">
<view class="section-title">2. 账号注册与使用</view>
<view class="section-content">
<view class="paragraph">2.1 用户在注册时应当提供真实准确完整的个人资料并保持信息的及时更新</view>
<view class="paragraph">2.2 用户应当妥善保管账号密码因用户保管不善造成的损失由用户自行承担</view>
<view class="paragraph">2.3 用户不得将账号借给他人使用否则用户应当承担由此产生的全部责任</view>
</view>
</view>
<view class="section">
<view class="section-title">3. 用户行为规范</view>
<view class="section-content">
<view class="paragraph">3.1 用户在使用本平台服务时应遵守中华人民共和国相关法律法规</view>
<view class="paragraph">3.2 用户不得利用本平台进行任何违法或不当的活动包括但不限于</view>
<view class="paragraph">a) 发布传送传播储存违反国家法律法规的信息</view>
<view class="paragraph">b) 发布传送传播储存侵害他人知识产权商业秘密等合法权利的信息</view>
<view class="paragraph">c) 恶意使用本平台服务进行刷单虚假交易等行为</view>
</view>
</view>
<view class="section">
<view class="section-title">4. 服务内容与服务变更</view>
<view class="section-content">
<view class="paragraph">4.1 本平台提供的具体服务内容由本平台根据实际情况提供</view>
<view class="paragraph">4.2 本平台有权在必要时变更中断或终止部分或全部的服务</view>
<view class="paragraph">4.3 因服务变更给用户造成的损失本平台不承担责任</view>
</view>
</view>
<view class="section">
<view class="section-title">5. 知识产权</view>
<view class="section-content">
<view class="paragraph">5.1 本平台提供的服务中所包含的任何文本图片图形音频和视频等资料均受版权商标和其他财产所有权法律的保护</view>
<view class="paragraph">5.2 未经本平台事先书面同意用户不得以任何方式擅自使用本平台的商标服务商标</view>
</view>
</view>
<view class="section">
<view class="section-title">6. 免责声明</view>
<view class="section-content">
<view class="paragraph">6.1 用户明确同意其使用本平台服务所存在的风险将完全由其自己承担</view>
<view class="paragraph">6.2 本平台不保证服务一定能满足用户的要求也不保证服务不会中断</view>
<view class="paragraph">6.3 对于因不可抗力或本平台不能控制的原因造成的服务中断或其他缺陷本平台不承担任何责任</view>
</view>
</view>
<view class="section">
<view class="section-title">7. 协议终止</view>
<view class="section-content">
<view class="paragraph">7.1 本协议自用户接受之日起生效在用户使用本平台服务的过程中持续有效</view>
<view class="paragraph">7.2 如用户违反本协议的规定本平台有权中断或终止对该用户的服务</view>
</view>
</view>
<view class="section">
<view class="section-title">8. 其他条款</view>
<view class="section-content">
<view class="paragraph">8.1 本协议构成用户与本平台之间的最终完整的协议取代双方之间先前的所有协议</view>
<view class="paragraph">8.2 本协议的任何条款无论因何种原因无效或不具可执行性其余条款仍有效</view>
<view class="paragraph">8.3 本协议的解释效力及纠纷的解决均适用中华人民共和国法律</view>
</view>
</view>
</view>
<view class="bottom-btn">
<button class="btn" @tap="goBack">我已阅读并同意</button>
</view>
</view>
</template>
<script setup>
const goBack = () => {
uni.navigateBack({
delta: 1
});
};
</script>
<style lang="scss" scoped>
.container {
padding: 40rpx 30rpx;
padding-bottom: 160rpx;
}
.header {
padding: 30rpx 0;
text-align: center;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.content {
margin-top: 30rpx;
}
.section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.section-content {
padding-left: 20rpx;
}
.paragraph {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 16rpx;
text-align: justify;
}
.bottom-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 30rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background: #07c160;
color: #fff;
font-size: 32rpx;
border-radius: 45rpx;
border: none;
}
.btn:active {
opacity: 0.8;
}
</style>

View File

@@ -0,0 +1,54 @@
.container {
position: absolute;
width: 100%;
height: 100%;
background-color: #f4f4f4;
}
.busy-detail-main {
width: 100%;
box-sizing: border-box;
padding: 24rpx;
.busy-detail-list {
width: 100%;
box-sizing: border-box;
background-color: #fff;
padding: 0 24rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
}
.btn-list {
width: 100%;
display: flex;
margin-top: 64rpx;
.btn-item {
line-height: 64rpx;
height: 64rpx;
font-size: 32rpx;
text-align: center;
border-radius: 32rpx;
-webkit-border-radius: 32rpx;
-moz-border-radius: 32rpx;
-ms-border-radius: 32rpx;
-o-border-radius: 32rpx;
margin-right: 16rpx;
&:last-child {
margin-right: 0;
}
&.confirm {
flex: 2;
background-color: #2edb2e;
color: #fff;
}
&.del {
flex: 1;
background-color: #ff0000;
color: #fff;
}
}
}
}

View File

@@ -0,0 +1,323 @@
<template>
<view class="container">
<view class="busy-detail-main">
<view class="busy-detail-list">
<up-form
ref="formRef"
:labelStyle="{
fontSize: '28rpx',
}"
labelWidth="150rpx"
labelPosition="left"
:model="from"
>
<up-form-item
label="开始时间"
prop="startTime"
borderBottom
ref="item1"
@tap="startTimeShow = true"
>
<my-datetime-picker
v-model:show="startTimeShow"
:closeOnClickOverlay="true"
v-model:modelValue="from.startTime"
mode="datetime"
:filter="timeFilter"
></my-datetime-picker>
<template #right>
<up-icon name="arrow-right"></up-icon>
</template>
</up-form-item>
<up-form-item
label="结束时间"
prop="startTime"
borderBottom
ref="item1"
@tap="endTimeShow = true"
>
<my-datetime-picker
v-model:show="endTimeShow"
:closeOnClickOverlay="true"
v-model:modelValue="from.endTime"
mode="datetime"
:filter="timeFilter"
></my-datetime-picker>
<template #right>
<up-icon name="arrow-right"></up-icon>
</template>
</up-form-item>
</up-form>
</view>
<view class="btn-list">
<view @click="submit" class="btn-item confirm">{{
type === "add" ? "新增" : "修改"
}}</view>
<view v-if="type === 'edit'" @tap="delBusy" class="btn-item del"
>删除</view
>
</view>
<!-- <up-datetime-picker
v-model:show="startTimeShow"
:closeOnClickOverlay="true"
v-model="from.startTime"
mode="datetime"
:filter="timeFilter"
@confirm="startTimeShow = false"
@cancel="startTimeShow = false"
@close="startTimeShow = false"
></up-datetime-picker> -->
</view>
</view>
</template>
<script setup>
import { addBusyHour, editBusyHour } from "@/api/busy/busy";
import { delBusyHour } from "@/api/busy/busy";
const from = ref({
startTime: null,
endTime: null,
});
const type = ref("add");
const id = ref("");
const startTimeShow = ref(false);
const endTimeShow = ref(false);
const formRef = ref();
// 格式化时间yyyy-MM-dd HH:mm:ss
const formatTime = (time) => {
let date = new Date(time);
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2, "0");
let day = date.getDate().toString().padStart(2, "0");
let hour = date.getHours().toString().padStart(2, "0");
let minute = date.getMinutes().toString().padStart(2, "0");
let second = date.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
};
const submit = async () => {
if (!from.value.startTime || !from.value.endTime) {
uni.showToast({
title: '请输入开始时间和结束时间',
icon: 'none'
});
return;
}
const now = new Date();
const startTime = new Date(from.value.startTime);
const endTime = new Date(from.value.endTime);
// 设置当前时间的分钟为0或30
now.setMinutes(Math.floor(now.getMinutes() / 30) * 30);
now.setSeconds(0);
now.setMilliseconds(0);
if (startTime < now) {
uni.showToast({
title: '开始时间不能小于当前时间',
icon: 'none'
});
return;
}
if (endTime <= startTime) {
uni.showToast({
title: '结束时间必需大于开始时间',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中'
});
try {
if (type.value === 'add') {
await addBusyHour({
startTime: formatTime(startTime),
endTime: formatTime(endTime)
});
uni.showToast({
title: '新增成功',
icon: 'success'
});
} else if (type.value === 'edit') {
await editBusyHour({
startTime: formatTime(startTime),
endTime: formatTime(endTime),
id: id.value
});
uni.showToast({
title: '修改成功',
icon: 'success'
});
}
uni.navigateBack();
} catch (error) {
uni.showToast({
title: '提交失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
};
const delBusy = () => {
uni.showModal({
title: "提示",
content: "确定删除该时间段吗?",
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: "删除中",
});
const { code } = await delBusyHour({ id: id.value });
if (code === "00000") {
uni.hideLoading();
uni.showToast({
title: "删除成功",
});
uni.navigateBack();
}
}
},
});
};
const timeFilter = (mode, options) => {
const d = new Date();
const e = new Date(d);
e.setDate(e.getDate() + 30); // 允许选择30天内的时间
if (mode === 'year') {
return options.filter(
(option) => option >= d.getFullYear() && option <= e.getFullYear()
);
}
if (mode === 'month') {
const currentYear = d.getFullYear();
const maxYear = e.getFullYear();
const currentMonth = d.getMonth() + 1;
const maxMonth = e.getMonth() + 1;
return options.filter((option) => {
if (currentYear === maxYear) {
return option >= currentMonth && option <= maxMonth;
}
if (currentYear < maxYear) {
return option >= currentMonth;
}
return false;
});
}
if (mode === 'day') {
const currentMonth = d.getMonth() + 1;
const maxMonth = e.getMonth() + 1;
const currentDay = d.getDate();
const maxDay = e.getDate();
return options.filter((option) => {
if (currentMonth === maxMonth) {
return option >= currentDay && option <= maxDay;
}
if (currentMonth < maxMonth) {
return option >= currentDay;
}
return true;
});
}
if (mode === 'hour') {
const currentHour = d.getHours();
if (from.value.startTime && new Date(from.value.startTime).getDate() === d.getDate()) {
return options.filter((option) => option >= currentHour);
}
return options;
}
if (mode === 'minute') {
const currentHour = d.getHours();
const currentMinute = d.getMinutes();
if (from.value.startTime &&
new Date(from.value.startTime).getDate() === d.getDate() &&
new Date(from.value.startTime).getHours() === currentHour) {
return options.filter((option) => option >= currentMinute && (option === '00' || option === '30'));
}
return options.filter((option) => option === '00' || option === '30');
}
return options;
};
const timeRule = (rule, value, callback) => {
if (from.value.endTime && from.value.startTime) {
if (from.value.endTime < from.value.startTime) {
// callback(new Error("结束时间不能小于开始时间"));
return true;
} else {
return false;
}
} else {
return false;
}
};
const rules = {
startTime: [
{
required: true,
message: "请输入开始时间",
trigger: ["blur", "change"],
},
// {
// validator: (rule, value, callback) => {
// timeRule(rule, value, callback);
// },
// message: "结束时间不能小于开始时间",
// trigger: ["change", "blur"],
// },
],
endTime: [
{
required: true,
message: "请输入结束时间",
trigger: ["blur", "change"],
},
// {
// validator: (rule, value, callback) => {
// timeRule(rule, value, callback);
// },
// message: "结束时间不能小于开始时间",
// trigger: ["change", "blur"],
// },
],
};
onReady(() => {
formRef.value.setRules(rules);
});
onLoad((options) => {
console.log(options);
type.value = options.type;
const { startTime, endTime } = options;
if (type.value === "edit") {
from.value.startTime = new Date(startTime.replace("+", " "));
from.value.endTime = new Date(endTime.replace("+", " "));
id.value = options.id;
}
});
</script>
<style lang="scss" scoped>
@import "./busy-detail.scss";
</style>

View File

@@ -0,0 +1,73 @@
.container {
position: absolute;
width: 100%;
height: 100%;
background-color: #f4f4f4;
}
.busy-main {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 24rpx;
display: flex;
flex-direction: column;
.add-btn {
margin-bottom: 24rpx;
}
.busy-scroll {
flex: 1;
height: 0;
width: 100%;
.busy-list {
width: 100%;
.busy-item {
width: 100%;
box-sizing: border-box;
padding: 24rpx 32rpx;
display: flex;
background-color: #fff;
align-items: center;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.busy-time {
flex: 1;
width: 0;
display: flex;
align-items: center;
.time-item {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
align-items: center;
.time-text {
font-size: 30rpx;
line-height: 30rpx;
color: #000;
}
.date-text {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 24rpx;
color: #00000088;
}
}
}
.busy-btn {
margin-left: 24rpx;
}
}
}
}
}

View File

@@ -0,0 +1,139 @@
<template>
<view class="container">
<view class="busy-main">
<view class="add-btn">
<up-button @tap="toAddBusy" type="primary" text="添加"></up-button>
</view>
<scroll-view class="busy-scroll" scroll-y="true">
<view class="busy-list">
<view
v-for="(item, index) in busyList"
:key="index"
class="busy-item"
@tap="toEditBusy(item)"
>
<view class="busy-time">
<view class="time-item start-time">
<view class="time-text">{{
wxs.formatTime(item.startTime, "HH:mm")
}}</view>
<view class="date-text">{{
wxs.formatTime(item.startTime, "MM月dd日")
}}</view>
</view>
<up-icon name="arrow-right"></up-icon>
<view class="time-item end-time">
<view class="time-text">{{
wxs.formatTime(item.endTime, "HH:mm")
}}</view>
<view class="date-text">{{
wxs.formatTime(item.endTime, "MM月dd日")
}}</view>
</view>
</view>
<view class="busy-btn">
<up-button
@tap="delBusy(item.id)"
type="error"
text="删除"
size="mini"
></up-button>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { getMyScheduleList, delBusyHour } from "@/api/busy/busy";
import number from "@/wxs/number";
import useUserStore from "@/stores/user/useUserStore";
const withdrawalRecordList = ref([]);
const userStore = useUserStore();
const wxs = number();
const busyList = ref([]);
onLoad(() => {
// getBusyList();
});
onShow(() => {
// 只有在未登录时才显示提示框
if (!userStore.isLogin) {
uni.showModal({
title: "提示",
content: "请先进行登录!",
cancelText: "取消",
confirmText: "确定",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/login/login",
});
} else {
uni.switchTab({
url: "/pages/index/index",
});
}
}
})
}
getBusyList();
});
const getBusyList = async () => {
const { data } = await getMyScheduleList();
busyList.value = data;
console.log(data);
};
const delBusy = (id) => {
uni.showModal({
title: "提示",
content: "确定删除该时间段吗?",
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: "删除中",
});
const { code } = await delBusyHour({ id });
if (code === "00000") {
uni.hideLoading();
uni.showToast({
title: "删除成功",
});
getBusyList();
}
}
},
});
};
const toAddBusy = () => {
uni.navigateTo({
url: "/pages/busy-detail/busy-detail?type=add",
});
};
const toEditBusy = (item) => {
uni.navigateTo({
url:
"/pages/busy-detail/busy-detail?type=edit" +
"&endTime=" +
item.endTime +
"&id=" +
item.id +
"&startTime=" +
item.startTime,
});
};
</script>
<style lang="scss" scoped>
@import "./busy-hour.scss";
</style>

View File

@@ -0,0 +1,165 @@
.container {
background: #fff;
}
.input-box {
margin-bottom: 50rpx;
background: #fff;
padding: 0 20rpx;
.section {
display: flex;
align-items: center;
width: 100%;
font-size: 28rpx;
padding: 30rpx 0;
line-height: 48rpx;
height: 100%;
box-sizing: border-box;
border-bottom: 2rpx solid #e5e5e5;
text {
width: 20%;
color: #333;
}
input {
width: 70%;
padding: 0 20rpx;
color: #333;
}
picker {
width: 70%;
padding: 0 30rpx;
}
.pca {
width: 70%;
padding: 0 20rpx;
}
.arrow {
width: 28rpx;
height: 28rpx;
image {
width: 100%;
height: 100%;
vertical-align: top;
}
}
.shortcuts {
margin-left: 20rpx;
color: #eb2444;
}
}
}
.btn-box {
padding: 5px 10px;
width: 100%;
text-align: center;
margin: auto;
text {
font-size: 30rpx;
}
.clear.btn {
width: 60%;
height: 80rpx;
line-height: 80rpx;
margin: auto;
text-align: center;
border: 1rpx solid #eb2444;
border-radius: 50rpx;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 1px 0 rgba(255, 255, 255, 0.3);
margin-top: 40rpx;
color: #eb2444;
background-color: #f8f0f1b6;
}
.keep {
color: #fff;
background-color: #eb2444;
}
}
.keep.btn {
width: 60%;
height: 80rpx;
line-height: 80rpx;
margin: auto;
text-align: center;
border: 1rpx solid #eb2444;
border-radius: 50rpx;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 1px 0 rgba(255, 255, 255, 0.3);
}
.infoText {
margin-top: 20rpx;
text-align: center;
width: 100%;
justify-content: center;
}
picker-view {
background-color: white;
padding: 0;
width: 100%;
height: 380rpx;
bottom: 0;
position: fixed;
text {
color: #999;
display: inline-flex;
position: fixed;
margin-top: 20rpx;
height: 50rpx;
text-align: center;
line-height: 50rpx;
font-size: 34rpx;
font-family: Arial, Helvetica, sans-serif;
}
}
picker-view-column {
view {
vertical-align: middle;
font-size: 28rpx;
line-height: 28rpx;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.animation-element-wrapper {
display: flex;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 999;
}
.animation-element {
display: flex;
position: fixed;
width: 100%;
height: 470rpx;
bottom: 0;
background-color: rgba(255, 255, 255, 1);
}
.animation-button {
top: 20rpx;
width: 290rpx;
height: 100rpx;
align-items: center;
}
.left-bt {
left: 30rpx;
}
.right-bt {
right: 20rpx;
top: 20rpx;
position: absolute;
width: 80rpx !important;
}
.line {
display: block;
position: fixed;
height: 2rpx;
width: 100%;
margin-top: 89rpx;
background-color: #eee;
}

View File

@@ -0,0 +1,109 @@
<template>
<view class="container">
<!--input列表 -->
<view class="input-box">
<view class="section" @tap="chooseLocation">
<text>所在地区</text>
<view class="pca"> {{ province }} {{ city }} {{ area }} </view>
<view class="arrow">
<image src="@/static/images/icon/more.png" />
</view>
</view>
<view class="section">
<text>详细地址</text>
<input
placeholder="如楼号/单元/门牌号"
type="text"
:value="addr"
@input="onAddrInput"
/>
</view>
</view>
<!-- end input列表 -->
<!-- 功能按钮 -->
<view class="btn-box">
<view class="keep btn" @tap="onSaveAddr">
<text>保存居住地址</text>
</view>
</view>
<!-- end 功能按钮 -->
</view>
</template>
<script setup>
import util from "@/utils/util.js";
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
// import { listByPid } from "@/api/submit-order/submit-order";
const city = ref("");
const area = ref("");
const addr = ref("");
const province = ref("");
onLoad( (options) => {
province.value = userStore.userInfo.province;
city.value = userStore.userInfo.city;
area.value = userStore.userInfo.area;
addr.value = userStore.userInfo.addr;
});
const chooseLocation = () => {
// #ifdef MP-WEIXIN
uni.chooseLocation({
success: async (res) => {
console.log(res);
const { result } = await util.getDetailedAddress({
latitude: res.latitude,
longitude: res.longitude,
});
console.log("aaa",result);
// {{ province }} {{ city }} {{ area }}
province.value = result.ad_info.province;
city.value = result.ad_info.city;
area.value = result.ad_info.district;
addr.value = res.name;
},
});
// #endif
};
const onAddrInput = (e) => {
addr.value = e.detail.value;
};
/**
* 保存地址
*/
const onSaveAddr = async () => {
if (!province.value || !city.value || !area.value || !addr.value) {
uni.showToast({
title: "请填写完整地址",
icon: "none",
});
return;
}
const success = await userStore.setUserInfo({
province: province.value,
city: city.value,
area: area.value,
addr: addr.value,
});
if (success) {
uni.showToast({
title: "保存成功",
});
uni.navigateBack();
} else {
uni.showToast({
title: "保存失败",
icon: "none",
});
}
};
</script>
<style scoped lang="scss">
@import "./editAddress.scss";
</style>

View File

@@ -0,0 +1,891 @@
.user-menu-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.user-menu {
width: 80%;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 20rpx;
border-bottom: 1px solid #f5f5f5;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item text {
margin-left: 20rpx;
font-size: 28rpx;
color: #333;
}
.logout-item {
margin-top: 20rpx;
}
.logout-item text {
color: #ff5722;
}
/* 其他样式保持不变 */
.container {
height: 100vh;
background: linear-gradient(180deg, #fadfc4 0%, #fafafa 36.92%);
}
.top-background {
left: 50%;
transform: translateX(-50%);
border-radius: 282px;
width: 282px;
position: absolute;
top: 0;
z-index: 0;
height: 269px;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.8);
filter: blur(32.5px);
pointer-events: none;
}
/* 顶部渐变背景 */
.top-gradient {
padding: 36rpx;
padding-bottom: 20rpx;
position: relative;
z-index: 0;
}
/* 用户信息部分 */
.user-info-section {
display: flex;
align-items: center;
position: relative;
z-index: 1;
}
.user-avatar {
position: relative;
cursor: pointer;
}
.user-avatar image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 2rpx solid #fff;
}
.user-avatar image:hover {
opacity: 0.9;
transition: opacity 0.2s;
}
.user-details {
flex: 1;
margin-left: 20rpx;
}
.user-id {
color: rgba(0, 0, 0, 0.95);
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 14px;
}
.vip-tag {
text-align: center;
display: inline-block;
width: 48px;
background: #f3f4e9;
height: 16px;
color: #67c23a;
font-size: 24rpx;
padding: 4rpx 10rpx;
font-weight: 400;
font-style: normal;
line-height: 16px;
font-size: 10px;
border-radius: 20px;
margin-top: 10rpx;
padding-top: 1px;
}
.action-buttons {
display: flex;
}
.service-btn,
.settings-btn {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 20rpx;
}
.service-btn image,
.settings-btn image {
width: 64rpx;
height: 64rpx;
}
.service-btn text,
.settings-btn text {
font-size: 24rpx;
color: #666;
}
/* 主要内容区域 */
.main-content {
padding: 0 20rpx;
margin-top: -30rpx;
position: relative;
z-index: 2;
}
/* 订单管理部分 */
.orders-section,
.points-section,
.channel-section {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 余额条 */
.balance-bar-container {
width: 93%;
margin: 0 auto;
}
.balance-bar-content {
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
padding: 0 30rpx;
color: white;
}
.balance-left {
display: flex;
color: rgba(250, 219, 188, 0.95);
font-size: 24rpx;
font-style: normal;
font-weight: 400;
flex: 1;
overflow: hidden;
}
.balance-info {
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
}
.balance-top {
display: flex;
align-items: center;
margin-bottom: 5rpx;
flex-wrap: nowrap;
overflow: hidden;
}
.balance-label {
font-size: 24rpx;
margin-right: 10rpx;
white-space: nowrap;
flex-shrink: 0;
}
.balance-value {
font-size: 26rpx;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
.balance-value image {
width: 24rpx;
height: 24rpx;
margin-left: 10rpx;
}
.balance-text {
color: rgba(245, 227, 209, 0.75);
font-size: 20rpx;
font-style: normal;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.balance-right {
display: flex;
align-items: center;
margin-left: 20rpx;
flex-shrink: 0;
}
.recharge-btn {
font-size: 20rpx;
font-style: normal;
color: rgba(77, 49, 38, 0.95);
font-weight: 500;
line-height: 40rpx;
height: 40rpx;
border-radius: 24rpx;
background: linear-gradient(98deg, #fedfc6 0%, #edc5a3 95.24%);
white-space: nowrap;
padding: 0 20rpx;
min-width: 80rpx;
text-align: center;
flex-shrink: 0;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
transition: all 0.2s;
}
.recharge-btn:active {
transform: scale(0.97);
}
.channel-section .section-header {
margin-bottom: 10rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 35rpx;
}
.section-title {
color: rgba(0, 0, 0, 0.95);
font-size: 14px;
font-style: normal;
font-weight: 600;
}
.view-all {
color: rgba(0, 0, 0, 0.55);
font-size: 10px;
font-style: normal;
font-weight: 400;
}
.channel-section .view-all {
display: block;
}
.arrow {
margin-left: 6rpx;
}
.order-status-list {
display: flex;
justify-content: space-between;
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 25%;
}
.status-item image {
width: 60rpx;
height: 60rpx;
margin-bottom: 10rpx;
margin-left: 5px;
}
.status-item text {
font-size: 26rpx;
color: #333;
}
.status-badge {
position: absolute;
top: -10rpx;
right: 20rpx;
background-color: #ff5722;
color: white;
font-size: 20rpx;
min-width: 34rpx;
height: 34rpx;
padding: 0 5rpx;
border-radius: 34rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 积分中心部分 */
.points-details {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.point-item {
text-align: center;
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
}
.point-label {
color: rgba(0, 0, 0, 0.55);
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.point-value {
color: rgba(0, 0, 0, 0.95);
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 16px;
margin-top: 8px;
}
.coupon-usage {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 8px;
background: #fff8f8;
}
.coupon-info {
display: flex;
align-items: center;
}
.coupon-info image {
width: 60rpx;
height: 60rpx;
margin-right: 10rpx;
}
.use-now-btn {
color: white;
font-size: 24rpx;
padding: 6rpx 20rpx;
border-radius: 12px;
background: #ff2e15;
line-height: 1.5;
}
/* 频道部分 */
.channel-list {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.channel-item {
margin: 4rpx 0;
display: flex;
flex-direction: row;
align-items: center;
}
.channel-item text {
margin-left: 2px;
}
/* 在确保频道列表垂直居中的样式中也需要修改 */
.flex-sections .channel-list {
height: calc(100% - 80rpx);
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
}
/* 错误样式 */
.error-container {
display: flex;
justify-content: center;
align-items: center;
height: 300rpx;
width: 100%;
margin-top: 100rpx;
}
.error-message {
display: flex;
flex-direction: column;
align-items: center;
color: #666;
}
.retry-btn {
margin-top: 20rpx;
background-color: #ff6b6b;
color: white;
font-size: 28rpx;
padding: 10rpx 30rpx;
border-radius: 30rpx;
}
/* 并排布局容器 */
.flex-sections {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
/* 调整两侧的宽度 */
.flex-sections .points-section {
width: 75%;
margin-bottom: 0;
}
.flex-sections .channel-section {
width: 25%;
margin-bottom: 0;
}
/* 频道图标微调 */
.flex-sections .channel-icon {
width: 60rpx;
height: 60rpx;
}
/* 添加这里缺失的基本样式 */
.channel-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.channel-icon image {
width: 60rpx;
height: 60rpx;
}
.channel-item text {
font-size: 26rpx;
color: #333;
margin-left: 15px;
}
/* 让领券中心内容更紧凑 */
.flex-sections .points-details {
margin-bottom: 15rpx;
}
.flex-sections .coupon-usage {
padding: 8px 26rpx;
box-sizing: border-box;
}
/* 确保频道列表垂直居中 */
.flex-sections .channel-list {
height: calc(100% - 80rpx);
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
}
.channel-section .section-header {
flex-direction: column;
align-items: flex-start;
}
.channel-section .view-all {
display: block;
text-align: left;
width: 100%;
margin-top: 5px;
}
/* 添加图标内容的样式 */
.channel-icon .icon-content {
position: absolute;
z-index: 1;
}
/* 小屏幕适配 */
@media screen and (max-width: 375px) {
.balance-bar {
height: 90rpx;
}
.balance-bar-content {
padding: 0 20rpx;
}
.balance-label,
.balance-value,
.balance-text {
font-size: 22rpx;
}
.recharge-btn {
font-size: 9px;
padding: 0 10rpx;
}
}
/* 大屏幕适配 */
@media screen and (min-width: 768px) {
.balance-bar {
height: 120rpx;
}
.balance-bar-content {
padding: 0 40rpx;
}
.balance-label,
.balance-value {
font-size: 28rpx;
}
.balance-text {
font-size: 24rpx;
}
.recharge-btn {
font-size: 12px;
padding: 0 20rpx;
height: 24px;
line-height: 24px;
}
}
.user-id.login-btn {
color: #ff7839;
font-weight: 600;
}
/* 未登录状态样式 */
.login-content {
padding: 40rpx;
margin-top: 20rpx;
}
.login-prompt {
background-color: #fff;
border-radius: 16rpx;
padding: 60rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.login-image {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
margin-bottom: 30rpx;
}
.login-text {
font-size: 28rpx;
color: #666;
margin-bottom: 40rpx;
}
.login-button {
background: linear-gradient(90deg, #ff7839 0%, #ff4b1f 100%);
color: white;
font-size: 30rpx;
padding: 20rpx 80rpx;
border-radius: 40rpx;
border: none;
}
/* 添加眼睛图标样式 */
.eye-icon {
cursor: pointer;
margin-left: 4rpx;
}
// 收益列表
.top-gradient {
.income-info {
margin-top: 30rpx;
display: flex;
width: 100%;
justify-content: space-between;
.income-item {
display: flex;
flex-direction: column;
font-size: 28rpx;
color: #ff7839;
.price,
.label {
text-align: center;
}
}
}
}
.user-main {
margin-top: 24rpx;
background: #fafafa;
border-radius: 30rpx 30rpx 0 0;
-webkit-border-radius: 30rpx 30rpx 0 0;
-moz-border-radius: 30rpx 30rpx 0 0;
-ms-border-radius: 30rpx 30rpx 0 0;
-o-border-radius: 30rpx 30rpx 0 0;
padding: 32rpx;
.icon-list {
display: flex;
width: 100%;
justify-content: space-between;
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
.icon-image {
margin-bottom: 8rpx;
width: 80rpx;
height: 80rpx;
image {
width: 100%;
height: 100%;
}
}
.label {
font-size: 26rpx;
}
}
}
.order-box {
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 24rpx;
.order-column {
width: 48%;
box-sizing: border-box;
padding: 24rpx;
border-radius: 24rpx;
background-color: #fff;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
.order-column__header {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
.order-column-title {
font-size: 32rpx;
line-height: 32rpx;
font-weight: 600;
}
}
.order-column__body {
margin-top: 24rpx;
.order-column__item {
display: flex;
width: 100%;
background-color: #67c23a22;
align-items: center;
box-sizing: border-box;
padding: 16rpx;
margin-top: 16rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
&:nth-child(1) {
background-color: #e8f9ef;
}
&:nth-child(2) {
background-color: #f0f2fe;
}
&:nth-child(3) {
background-color: #fcf7e9;
}
&:first-child {
margin-top: 0;
}
.order-image {
width: 70rpx;
height: 70rpx;
margin-right: 32rpx;
image {
width: 100%;
height: 100%;
}
}
.order-info {
font-size: 28rpx;
line-height: 28rpx;
.order-label {
color: #00000099;
}
.order-value {
font-weight: 600;
margin-top: 16rpx;
}
}
}
}
}
}
.wallet-box {
position: relative;
margin-top: 24rpx;
width: 100%;
box-sizing: border-box;
background-color: #fff;
border-radius: 24rpx;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
padding: 32rpx 24rpx 16rpx 24rpx;
.wallet__tb {
position: absolute;
width: 50rpx;
height: 50rpx;
border-radius: 50%;
top: 296rpx;
background-color: #fafafa;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
&.left {
left: -25rpx;
}
&.right {
right: -25rpx;
}
}
.wallet__header {
width: 100%;
padding-bottom: 36rpx;
border-bottom: 2rpx dotted #0000001a;
.evaluate-list {
display: flex;
width: 100%;
justify-content: space-between;
.evaluate-item {
font-size: 24rpx;
.evaluate-value {
width: 100%;
text-align: center;
}
.evaluate-label {
width: 100%;
margin-top: 8rpx;
color: #000000cc;
text-align: center;
}
}
}
}
.wallet__body {
width: 100%;
margin-top: 24rpx;
display: flex;
border-bottom: 2rpx dotted #0000001a;
padding-bottom: 26rpx;
.wallet-column {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
.wallet-column-label {
font-size: 24rpx;
line-height: 24rpx;
color: #606266;
display: flex;
align-items: center;
margin-bottom: 24rpx;
&.smoll {
line-height: 20rpx;
font-size: 20rpx;
}
}
.wallet-column-value {
margin-bottom: 12rpx;
font-size: 36rpx;
line-height: 36rpx;
}
}
}
.wallet__footer {
display: flex;
justify-content: space-between;
margin-top: 48rpx;
align-items: center;
.wallet-tip {
font-size: 24rpx;
line-height: 24rpx;
}
.wallet-btn {
font-size: 24rpx;
padding: 0 24rpx;
height: 42rpx;
line-height: 42rpx;
background: #fa610f;
color: #ffdfc5f2;
}
}
}
}

View File

@@ -0,0 +1,678 @@
<template>
<view class="container">
<view class="error-container" v-if="false">
<view class="error-message">
<text>加载出错了请下拉刷新重试</text>
<button class="retry-btn" @tap="retryLoading">重试</button>
</view>
</view>
<view v-else>
<!-- 顶部背景区域 -->
<view class="top-gradient">
<!-- 用户信息 -->
<view class="user-info-section">
<view
class="user-avatar"
@tap.stop="isAuthInfo ? handleUpdateAvatar() : toLogin()"
>
<image
:src="isAuthInfo && loginResult.pic ? (loginResult.pic.indexOf('http') === -1 ? picDomain + loginResult.pic : loginResult.pic) : 'https://qiniu.pxdj.tashowz.com/2025/05/3eaec196d9c5478c972324136123dfdc.png'"
/>
</view>
<view class="user-details">
<view class="user-id" v-if="isAuthInfo">
{{ loginResult.nickName || loginResult.userMobile || '' }}
</view>
<view class="user-id login-btn" v-else @tap="toLogin">
点击登录
</view>
<view class="vip-tag" v-if="isAuthInfo">
{{ (loginResult.vipGrade || 1) + '星用户' }}
</view>
</view>
<view class="action-buttons">
<view class="service-btn" @tap="handleTips">
<image
src="/static/images/icon/customer.png"
mode="aspectFit"
class="icon-image"
></image>
<text>客服</text>
</view>
<view class="settings-btn" @tap="toSetting">
<image
src="/static/images/icon/set.png"
mode="aspectFit"
class="icon-image"
></image>
<text>设置</text>
</view>
</view>
</view>
<view class="income-info">
<view class="income-item">
<view class="price">
{{ wxs.parsePrice(merchantInfo.totalIncome)[0] }}.{{
wxs.parsePrice(merchantInfo.totalIncome)[1]
}}
</view>
<view class="label"> 总收益 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.totalSales }} </view>
<view class="label"> 总销量 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.commentCount }} </view>
<view class="label"> 用户评价 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.visitCount }} </view>
<view class="label"> 收藏人数 </view>
</view>
</view>
</view>
<!-- 用户菜单弹出层 -->
<view class="user-menu-overlay" v-if="showMenu" @tap="hideUserMenu">
<view class="user-menu" @tap.stop>
<view class="menu-item" @tap="handleUpdateAvatar">
<u-icon name="photo" size="24"></u-icon>
<text>更换头像</text>
</view>
<view class="menu-item" @tap="openUserInfoEditor">
<u-icon name="account" size="24"></u-icon>
<text>个人资料</text>
</view>
<view class="menu-item" @tap="handleTips">
<u-icon name="star" size="24"></u-icon>
<text>我的收藏</text>
</view>
<view class="menu-item logout-item" @tap="logout">
<u-icon name="arrow-right" size="24"></u-icon>
<text>退出登录</text>
</view>
</view>
</view>
</view>
<view class="user-main">
<view class="icon-list">
<view @tap="toBusyHour" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/time.png"
mode="aspectFit"
></image>
</view>
<view class="label"> 忙时设置 </view>
</view>
<view @tap="toMyProject" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/sign-in.png"
mode="aspectFit"
class="icon-image"
></image>
</view>
<view class="label"> 我的项目 </view>
</view>
<view @tap="toMyWallet" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/user.png"
mode="aspectFit"
class="icon-image"
></image>
</view>
<view class="label"> 我的钱包 </view>
</view>
</view>
<view class="order-box" v-if="prodSwitch">
<view class="order-column">
<view class="order-column__header" @tap="toOrderListPage">
<view class="order-column-title"> 订单管理 </view>
<up-icon name="arrow-right" size="24rpx"></up-icon>
</view>
<view class="order-column__body">
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="2">
<view class="order-image">
<image
src="@/static/images/icon/index/to-be-service.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 待服务 </view>
<view class="order-value"> {{ orderInfo?.prePareCount }} </view>
</view>
</view>
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="4,5,6">
<view class="order-image">
<image
src="@/static/images/icon/index/in-progress.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 进行中 </view>
<view class="order-value"> {{ orderInfo?.ingCount }} </view>
</view>
</view>
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="7,9">
<view class="order-image">
<image
src="@/static/images/icon/index/completed.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 已完成 </view>
<view class="order-value" > {{ orderInfo.finishedCount }} </view>
</view>
</view>
</view>
</view>
<view class="order-column">
<view class="order-column__header">
<view class="order-column-title"> 订单数据 </view>
<!-- <up-icon name="arrow-right" size="24rpx"></up-icon> -->
</view>
<view class="order-column__body">
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/income.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 订单收入 </view>
<view class="order-value"> {{ orderInfo.orderIncome }} </view>
</view>
</view>
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/number-orders.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 总订单数 </view>
<view class="order-value"> {{ orderInfo.orderCount }} </view>
</view>
</view>
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/refund-amount.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 退款金额 </view>
<view class="order-value"> {{ orderInfo.refundSum }} </view>
</view>
</view>
</view>
</view>
</view>
<view class="wallet-box">
<view class="wallet__tb left"> </view>
<view class="wallet__tb right"> </view>
<!-- <view class="wallet__header">
<view class="evaluate-list">
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 综合评分 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0% </view>
<view class="evaluate-label"> 成交率 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 服务次数 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 信用分 </view>
</view>
</view>
</view> -->
<view class="wallet__body">
<view class="wallet-column">
<view class="wallet-column-label"> 账户余额() </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.balance)[0] }}.{{
wxs.parsePrice(merchantInfo.balance)[1]
}}
</view>
<view class="wallet-column-label smoll">
立即提现 <up-icon name="arrow-right" size="18rpx"></up-icon
></view>
<view class="wallet-column-label"> 今日收益 </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.dayWithdrawal)[0] }}.{{
wxs.parsePrice(merchantInfo.dayWithdrawal)[1]
}}
</view>
</view>
<view class="wallet-column">
<view class="wallet-column-label"> 本月提现</view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.monthWithdrawal)[0] }}.{{
wxs.parsePrice(merchantInfo.monthWithdrawal)[1]
}}
</view>
<view class="wallet-column-label smoll">
立即提现 <up-icon name="arrow-right" size="18rpx"></up-icon
></view>
<view class="wallet-column-label"> 本月收益 </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.monthIncome)[0] }}.{{
wxs.parsePrice(merchantInfo.monthIncome)[1]
}}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { onShow, onPullDownRefresh } from "@dcloudio/uni-app";
import { ref, computed, onMounted } from "vue";
import useUserStore from "@/stores/user/useUserStore";
import {
getPackageData,getTel,
getOrderManageData,
getManageData,
} from "@/api/index/index";
import type { LoginResult } from "@/types/user";
import number from "@/wxs/number";
import tabbar from "@/components/tabbar/tabbar.vue";
const wxs = number();
// @ts-ignore
const picDomain = import.meta.env.VITE_APP_RESOURCES_URL;
// const isAuthInfo = ref<boolean>(false); // 默认未登录,需要检查登录状态
const loginResult = ref<LoginResult>({});
const orderAmount = ref<any>({});
const collectionCount = ref<number>(0);
const showMenu = ref<boolean>(false); // 控制用户菜单显示
const showBalance = ref<boolean>(true); // 控制余额显示隐藏,默认显示
const userStore = useUserStore();
const isAuthInfo = computed(() => {
return userStore.isLogin;
});
const merchantInfo = ref({
totalIncome: 0, //总收益
totalSales: 0, //总销量
commentCount: 0, //用户评价
visitCount: 0, //访客人数
balance: 0, //账户余额
monthWithdrawal: 0, //本月提现
dayWithdrawal: 0, //今日提现
monthIncome: 0, //本月收益
});
const orderInfo = ref({
prePareCount: 0, //待服务
ingCount: 0, //进行中
finishedCount: 0, //已完成
orderIncome: 0, //订单收入
orderCount: 0, //总订单数
refundSum: 0, //退款金额
});
const prodSwitch = ref(true);
onLoad( async ()=>{
await userStore.updateProdSwitch();
prodSwitch.value =userStore.getProdSwitch() !== 0;
!prodSwitch.value?await uni.hideTabBar():await uni.showTabBar();
//if(!prodSwitch.value)await uni.hideTabBar();
})
onShow(() => {
console.log("用户页面显示 - onShow");
getAllDate();
try {
checkLoginStatus();
loadUserData();
} catch (error) {
console.error("页面显示出错:", error);
}
});
const getAllDate = async () => {
try {
await Promise.all([
getMerchantInfo(),
getOrderInfo()
]);
return Promise.resolve();
} catch (error) {
console.error("获取数据失败:", error);
return Promise.reject(error);
}
};
//获取收益,销量,评价,收藏
const getMerchantInfo = async () => {
const { data } = await getPackageData();
console.log("获取数据成功", data);
const { data: mData } = await getManageData();
merchantInfo.value.totalIncome = parseInt(data?.totalIncome) ?? 0;
merchantInfo.value.totalSales = data?.totalSales ?? 0;
merchantInfo.value.commentCount = data?.commentCount ?? 0;
merchantInfo.value.visitCount = data?.visitCount ?? 0;
merchantInfo.value.balance = mData?.balance ?? 0;
merchantInfo.value.monthWithdrawal = parseInt(mData?.monthWithdrawal) ?? 0;
merchantInfo.value.dayWithdrawal = parseInt(mData?.dayWithdrawal) ?? 0;
merchantInfo.value.monthIncome = parseInt(mData?.monthIncome) ?? 0;
};
// 获取订单数据
const getOrderInfo = async () => {
const { data } = await getOrderManageData();
orderInfo.value = {
prePareCount: data?.prePareCount ?? 0,
ingCount: data?.ingCount ?? 0,
finishedCount: data?.finishedCount ?? 0,
orderIncome: data?.orderIncome ?? 0,
orderCount: data?.orderCount ?? 0,
refundSum: data?.refundSum ?? 0,
};
};
// 检查登录状态
const checkLoginStatus = () => {
// // 读取本地缓存中的登录信息
// loginResult.value = uni.getStorageSync("loginResult");
// let token = uni.getStorageSync("Token");
// // Token不存在但有accessToken则更新Token
// if (!token && loginResult.value && loginResult.value.accessToken) {
// token = loginResult.value.accessToken;
// uni.setStorageSync("Token", token);
// console.log("从loginResult中提取token并保存:", token);
// }
// // 更新Vuex中的token和登录状态
// if (token) {
// const userStore = useUserStore();
// userStore.token = token;
// isAuthInfo.value = true;
// loadUserData();
// } else {
// // 如果没有token但有loginResult清除过期数据
// if (loginResult.value) {
// uni.removeStorageSync("loginResult");
// loginResult.value = "";
// }
// isAuthInfo.value = false;
// }
};
// 显示用户菜单
const showUserMenu = () => {
console.log("显示用户菜单");
showMenu.value = true;
};
// 隐藏用户菜单
const hideUserMenu = () => {
console.log("隐藏用户菜单");
showMenu.value = false;
};
// 跳转忙时设置
const toBusyHour = () => {
uni.navigateTo({
url: "/pages/busy-hour/busy-hour",
});
};
// 加载用户数据
const loadUserData = async () => {
try {
const userStore = useUserStore();
const userInfo = await userStore.getUserInfo();
if (userInfo) {
loginResult.value = userInfo;
// uni.setStorageSync("loginResult", userInfo);
}
// 获取订单数量
// const orderCountRes = await getOrderCount();
// if (orderCountRes.data) {
// orderAmount.value = orderCountRes.data;
// }
return Promise.resolve();
} catch (error) {
console.error("加载用户数据失败:", error);
uni.showToast({
title: "加载用户数据失败",
icon: "none",
});
return Promise.reject(error);
}
};
const toMyWallet = () => {
uni.navigateTo({
url: "/pages/my-wallet/my-wallet",
});
};
const toMyProject = () => {
uni.navigateTo({
url: "/pages/my-project/my-project",
});
};
// 退出登录
const logout = () => {
// 关闭菜单弹窗
hideUserMenu();
// 清除登录信息
uni.removeStorageSync("loginResult");
uni.removeStorageSync("Token");
// 清除userStore中的token
const userStore = useUserStore();
userStore.token = "";
// 重置页面数据
isAuthInfo.value = false;
loginResult.value = "";
uni.showToast({
title: "退出成功",
icon: "none",
});
setTimeout(() => {
uni.navigateTo({
url: "/pages/index/index",
});
}, 1000);
};
// 去登录页面
const toLogin = () => {
// 保存当前页面路径,登录成功后可以跳回
uni.setStorageSync("routeUrlAfterLogin", "/pages/user/user");
if(prodSwitch.value){
uni.navigateTo({
url: "/pages/login/login",
});
}else {
uni.navigateTo({
url: "/pages/accountLogin/accountLogin",
});
}
};
// 添加充值方法
const toRecharge = () => {
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/recharge/recharge",
});
};
// 处理头像更新
const handleUpdateAvatar = () => {
// 检查登录状态
// if (!isAuthInfo.value) {
// toLogin();
// return;
// }
// // 隐藏菜单
// hideUserMenu();
// // 显示操作菜单
// uni.showActionSheet({
// itemList: ["从相册选择", "拍照"],
// success: (res) => {
// const sourceType = res.tapIndex === 0 ? ["album"] : ["camera"];
// // 调用系统选择图片
// uni.chooseImage({
// count: 1, // 默认9
// sizeType: ["original", "compressed"],
// sourceType: sourceType,
// success: async (res) => {
// const tempFilePaths = res.tempFilePaths;
// try {
// // 上传文件
// const fileUrl = await uploadFile(tempFilePaths[0]);
// // 更新用户头像
// await updateUserAvatar({
// avatarUrl: fileUrl,
// });
// // 更新本地存储的用户信息
// const userInfo = uni.getStorageSync("loginResult");
// if (userInfo) {
// userInfo.pic = fileUrl;
// uni.setStorageSync("loginResult", userInfo);
// loginResult.value = userInfo;
// }
// uni.showToast({
// title: "头像更新成功",
// icon: "success",
// });
// } catch (error) {
// uni.showToast({
// title: "头像更新失败",
// icon: "error",
// });
// }
// },
// });
// },
// });
};
const toSetting = () => {
uni.navigateTo({
url: "/pages/setting/setting",
});
};
// 切换余额显示
const toggleBalance = () => {
showBalance.value = !showBalance.value;
};
// 打开个人信息编辑
const openUserInfoEditor = () => {
// 隐藏菜单
hideUserMenu();
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/user-info/user-info",
});
};
// 处理提示信息
const handleTips = async () => {
try {
const res = await getTel();
if (res.data) {
uni.makePhoneCall({
phoneNumber: res.data
});
} else {
uni.showToast({
title: '获取客服电话失败',
icon: 'none'
});
}
} catch (error) {
uni.showToast({
title: '联系客服失败',
icon: 'none'
});
}
};
const toOrderListPage = (e) => {
useUserStore().setActive(1);
const status = e.currentTarget.dataset.status;
uni.setStorageSync('orderListStatus', status);
uni.switchTab({
url: '/pages/order-list/order-list'
});
};
// 去优惠券中心
const toCouponCenter = () => {
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/coupon-center/coupon-center",
});
};
// 重试加载
const retryLoading = () => {
loadUserData();
};
// 添加页面级下拉刷新
onPullDownRefresh(() => {
getAllDate()
loadUserData().then(() => {
uni.stopPullDownRefresh()
}).catch(() => {
uni.stopPullDownRefresh()
})
})
</script>
<style lang="scss" scoped>
@import "./index.scss";
</style>

View File

@@ -0,0 +1,31 @@
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
const doWxLogin = async (params) => {
const data = await userStore.doWxLogin(params);
if (data) {
doAfterLogin(data);
}
};
const doPhoneLogin = async (params) => {
const data = await userStore.doPhoneLogin(params);
if (data) {
doAfterLogin(data);
}
};
const doAfterLogin = async (loginRes) => {
userStore.token = loginRes.accessToken;
const userInfo = await userStore.getUserInfo();
uni.showToast({
title: "登录成功",
icon: "success",
});
uni.reLaunch({
url: "/pages/index/index",
});
};
export { doWxLogin, doPhoneLogin };

View File

@@ -0,0 +1,457 @@
.container {
padding: 40rpx;
min-height: 100vh;
background: linear-gradient(to bottom, #f8f8f8, #ffffff);
display: flex;
flex-direction: column;
align-items: center;
.login-title {
font-size: 42rpx;
font-weight: bold;
color: #07c160;
margin: 60rpx 0 40rpx;
text-align: center;
letter-spacing: 2rpx;
}
.input-group {
width: 100%;
margin-bottom: 40rpx;
input {
width: 100%;
height: 90rpx;
background: #f8f8f8;
border-radius: 45rpx;
margin-bottom: 30rpx;
padding: 0 40rpx;
box-sizing: border-box;
font-size: 28rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:focus {
box-shadow: 0 6rpx 16rpx rgba(7, 193, 96, 0.15);
transform: translateY(-2rpx);
}
}
}
.btn-group {
width: 100%;
margin-top: 60rpx;
.login-btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background: linear-gradient(135deg, #07c160, #05a350);
border-radius: 45rpx;
color: #fff;
font-size: 32rpx;
text-align: center;
border: none;
box-shadow: 0 8rpx 20rpx rgba(7, 193, 96, 0.3);
transition: all 0.3s ease;
&:active {
opacity: 0.8;
transform: translateY(2rpx);
box-shadow: 0 4rpx 10rpx rgba(7, 193, 96, 0.2);
}
}
.switch-type {
text-align: center;
font-size: 28rpx;
color: #07c160;
margin-top: 30rpx;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
padding-bottom: 4rpx;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2rpx;
background-color: #07c160;
transform: scaleX(0);
transition: transform 0.3s ease;
}
&:active::after {
transform: scaleX(1);
}
}
}
.wx-login-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
.auth-info {
text-align: center;
margin-bottom: 60rpx;
.wx-logo {
width: 240rpx;
height: 240rpx;
margin: 60rpx auto;
display: block;
border-radius: 50%;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
}
.auth-text {
font-size: 34rpx;
color: #333;
margin-bottom: 20rpx;
font-weight: 500;
}
.auth-desc {
font-size: 28rpx;
color: #999;
line-height: 1.5;
}
}
.user-info {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 60rpx;
.avatar {
width: 150rpx;
height: 150rpx;
border-radius: 50%;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
border: 4rpx solid #fff;
}
}
.auth-btn,
.wx-login-btn {
width: 600rpx;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: 32rpx;
text-align: center;
border: none;
background: linear-gradient(135deg, #07c160, #05a350);
color: #fff;
box-shadow: 0 8rpx 20rpx rgba(7, 193, 96, 0.3);
transition: all 0.3s ease;
&:active {
opacity: 0.8;
transform: translateY(2rpx);
box-shadow: 0 4rpx 10rpx rgba(7, 193, 96, 0.2);
}
}
}
.invite-input {
width: 100%;
margin-top: 30rpx;
input {
width: 100%;
height: 90rpx;
background: #f8f8f8;
border-radius: 45rpx;
padding: 0 40rpx;
box-sizing: border-box;
font-size: 28rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
}
button::after {
border: none;
}
}
// 全局样式
page {
background: linear-gradient(to bottom, #f8f8f8, #ffffff);
}
// 其他样式
.c-logo {
display: block;
width: 180rpx;
height: 180rpx;
margin: 20rpx auto 60rpx;
border-radius: 50%;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
}
.title {
font-size: 38rpx;
color: #333;
text-align: center;
margin-bottom: 100rpx;
font-weight: 600;
}
.wx-login-box {
margin-top: 80rpx;
}
.wx-login-btn {
.wx-icon {
width: 48rpx;
height: 48rpx;
margin-right: 16rpx;
margin-top: -4rpx;
}
text {
color: #fff;
font-size: 32rpx;
font-weight: 500;
display: inline-block;
vertical-align: middle;
}
}
.login-tips {
font-size: 26rpx;
color: #999;
text-align: center;
margin-top: 30rpx;
}
.invite-box {
margin-top: 60rpx;
padding: 0 30rpx;
}
.input-label {
font-size: 28rpx;
color: #333;
margin-right: 30rpx;
font-weight: 500;
}
.invite-tip {
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
padding-left: 30rpx;
}
image {
display: block;
width: 150rpx;
height: 150rpx;
margin: auto;
margin-top: 100rpx;
border-radius: 50%;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
}
.msg {
font-size: 26rpx;
color: #666;
width: 100%;
text-align: center;
margin-top: 20rpx;
}
.title {
font-size: 32rpx;
color: #333;
width: 100%;
text-align: center;
margin: 30rpx 0;
font-weight: 600;
}
button {
margin: 0;
padding: 0;
line-height: 1;
background: none;
border: none;
transition: all 0.3s ease;
}
.button-hover {
background-color: #fff;
color: #07c160;
}
.con {
padding-top: 120rpx;
}
.logo {
display: flex;
justify-content: center;
height: 160rpx;
margin-bottom: 10%;
image {
display: block;
width: 100%;
height: 100%;
}
}
.login-form {
width: 90%;
margin: 0 auto;
margin-bottom: 10%;
}
.authorized-btn {
width: 100%;
margin: 0 auto;
text-align: center;
background: linear-gradient(135deg, #07c160, #05a350);
border: none;
color: #fff;
border-radius: 45rpx;
font-size: 30rpx;
margin-top: 60rpx;
height: 90rpx;
line-height: 90rpx;
box-shadow: 0 8rpx 20rpx rgba(7, 193, 96, 0.3);
transition: all 0.3s ease;
&:active {
opacity: 0.8;
transform: translateY(2rpx);
box-shadow: 0 4rpx 10rpx rgba(7, 193, 96, 0.2);
}
}
.to-idx-btn {
width: 100%;
margin: 0 auto;
text-align: center;
background-color: #f5f5f5;
border: none;
color: #666;
border-radius: 45rpx;
font-size: 30rpx;
margin-top: 30rpx;
height: 90rpx;
line-height: 90rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:active {
opacity: 0.8;
background-color: #eeeeee;
}
}
.form-title {
width: 100%;
margin-bottom: 60rpx;
font-size: 36rpx;
text-align: center;
color: #07c160;
font-weight: 600;
}
.item {
display: block;
margin-bottom: 30rpx;
}
.account {
display: flex;
background: #f8f8f8;
padding: 15rpx 20rpx;
box-sizing: border-box;
font-size: 28rpx;
align-items: center;
border-radius: 45rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
input {
padding-left: 20rpx;
width: 75%;
height: 70rpx;
}
.inp-palcehoder {
font-size: 28rpx;
}
.int-yzm {
width: 410rpx;
padding-right: 10rpx;
box-sizing: border-box;
}
}
.input-btn {
width: 152rpx;
font-size: 26rpx;
color: #07c160;
text-align: center;
font-weight: 500;
background: rgba(7, 193, 96, 0.1);
padding: 12rpx 0;
border-radius: 30rpx;
}
.operate {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 30rpx;
.forgot-password,
.to-register {
font-size: 28rpx;
color: #07c160;
position: relative;
padding-bottom: 4rpx;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2rpx;
background-color: #07c160;
transform: scaleX(0);
transition: transform 0.3s ease;
}
&:active::after {
transform: scaleX(1);
}
}
}
.invite-tip {
font-size: 24rpx;
color: #999;
padding: 10rpx 0 0 180rpx;
}
.input-item {
width: 160rpx;
text-align: right;
margin-right: 20rpx;
}

View File

@@ -0,0 +1,248 @@
<!-- pages/login/login.vue -->
<template>
<view class="container">
<!-- 微信登录 -->
<template v-if="loginType === 'wxLogin'">
<view class="login-title">手机号快捷登录</view>
<view class="wx-login-container">
<template>
<view class="login-status">
<text>请授权您的手机号码完成登录</text>
</view>
<button
v-if="autoWxLoginFlag"
class="wx-login-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
:disabled="!isAgree"
:class="{'btn-disabled': !isAgree}"
>
<text class="btn-text">授权手机号</text>
</button>
<!-- 用户协议 -->
<view class="agreement-container">
<checkbox-group @change="handleAgreeChange">
<label class="agreement-item">
<checkbox :checked="isAgree" color="#07c160" style="transform:scale(0.7)" />
<text class="agreement-text">我已阅读并同意</text>
<text class="agreement-link" @tap.stop="openUserAgreement">用户协议</text>
<text class="agreement-text"></text>
<text class="agreement-link" @tap.stop="openPrivacyPolicy">隐私政策</text>
</label>
</checkbox-group>
</view>
</template>
</view>
</template>
</view>
</template>
<script setup>
import { getWeChatOpenId } from '@/api/user/user.ts'
import useUserStore from "@/stores/user/useUserStore"
import { doWxLogin } from "./hooks/login.ts"
const userStore = useUserStore();
const isRegister = ref(false);
const inviteCode = ref("");
const userInfo = ref({});
const isAgree = ref(false);
const phoneParams = ref({});
const loginType = ref("wxLogin");
const autoWxLoginFlag = ref(false);
// 生命周期
onLoad((options) => {
isRegister.value = options.isRegister == 0;
loginType.value = options.loginType || "wxLogin";
if (options.inviteCode) {
inviteCode.value = options.inviteCode;
}
autoWxLogin();
});
const getAppId = () => {
const appId = uni.getAccountInfoSync().miniProgram.appId;
return appId;
};
const autoWxLogin = () => {
uni.login({
provider: 'weixin',
success: function (loginRes) {
// 登录成功后显示提示用户需要点击授权
uni.showModal({
title: '登录提示',
content: '请点击下方按钮授权手机号来完成登录',
showCancel: false,
success: () => {
autoWxLoginFlag.value = true;
}
});
},
fail: (err) => {
console.error('微信登录失败:', err)
uni.showToast({
title: '微信登录失败,请重试',
icon: 'none'
})
}
})
}
const getOpenID = async (code) => {
try {
const { data } = await getWeChatOpenId({ code });
phoneParams.value = {
sessionKey: data.session_key,
openid: data.openid,
unionid: data?.unionid ?? "",
};
uni.showToast({ title: "请授权手机号", icon: "none" });
} catch (error) {
console.error("获取OpenID失败:", error);
}
};
const getPhoneNumber = async (e) => {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: "需要授权手机号才能登录", icon: "none" });
return
}
if (!isAgree.value) {
uni.showToast({ title: "请先同意用户协议和隐私政策", icon: "none" });
return
}
uni.login({
provider: 'weixin',
success: function (loginRes) {
const params = {
openIdCode: loginRes.code,
phoneCode: e.detail.code,
appId: getAppId()
}
uni.showLoading({
title: '登录中...',
mask: true
})
doWxLogin(params).then((res) => {
uni.hideLoading()
/* uni.reLaunch({
url: '/pages/index/index'
}) */
}).catch(err => {
uni.hideLoading()
uni.showToast({
title: err.msg || '登录失败,请重试',
icon: 'none'
})
})
}
})
}
// 同意协议变更
const handleAgreeChange = (e) => {
isAgree.value = e.detail.value.length > 0;
}
// 打开用户协议
const openUserAgreement = () => {
uni.navigateTo({
url: '/pages/agreement/user-agreement'
})
}
// 打开隐私政策
const openPrivacyPolicy = () => {
uni.navigateTo({
url: '/pages/agreement/privacy-policy'
})
}
</script>
<style scoped lang="scss">
@import "./login.scss";
.login-status {
margin: 80rpx 0 40rpx;
color: #333;
font-size: 30rpx;
text-align: center;
font-weight: 500;
}
.agreement-container {
margin-top: 40rpx;
width: 100%;
display: flex;
justify-content: center;
}
.agreement-item {
display: flex;
align-items: center;
justify-content: center;
}
.agreement-text {
font-size: 26rpx;
color: #666;
}
.agreement-link {
font-size: 26rpx;
color: #07c160;
}
.wx-login-btn {
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255, 255, 255, 0.2);
transform: rotate(45deg);
opacity: 0;
}
&:active::after {
animation: btnRipple 0.6s ease-out;
}
.btn-text {
font-weight: 500;
letter-spacing: 4rpx;
}
}
.btn-disabled {
opacity: 0.6;
background: linear-gradient(135deg, #cccccc, #aaaaaa) !important;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1) !important;
}
@keyframes btnRipple {
0% {
opacity: 0;
transform: translate(-50%, -50%) rotate(45deg) scale(0);
}
50% {
opacity: 0.5;
}
100% {
opacity: 0;
transform: translate(-50%, -50%) rotate(45deg) scale(2);
}
}
</style>

View File

@@ -0,0 +1,106 @@
.container {
position: absolute;
width: 100%;
height: 100%;
background-color: #f4f4f4;
color: #333;
}
.project-tit {
position: fixed;
//top: 0;
display: flex;
justify-content: space-around;
z-index: 999;
width: 100%;
height: 100rpx;
line-height: 100rpx;
background-color: #fff;
border-bottom: 2rpx solid #f4f4f4;
text {
display: block;
font-size: 28rpx;
color: 999;
width: 100rpx;
text-align: center;
}
text.on {
color: #f96012;
}
}
.project-main {
height: calc(100% - 102rpx);
width: 100%;
box-sizing: border-box;
padding: 24rpx;
display: flex;
flex-direction: column;
.project-scroll {
flex: 1;
height: 0;
width: 100%;
::-webkit-scrollbar {
display: none;
}
.project-item {
position: relative;
width: 100%;
box-sizing: border-box;
padding: 24rpx;
background-color: #fff;
border-radius: 16rpx;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
margin-bottom: 24rpx;
display: flex;
.project-image {
width: 128rpx;
height: 128rpx;
margin-right: 24rpx;
image {
width: 100%;
height: 100%;
}
}
.project-info {
flex: 1;
display: flex;
flex-direction: column;
width: 0;
justify-content: space-between;
.project-name {
color: #000000f2;
font-size: 28rpx;
font-weight: 600;
line-height: 28rpx; /* 100% */
}
.project-tip {
color: #0000008c;
font-size: 20rpx;
line-height: 20rpx; /* 100% */
}
.project-price {
margin-top: 20rpx;
color: #f96012;
font-weight: 500;
.symbol {
font-size: 20rpx;
line-height: 20rpx; /* 100% */
}
.price {
font-size: 28rpx;
line-height: 28rpx;
}
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}

View File

@@ -0,0 +1,179 @@
<template>
<view class="container">
<!-- 头部菜单 -->
<view class="project-tit">
<text data-sts="all" :class="sts == 'all' ? 'on' : ''" @tap="onStsTap">
全部
</text>
<text data-sts="1" :class="sts == '1' ? 'on' : ''" @tap="onStsTap">
已上架
</text>
<text data-sts="0" :class="sts == 0 ? 'on' : ''" @tap="onStsTap">
未上架
</text>
</view>
<view style="width: 100%; height: 102rpx"></view>
<view class="project-main">
<scroll-view scroll-y="true" class="project-scroll">
<view class="project-list">
<view
v-for="(item, index) in projectList"
:key="index"
class="project-item"
>
<view class="project-image">
<image :src="item.pic"></image>
</view>
<view class="project-info">
<view class="project-name">{{ item.name }}</view>
<view class="project-tip">
<view class="project-time"
>服务时长{{ item.duration }}分钟</view
>
</view>
<view class="project-price"
><text class="symbol"></text
><text class="price"
>{{ wxs.parsePrice(item.price)[0] }}.{{
wxs.parsePrice(item.price)[1]
}}</text
></view
>
</view>
<view class="project-btn">
<up-switch
v-model="item.isSelected"
asyncChange
:activeValue="1"
:inactiveValue="0"
size="20"
activeColor="#E66D4B"
inactiveColor="#D9D9D9AB"
@change="changeSelected(item)"
></up-switch>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import {
getMyProjectList,
addRelation,
delRelation,
} from "@/api/project/project";
import number from "@/wxs/number";
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
onShow(() => {
// 只有在未登录时才显示提示框
if (!userStore.isLogin) {
uni.showModal({
title: "提示",
content: "请先进行登录!",
cancelText: "取消",
confirmText: "确定",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/login/login",
});
} else {
uni.switchTab({
url: "/pages/index/index",
});
}
}
})
}
})
const wxs = number();
const sts = ref("1");
const projectList = ref([]);
const value3 = ref(1);
onLoad(() => {
getProjectList();
});
const getProjectList = async () => {
uni.showLoading({
title: "加载中",
});
const { data } = await getMyProjectList();
if (data) {
if (sts.value === "all") {
projectList.value = data;
} else {
projectList.value = data.filter((item) => item.isSelected == sts.value);
}
}
uni.hideLoading();
};
const onStsTap = (e) => {
sts.value = e.currentTarget.dataset.sts;
getProjectList();
};
const changeSelected = async (item) => {
// console.log(item);
uni.showModal({
content: !item.isSelected ? "确定要上架吗" : "确定要下架吗",
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: "加载中",
});
let _code = "";
if (!item.isSelected) {
const { code } = await addRelation({
merchantId: userStore.userInfo.userId,
projectId: item.id,
});
_code = code;
} else {
const { code } = await delRelation({
merchantId: userStore.userInfo.userId,
projectId: item.id,
});
_code = code;
}
if (_code === "00000") {
uni.hideLoading();
uni.showToast({
title: !item.isSelected ? "上架成功" : "下架成功",
});
getProjectList();
}
}
},
});
};
</script>
<style lang="scss" scoped>
@import "./my-project.scss";
</style>

View File

@@ -0,0 +1,185 @@
.container {
// position: absolute;
width: 100%;
height: 100vh;
background-color: #f4f4f4;
z-index: -1;
display: flex;
flex-direction: column;
}
.wallet-tip {
text-align: center;
font-size: 24rpx;
color: #e5e5e5;
/* Add new styles here */
padding: 10rpx;
margin: 0rpx auto; /* Center the tip and add margin */
margin-top: 5rpx;
width: fit-content; /* Make the background fit the content */
}
.scroll-wallet {
flex: 1;
width: 100%;
height: 100%;
::-webkit-scrollbar {
display: none;
}
}
.wallet-box {
background-color: #1b78e2;
width: 100%;
color: #fff;
display: flex;
padding-top: 40rpx;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding-bottom: 32rpx;
.wallet-title {
font-size: 30rpx;
margin-bottom: 20rpx;
}
.current-wallet {
font-size: 50rpx;
.not {
color: #ffffffaa;
font-size: 28rpx;
}
}
.wallet-btn {
margin-top: 24rpx;
background: #ff6600;
font-size: 32rpx;
line-height: 64rpx;
height: 64rpx;
padding: 0 28rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
}
.wallet-info {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 30rpx;
.wallet-item {
display: flex;
flex-direction: column;
align-items: center;
font-size: 24rpx;
flex: 1;
.sorce {
margin-bottom: 10rpx;
}
}
}
}
.audit-records-box {
width: 100%;
box-sizing: border-box;
padding: 24rpx 12rpx;
padding-bottom: 40rpx;
.audit-records-title {
width: 100%;
text-align: center;
font-size: 32rpx;
line-height: 32rpx;
margin-bottom: 32rpx;
}
.audit-records-main {
width: 100%;
.audit-records-list {
width: 100%;
padding-bottom: 20rpx;
}
.audit-records-item {
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background: #fff;
margin-bottom: 24rpx;
border-radius: 8rpx;
.audit-records-info {
display: flex;
flex-direction: column;
.audit-records-tag {
margin-bottom: 8rpx;
}
.audit-records-time {
margin-top: 16rpx;
font-size: 24rpx;
line-height: 24rpx;
color: #00000077;
}
}
.audit-records-price {
font-size: 32rpx;
font-weight: bold;
color: #ff6600;
}
.audit-records-price-wrapper {
display: flex;
flex-direction: column;
align-items: flex-end;
.audit-records-price {
font-size: 32rpx;
font-weight: bold;
color: #ff6600;
margin-bottom: 8rpx;
}
.audit-records-actual-price {
font-size: 24rpx;
color: #999;
background-color: #f9f9f9;
padding: 4rpx 12rpx;
border-radius: 20rpx;
border: 1rpx solid #eee;
.actual-amount {
color: #1b78e2;
font-weight: 500;
}
}
}
}
}
}
:deep(.u-transition) {
z-index: 999 !important;
}
.loadmore {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 10rpx;
}
.no-more {
width: 100%;
font-size: 24rpx;
color: #999999;
text-align: center;
padding-bottom: 20rpx;
}

View File

@@ -0,0 +1,271 @@
<template>
<view class="container">
<scroll-view
:scroll-y="true"
class="scroll-wallet"
@scrolltolower="getWithdrawalRecordList"
:lower-threshold="50"
:enable-flex="true"
>
<view class="wallet-mian">
<view class="wallet-box">
<view bind:tap="toPointsDetail" class="wallet-title">
<text>当前余额(未到账)</text>
</view>
<view bind:tap="toPointsDetail" class="current-wallet">
<text>{{ myWalletData.balance }}</text
><text class="not">({{ myWalletData.waitPrice }})</text>
</view>
<view @tap="showModel" class="wallet-btn"> 立刻提现 </view>
<!-- <view class="wallet-tip">
每次提现平台将扣除 {{ (commissionRate * 100).toFixed(0) }}% 手续费
</view> -->
<view class="wallet-info">
<view bind:tap="toPointsDetail" class="wallet-item">
<text class="sorce">{{ myWalletData.totalIncome }}</text>
<text>总收益</text>
</view>
<view bind:tap="toPointsDetail" class="wallet-item">
<text class="sorce">{{ myWalletData.dayIncome }}</text>
<text>今日收益</text>
</view>
<view bind:tap="toPointsDetail" class="wallet-item">
<text class="sorce">{{ myWalletData.monthWithdrawal }}</text>
<text>本月提现</text>
</view>
<view bind:tap="toPointsDetail" class="wallet-item">
<text class="sorce">{{ myWalletData.monthIncome }}</text>
<text>本月收益</text>
</view>
</view>
</view>
<view class="audit-records-box">
<view class="audit-records-title">提现记录</view>
<view class="audit-records-main">
<view class="audit-records-list">
<view
v-for="(item, index) in withdrawalRecordList"
:key="index"
class="audit-records-item"
>
<view class="audit-records-info">
<view class="audit-records-tag">
<up-tag
v-if="item.status == 0"
text="审核中"
type="warning"
>
</up-tag>
<up-tag v-if="item.status == 2" text="未通过" type="error">
</up-tag>
<up-tag
v-if="item.status == 1"
text="已到账"
type="success"
>
</up-tag>
</view>
<view class="audit-records-time"> {{ item.applyTime }} </view>
</view>
<view class="audit-records-price-wrapper">
<view class="audit-records-price">+{{item.amount}}</view>
<view class="audit-records-actual-price">实际到账: <text class="actual-amount">{{item.actualAmount || (item.amount * (1-commissionRate)).toFixed(2)}}</text></view>
</view>
</view>
<view v-if="ajaxFLag" class="loadmore">
<up-loading-icon
text="加载中"
size="24rpx"
textSize="24rpx"
></up-loading-icon>
</view>
<view
v-if="total != -1 && total <= withdrawalRecordList.length"
class="no-more"
>
已经到底了
</view>
</view>
</view>
<up-modal
showCancelButton
:show="modalShow"
title="申请提现"
@confirm="confirm"
@cancel="cancel"
>
<view class="slot-content">
<up-input
type="number"
placeholder="请输入提现金额"
border="surround"
v-model="withdrawalAmount"
></up-input>
</view>
</up-modal>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import {
getWithdrawalList,
getMyWallet,
applyWithdrawal,
} from "@/api/wallet/wallet";
import { onShow } from '@dcloudio/uni-app';
import useUserStore from "@/stores/user/useUserStore";
const withdrawalRecordList = ref([]);
const userStore = useUserStore();
const commissionRate = ref(0);
onShow(() => {
// 只有在未登录时才显示提示框
if (!userStore.isLogin) {
uni.showModal({
title: "提示",
content: "请先进行登录!",
cancelText: "取消",
confirmText: "确定",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/login/login",
});
} else {
uni.switchTab({
url: "/pages/index/index",
});
}
}
})
}
})
const withdrawalAmount = ref("");
const pageForm = ref({
size: 10,
current: 1,
});
const modalShow = ref(false);
const myWalletData = ref({
waitPrice: 0, //待提现金额
balance: 0, //账户余额
monthWithdrawal: 0, //本月提现
monthIncome: 0, //本月收益
dayIncome: 0, //今日收益
totalIncome: 0, //总收益
});
const total = ref(-1);
const ajaxFLag = ref(false);
const getWithdrawalRecordList = async () => {
if (ajaxFLag.value) {
return;
}
if (total.value != -1 && withdrawalRecordList.value.length >= total.value) {
return;
}
ajaxFLag.value = true;
const { data } = await getWithdrawalList({
...pageForm.value,
});
ajaxFLag.value = false;
total.value = data.total;
// 保存佣金比例
if (data.commission !== undefined) {
commissionRate.value = data.commission;
}
if (data.total > withdrawalRecordList.value.length) {
pageForm.value.current++;
withdrawalRecordList.value = [
...withdrawalRecordList.value,
...data.records,
];
}
};
const getMyWalletData = async () => {
uni.showLoading({
title: "加载中...",
});
const { data } = await getMyWallet();
myWalletData.value.waitPrice = data.waitPrice;
myWalletData.value.balance = data.balance;
myWalletData.value.monthWithdrawal = data.monthWithdrawal;
myWalletData.value.monthIncome = data.monthIncome;
myWalletData.value.dayIncome = data.dayIncome;
myWalletData.value.totalIncome = data.totalIncome;
uni.hideLoading();
};
const apply = async (amount) => {
const { data } = await applyWithdrawal({
amount: amount,
});
uni.showToast({
title: "申请成功",
});
};
const cancel = () => {
modalShow.value = false;
};
const confirm = async () => {
if (!withdrawalAmount.value) {
uni.showToast({
title: "请输入提现金额",
icon: "none",
});
return;
}
if (withdrawalAmount.value > myWalletData.value.balance) {
uni.showToast({
title: "余额不足",
icon: "none",
});
return;
}
uni.showLoading({
title: "提交中",
});
await apply(withdrawalAmount.value);
modalShow.value = false;
uni.hideLoading();
getMyWalletData();
pageForm.value.current = 1;
total.value = -1;
withdrawalRecordList.value = [];
getWithdrawalRecordList();
};
const showModel = () => {
withdrawalAmount.value = "";
modalShow.value = true;
};
onLoad(() => {
getWithdrawalRecordList();
getMyWalletData();
});
</script>
<script>
export default { options: { styleIsolation: "shared" } };
</script>
<style lang="scss" scoped>
@import "./my-wallet.scss";
</style>

View File

@@ -0,0 +1,636 @@
// .container {
// background: #f4f4f4;
// }
// .order-detail {
// margin-bottom: 120rpx;
// padding-bottom: 160rpx;
// .delivery-addr {
// padding: 20rpx 30rpx;
// background: #fff;
// .user-info {
// line-height: 48rpx;
// word-wrap: break-word;
// word-break: break-all;
// overflow: hidden;
// text-overflow: ellipsis;
// display: -webkit-box;
// -webkit-line-clamp: 1;
// -webkit-box-orient: vertical;
// .item {
// font-size: 28rpx;
// margin-right: 30rpx;
// vertical-align: top;
// display: inline-block;
// }
// }
// .addr {
// font-size: 26rpx;
// line-height: 36rpx;
// color: #999;
// word-wrap: break-word;
// }
// }
// }
// .prod-item {
// background-color: #fff;
// margin-top: 15rpx;
// font-size: 28rpx;
// .item-cont {
// .prod-pic {
// image {
// width: 180rpx;
// height: 180rpx;
// width: 100%;
// height: 100%;
// }
// font-size: 0;
// display: block;
// width: 160rpx;
// height: 160rpx;
// overflow: hidden;
// background: #fff;
// margin-right: 16rpx;
// }
// display: flex;
// align-items: center;
// padding: 30rpx;
// border-top: 2rpx solid #f1f1f1;
// .prod-info {
// margin-left: 10rpx;
// font-size: 28rpx;
// width: 100%;
// position: relative;
// height: 80px;
// -webkit-flex: 1;
// -ms-flex: 1;
// -webkit-box-flex: 1;
// -moz-box-flex: 1;
// flex: 1;
// .prodname {
// font-size: 28rpx;
// line-height: 40rpx;
// max-height: 86rpx;
// overflow: hidden;
// display: -webkit-box;
// -webkit-line-clamp: 1;
// -webkit-box-orient: vertical;
// text-overflow: ellipsis;
// word-break: break-all;
// }
// .prod-info-cont {
// position: relative;
// color: #999;
// margin-top: 10rpx;
// font-size: 24rpx;
// .info-item {
// color: #999;
// height: 28rpx;
// margin-top: 10rpx;
// font-size: 24rpx;
// overflow: hidden;
// display: -webkit-box;
// -webkit-line-clamp: 1;
// -webkit-box-orient: vertical;
// text-overflow: ellipsis;
// word-break: break-all;
// width: 70%;
// }
// .number {
// float: left;
// margin-right: 20rpx;
// }
// }
// }
// }
// .price-nums {
// margin-top: 30rpx;
// .prodprice {
// color: #333;
// height: 50rpx;
// line-height: 50rpx;
// font-size: 24rpx;
// float: left;
// }
// .btn-box {
// float: right;
// text-align: right;
// .btn {
// padding: 6rpx 30rpx;
// line-height: 36rpx;
// margin-left: 20rpx;
// font-size: 24rpx;
// display: inline-block;
// border: 2rpx solid #e4e4e4;
// border-radius: 50rpx;
// }
// }
// }
// }
// .order-msg {
// background: #fff;
// margin-top: 15rpx;
// font-size: 28rpx;
// .msg-item {
// padding: 20rpx;
// border-top: 2rpx solid #f1f1f1;
// &:first-child {
// border: 0;
// }
// .item {
// display: flex;
// padding: 10rpx 0;
// align-items: center;
// box-sizing: border-box;
// .item-tit {
// min-width: 140rpx;
// color: #999;
// line-height: 48rpx;
// }
// .item-txt {
// flex: 1;
// line-height: 48rpx;
// }
// .item-txt.remarks {
// max-width: 600rpx;
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
// }
// .copy-btn {
// display: block;
// margin-left: 20rpx;
// border: 2rpx solid #e4e4e4;
// padding: 6rpx 24rpx;
// border-radius: 50rpx;
// font-size: 24rpx;
// line-height: 28rpx;
// }
// .item-txt.price {
// text-align: right;
// }
// }
// .item.payment {
// border-top: 2rpx solid #f1f1f1;
// color: #eb2444;
// padding-top: 30rpx;
// }
// }
// }
// .order-detail-footer {
// position: fixed;
// bottom: 0;
// width: 100%;
// max-width: 750rpx;
// background: #fff;
// margin: auto;
// display: -webkit-flex;
// display: -webkit-box;
// display: -moz-box;
// display: -ms-flexbox;
// display: flex;
// padding: 22rpx 0;
// font-size: 26rpx;
// box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.05);
// .dele-order {
// margin-left: 20rpx;
// line-height: 60rpx;
// display: block;
// margin-right: 20rpx;
// width: 150rpx;
// text-align: center;
// }
// .footer-box {
// flex: 1;
// text-align: right;
// line-height: 60rpx;
// .buy-again {
// font-size: 26rpx;
// color: #fff;
// background: #eb2444;
// border-radius: 50rpx;
// padding: 10rpx 20rpx;
// margin-right: 20rpx;
// }
// .apply-service {
// font-size: 26rpx;
// border-radius: 50rpx;
// padding: 10rpx 20rpx;
// border: 1px solid #e4e4e4;
// margin-right: 20rpx;
// }
// }
// }
// .clearfix {
// &:after {
// content: " ";
// display: table;
// clear: both;
// }
// }
// .order-state {
// height: 70rpx;
// line-height: 70rpx;
// text-align: right;
// margin-right: 20rpx;
// .order-sts {
// color: #eb2444;
// font-size: 28rpx;
// }
// .order-sts.gray {
// color: #999;
// height: 32rpx;
// line-height: 32rpx;
// }
// .order-sts.normal {
// color: #333;
// }
// }
.container {
--iphone-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
--iphone-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
padding-bottom: calc(100rpx + var(--iphone-bottom));
position: relative;
box-sizing: border-box;
width: 100%;
height: 100vh;
background-color: #f4f4f4;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.order__header {
width: 100%;
box-sizing: border-box;
padding: 24rpx 40rpx;
display: flex;
justify-content: space-between;
color: #000000f2;
font-size: 36rpx;
line-height: 36rpx; /* 100% */
}
.order__body {
width: 100%;
box-sizing: border-box;
padding: 0 24rpx;
// padding-bottom:160rpx;
.order-detail-cart {
width: 100%;
box-sizing: border-box;
padding: 24rpx;
background-color: #fff;
border-radius: 16rpx;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.cart__header {
display: flex;
width: 100%;
margin-bottom: 40rpx;
.cart__header__title {
color: #000000f2;
font-size: 28rpx;
font-weight: 600;
line-height: 28rpx; /* 100% */
}
}
.order-detail-line {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
.item-label {
color: #0000008c;
font-size: 28rpx;
line-height: 28rpx; /* 100% */
}
.item-value {
color: #000000f2;
font-size: 28rpx;
line-height: 28rpx; /* 100% */
}
&.address {
position: relative;
.copy-btn {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
padding: 6rpx 20rpx;
font-size: 24rpx;
color: #3e62ad;
border: 1px solid #3e62ad;
border-radius: 30rpx;
background-color: #fff;
}
}
}
}
.order-location {
.order-detail-line {
margin-bottom: 18rpx;
.location-icon {
width: 24rpx;
height: 30rpx;
flex-shrink: 0;
margin-right: 24rpx;
image {
width: 100%;
height: 100%;
}
}
.location-address {
flex: 1;
width: 0;
color: #000000f2;
font-size: 28rpx;
line-height: 42rpx; /* 150% */
position: relative;
}
.user-info {
display: flex;
margin-left: 48rpx;
color: #0000008c;
font-size: 24rpx;
line-height: 24rpx; /* 150% */
.user-name {
margin-right: 16rpx;
}
}
}
.user-info {
display: flex;
margin-left: 48rpx;
.item {
color: #0000008c;
font-size: 24rpx;
line-height: 24rpx; /* 150% */
margin-right: 12rpx;
&.phone-item {
display: flex;
align-items: center;
.phone-icon {
margin-left: 8rpx;
font-size: 22rpx;
color: #ffffff;
background-color: #3e62ad;
padding: 4rpx 10rpx;
border-radius: 20rpx;
}
}
}
}
}
.order-project {
.order-detail-line.project-main {
.project-image {
flex-shrink: 0;
width: 128rpx;
height: 128rpx;
margin-right: 24rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.project-info {
flex: 1;
width: 0;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.project-name {
color: #000000f2;
font-size: 28rpx;
font-weight: 600;
line-height: 28rpx; /* 100% */
flex-shrink: 0;
}
.project-tip {
margin-top: 8rpx;
display: flex;
justify-content: space-between;
align-items: center;
.project-time {
color: #0000008c;
font-size: 28rpx;
line-height: 28px; /* 100% */
}
.project-price {
color: #000000f2;
font-weight: 500;
.symbol {
font-size: 24rpx;
line-height: 24rpx; /* 100% */
}
.price {
font-size: 40rpx;
line-height: 40rpx;
}
}
}
}
}
.amount-to {
.item-value {
display: flex;
.amount-to-label {
color: #000000f2;
font-size: 128rx;
line-height: 28rpx; /* 100% */
}
.amount-to-value {
color: #ff4b10f2;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
}
}
}
}
// 核销码样式
.verification-code {
.verification-code-container {
width: 100%;
display: flex;
flex-direction: column;
.verification-code-layout {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
border-bottom: 1px solid #f4f4f4;
padding-bottom: 24rpx;
gap: 0;
.verification-code-qr {
width: 350rpx;
height: 300rpx;
padding-left:20rpx;
.verification-code-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.verification-code-barcode {
width: 130rpx;
height: 300rpx;
padding: 0;
.verification-code-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
.verification-code-info {
padding-top: 24rpx;
.verification-code-text {
font-size: 32rpx;
font-weight: 500;
color: #000000;
line-height: 48rpx;
}
.verification-code-number {
margin-top: 8rpx;
font-size: 28rpx;
color: #000000;
line-height: 40rpx;
display: flex;
align-items: center;
.verification-code-copy {
color: #999999;
margin-left: 16rpx;
font-size: 24rpx;
}
.action-use {
margin-left: auto;
background-color: #f5f5f5;
border: 1px solid #f5f5f5;
color: #999999;
padding: 8rpx 24rpx;
border-radius: 24rpx;
font-size: 28rpx;
font-weight: 500;
}
}
}
}
}
}
.order__footer {
--iphone-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
--iphone-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
position: fixed;
bottom: 0;
width: 100%;
left: 0;
background: #fff;
box-sizing: border-box;
padding: 12rpx 24rpx;
padding-bottom: calc(12rpx + var(--iphone-bottom));
z-index: 100;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
&:before {
content: '';
position: absolute;
top: -1px;
left: 0;
width: 100%;
height: 1px;
background-color: #f4f4f4;
}
.tip-text {
color: #666;
font-size: 24rpx;
margin-right: auto;
}
.btn-list {
display: flex;
justify-content: flex-end;
.footer-spacer {
flex: 1;
}
.btn-item {
color: #000;
font-size: 28rpx;
font-weight: 400;
height: 76rpx;
line-height: 76rpx;
padding: 0 48rpx;
border: 2rpx solid #ededed;
border-radius: 42rpx;
-webkit-border-radius: 42rpx;
-moz-border-radius: 42rpx;
-ms-border-radius: 42rpx;
-o-border-radius: 42rpx;
margin-right: 16rpx;
&:last-child {
margin-right: 0;
}
&.cancel {
color: #000;
background: #fff;
border-color: #ededed;
}
&.confirm {
border-color: #ffb28b;
background: #ffe4d6;
color: #f96012;
}
}
}
}
.call-phone {
text-decoration: underline;
}

View File

@@ -0,0 +1,686 @@
<template>
<view class="container">
<view class="order__header">
<view class="order__header-title">{{
statusList[orderInfo.status]
}}</view>
</view>
<view class="order__body">
<view class="order-detail-cart order-location" v-if="prodSwitch">
<view class="order-detail-line address">
<view class="location-icon">
<image src="@/static/images/icon/order/location.png"></image>
</view>
<view class="location-address" @tap="openLocation(userAddrOrder)" @longpress="copyAddress(userAddrOrder)">
{{ userAddrOrder.province }}{{ userAddrOrder.city
}}{{ userAddrOrder.area }}{{ userAddrOrder.addr }}
</view>
</view>
<view class="user-info">
<text class="item"> {{ userAddrOrder.receiver }} </text>
<view class="item phone-item" @tap="callPhone(userAddrOrder.mobile)" @longpress="copyMobile(userAddrOrder.mobile)">
<text class="call-phone">{{ userAddrOrder.mobile }}</text>
</view>
</view>
</view>
<view class="order-detail-cart order-project">
<view class="order-detail-line project-main">
<view class="project-image">
<image :src="project.pic"></image>
</view>
<view class="project-info">
<view class="project-name"
>{{ project.name }}×{{ orderInfo.productNums }}</view
>
<view class="project-tip">
<view class="project-time"
>服务时长{{ project.duration }}分钟</view
>
<view class="project-price"
><text class="symbol"></text
><text class="price"
>{{
wxs.parsePrice(
orderInfo.total - orderInfo.freightAmount
)[0]
}}.{{
wxs.parsePrice(
orderInfo.total - orderInfo.freightAmount
)[1]
}}</text
></view
>
</view>
</view>
</view>
<view class="order-detail-line" v-if="prodSwitch">
<view class="item-label"> 出行方式 </view>
<view class="item-value">滴滴</view>
</view>
<view class="order-detail-line" v-if="prodSwitch">
<view class="item-label"> 预估车费 </view>
<view class="item-value"
>{{ wxs.parsePrice(orderInfo.freightAmount)[0] }}.{{
wxs.parsePrice(orderInfo.freightAmount)[1]
}}</view
>
</view>
<view class="order-detail-line amount-to">
<view class="item-label"> </view>
<view class="item-value">
<view class="amount-to-label">合计</view>
<view class="amount-to-value">{{ wxs.parsePrice(orderInfo.total)[0] }}.{{ wxs.parsePrice(orderInfo.total)[1] }}</view>
</view>
</view>
</view>
<view class="order-detail-cart order-info">
<view class="cart__header">
<view class="cart__header__title">订单信息</view>
</view>
<view class="order-detail-line">
<view class="item-label"> 订单编号 </view>
<view class="item-value" @longpress="copyText(orderInfo.orderNumber)">{{ orderInfo.orderNumber }}</view>
</view>
<view class="order-detail-line">
<view class="item-label"> 下单时间 </view>
<view class="item-value" @longpress="copyText(orderInfo.createTime)">{{ orderInfo.createTime }}</view>
</view>
<view class="order-detail-line">
<view class="item-label"> 服务商户 </view>
<view class="item-value" @longpress="copyText(orderInfo.prodName)">{{ orderInfo.merchant.merchantName }}</view>
</view>
<view class="order-detail-line">
<view class="item-label"> 预约时间 </view>
<view class="item-value" @longpress="copyText(orderInfo.reserveTime)">{{ orderInfo.reserveTime }}</view>
</view>
</view>
</view>
<view class="order__footer" v-if="orderInfo.status != 7">
<view class="btn-list">
<!-- 取消订单按钮 -->
<view
v-if="(orderInfo.status == 5 || orderInfo.status == 6) && orderInfo.isIdle !== 1 &&
(orderInfo.reachTime && new Date() > new Date(orderInfo.reachTime) && new Date() - new Date(orderInfo.reachTime) >= 20 * 60 * 1000)"
@tap.stop="changeOrder(orderInfo, 8)"
class="btn-item cancel"
>取消订单</view>
<!-- 接单按钮 -->
<view
@tap.stop="changeOrder(orderInfo, 3)"
v-if="orderInfo.status === 2"
class="btn-item confirm"
>{{ orderInfo.isIdle === 1 ? '立刻抢单' : '立刻接单' }}</view>
<!-- 出发按钮 -->
<view
@tap.stop="changeOrder(orderInfo, 4)"
v-if="orderInfo.status === 3"
class="btn-item confirm"
>立刻出发</view>
<!-- 确认到达按钮 -->
<view
@tap.stop="changeOrder(orderInfo, 5)"
v-if="orderInfo.status === 4"
class="btn-item confirm"
>确认到达</view>
<!-- 开始服务按钮 -->
<view
@tap.stop="changeOrder(orderInfo, 6)"
v-if="orderInfo.status === 5"
class="btn-item confirm"
>开始服务</view>
<!-- 完成服务按钮 -->
<view
@tap.stop="changeOrder(orderInfo, 9)"
v-if="orderInfo.status === 6"
class="btn-item confirm"
>完成服务</view>
</view>
</view>
</view>
</template>
<script setup>
import {
getOrderDetail,
userCancelOrder,
obligationPay,
updateOrder,
grabOrders,
merchantCancelOrder,
} from "@/api/order/order";
import number from "@/wxs/number";
import useUserStore from "@/stores/user/useUserStore";
import { ref, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import util from "@/utils/util";
const userStore = useUserStore();
const wxs = number();
const prodSwitch = ref(true);
const statusList = computed(() => {
if (prodSwitch.value) {
return {
1: "待付款",
2: "待接单",
3: "已接单",
4: "已出发",
5: "已到达",
6: "服务中",
7: "已完成",
8: "已取消",
9: "服务完成",
10: "退款中",
11: "退款成功",
12: "退款失败",
};
} else {
return {
1: "待付款",
2: "待核销",
3: "待核销",
4: "待核销",
5: "待核销",
6: "服务中",
7: "已完成",
8: "已取消",
9: "服务完成",
10: "退款中",
11: "退款成功",
12: "退款失败",
};
}
});
// 添加订单状态变更方法
const changeOrder = async (item, status) => {
// 如果是取消订单,检查是否允许取消
// if (status === 8) {
// const reachTime = new Date(item.reachTime);
// const nowTime = new Date();
// // reachTime是否比nowTime多20分钟以上
// if (reachTime && (reachTime - nowTime < 20 * 60 * 1000)) {
// uni.showToast({ title: '服务期间无法取消订单', icon: 'none' });
// return;
// }
// }
uni.showModal({
title: '提示',
content: '确定要修改吗?',
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({ title: '正在处理' });
// 如果是取消订单调用merchantCancelOrder接口
if (status === 8) {
try {
const { data } = await merchantCancelOrder({
orderNumber: item.orderNumber
});
uni.hideLoading();
uni.showToast({
title: '取消成功',
icon: 'none'
});
// 重新加载订单详情
loadOrderDetail(item.orderNumber);
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.data?.msg || '取消失败',
icon: 'none'
});
}
} else if (status === 5) {
// 获取当前位置
const locationData = await util.getFuzzyLocation();
const { latitude, longitude } = locationData;
// 发送请求,包含当前位置信息
const { data } = await updateOrder({
orderNumber: item.orderNumber,
status: status,
currentLatitude: latitude,
currentLongitude: longitude
});
uni.hideLoading();
uni.showToast({
title: '操作成功',
icon: 'none'
});
// 重新加载订单详情
loadOrderDetail(item.orderNumber);
} else if (status === 3 && item.isIdle === 1) {
// 处理抢单的情况
try {
const { data } = await grabOrders({
orderNumber: item.orderNumber,
status: status
});
uni.hideLoading();
uni.showToast({
title: '抢单成功',
icon: 'none'
});
// 重新加载订单详情
loadOrderDetail(item.orderNumber);
} catch (error) {
uni.hideLoading();
if (error.data && error.data.msg) {
uni.showToast({
title: error.data.msg,
icon: 'none'
});
} else {
uni.showToast({
title: '抢单失败',
icon: 'none'
});
}
}
} else {
// 其他状态直接发送请求
const { data } = await updateOrder({
orderNumber: item.orderNumber,
status: status
});
uni.hideLoading();
uni.showToast({
title: '操作成功',
icon: 'none'
});
// 重新加载订单详情
loadOrderDetail(item.orderNumber);
}
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '操作失败',
icon: 'none'
});
console.error("更新订单状态失败:", error);
}
}
});
};
/**
* 生命周期函数--监听页面加载
*/
onLoad(async (options) => {
loadOrderDetail(options.orderId);
});
const remarks = ref("");
const orderItemDtos = ref([]);
const reduceAmount = ref("");
const transfee = ref("");
const status = ref(0);
const actualTotal = ref(0);
const userAddrDto = ref(null);
const orderNumber = ref("");
const createTime = ref("");
const total = ref(0); // 商品总额
const project = ref({}); // 项目信息
const orderInfo = ref({});
const userAddrOrder = ref({});
/**
* 加载订单数据
*/
const loadOrderDetail = async (orderNum) => {
uni.showLoading(); // 加载订单详情
const { data } = await getOrderDetail({
orderNumber: orderNum,
});
console.log(data);
orderNumber.value = orderNum;
actualTotal.value = data.actualTotal;
userAddrDto.value = data.userAddrDto;
remarks.value = data.remarks;
orderItemDtos.value = data.orderItemDtos;
createTime.value = data.createTime;
status.value = data.status;
transfee.value = data.transfee;
reduceAmount.value = data.reduceAmount;
total.value = data.total;
userAddrOrder.value = data.userAddrOrder;
project.value = data.project;
orderInfo.value = data;
uni.hideLoading();
// uni.showLoading(); // 加载订单详情
// http
// .request({
// url: "/p/myOrder/orderDetail",
// method: "GET",
// data: {
// orderNumber: orderNum,
// },
// })
// .then(({ data }) => {
// });
};
const onCancelOrder = async (e) => {
const status = orderInfo.value.status;
const ordernum = orderInfo.value.orderNumber;
let content = "";
if (status == 1) {
content = "您要取消此订单?";
} else {
content = "您要发起退款审核?";
}
uni.showModal({
title: "提示",
content: content,
confirmColor: "#3e62ad",
cancelColor: "#3e62ad",
cancelText: "否",
confirmText: "是",
async success(res) {
if (res.confirm) {
uni.showLoading({ mask: true });
try {
await userCancelOrder(ordernum);
loadOrderDetail();
} catch (error) {
console.error("取消订单失败:", error);
} finally {
uni.hideLoading();
}
}
},
});
};
const toEvaluatePage = (item) => {
const merchantId = orderInfo.value.merchantId;
const orderItemId = orderInfo.value.orderId;
const orderNumber = orderInfo.value.orderNumber;
uni.navigateTo({
url: `/pages/evaluate/evaluate?orderNum=${orderNumber}&merchantId=${merchantId}&orderItemId=${orderItemId}`,
});
};
const normalPay = async (e) => {
uni.showLoading({ mask: true });
try {
const res = await obligationPay({
orderNumber: orderInfo.value.orderNumber,
});
console.log(1);
if (res.success) {
//ifdef MP-WEIXIN
console.log(2);
console.log("data:", res.data);
weixinPay(res.data);
//endif
} else {
uni.showToast({
title: "支付失败!",
icon: "none",
});
}
} catch (error) {
console.error("支付失败:", error);
uni.showToast({
title: "支付失败!",
icon: "none",
});
} finally {
uni.hideLoading();
}
};
const weixinPay = (res) => {
console.log("weixinPay:", res);
if (!res.timestamp || !res.noncestr || !res.package || !res.sign) {
console.error("支付参数缺失", res);
uni.showToast({
title: "支付参数错误",
icon: "none",
});
return;
}
wx.requestPayment({
timeStamp: res.timestamp.toString(),
nonceStr: res.noncestr.toString(),
package: res.package,
signType: "RSA",
paySign: res.sign,
success: (e) => {
console.log("支付成功");
uni.showToast({
title: "支付成功",
icon: "none",
});
setTimeout(() => {
loadOrderDetail();
}, 1200);
},
fail: (err) => {
console.log("支付失败", err);
uni.showToast({
title: "支付失败",
icon: "none",
});
setTimeout(() => {
loadOrderDetail();
}, 1200);
},
});
};
/**
* 删除已完成||已取消的订单
*/
const delOrderList = () => {
uni.showModal({
title: "",
content: "确定要删除此订单吗?",
confirmColor: "#eb2444",
success(res) {
if (res.confirm) {
uni.showLoading();
http
.request({
url: "/p/myOrder/" + orderNumber.value,
method: "DELETE",
})
.then(() => {
uni.hideLoading();
uni.showToast({
title: res || "删除成功",
icon: "none",
});
setTimeout(() => {
uni.redirectTo({
url: "/pages/orderList/orderList",
});
}, 1000);
});
}
},
});
};
const copyVerificationCode = () => {
const code = '0203 6811 4843';
uni.setClipboardData({
data: code,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
});
};
// 添加导航方法
const openLocation = async (address) => {
try {
if (!address) {
uni.showToast({
title: '地址信息不存在',
icon: 'none'
});
return;
}
// 检查是否有经纬度信息
const latitude =address.lat;
const longitude = address.lon;
// 如果经纬度不存在,提示错误
if (!latitude || !longitude) {
uni.showToast({
title: '地址缺少定位信息,无法导航',
icon: 'none'
});
console.log('地址缺少经纬度:', address);
return;
}
// 使用目的地的经纬度进行导航
util.openLocation({
latitude: parseFloat(latitude),
longitude: parseFloat(longitude),
name: `${address.province}${address.city}${address.area}${address.addr}`,
address: `${address.province}${address.city}${address.area}${address.addr}`
});
} catch (error) {
uni.showToast({
title: '导航失败,请稍后重试',
icon: 'none'
});
console.error('导航错误:', error);
}
};
// 添加复制地址方法
const copyAddress = (address) => {
if (!address) {
uni.showToast({
title: '地址信息不存在',
icon: 'none'
});
return;
}
const fullAddress = `${address.province}${address.city}${address.area}${address.addr}`;
uni.setClipboardData({
data: fullAddress,
success: () => {
uni.showToast({
title: '地址复制成功',
icon: 'none'
});
}
});
};
// 添加复制手机号码方法
const copyMobile = (mobile) => {
if (!mobile) {
uni.showToast({
title: '手机号不存在',
icon: 'none'
});
return;
}
uni.setClipboardData({
data: mobile,
success: () => {
uni.showToast({
title: '手机号复制成功',
icon: 'none'
});
}
});
};
// 添加拨打电话功能
const callPhone = (mobile) => {
if (!mobile) {
uni.showToast({
title: '电话号码不存在',
icon: 'none'
});
return;
}
uni.makePhoneCall({
phoneNumber: mobile,
fail: () => {
uni.showToast({
title: '拨号失败',
icon: 'none'
});
}
});
};
// 添加通用复制文本函数
const copyText = (text) => {
if (!text) {
uni.showToast({
title: '无内容可复制',
icon: 'none'
});
return;
}
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
});
};
</script>
<style scoped lang="scss">
@import "./order-detail.scss";
</style>

View File

@@ -0,0 +1,409 @@
<template>
<view class="container">
<!-- 头部菜单 -->
<view class="order-tit">
<text
data-sts=""
:class="sts == '' ? 'on' : ''"
@tap="onStsTap"
>
全部
</text>
<text
data-sts="4,5,6"
:class="sts == '4,5,6' ? 'on' : ''"
@tap="onStsTap"
>
进行中
</text>
<text data-sts="3" :class="sts == 3 ? 'on' : ''" @tap="onStsTap">
已接单
</text>
<text data-sts="2" :class="sts == 2 ? 'on' : ''" @tap="onStsTap">
待接单
</text>
<text data-sts="7,9" :class="sts == '7,9' ? 'on' : ''" @tap="onStsTap">
已完成
</text>
<text data-sts="8" :class="sts == 8 ? 'on' : ''" @tap="onStsTap">
已取消
</text>
</view>
<view style="width: 100%; height: 102rpx"></view>
<view class="order-main">
<scroll-view
:scroll-y="true"
class="order-scroll"
@scrolltolower="getOrderList"
:lower-threshold="50"
:enable-flex="true"
>
<view class="order-list">
<view
v-for="(item, index) in orderList"
:key="index"
class="order-item"
@tap="toOrderDetail(item)"
>
<view class="order-state">{{ statusList[item.status] }}</view>
<view class="order-header">
<view class="order-date"
>服务时间{{
wxs.formatTime(item.reserveTime, "yyyy-MM-dd HH:mm")
}}</view
>
</view>
<view class="order-body">
<view class="order-image">
<image :src="item.project.pic"></image>
</view>
<view class="order-info">
<view class="order-name"
>{{ item.prodName }}×{{ item.productNums }}</view
>
<view class="order-tip">
<view class="order-time">
服务时长{{ item.project.duration }}分钟
</view>
<view class="order-price">
<view class="symbol"></view
><text class="order-price-value">{{
item.total
}}</text></view
>
</view>
</view>
</view>
<view class="order-footer">
<view class="nice-type" :class="{'nice-type-0': item.niceType === 2, 'nice-type-1': item.niceType === 1, 'nice-type-2': item.niceType === 0}">
{{ item.niceType ===2 ? '优质用户' : (item.niceType === 1 ? '一般用户' : '普通用户') }}
</view>
<view class="order-btns">
<view
v-if="(item.status == 5 || item.status == 6) && item.isIdle !== 1 &&
(item.reachTime && new Date() - new Date(item.reachTime) >= 20 * 60 * 1000)"
@tap.stop="changeOrder(item, 8)"
class="order-btn cancel"
>取消订单</view
>
<view
@tap.stop="changeOrder(item, 3)"
v-if="item.status === 2"
class="order-btn confirm"
>{{ item.isIdle === 1 ? '立刻抢单' : '立刻接单' }}</view
>
<view
@tap.stop="changeOrder(item, 4)"
v-if="item.status === 3"
class="order-btn confirm"
>立刻出发</view
>
<view
@tap.stop="changeOrder(item, 5)"
v-if="item.status === 4"
class="order-btn confirm"
>确认到达</view
>
<view
@tap.stop="changeOrder(item, 6)"
v-if="item.status === 5"
class="order-btn confirm"
>开始服务</view
>
<view
@tap.stop="changeOrder(item, 9)"
v-if="item.status === 6"
class="order-btn confirm"
>完成服务</view
>
</view>
</view>
</view>
<view v-if="ajaxFLag" class="loadmore">
<up-loading-icon
text="加载中"
size="24rpx"
textSize="24rpx"
></up-loading-icon>
</view>
<view v-if="total != -1 && total <= orderList.length" class="no-more">
已经到底了
</view>
</view>
</scroll-view>
</view>
<!-- end 头部菜单 -->
<!-- <tabbar></tabbar>-->
</view>
</template>
<script setup>
import { getMerchantOrderList, updateOrder, grabOrders, merchantCancelOrder } from "@/api/order/order";
import number from "@/wxs/number";
import util from "@/utils/util";
const orderList = ref([]);
const wxs = number();
const sts = ref("");
const pageForm = ref({
size: 10,
current: 1,
});
const total = ref(-1);
const ajaxFLag = ref(false);
// 1:待付款 2:待服务3已接单 4:已出发 5:已到达6:服务中 7:已完成8取消订单
const statusList = ref({
1: "待付款",
2: "待接单",
3: "已接单",
4: "已出发",
5: "已到达",
6: "服务中",
7: "已完成",
8: "已取消",
9: "服务完成",
10: "退款中",
11: "退款成功",
12: "退款失败",
});
const clear = () => {
pageForm.value.current = 1;
pageForm.value.size = 10;
total.value = -1;
ajaxFLag.value = false;
orderList.value = [];
};
const onStsTap = (e) => {
const status = e.currentTarget.dataset.sts;
sts.value = status;
pageForm.value.current = 1;
orderList.value = [];
total.value = -1;
getOrderList();
};
const changeOrder = async (item, status) => {
if (ajaxFLag.value) {
return;
}
ajaxFLag.value = true;
uni.showModal({
title: "提示",
content: "确定要修改吗?",
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: "正在处理",
});
// 如果是取消订单调用merchantCancelOrder接口
if (status === 8) {
try {
const { data } = await merchantCancelOrder({
orderNumber: item.orderNumber,
});
uni.showToast({
title: "取消成功",
icon: "none",
});
sts.value = "8";
clear();
getOrderList();
} catch (cancelError) {
uni.showToast({
title: cancelError.data?.msg || "取消失败",
icon: "none",
duration: 2000
});
}
} else if (status === 5) {
// 获取当前位置
const locationData = await util.getFuzzyLocation();
const { latitude, longitude } = locationData;
// 发送请求,包含当前位置信息
try {
const { data } = await updateOrder({
orderNumber: item.orderNumber,
status: status,
currentLatitude: latitude,
currentLongitude: longitude
});
uni.showToast({
title: "操作成功",
icon: "none",
});
clear();
getOrderList();
} catch (updateError) {
uni.hideLoading();
if (updateError.code==500) {
uni.showModal({
title: updateError.data
});
}
}
} else {
// 判断是否为接单操作且订单为空闲状态
if (status === 3 && item.isIdle === 1) {
try {
const { data } = await grabOrders({
orderNumber: item.orderNumber,
status: status,
});
uni.showModal({
title: "抢单成功",
content: '',
});
sts.value = "3";
clear();
getOrderList();
} catch (updateError) {
if (updateError.code==409) {
uni.showModal({
title:updateError.data,
content: ''
});
clear();
getOrderList();
}
}
} else {
// 其他状态直接发送请求
try {
const { data } = await updateOrder({
orderNumber: item.orderNumber,
status: status,
});
uni.showToast({
title: "操作成功",
icon: "none",
});
if (status === 3) {
sts.value = "3";
} else if (status === 4 || status === 5 || status === 6) {
sts.value = "4,5,6";
} else if (status === 9) {
sts.value = "7,9";
} else if (status === 8) {
sts.value = "8";
}
clear();
getOrderList();
} catch (updateError) {
if (updateError.data && updateError.data.msg) {
uni.showToast({
title: updateError.data.msg || "获取位置失败,无法确认到达",
icon: 'none',
duration: 2000
});
}
}
}
}
}
ajaxFLag.value = false;
} catch (error) {
uni.hideLoading();
ajaxFLag.value = false;
} finally {
uni.hideLoading();
}
},
fail: () => {
ajaxFLag.value = false;
}
});
};
const getOrderList = async () => {
if (ajaxFLag.value) {
return Promise.resolve();
}
if (total.value != -1 && orderList.value.length >= total.value) {
return Promise.resolve();
}
ajaxFLag.value = true;
try {
const { data } = await getMerchantOrderList({
...pageForm.value,
status: sts.value,
});
ajaxFLag.value = false;
total.value = data.total;
if (data.total > orderList.value.length) {
pageForm.value.current++;
orderList.value = [...orderList.value, ...data.records];
}
return Promise.resolve();
} catch (error) {
ajaxFLag.value = false;
return Promise.reject(error);
}
};
const toOrderDetail = (item) => {
const orderId = item.orderNumber
uni.navigateTo({
url: `/pages/order-detail/order-detail?orderId=${orderId}`
})
}
onShow(() => {
const status = uni.getStorageSync('orderListStatus')
if (status) {
sts.value = status
uni.removeStorageSync('orderListStatus')
}
clear()
getOrderList()
})
// 添加页面级下拉刷新
onPullDownRefresh(() => {
clear()
getOrderList().then(() => {
uni.stopPullDownRefresh()
})
})
</script>
<style lang="scss" scoped>
@import "./order.list.scss";
.order-scroll {
position: relative;
height: calc(100vh - 102rpx);
}
.refresher-text {
width: 100%;
height: 60rpx;
line-height: 60rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
</style>

View File

@@ -0,0 +1,227 @@
.container {
position: absolute;
width: 100%;
height: 100%;
background-color: #f4f4f4;
color: #333;
}
.order-tit {
position: fixed;
//top: 0;
display: flex;
justify-content: space-around;
z-index: 999;
width: 100%;
height: 100rpx;
line-height: 100rpx;
background-color: #fff;
border-bottom: 2rpx solid #f4f4f4;
text {
display: block;
font-size: 28rpx;
color: #999;
width: 100rpx;
text-align: center;
}
text.on {
color: #f96012;
}
}
.order-main {
height: calc(100% - 100rpx - 10rpx);
width: 100%;
box-sizing: border-box;
padding: 24rpx;
padding-bottom: 0;
display: flex;
flex-direction: column;
.order-scroll {
flex: 1;
height: 100%;
width: 100%;
::-webkit-scrollbar {
display: none;
}
.order-list {
width: 100%;
padding-bottom: 24rpx;
.order-item {
position: relative;
width: 100%;
box-sizing: border-box;
background-color: #ffffff;
padding: 24rpx;
border-radius: 12rpx 30rpx 12rpx 12rpx;
-webkit-border-radius: 12rpx 30rpx 12rpx 12rpx;
-moz-border-radius: 12rpx 30rpx 12rpx 12rpx;
-ms-border-radius: 12rpx 30rpx 12rpx 12rpx;
-o-border-radius: 12rpx 30rpx 12rpx 12rpx;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.order-state {
position: absolute;
top: 0;
right: 0;
border-top: 8rpx solid #f4f4f4;
border-right: 8rpx solid #f4f4f4;
background: #f4f6fa;
color: #0000008c;
font-size: 24rpx;
width: 160rpx;
height: 48rpx;
text-align: center;
line-height: 48rpx;
border-radius: 0rpx 30rpx 0 60rpx;
-webkit-border-radius: 0rpx 30rpx 0 60rpx;
-moz-border-radius: 0rpx 30rpx 0 60rpx;
-ms-border-radius: 0rpx 30rpx 0 60rpx;
-o-border-radius: 0rpx 30rpx 0 60rpx;
}
.order-header {
width: 100%;
.order-date {
color: #0000008c;
font-size: 24rpx;
line-height: 24rpx; /* 100% */
}
}
.order-body {
margin-top: 48rpx;
width: 100%;
display: flex;
.order-image {
width: 128rpx;
height: 128rpx;
margin-right: 24rpx;
image {
width: 100%;
height: 100%;
}
}
.order-info {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
justify-content: center;
.order-name {
color: #000000f2;
font-size: 28rpx;
line-height: 28rpx; /* 100% */
}
.order-tip {
margin-top: 8rpx;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.order-time {
color: #0000008c;
font-size: 24rpx;
line-height: 24rpx; /* 100% */
}
.order-price {
display: flex;
align-items: flex-end;
color: #f96012;
.symbol {
font-size: 24rpx;
font-weight: 500;
line-height: 24rpx; /* 100% */
}
.order-price-value {
font-size: 40rpx;
font-weight: 500;
line-height: 40rpx;
}
}
}
}
}
.order-footer {
margin-top: 24rpx;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.nice-type {
padding: 10rpx 30rpx;
border-radius: 10rpx;
font-size: 24rpx;
&.nice-type-0 {
background-color: #e8f5e9;
color: #4caf50;
}
&.nice-type-1 {
background-color: #fff3e0;
color: #ff9800;
}
&.nice-type-2 {
background-color: #ffebee;
color: #f44336;
}
}
.order-btns {
display: flex;
.order-btn {
color: #000;
font-size: 24rpx;
font-weight: 400;
height: 54rpx;
line-height: 54rpx;
padding: 0 24rpx;
border: 2rpx solid #ededed;
border-radius: 42rpx;
-webkit-border-radius: 42rpx;
-moz-border-radius: 42rpx;
-ms-border-radius: 42rpx;
-o-border-radius: 42rpx;
margin-right: 16rpx;
&:last-child {
margin-right: 0;
}
&.cancel {
color: #000;
background: #fff;
border-color: #ededed;
&.disable {
//透明度
background: #00000033;
color: #00000066;
opacity: 0.5;
}
}
&.confirm {
border-color: #ffb28b;
background: #ffe4d6;
color: #f96012;
}
}
}
}
}
}
}
}
.loadmore {
width: 100%;
display: flex;
justify-content: center;
margin-bottom: 10rpx;
}
.no-more {
width: 100%;
font-size: 24rpx;
color: #999999;
text-align: center;
padding-bottom: 10rpx;
}

View File

@@ -0,0 +1,124 @@
.con {
background: #fff;
height: 100%;
margin-top: 50px;
}
image {
display: block;
width: 150rpx;
height: 150rpx;
margin: auto;
border-radius: 50%;
width: 150rpx;
height: 150rpx;
margin-bottom: 8%;
}
.login-form {
width: 90%;
margin: 0 auto;
margin-bottom: 20%;
}
.authorized-btn {
width: 90%;
margin: 0 auto;
text-align: center;
background-color: #0ab906;
border: 1rpx solid #0ab906;
color: #fff;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
margin-top: 80rpx;
border: 1rpx solid #0ab906;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
margin-top: 80rpx;
}
.to-idx-btn {
width: 90%;
margin: 0 auto;
text-align: center;
background-color: #eeeeee;
color: #333;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
margin-top: 30rpx;
border-radius: 6rpx;
font-size: 26rpx;
padding: 8rpx;
margin-top: 30rpx;
}
.form-title {
width: 100%;
margin-bottom: 50rpx;
font-size: 32rpx;
text-align: center;
color: #00a0e9;
margin-bottom: 50rpx;
font-size: 32rpx;
}
.item {
display: block;
margin-bottom: 30rpx;
margin-bottom: 30rpx;
}
.account {
display: flex;
background: #f8f8f8;
padding: 15rpx;
box-sizing: border-box;
font-size: 26rpx;
align-items: center;
input {
padding-left: 20rpx;
width: 75%;
padding-left: 20rpx;
}
}
button {
&::after {
border: 0 !important;
}
}
.operate {
display: flex;
justify-content: space-between;
align-items: center;
}
.to-register {
font-size: 28rpx;
color: #00AAFF;
font-size: 28rpx;
}
.error {
.error-text {
display: block;
width: 100%;
font-size: 28rpx;
color: #e43130;
text-align: left;
margin-top: 10rpx;
font-size: 28rpx;
margin-top: 10rpx;
.warning-icon {
display: inline-block;
color: #fff;
width: 26rpx;
height: 26rpx;
line-height: 26rpx;
background: #e43130;
border-radius: 50%;
text-align: center;
margin-right: 12rpx;
font-size: 22rpx;
width: 26rpx;
height: 26rpx;
line-height: 26rpx;
margin-right: 12rpx;
font-size: 22rpx;
}
}
}

View File

@@ -0,0 +1,166 @@
<template>
<view class="register">
<view class="con">
<image src="@/static/logo.png" />
<!-- 登录 -->
<view class="login-form">
<view :class="['item',errorTips==1? 'error':'']">
<view class="account">
<text class="input-item">
账号
</text>
<input
type="text"
data-type="account"
placeholder-class="inp-palcehoder"
placeholder="请输入账号名称"
@input="getInputVal"
>
</view>
<view
v-if="errorTips==1"
class="error-text"
>
<text class="warning-icon">
!
</text>
请输入账号
</view>
</view>
<view :class="['item',errorTips==2? 'error':'']">
<view class="account">
<text class="input-item">
密码
</text>
<input
type="password"
data-type="password"
placeholder-class="inp-palcehoder"
placeholder="请输入密码"
@input="getInputVal"
>
</view>
<view
v-if="errorTips==2"
class="error-text"
>
<text class="warning-icon">
!
</text>
请输入密码
</view>
</view>
<view class="operate">
<view
class="to-register"
@tap="toLogin"
>
已有账号
<text>去登录></text>
</view>
</view>
</view>
<view>
<button
class="authorized-btn"
@tap="toRegister"
>
注册
</button>
<button
class="to-idx-btn"
@tap="toIndex"
>
回到首页
</button>
</view>
</view>
</view>
</template>
<script setup>
import { encrypt } from '@/utils/crypto.js'
/**
* 生命周期函数--监听页面显示
*/
onShow(() => {
// 头部导航标题
uni.setNavigationBarTitle({
title: '用户注册'
})
})
const principal = ref('') // 账号
const credentials = ref('') // 密码
/**
* 输入框的值
*/
const getInputVal = (e) => {
const type = e.currentTarget.dataset.type
if (type == 'account') {
principal.value = e.detail.value
} else if (type == 'password') {
credentials.value = e.detail.value
}
}
const errorTips = ref(0) // 输入错误提示: 1账号输入 2密码输入
/**
* 注册
*/
const toRegister = () => {
if (principal.value.length == 0) {
errorTips.value = 1
} else if (credentials.value.length == 0) {
errorTips.value = 2
} else {
errorTips.value = 0
uni.showLoading()
http.request({
url: '/user/register',
method: 'post',
data: {
userName: principal.value,
passWord: encrypt(credentials.value)
}
})
.then(() => {
uni.hideLoading()
uni.showToast({
title: '注册成功,请登录',
icon: 'none',
duration: 1500
})
setTimeout(() => {
uni.navigateTo({
url: '/pages/accountLogin/accountLogin'
})
}, 1800)
})
}
}
/**
* 去登陆
*/
const toLogin = () => {
uni.navigateTo({
url: '/pages/accountLogin/accountLogin'
})
}
/**
* 回到首页
*/
const toIndex = () => {
uni.switchTab({
url: '/pages/index/index'
})
}
</script>
<style lang="scss" scoped>
@import "./register.scss";
</style>

View File

@@ -0,0 +1,11 @@
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
const userInfo = userStore.userInfo;
const setSetting = async (params) => {
const success = await userStore.setUserInfo(params);
return success;
};
export { setSetting, userInfo };

View File

@@ -0,0 +1,56 @@
<template>
<view class="container modify">
<view class="modify-mian">
<up-textarea
color="#ffffff"
v-model="brief"
autoHeight
placeholder="请输入内容"
maxlength="200"
></up-textarea>
<view class="modify-btn">
<button @click="updateBrief" class="confrim-btn">保存</button>
</view>
</view>
</view>
</template>
<script setup>
import { setSetting } from "../hooks/useSetting";
import useUserStore from "@/stores/user/useUserStore";
const userStore = useUserStore();
const brief = ref("");
const updateBrief = async () => {
if (!brief.value) {
uni.showToast({
title: "请输入您的简介",
icon: "none",
});
return;
}
uni.showLoading({
title: "修改中",
mask: true,
});
await setSetting({
brief: brief.value,
});
uni.showToast({
title: "修改成功",
});
uni.hideLoading();
uni.navigateBack();
};
onLoad(() => {
brief.value = userStore.userInfo.brief;
});
</script>
<style lang="scss" scoped>
@import "../setting.scss";
</style>

View File

@@ -0,0 +1,166 @@
<template>
<view class="container modify">
<view class="modify-mian">
<!-- <view class="album-list">
<view
class="album-item"
v-for="(item, index) in albumList"
:key="index"
@tap="clickAlbum(item, index)"
>
<image :src="item.pic" mode="aspectFill"></image>
</view>
<view class="album-item album-add">
<view class="album-add-icon">
<up-icon name="plus" color="#fff" size="28"></up-icon>
</view>
</view>
</view> -->
<uni-file-picker
v-model="albumList"
:limit="99"
fileMediatype="image"
mode="grid"
@select="selectUpload"
@progress="progressUpload"
@success="successUpload"
@fail="failUpload"
@delete="deleteImg"
/>
<view @tap="confirmAlbum" class="confirm-btn">保存相册</view>
</view>
</view>
</template>
<script setup>
import uitl from "@/utils/util.js";
import { getImages } from "@/api/user/user-info";
import { setSetting } from "../hooks/useSetting";
const picDomain = import.meta.env.VITE_APP_RESOURCES_URL;
const albumList = ref([]);
const flieForm = ref([]);
onLoad(async () => {
uni.showLoading({
title: "加载中...",
});
const res = await getImages();
uni.hideLoading();
let albumListC = [];
let flieFormC = [];
if (res.success) {
const images = res.data?.split(",") || [];
images.forEach((item) => {
let uuid = uitl.randomString(10);
console.log("uuid:", uuid);
albumListC.push({
name: item,
extname: item.split(".").pop(),
url: item,
uuid: uuid,
});
flieFormC.push({
fileName: item,
fileUrl: item,
uuid: uuid,
});
});
console.log("albumList:", albumList.value);
console.log("flieForm:", flieForm.value);
albumList.value = albumListC;
flieForm.value = flieFormC;
}
// albumList.value = res.data;
});
const confirmAlbum = async () => {
console.log("flieForm:", flieForm.value);
const images = flieForm.value.map((item) => item.fileUrl).join(",");
const success = await setSetting({
images: images,
});
if (success) {
uni.showToast({
title: "保存成功",
icon: "none",
});
uni.navigateBack();
}
};
// 预览图片
const previewImage = (photoImg) => {
console.log("photoImg", photoImg);
let imgsArray = [];
imgsArray[0] = photoImg;
console.log("imgsArray[0]", imgsArray[0]);
uni.previewImage({
current: 0,
urls: imgsArray,
});
};
const selectUpload = async (e) => {
console.log("select:", e);
const uploadList = e.tempFiles.map((item) => {
return {
url: item.url,
uuid: item.uuid,
};
});
uni.showLoading({
title: "上传中...",
});
const data = await util.wxUploadFile(uploadList, "url", "uuid");
uni.hideLoading();
flieForm.value.push(...data);
console.log("flieForm:", flieForm.value);
console.log("data:", data);
};
const successUpload = (e) => {
console.log("success:", e);
};
const failUpload = (e) => {
console.log("fail:", e);
};
const progressUpload = (e) => {
console.log("progress:", e);
};
const deleteImg = (e) => {
console.log("delete:", e);
const uuid = e.tempFile.uuid;
flieForm.value = flieForm.value.filter((item) => item.uuid !== uuid);
};
const previewImages = (indexImg, photoImg) => {
let photoList = photoImg.map((item) => {
return item.pic;
});
uni.previewImage({
current: indexImg,
urls: photoList,
});
};
const clickAlbum = (item, index) => {
previewImages(index, albumList.value);
};
</script>
<style lang="scss" scoped>
@import "../setting.scss";
</style>

View File

@@ -0,0 +1,43 @@
<template>
<view class="container locantion modify">
<view
:class="{
'have-locantion': locantion,
}"
class="locantion-mian"
>
<view v-if="locantion" class="address-box">
<view class="address-info">
<view class="address-value">{{ locantion.address }}</view>
<view class="house">{{ locantion.house }}</view>
</view>
<view class="address-btn"> 修改地址 </view>
</view>
<view class="locantion-box">
<view v-if="!locantion" class="locantion-btn">选择居住地址</view>
<view class="locantion-line">
<view class="locantion-label">详细地址</view>
<view class="locantion-input">
<input type="text" placeholder="请输入详细地址" v-model="address" />
</view>
</view>
<view class="modify-btn">
<button class="confrim-btn">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
const address = ref("");
const locantion = {
house: "福建省福州市台江区江滨大道366号(金融街万达广场)",
address: "福州天元国际威斯订酒店",
};
</script>
<style lang="scss" scoped>
@import "../setting.scss";
</style>

View File

@@ -0,0 +1,60 @@
<template>
<view class="container modify">
<view class="modify-mian">
<view class="modify-label"> 我的名字 </view>
<view class="modify-input">
<input
type="text"
v-model="name"
placeholder="请输入您的名字"
placeholder-style="color: #999999;"
/>
</view>
<view class="modify-btn">
<button @tap="updateNickname" class="confrim-btn">保存</button>
</view>
</view>
</view>
</template>
<script setup>
import useUserStore from "@/stores/user/useUserStore";
import { setSetting } from "../hooks/useSetting";
const userStore = useUserStore();
const name = ref("");
const updateNickname = async () => {
if (!name.value) {
uni.showToast({
title: "请输入您的名字",
icon: "none",
});
return;
}
uni.showLoading({
title: "修改中",
mask: true,
});
const success = await setSetting({
nickName: name.value,
});
if (success) {
uni.showToast({
title: "修改成功",
});
}
uni.hideLoading();
uni.navigateBack();
};
onLoad(() => {
name.value = userStore.userInfo.nickName;
});
</script>
<style lang="scss" scoped>
@import "../setting.scss";
</style>

View File

@@ -0,0 +1,31 @@
<template>
<view class="container modify">
<view class="modify-mian">
<view class="modify-label"> 我的名字 </view>
<view class="modify-input">
<input
type="number"
v-model="value"
placeholder="请输入您的电话"
placeholder-style="color: #999999;"
/>
</view>
<view class="modify-btn">
<button class="confrim-btn">保存</button>
</view>
</view>
</view>
</template>
<script setup>
const value = ref("");
const isPhoneNumber = (phoneNumber) => {
return /^1[3456789]\d{9}$/.test(phoneNumber);
};
</script>
<style lang="scss" scoped>
@import "../setting.scss";
</style>

View File

@@ -0,0 +1,339 @@
.container {
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(180deg, #fadfc4 0%, #fafafa 36.92%);
&.modify {
background: #161922;
}
&.locantion {
overflow: hidden;
}
}
.setting-main {
position: relative;
background-color: #000920bb;
width: 100%;
height: calc(100% - 200rpx);
box-sizing: border-box;
padding: 128rpx 32rpx 24rpx 32rpx;
display: flex;
flex-direction: column;
border-radius: 24rpx 24rpx 0 0;
-webkit-border-radius: 24rpx 24rpx 0 0;
-moz-border-radius: 24rpx 24rpx 0 0;
-ms-border-radius: 24rpx 24rpx 0 0;
-o-border-radius: 24rpx 24rpx 0 0;
.avatar {
--r: 212rpx;
position: absolute;
top: calc(var(--r) / -2);
left: 50%;
transform: translateX(-50%);
width: var(--r);
height: var(--r);
border-radius: 50%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
overflow: hidden;
padding: 0;
background: none;
&::after {
border: none;
}
.avatar-image {
width: 100%;
height: 100%;
}
.avatar-tip {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.camera-icon {
width: 64rpx;
height: 64rpx;
margin-bottom: 6rpx;
}
.tip-text {
margin-top: 6rpx;
color: #fff;
font-size: 28rpx;
}
}
}
.scroll-view {
flex: 1;
height: 0;
width: 100%;
::-webkit-scrollbar {
display: none;
}
.setting-list {
width: 100%;
.setting-item {
width: 100%;
display: flex;
color: #ffffffbb;
font-size: 32rpx;
align-items: center;
height: 72rpx;
.setting-label {
width: 180rpx;
}
.setting-value {
flex: 1;
width: 0;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.is-null {
color: #ffffffbb;
}
}
.setting-icon {
margin-left: 20rpx;
}
}
.login-out {
margin-top: 64rpx;
width: 100%;
background: #e60909;
color: #fff;
font-size: 32rpx;
height: 64rpx;
line-height: 64rpx;
text-align: center;
border-radius: 32rpx;
-webkit-border-radius: 32rpx;
-moz-border-radius: 32rpx;
-ms-border-radius: 32rpx;
-o-border-radius: 32rpx;
&.disabled {
background: #858585;
color: #eee;
}
}
}
}
}
.tips {
margin-top: 20px;
font-size: 12px;
color: #fff;
}
// 修改名字
.modify-mian {
width: 100%;
box-sizing: border-box;
padding: 24rpx;
.modify-label {
font-size: 24rpx;
line-height: 24rpx;
color: #ffffffaa;
margin-bottom: 16rpx;
}
.confirm-btn {
width: 100%;
background: #e60909;
color: #fff;
height: 64rpx;
text-align: center;
line-height: 64rpx;
margin-top: 40rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
}
.modify-input {
input {
font-size: 28rpx;
line-height: 28rpx;
color: #ffffff;
caret-color: #ffee01;
}
}
.modify-btn {
position: absolute;
bottom: 24rpx;
width: 100%;
left: 0;
box-sizing: border-box;
padding: 24rpx;
.confrim-btn {
width: 100%;
font-size: 32rpx;
background-color: #e60909;
color: #fff;
}
}
}
// 选择地址
.locantion-mian {
position: absolute;
top: 32rpx;
left: 24rpx;
width: calc(100% - 48rpx);
height: calc(100% - 32rpx);
display: flex;
flex-direction: column;
&.have-locantion {
top: 300rpx;
}
.address-box {
width: 100%;
background: #fff;
padding: 24rpx;
border-radius: 16rpx;
-webkit-border-radius: 16rpx;
-moz-border-radius: 16rpx;
-ms-border-radius: 16rpx;
-o-border-radius: 16rpx;
box-sizing: border-box;
display: flex;
align-items: center;
margin-bottom: 24rpx;
.address-info {
flex: 1;
width: 0;
.address-value {
font-size: 32rpx;
line-height: 32rpx;
color: #000;
margin-bottom: 12rpx;
}
.house {
font-size: 28rpx;
line-height: 28rpx;
color: #000000aa;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.address-btn {
font-size: 28rpx;
border: 2rpx solid #f48520;
color: #f48520;
line-height: 52rpx;
padding: 0 12rpx;
border-radius: 8rpx;
-webkit-border-radius: 8rpx;
-moz-border-radius: 8rpx;
-ms-border-radius: 8rpx;
-o-border-radius: 8rpx;
margin-left: 24rpx;
}
}
.locantion-box {
flex: 1;
height: 0;
width: 100%;
background: #fff;
border-radius: 16rpx 16rpx 0 0;
-webkit-border-radius: 16rpx 16rpx 0 0;
-moz-border-radius: 16rpx 16rpx 0 0;
-ms-border-radius: 16rpx 16rpx 0 0;
-o-border-radius: 16rpx 16rpx 0 0;
box-sizing: border-box;
padding: 24rpx;
.locantion-btn {
width: 100%;
box-sizing: border-box;
color: #f48520;
text-align: center;
font-size: 32rpx;
line-height: 72rpx;
border: 2rpx solid #f48520;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
}
.locantion-line {
width: 100%;
display: flex;
padding: 32rpx 0;
.locantion-label {
font-size: 28rpx;
margin-right: 24rpx;
}
.locantion-input {
flex: 1;
width: 0;
input {
font-size: 28rpx;
}
}
}
}
.modify-btn {
margin-top: 24rpx;
width: 100%;
box-sizing: border-box;
.confrim-btn {
width: 100%;
font-size: 32rpx;
background-color: #fee541;
color: #000;
}
}
}
.album-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.album-item {
width: 218rpx;
height: 218rpx;
margin-right: 24rpx;
margin-bottom: 24rpx;
&:nth-child(3n) {
margin-right: 0;
}
image {
width: 100%;
height: 100%;
}
&.album-add {
.album-add-icon {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #2c2c2c;
}
}
}
}

View File

@@ -0,0 +1,348 @@
<template>
<view class="container">
<view style="height: 200rpx; width: 100%"></view>
<view class="setting-main">
<button
open-type="chooseAvatar"
@chooseavatar="handleUpdateAvatar"
class="avatar"
:disabled="userInfo.flagStatus === 1"
>
<image
class="avatar-image"
:src="
userInfo.pic ??
'https://qiniu.pxdj.tashowz.com/2025/05/3eaec196d9c5478c972324136123dfdc.png'
"
></image>
<view class="avatar-tip" v-if="userInfo.flagStatus !== 1 ">
<image
class="camera-icon"
src="@/static/images/icon/setting/camera.png"
>
</image>
<view class="tip-text">更换头像</view>
</view>
</button>
<view class="scroll-view" scroll-y="true">
<view class="setting-list">
<view class="setting-item">
<view class="setting-label">名字</view>
<view
class="setting-value"
@tap="toSettingDetail('setting-name')"
>{{ userInfo.nickName ?? "设置名字" }}</view
>
<up-icon
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="setting-item">
<view class="setting-label">电话</view>
<view
:class="{ 'is-null': !userInfo.userMobile }"
class="setting-value"
>{{ userInfo.userMobile ?? "设置电话" }}</view
>
<up-icon
v-if="!userInfo.userMobile"
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="setting-item">
<view class="setting-label">简介</view>
<view
@tap="toSettingDetail('brief-introduction')"
:class="{ 'is-null': !userInfo.brief }"
class="setting-value"
>{{ userInfo.brief ?? "介绍自己" }}</view
>
<up-icon
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="setting-item">
<view class="setting-label">性别 </view>
<view
v-if="!userInfo.sex"
@tap="show = true"
class="setting-value is-null"
>选择性别</view
>
<view v-else @tap="show = true" class="setting-value">{{
userInfo.sex == "M" ? "男" : "女"
}}</view>
<up-icon
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="setting-item">
<view class="setting-label">居住地</view>
<view @tap="toEditAddress" class="setting-value is-null">{{
add ?? "设置居住地"
}}</view>
<up-icon
@tap="toEditAddress"
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="setting-item">
<view class="setting-label">相册</view>
<view @tap="toMyAlbum" class="setting-value is-null">点击查看</view>
<up-icon
@tap="toMyAlbum"
class="setting-icon"
size="24rpx"
name="arrow-right"
color="#ffffffbb"
></up-icon>
</view>
<view class="tips" v-if="userInfo.flagStatus === 3" >审核失败内容不符合规范请重新提交审核</view>
<view @tap="handleActiveAccount" v-if="userInfo.flagStatus === 0" class="login-out">激活账号</view>
<view @tap="handleActiveStatus" v-if="userInfo.flagStatus == 1" class="login-out disabled">审核中</view>
<view @tap="handleActiveAccount" v-if="userInfo.flagStatus === 4 || userInfo.flagStatus === 3" class="login-out">审核</view>
<view @tap="handleLoginOut" class="login-out">退出登录</view>
</view>
</view>
</view>
<up-picker
closeOnClickOverlay
v-model:show="show"
:columns="sexList"
keyName="label"
@confirm="setSex"
></up-picker>
</view>
</template>
<script setup>
import useUserStore from "@/stores/user/useUserStore";
import { setSetting } from "./hooks/useSetting";
import { onShow } from '@dcloudio/uni-app';
const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo);
const show = ref(false);
const add = computed(() => {
if (
userStore.userInfo &&
userStore.userInfo.province &&
userStore.userInfo.city &&
userStore.userInfo.area
) {
return `${userStore.userInfo.province} ${userStore.userInfo.city} ${userStore.userInfo.area}`;
}
return null;
});
const sexList = ref([
[
{
label: "男",
value: "M",
},
{
label: "女",
value: "F",
},
],
]);
const toMyAlbum = () => {
if (disabledStatus()) return
uni.navigateTo({
url: "/pages/setting/setting-detail/my-album",
});
};
const toEditAddress = () => {
if (disabledStatus()) return
uni.navigateTo({
url: "/pages/editAddress/editAddress",
});
};
const handleLoginOut = () => {
uni.showModal({
title: "提示",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
userStore.doLogout();
uni.reLaunch({
url: "/pages/index/index",
});
}
},
});
};
const handleActiveStatus = () => {
uni.showToast({
title:"审核中...",
icon:"none"
})
}
const disabledStatus = () => {
if(userInfo.value.flagStatus === 1){
uni.showToast({
title:"审核中,暂时无法修改",
icon:"none"
})
return true
}
return false
}
// 处理头像更新
const handleUpdateAvatar = async (e) => {
if (disabledStatus()) return
// 检查登录状态
// 显示操作菜单
// #ifdef MP-WEIXIN
const { avatarUrl } = e.detail;
await userStore.uploadAvatar(avatarUrl);
uni.showToast({
title: "修改成功",
icon: "none",
});
// initUserInfo();
// #endif
// #ifndef MP-WEIXIN
uni.showActionSheet({
itemList: ["从相册选择", "拍照"],
success: (res) => {
const sourceType = res.tapIndex === 0 ? ["album"] : ["camera"];
// 调用系统选择图片
uni.chooseImage({
count: 1, // 默认9
sizeType: ["original", "compressed"], // 可以指定是原图还是压缩图,默认二者都有
sourceType: sourceType, // 从相册选择或拍照
success: (res) => {
const tempFilePaths = res.tempFilePaths;
uploadFile(tempFilePaths[0]);
},
});
},
});
// #endif
};
const setSex = async (e) => {
if (disabledStatus()) return
const sex = e.value[0].value;
uni.showLoading({
title: "修改中",
mask: true,
});
const success = await setSetting({ sex });
if (success) {
uni.hideLoading();
uni.showToast({
title: "修改成功",
icon: "none",
});
}
};
const toSettingDetail = (path) => {
if (disabledStatus()) return
uni.navigateTo({
url: `/pages/setting/setting-detail/${path}`,
});
};
const handleActiveAccount = async () => {
// return
const params = userInfo.value;
if(!params.pic){
uni.showToast({
title: "请先设置头像",
icon: "none",
});
return
}
if(!params.addr){
uni.showToast({
title: "请先设置居住地",
icon: "none",
});
return
}
if(!params.images){
uni.showToast({
title: "请先设置相册",
icon: "none",
});
return
}
if(!params.brief){
uni.showToast({
title: "请填写简介",
icon: "none",
});
return
}
await userStore.onActivation({
...userInfo.value,
avatarUrl:userInfo.value.pic,
})
// uni.
}
// 页面加载时检查登录状态
onShow(() => {
// 只有在未登录时才显示提示框
if (!userStore.isLogin) {
uni.showModal({
title: "提示",
content: "请先进行登录!",
cancelText: "取消",
confirmText: "确定",
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: "/pages/login/login",
});
} else {
uni.switchTab({
url: "/pages/index/index",
});
}
}
})
}
})
</script>
<style lang="scss" scoped>
@import "./setting.scss";
</style>

View File

@@ -0,0 +1,906 @@
.user-menu-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
}
.user-menu {
width: 80%;
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 20rpx;
border-bottom: 1px solid #f5f5f5;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item text {
margin-left: 20rpx;
font-size: 28rpx;
color: #333;
}
.logout-item {
margin-top: 20rpx;
}
.logout-item text {
color: #ff5722;
}
/* 其他样式保持不变 */
.container {
height: 100vh;
background: linear-gradient(180deg, #fadfc4 0%, #fafafa 36.92%);
}
.top-background {
left: 50%;
transform: translateX(-50%);
border-radius: 282px;
width: 282px;
position: absolute;
top: 0;
z-index: 0;
height: 269px;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.8);
filter: blur(32.5px);
pointer-events: none;
}
/* 顶部渐变背景 */
.top-gradient {
padding: 36rpx;
padding-bottom: 20rpx;
position: relative;
z-index: 0;
}
/* 用户信息部分 */
.user-info-section {
display: flex;
align-items: center;
position: relative;
z-index: 1;
}
.user-avatar {
position: relative;
cursor: pointer;
}
.user-avatar::after {
content: "";
position: absolute;
right: 0;
bottom: 0;
width: 30rpx;
height: 30rpx;
background-color: #f5f5f5;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: 2rpx solid #fff;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
}
.user-avatar image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 2rpx solid #fff;
}
.user-avatar image:hover {
opacity: 0.9;
transition: opacity 0.2s;
}
.user-details {
flex: 1;
margin-left: 20rpx;
}
.user-id {
color: rgba(0, 0, 0, 0.95);
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 14px;
}
.vip-tag {
text-align: center;
display: inline-block;
width: 48px;
background: #f3f4e9;
height: 16px;
color: #67c23a;
font-size: 24rpx;
padding: 4rpx 10rpx;
font-weight: 400;
font-style: normal;
line-height: 16px;
font-size: 10px;
border-radius: 20px;
margin-top: 10rpx;
padding-top: 1px;
}
.action-buttons {
display: flex;
}
.service-btn,
.settings-btn {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 20rpx;
}
.service-btn image,
.settings-btn image {
width: 64rpx;
height: 64rpx;
}
.service-btn text,
.settings-btn text {
font-size: 24rpx;
color: #666;
}
/* 主要内容区域 */
.main-content {
padding: 0 20rpx;
margin-top: -30rpx;
position: relative;
z-index: 2;
}
/* 订单管理部分 */
.orders-section,
.points-section,
.channel-section {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
/* 余额条 */
.balance-bar-container {
width: 93%;
margin: 0 auto;
}
.balance-bar-content {
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
padding: 0 30rpx;
color: white;
}
.balance-left {
display: flex;
color: rgba(250, 219, 188, 0.95);
font-size: 24rpx;
font-style: normal;
font-weight: 400;
flex: 1;
overflow: hidden;
}
.balance-info {
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
}
.balance-top {
display: flex;
align-items: center;
margin-bottom: 5rpx;
flex-wrap: nowrap;
overflow: hidden;
}
.balance-label {
font-size: 24rpx;
margin-right: 10rpx;
white-space: nowrap;
flex-shrink: 0;
}
.balance-value {
font-size: 26rpx;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
.balance-value image {
width: 24rpx;
height: 24rpx;
margin-left: 10rpx;
}
.balance-text {
color: rgba(245, 227, 209, 0.75);
font-size: 20rpx;
font-style: normal;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.balance-right {
display: flex;
align-items: center;
margin-left: 20rpx;
flex-shrink: 0;
}
.recharge-btn {
font-size: 20rpx;
font-style: normal;
color: rgba(77, 49, 38, 0.95);
font-weight: 500;
line-height: 40rpx;
height: 40rpx;
border-radius: 24rpx;
background: linear-gradient(98deg, #fedfc6 0%, #edc5a3 95.24%);
white-space: nowrap;
padding: 0 20rpx;
min-width: 80rpx;
text-align: center;
flex-shrink: 0;
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
transition: all 0.2s;
}
.recharge-btn:active {
transform: scale(0.97);
}
.channel-section .section-header {
margin-bottom: 10rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 35rpx;
}
.section-title {
color: rgba(0, 0, 0, 0.95);
font-size: 14px;
font-style: normal;
font-weight: 600;
}
.view-all {
color: rgba(0, 0, 0, 0.55);
font-size: 10px;
font-style: normal;
font-weight: 400;
}
.channel-section .view-all {
display: block;
}
.arrow {
margin-left: 6rpx;
}
.order-status-list {
display: flex;
justify-content: space-between;
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 25%;
}
.status-item image {
width: 60rpx;
height: 60rpx;
margin-bottom: 10rpx;
margin-left: 5px;
}
.status-item text {
font-size: 26rpx;
color: #333;
}
.status-badge {
position: absolute;
top: -10rpx;
right: 20rpx;
background-color: #ff5722;
color: white;
font-size: 20rpx;
min-width: 34rpx;
height: 34rpx;
padding: 0 5rpx;
border-radius: 34rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 积分中心部分 */
.points-details {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.point-item {
text-align: center;
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
}
.point-label {
color: rgba(0, 0, 0, 0.55);
font-size: 12px;
font-style: normal;
font-weight: 400;
}
.point-value {
color: rgba(0, 0, 0, 0.95);
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 16px;
margin-top: 8px;
}
.coupon-usage {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 8px;
background: #fff8f8;
}
.coupon-info {
display: flex;
align-items: center;
}
.coupon-info image {
width: 60rpx;
height: 60rpx;
margin-right: 10rpx;
}
.use-now-btn {
color: white;
font-size: 24rpx;
padding: 6rpx 20rpx;
border-radius: 12px;
background: #ff2e15;
line-height: 1.5;
}
/* 频道部分 */
.channel-list {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
.channel-item {
margin: 4rpx 0;
display: flex;
flex-direction: row;
align-items: center;
}
.channel-item text {
margin-left: 2px;
}
/* 在确保频道列表垂直居中的样式中也需要修改 */
.flex-sections .channel-list {
height: calc(100% - 80rpx);
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
}
/* 错误样式 */
.error-container {
display: flex;
justify-content: center;
align-items: center;
height: 300rpx;
width: 100%;
margin-top: 100rpx;
}
.error-message {
display: flex;
flex-direction: column;
align-items: center;
color: #666;
}
.retry-btn {
margin-top: 20rpx;
background-color: #ff6b6b;
color: white;
font-size: 28rpx;
padding: 10rpx 30rpx;
border-radius: 30rpx;
}
/* 并排布局容器 */
.flex-sections {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
/* 调整两侧的宽度 */
.flex-sections .points-section {
width: 75%;
margin-bottom: 0;
}
.flex-sections .channel-section {
width: 25%;
margin-bottom: 0;
}
/* 频道图标微调 */
.flex-sections .channel-icon {
width: 60rpx;
height: 60rpx;
}
/* 添加这里缺失的基本样式 */
.channel-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.channel-icon image {
width: 60rpx;
height: 60rpx;
}
.channel-item text {
font-size: 26rpx;
color: #333;
margin-left: 15px;
}
/* 让领券中心内容更紧凑 */
.flex-sections .points-details {
margin-bottom: 15rpx;
}
.flex-sections .coupon-usage {
padding: 8px 26rpx;
box-sizing: border-box;
}
/* 确保频道列表垂直居中 */
.flex-sections .channel-list {
height: calc(100% - 80rpx);
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
}
.channel-section .section-header {
flex-direction: column;
align-items: flex-start;
}
.channel-section .view-all {
display: block;
text-align: left;
width: 100%;
margin-top: 5px;
}
/* 添加图标内容的样式 */
.channel-icon .icon-content {
position: absolute;
z-index: 1;
}
/* 小屏幕适配 */
@media screen and (max-width: 375px) {
.balance-bar {
height: 90rpx;
}
.balance-bar-content {
padding: 0 20rpx;
}
.balance-label,
.balance-value,
.balance-text {
font-size: 22rpx;
}
.recharge-btn {
font-size: 9px;
padding: 0 10rpx;
}
}
/* 大屏幕适配 */
@media screen and (min-width: 768px) {
.balance-bar {
height: 120rpx;
}
.balance-bar-content {
padding: 0 40rpx;
}
.balance-label,
.balance-value {
font-size: 28rpx;
}
.balance-text {
font-size: 24rpx;
}
.recharge-btn {
font-size: 12px;
padding: 0 20rpx;
height: 24px;
line-height: 24px;
}
}
.user-id.login-btn {
color: #ff7839;
font-weight: 600;
}
/* 未登录状态样式 */
.login-content {
padding: 40rpx;
margin-top: 20rpx;
}
.login-prompt {
background-color: #fff;
border-radius: 16rpx;
padding: 60rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.login-image {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
margin-bottom: 30rpx;
}
.login-text {
font-size: 28rpx;
color: #666;
margin-bottom: 40rpx;
}
.login-button {
background: linear-gradient(90deg, #ff7839 0%, #ff4b1f 100%);
color: white;
font-size: 30rpx;
padding: 20rpx 80rpx;
border-radius: 40rpx;
border: none;
}
/* 添加眼睛图标样式 */
.eye-icon {
cursor: pointer;
margin-left: 4rpx;
}
// 收益列表
.top-gradient {
.income-info {
margin-top: 30rpx;
display: flex;
width: 100%;
justify-content: space-between;
.income-item {
display: flex;
flex-direction: column;
font-size: 28rpx;
color: #ff7839;
.price,
.label {
text-align: center;
}
}
}
}
.user-main {
margin-top: 24rpx;
background: #fafafa;
border-radius: 30rpx 30rpx 0 0;
-webkit-border-radius: 30rpx 30rpx 0 0;
-moz-border-radius: 30rpx 30rpx 0 0;
-ms-border-radius: 30rpx 30rpx 0 0;
-o-border-radius: 30rpx 30rpx 0 0;
padding: 32rpx;
.icon-list {
display: flex;
width: 100%;
justify-content: space-between;
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
.icon-image {
margin-bottom: 8rpx;
width: 80rpx;
height: 80rpx;
image {
width: 100%;
height: 100%;
}
}
.label {
font-size: 26rpx;
}
}
}
.order-box {
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 24rpx;
.order-column {
width: 48%;
box-sizing: border-box;
padding: 24rpx;
border-radius: 24rpx;
background-color: #fff;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
.order-column__header {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
.order-column-title {
font-size: 32rpx;
line-height: 32rpx;
font-weight: 600;
}
}
.order-column__body {
margin-top: 24rpx;
.order-column__item {
display: flex;
width: 100%;
background-color: #67c23a22;
align-items: center;
box-sizing: border-box;
padding: 16rpx;
margin-top: 16rpx;
border-radius: 12rpx;
-webkit-border-radius: 12rpx;
-moz-border-radius: 12rpx;
-ms-border-radius: 12rpx;
-o-border-radius: 12rpx;
&:nth-child(1) {
background-color: #e8f9ef;
}
&:nth-child(2) {
background-color: #f0f2fe;
}
&:nth-child(3) {
background-color: #fcf7e9;
}
&:first-child {
margin-top: 0;
}
.order-image {
width: 70rpx;
height: 70rpx;
margin-right: 32rpx;
image {
width: 100%;
height: 100%;
}
}
.order-info {
font-size: 28rpx;
line-height: 28rpx;
.order-label {
color: #00000099;
}
.order-value {
font-weight: 600;
margin-top: 16rpx;
}
}
}
}
}
}
.wallet-box {
position: relative;
margin-top: 24rpx;
width: 100%;
box-sizing: border-box;
background-color: #fff;
border-radius: 24rpx;
-webkit-border-radius: 24rpx;
-moz-border-radius: 24rpx;
-ms-border-radius: 24rpx;
-o-border-radius: 24rpx;
padding: 32rpx 24rpx 16rpx 24rpx;
.wallet__tb {
position: absolute;
width: 50rpx;
height: 50rpx;
border-radius: 50%;
top: 296rpx;
background-color: #fafafa;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
&.left {
left: -25rpx;
}
&.right {
right: -25rpx;
}
}
.wallet__header {
width: 100%;
padding-bottom: 36rpx;
border-bottom: 2rpx dotted #0000001a;
.evaluate-list {
display: flex;
width: 100%;
justify-content: space-between;
.evaluate-item {
font-size: 24rpx;
.evaluate-value {
width: 100%;
text-align: center;
}
.evaluate-label {
width: 100%;
margin-top: 8rpx;
color: #000000cc;
text-align: center;
}
}
}
}
.wallet__body {
width: 100%;
margin-top: 24rpx;
display: flex;
border-bottom: 2rpx dotted #0000001a;
padding-bottom: 26rpx;
.wallet-column {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
.wallet-column-label {
font-size: 24rpx;
line-height: 24rpx;
color: #606266;
display: flex;
align-items: center;
margin-bottom: 24rpx;
&.smoll {
line-height: 20rpx;
font-size: 20rpx;
}
}
.wallet-column-value {
margin-bottom: 12rpx;
font-size: 36rpx;
line-height: 36rpx;
}
}
}
.wallet__footer {
display: flex;
justify-content: space-between;
margin-top: 48rpx;
align-items: center;
.wallet-tip {
font-size: 24rpx;
line-height: 24rpx;
}
.wallet-btn {
font-size: 24rpx;
padding: 0 24rpx;
height: 42rpx;
line-height: 42rpx;
background: #fa610f;
color: #ffdfc5f2;
}
}
}
}

View File

@@ -0,0 +1,649 @@
<template>
<view class="container">
<view class="error-container" v-if="false">
<view class="error-message">
<text>加载出错了请下拉刷新重试</text>
<button class="retry-btn" @tap="retryLoading">重试</button>
</view>
</view>
<view v-else>
<!-- 顶部背景区域 -->
<view class="top-gradient">
<!-- 用户信息 -->
<view class="user-info-section">
<view
class="user-avatar"
@tap.stop="isAuthInfo ? handleUpdateAvatar() : toLogin()"
>
<image
:src="isAuthInfo && loginResult.pic ? (loginResult.pic.indexOf('http') === -1 ? picDomain + loginResult.pic : loginResult.pic) : 'https://img0.baidu.com/it/u=3600954679,641662266&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500'"
/>
</view>
<view class="user-details">
<view class="user-id" v-if="isAuthInfo">
{{ loginResult.nickName || loginResult.userMobile || '' }}
</view>
<view class="user-id login-btn" v-else @tap="toLogin">
点击登录11111111111111111111111111
</view>
<view class="vip-tag" v-if="isAuthInfo">
{{ (loginResult.vipGrade || 1) + '星用户' }}
</view>
</view>
<view class="action-buttons">
<view class="service-btn" @tap="handleTips">
<image
src="/static/images/icon/customer.png"
mode="aspectFit"
class="icon-image"
></image>
<text>客服</text>
</view>
<view class="settings-btn" @tap="toSetting">
<image
src="/static/images/icon/set.png"
mode="aspectFit"
class="icon-image"
></image>
<text>设置</text>
</view>
</view>
</view>
<view class="income-info">
<view class="income-item">
<view class="price">
{{ wxs.parsePrice(merchantInfo.totalIncome)[0] }}.{{
wxs.parsePrice(merchantInfo.totalIncome)[1]
}}
</view>
<view class="label"> 总收益 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.totalSales }} </view>
<view class="label"> 总销量 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.commentCount }} </view>
<view class="label"> 用户评价 </view>
</view>
<view class="income-item">
<view class="price"> {{ merchantInfo.visitCount }} </view>
<view class="label"> 收藏人数 </view>
</view>
</view>
</view>
<!-- 用户菜单弹出层 -->
<view class="user-menu-overlay" v-if="showMenu" @tap="hideUserMenu">
<view class="user-menu" @tap.stop>
<view class="menu-item" @tap="handleUpdateAvatar">
<u-icon name="photo" size="24"></u-icon>
<text>更换头像</text>
</view>
<view class="menu-item" @tap="openUserInfoEditor">
<u-icon name="account" size="24"></u-icon>
<text>个人资料</text>
</view>
<view class="menu-item" @tap="handleTips">
<u-icon name="star" size="24"></u-icon>
<text>我的收藏</text>
</view>
<view class="menu-item logout-item" @tap="logout">
<u-icon name="arrow-right" size="24"></u-icon>
<text>退出登录</text>
</view>
</view>
</view>
</view>
<view class="user-main">
<view class="icon-list">
<view @tap="toBusyHour" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/time.png"
mode="aspectFit"
></image>
</view>
<view class="label"> 忙时设置 </view>
</view>
<view @tap="toMyProject" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/sign-in.png"
mode="aspectFit"
class="icon-image"
></image>
</view>
<view class="label"> 我的项目 </view>
</view>
<view @tap="toMyWallet" class="icon-item">
<view class="icon-image">
<image
src="@/static/images/icon/index/user.png"
mode="aspectFit"
class="icon-image"
></image>
</view>
<view class="label"> 我的钱包 </view>
</view>
</view>
<view class="order-box">
<view class="order-column">
<view class="order-column__header" @tap="toOrderListPage">
<view class="order-column-title"> 订单管理 </view>
<up-icon name="arrow-right" size="24rpx"></up-icon>
</view>
<view class="order-column__body">
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="2">
<view class="order-image">
<image
src="@/static/images/icon/index/to-be-service.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 待服务 </view>
<view class="order-value"> {{ orderInfo?.prePareCount }} </view>
</view>
</view>
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="4,5,6">
<view class="order-image">
<image
src="@/static/images/icon/index/in-progress.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 进行中 </view>
<view class="order-value"> {{ orderInfo?.ingCount }} </view>
</view>
</view>
<view class="order-column__item" @tap="toOrderListPage($event)" data-status="7">
<view class="order-image">
<image
src="@/static/images/icon/index/completed.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 已完成 </view>
<view class="order-value" > {{ orderInfo.finishedCount }} </view>
</view>
</view>
</view>
</view>
<view class="order-column">
<view class="order-column__header">
<view class="order-column-title"> 订单数据 </view>
<!-- <up-icon name="arrow-right" size="24rpx"></up-icon> -->
</view>
<view class="order-column__body">
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/income.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 订单收入 </view>
<view class="order-value"> {{ orderInfo.orderIncome }} </view>
</view>
</view>
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/number-orders.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 总订单数 </view>
<view class="order-value"> {{ orderInfo.orderCount }} </view>
</view>
</view>
<view class="order-column__item">
<view class="order-image">
<image
src="@/static/images/icon/index/refund-amount.png"
mode="aspectFit"
></image>
</view>
<view class="order-info">
<view class="order-label"> 退款金额 </view>
<view class="order-value"> {{ orderInfo.refundSum }} </view>
</view>
</view>
</view>
</view>
</view>
<view class="wallet-box">
<view class="wallet__tb left"> </view>
<view class="wallet__tb right"> </view>
<!-- <view class="wallet__header">
<view class="evaluate-list">
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 综合评分 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0% </view>
<view class="evaluate-label"> 成交率 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 服务次数 </view>
</view>
<view class="evaluate-item">
<view class="evaluate-value"> 0 </view>
<view class="evaluate-label"> 信用分 </view>
</view>
</view>
</view> -->
<view class="wallet__body">
<view class="wallet-column">
<view class="wallet-column-label"> 账户余额() </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.balance)[0] }}.{{
wxs.parsePrice(merchantInfo.balance)[1]
}}
</view>
<view class="wallet-column-label smoll">
立即提现 <up-icon name="arrow-right" size="18rpx"></up-icon
></view>
<view class="wallet-column-label"> 今日收益 </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.dayWithdrawal)[0] }}.{{
wxs.parsePrice(merchantInfo.dayWithdrawal)[1]
}}
</view>
</view>
<view class="wallet-column">
<view class="wallet-column-label"> 本月提现</view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.monthWithdrawal)[0] }}.{{
wxs.parsePrice(merchantInfo.monthWithdrawal)[1]
}}
</view>
<view class="wallet-column-label smoll">
立即提现 <up-icon name="arrow-right" size="18rpx"></up-icon
></view>
<view class="wallet-column-label"> 本月收益 </view>
<view class="wallet-column-value">
{{ wxs.parsePrice(merchantInfo.monthIncome)[0] }}.{{
wxs.parsePrice(merchantInfo.monthIncome)[1]
}}
</view>
</view>
</view>
<view class="wallet__footer">
<view class="wallet-tip"> 每次提现平台将扣除相应手续费 </view>
<view class="wallet-btn"> 钱包明细 </view>
</view>
</view>
</view>
</view>
<tabbar></tabbar>
</template>
<script setup lang="ts">
import { onShow } from "@dcloudio/uni-app";
import { ref, computed, onMounted } from "vue";
import useUserStore from "@/stores/user/useUserStore";
import {
getPackageData,
getOrderManageData,
getManageData,
} from "@/api/user/user";
import type { LoginResult } from "@/types/user";
import number from "@/wxs/number";
import tabbar from "@/components/tabbar/tabbar.vue";
const wxs = number();
// @ts-ignore
const picDomain = import.meta.env.VITE_APP_RESOURCES_URL;
// const isAuthInfo = ref<boolean>(false); // 默认未登录,需要检查登录状态
const loginResult = ref<LoginResult>({});
const orderAmount = ref<any>({});
const collectionCount = ref<number>(0);
const showMenu = ref<boolean>(false); // 控制用户菜单显示
const showBalance = ref<boolean>(true); // 控制余额显示隐藏,默认显示
const userStore = useUserStore();
const isAuthInfo = computed(() => {
return userStore.isLogin;
});
const merchantInfo = ref({
totalIncome: 0, //总收益
totalSales: 0, //总销量
commentCount: 0, //用户评价
visitCount: 0, //访客人数
balance: 0, //账户余额
monthWithdrawal: 0, //本月提现
dayWithdrawal: 0, //今日提现
monthIncome: 0, //本月收益
});
const orderInfo = ref({
prePareCount: 0, //待服务
ingCount: 0, //进行中
finishedCount: 0, //已完成
orderIncome: 0, //订单收入
orderCount: 0, //总订单数
refundSum: 0, //退款金额
});
// 生命周期钩子
onMounted(() => {
console.log("用户页面加载完成 - onLoad");
try {
checkLoginStatus();
loadUserData();
} catch (error) {
console.error("页面加载出错:", error);
}
});
onShow(() => {
console.log("用户页面显示 - onShow");
getAllDate();
try {
checkLoginStatus();
loadUserData();
} catch (error) {
console.error("页面显示出错:", error);
}
});
const getAllDate = async () => {
getMerchantInfo();
getOrderInfo();
};
//获取收益,销量,评价,收藏
const getMerchantInfo = async () => {
const { data } = await getPackageData();
console.log("获取数据成功", data);
const { data: mData } = await getManageData();
merchantInfo.value.totalIncome = parseInt(data?.totalIncome) ?? 0;
merchantInfo.value.totalSales = data?.totalSales ?? 0;
merchantInfo.value.commentCount = data?.commentCount ?? 0;
merchantInfo.value.visitCount = data?.visitCount ?? 0;
merchantInfo.value.balance = mData?.balance ?? 0;
merchantInfo.value.monthWithdrawal = parseInt(mData?.monthWithdrawal) ?? 0;
merchantInfo.value.dayWithdrawal = parseInt(mData?.dayWithdrawal) ?? 0;
merchantInfo.value.monthIncome = parseInt(mData?.monthIncome) ?? 0;
};
// 获取订单数据
const getOrderInfo = async () => {
const { data } = await getOrderManageData();
orderInfo.value = {
prePareCount: data?.prePareCount ?? 0,
ingCount: data?.ingCount ?? 0,
finishedCount: data?.finishedCount ?? 0,
orderIncome: data?.orderIncome ?? 0,
orderCount: data?.orderCount ?? 0,
refundSum: data?.refundSum ?? 0,
};
};
// 检查登录状态
const checkLoginStatus = () => {
// // 读取本地缓存中的登录信息
// loginResult.value = uni.getStorageSync("loginResult");
// let token = uni.getStorageSync("Token");
// // Token不存在但有accessToken则更新Token
// if (!token && loginResult.value && loginResult.value.accessToken) {
// token = loginResult.value.accessToken;
// uni.setStorageSync("Token", token);
// console.log("从loginResult中提取token并保存:", token);
// }
// // 更新Vuex中的token和登录状态
// if (token) {
// const userStore = useUserStore();
// userStore.token = token;
// isAuthInfo.value = true;
// loadUserData();
// } else {
// // 如果没有token但有loginResult清除过期数据
// if (loginResult.value) {
// uni.removeStorageSync("loginResult");
// loginResult.value = "";
// }
// isAuthInfo.value = false;
// }
};
// 显示用户菜单
const showUserMenu = () => {
console.log("显示用户菜单");
showMenu.value = true;
};
// 隐藏用户菜单
const hideUserMenu = () => {
console.log("隐藏用户菜单");
showMenu.value = false;
};
// 跳转忙时设置
const toBusyHour = () => {
uni.navigateTo({
url: "/pages/busy-hour/busy-hour",
});
};
// 加载用户数据
const loadUserData = async () => {
try {
const userStore = useUserStore();
const userInfo = await userStore.getUserInfo();
if (userInfo) {
loginResult.value = userInfo;
// uni.setStorageSync("loginResult", userInfo);
}
// 获取订单数量
// const orderCountRes = await getOrderCount();
// if (orderCountRes.data) {
// orderAmount.value = orderCountRes.data;
// }
} catch (error) {
console.error("加载用户数据失败:", error);
uni.showToast({
title: "加载用户数据失败",
icon: "none",
});
}
};
const toMyWallet = () => {
uni.navigateTo({
url: "/pages/my-wallet/my-wallet",
});
};
const toMyProject = () => {
uni.navigateTo({
url: "/pages/my-project/my-project",
});
};
// 退出登录
const logout = () => {
// 关闭菜单弹窗
hideUserMenu();
// 清除登录信息
uni.removeStorageSync("loginResult");
uni.removeStorageSync("Token");
// 清除userStore中的token
const userStore = useUserStore();
userStore.token = "";
// 重置页面数据
isAuthInfo.value = false;
loginResult.value = "";
uni.showToast({
title: "退出成功",
icon: "none",
});
setTimeout(() => {
uni.switchTab({
url: "/pages/index/index",
});
}, 1000);
};
// 去登录页面
const toLogin = () => {
// 保存当前页面路径,登录成功后可以跳回
uni.setStorageSync("routeUrlAfterLogin", "/pages/user/user");
uni.navigateTo({
url: "/pages/login/login",
});
};
// 添加充值方法
const toRecharge = () => {
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/recharge/recharge",
});
};
// 处理头像更新
const handleUpdateAvatar = () => {
// 检查登录状态
// if (!isAuthInfo.value) {
// toLogin();
// return;
// }
// // 隐藏菜单
// hideUserMenu();
// // 显示操作菜单
// uni.showActionSheet({
// itemList: ["从相册选择", "拍照"],
// success: (res) => {
// const sourceType = res.tapIndex === 0 ? ["album"] : ["camera"];
// // 调用系统选择图片
// uni.chooseImage({
// count: 1, // 默认9
// sizeType: ["original", "compressed"],
// sourceType: sourceType,
// success: async (res) => {
// const tempFilePaths = res.tempFilePaths;
// try {
// // 上传文件
// const fileUrl = await uploadFile(tempFilePaths[0]);
// // 更新用户头像
// await updateUserAvatar({
// avatarUrl: fileUrl,
// });
// // 更新本地存储的用户信息
// const userInfo = uni.getStorageSync("loginResult");
// if (userInfo) {
// userInfo.pic = fileUrl;
// uni.setStorageSync("loginResult", userInfo);
// loginResult.value = userInfo;
// }
// uni.showToast({
// title: "头像更新成功",
// icon: "success",
// });
// } catch (error) {
// uni.showToast({
// title: "头像更新失败",
// icon: "error",
// });
// }
// },
// });
// },
// });
};
const toSetting = () => {
uni.navigateTo({
url: "/pages/setting/setting",
});
};
// 切换余额显示
const toggleBalance = () => {
showBalance.value = !showBalance.value;
};
// 打开个人信息编辑
const openUserInfoEditor = () => {
// 隐藏菜单
hideUserMenu();
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/user-info/user-info",
});
};
// 处理提示信息
const handleTips = () => {
uni.showToast({
title: "该功能正在开发中",
icon: "none",
duration: 2000,
});
};
const toOrderListPage = (e) => {
useUserStore().setActive(1);
const status = e.currentTarget.dataset.status;
uni.setStorageSync('orderListStatus', status);
uni.switchTab({
url: '/pages/order-list/order-list'
});
};
// 去优惠券中心
const toCouponCenter = () => {
if (!isAuthInfo.value) {
toLogin();
return;
}
uni.navigateTo({
url: "/pages/coupon-center/coupon-center",
});
};
// 重试加载
const retryLoading = () => {
loadUserData();
};
</script>
<style lang="scss" scoped>
@import "./user.scss";
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Some files were not shown because too many files have changed in this diff Show More