Kaynağa Gözat

页面创建

zuy 1 yıl önce
ebeveyn
işleme
fb068bdc55

+ 1 - 1
src/pages.json

@@ -37,7 +37,7 @@
   ],
   "subPackages": [
     {
-      "root": "pages/z-common",
+      "root": "pages/sub-user",
       "pages": [
         {
           "path": "profile/index",

+ 0 - 170
src/pages/order/b.vue

@@ -1,170 +0,0 @@
-<template>
-	<view class="page_order">
-		<view>
-			<u-subsection :list="list" :current="curNow" @change="sectionChange" activeColor="#25A785"
-				bgColor="#eeeeee"></u-subsection>
-		</view>
-		<view class="order_card">
-			<view class="card" v-for="(item,index) in pro">
-				<view class="card_top">
-					<view style="width: 80%;" class="font14">订单号:{{item.number}}</view>
-					<view class="font14" style="color: #25A785;">待付款</view>
-				</view>
-				<u-line color="#eaeaea" :hairline="false"></u-line>
-				<view class="card_medium">
-					<image src="../../tupian/maomi.jpg" class="card_img"></image>
-					<view class="med_right">
-						<view class="font16 fontbold med_right1">{{item.name}}</view>
-						<view class="font12 med_right2">预约时间:{{item.time}}</view>
-						<view class="med_right3">
-							<view class="font12">优惠:{{item.Discount}}</view>
-							<view class="font12" style="margin-left: 20rpx;">实付:</view>
-							<view class="font12 col_red">¥</view>
-							<view class="font16 col_red">{{item.money}}</view>
-						</view>
-					</view>
-				</view>
-				<view class="card_btm">
-					<u-button class="btm1" shape="circle">取消订单</u-button>
-					<u-button class="btm2" shape="circle">立即付款</u-button>
-				</view>
-			</view>
-		</view>
-	</view>
-
-</template>
-
-<script>
-	export default {
-		data() {
-			return {
-				pro:[{
-					name:"专业疏通管道服务",
-					number:"X45344354544321",
-					time:"2021-12-10",
-					Discount:"104元",
-					money:"130.00"
-				},
-				{
-					name:"专业疏通管道服务",
-					number:"X45344354544321",
-					time:"2021-12-10",
-					Discount:"104元",
-					money:"130.00"
-				},
-				{
-					name:"专业疏通管道服务",
-					number:"X45344354544321",
-					time:"2021-12-10",
-					Discount:"104元",
-					money:"130.00"
-				}
-				],
-				list: ['全部订单', '已完成订单', '未完成订单', '待评价'],
-				curNow: 0
-			}
-		},
-		methods: {
-			sectionChange(index) {
-				this.curNow = index;
-			}
-		}
-	}
-</script>
-
-<style lang="scss">
-	.page_order {
-		width: 100vw;
-		min-height: 100vh;
-
-		.col_red {
-			color: red;
-		}
-		.font12{
-			font-size: 12px;
-		}
-		.font14{
-			font-size: 14px;
-		}
-		.font16{
-			font-size: 16px;
-		}
-		.order_card {
-			width: 100vw;
-			display: flex;
-			flex-wrap: wrap;
-			justify-content: center;
-
-			.card {
-				width: 92%;
-				height: 400rpx;
-				margin-top: 30rpx;
-				border-radius: 10px;
-				background-color: white;
-				box-shadow: 1px 1px 5px #d5d5d5;
-
-				.card_top {
-					display: flex;
-					padding: 15rpx;
-				}
-
-				.card_medium {
-					width: 95%;
-					height: 220rpx;
-					display: flex;
-					align-items: center;
-					padding: 15rpx;
-
-					.card_img {
-						height: 180rpx;
-						width: 180rpx;
-						border-radius: 10px;
-					}
-
-					.med_right {
-						height: 180rpx;
-						margin-left: 20rpx;
-
-						.med_right1 {
-							margin-bottom: 30rpx;
-						}
-
-						.med_right2 {
-							color: #909090;
-							margin-bottom: 25rpx;
-							
-						}
-
-						.med_right3 {
-							display: flex;
-							align-items: center;
-						}
-					}
-				}
-
-				.card_btm {
-					display: flex;
-					height: 80rpx;
-					width: 100%;
-					align-items: center;
-
-					.btm1 {
-						height: 60rpx;
-						width: 200rpx;
-						background-color: #ffffff;
-						color: #25A785;
-						border: #25A785 1px solid;
-						margin-left: 260rpx;
-					}
-
-					.btm2 {
-						height: 60rpx;
-						width: 200rpx;
-						background-color: #25A785;
-						color: white;
-					}
-				}
-			}
-		}
-	}
-</style>

+ 0 - 264
src/pages/order/c.vue

@@ -1,264 +0,0 @@
-<template>
-	<view>
-		<view class="wrap" >
-				<swiper-item class="swiper-item">
-					<scroll-view scroll-y style="height: 100%;width: 100%;" @scrolltolower="reachBottom">
-						<view class="page-box">
-							<view class="order" v-for="(item,index) in pro">
-								<view class="top">
-									<view class="left">
-										<u-icon name="home" :size="20" color="rgb(94,94,94)"></u-icon>
-										<view class="store">{{item.store}}</view>
-										<u-icon name="arrow-right" color="rgb(203,203,203)" :size="15"></u-icon>
-									</view>
-									<view class="right">{{item.right}}</view>
-								</view>
-								<view class="item" @click="jump_detail(132)" >
-									<view class="left">
-										<u--image :src="item.src" width="90px" height="100px"></u--image>
-									</view>
-									<view class="content">
-										<view class="title u-line-2">{{item.title}}</view>
-										<view class="delivery-time">下单时间:{{item.time}}</view>
-									</view>
-									<view class="right">
-										<view class="price">
-											¥{{item.price}}
-											<text class="decimal">.{{item.decimal}}</text>
-										</view>
-										<view class="number">x{{item.number}}</view>
-									</view>
-								</view>
-								<view class="total">
-									共{{item.total}}件商品 合计:
-									<text class="total-price">
-									¥{{item.price}}.
-									<text class="decimal">{{item.decimal}}</text>
-									</text>
-								</view>
-							</view>
-						</view>
-					</scroll-view>
-				</swiper-item>
-			</swiper>
-		</view>
-	</view>
-</template>
-
-<script>
-
-	export default {
-		data() {
-			return {
-				pro:[{
-					store:"小王的店",
-					right:"未支付",
-					title:"荣耀30 Pro 50倍远摄麒麟990 5G 4000万超感光...",
-					time:"2021-12-10",
-					price:"3999",
-					decimal:"00",
-					number:"1",
-					total:"1",
-					src:require('@/tupian/touxiang.jpg'),
-				},
-				{
-					store:"小王的店",
-					right:"未支付",
-					title:"荣耀30 Pro 50倍远摄麒麟990 5G 4000万超感光...",
-					time:"2021-12-10",
-					price:"3999",
-					decimal:"00",
-					number:"1",
-					total:"1",
-					src:require('@/tupian/touxiang.jpg'),
-				},
-				{
-					store:"小王的店",
-					right:"未支付",
-					title:"荣耀30 Pro 50倍远摄麒麟990 5G 4000万超感光...",
-					time:"2021-12-10",
-					price:"3999",
-					decimal:"00",
-					number:"1",
-					total:"1",
-					src:require('@/tupian/touxiang.jpg'),
-				},
-				],
-				dataList: [],
-				
-				
-			};
-		},
-	}
-</script>
-
-<style>
-	/* #ifndef H5 */
-	page {
-		height: 100%;
-		background-color: #f2f2f2;
-	}
-
-	/* #endif */
-</style>
-
-<style lang="scss" scoped>
-	.order {
-		width: 710rpx;
-		background-color: #ffffff;
-		margin: 20rpx auto;
-		border-radius: 20rpx;
-		box-sizing: border-box;
-		padding: 20rpx;
-		font-size: 28rpx;
-
-		.top {
-			display: flex;
-			justify-content: space-between;
-
-			.left {
-				display: flex;
-				align-items: center;
-
-				.store {
-					margin: 0 10rpx;
-					font-size: 32rpx;
-					font-weight: bold;
-				}
-			}
-
-			.right {
-				color:  #f29100;
-			}
-		}
-
-		.item {
-			display: flex;
-			margin: 20rpx 0 0;
-
-			.left {
-				margin-right: 20rpx;
-
-				image {
-					width: 200rpx;
-					height: 200rpx;
-					border-radius: 10rpx;
-				}
-			}
-
-			.content {
-				width: 60%;
-
-				.title {
-					font-size: 28rpx;
-					line-height: 50rpx;
-				}
-
-				.type {
-					margin: 10rpx 0;
-					font-size: 24rpx;
-					color: $u-tips-color;
-				}
-
-				.delivery-time {
-					color: #e5d001;
-					font-size: 24rpx;
-				}
-			}
-
-			.right {
-				margin-left: 10rpx;
-				padding-top: 20rpx;
-				text-align: right;
-
-				.decimal {
-					font-size: 24rpx;
-					margin-top: 4rpx;
-				}
-
-				.number {
-					color: $u-tips-color;
-					font-size: 24rpx;
-				}
-			}
-		}
-
-		.total {
-			margin-top: 20rpx;
-			text-align: right;
-			font-size: 24rpx;
-
-			.total-price {
-				font-size: 32rpx;
-			}
-		}
-
-		.bottom {
-			display: flex;
-			margin-top: 40rpx;
-			padding: 0 10rpx;
-			justify-content: space-between;
-			align-items: center;
-
-			.btn {
-				line-height: 52rpx;
-				width: 160rpx;
-				border-radius: 26rpx;
-				border: 2rpx solid $u-border-color;
-				font-size: 26rpx;
-				text-align: center;
-				color:#f29100;
-			}
-
-			.evaluate {
-				color: #f29100;
-				border-color:  #f29100;
-			}
-		}
-	}
-
-	.centre {
-		text-align: center;
-		margin: 200rpx auto;
-		font-size: 32rpx;
-
-		image {
-			width: 164rpx;
-			height: 164rpx;
-			border-radius: 50%;
-			margin-bottom: 20rpx;
-		}
-
-		.tips {
-			font-size: 24rpx;
-			color: #999999;
-			margin-top: 20rpx;
-		}
-
-		.btn {
-			margin: 80rpx auto;
-			width: 200rpx;
-			border-radius: 32rpx;
-			line-height: 64rpx;
-			color: #ffffff;
-			font-size: 26rpx;
-			background: linear-gradient(270deg, rgba(249, 116, 90, 1) 0%, rgba(255, 158, 1, 1) 100%);
-		}
-	}
-
-	.wrap {
-		display: flex;
-		flex-direction: column;
-		height: calc(100vh - var(--window-top));
-		width: 100%;
-		height: 100%;
-	}
-
-	.swiper-box {
-		flex: 1;
-	}
-
-	.swiper-item {
-		height: 100%;
-	}
-</style>

+ 0 - 0
src/pages/z-common/account/index.vue → src/pages/sub-order/detail/index.vue


+ 0 - 0
src/pages/z-common/contact/index.vue → src/pages/sub-order/pay/index.vue


+ 0 - 0
src/pages/z-common/faq/index.vue → src/pages/sub-order/process/index.vue


+ 0 - 0
src/pages/z-common/fav/index.vue → src/pages/sub-user/account/index.vue


+ 0 - 0
src/pages/z-common/profile/index.vue → src/pages/sub-user/contact/index.vue


+ 153 - 0
src/pages/sub-user/faq/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <view class="page">
+    <view v-if="state.questions.length <= 0" class="flex-center mt-40 animation-loading">
+      <uv-icon name="photo"></uv-icon>
+    </view>
+    <view class="sheet">
+      <view class="sheet_bar" v-for="(item, index) in state.questions" :key="index">
+        <view class="head" @click="toggle(index)">
+          <view class="pt-32 pb-32 flex-align-center">
+            <view class="fs-28 fw-500 color-000">{{ item.question }}</view>
+            <view
+                class="width-40 ml-auto flex-end flex-shrink transition"
+                :style="{
+                transform: item.open ? 'rotate(180deg)' : 'rotate(0deg)',
+              }"
+            >
+              <uv-icon name="arrow-down"></uv-icon>
+
+            </view>
+          </view>
+        </view>
+        <view :class="['body', item.open ? 'body-open' : 'body-hidden']">
+          <view
+              v-for="(answerItem, answerIndex) in item.answer"
+              :key="answerIndex"
+              :class="[
+              'fs-28',
+              'lh-48',
+              'color-666',
+              answerIndex === 0 ? 'mt-0' : 'mt-10',
+            ]"
+          >{{ answerItem }}
+          </view
+          >
+        </view>
+      </view>
+    </view>
+    <view
+        class="flex-center flex-column contact mt-20"
+        hover-class="hover-scale"
+        @click="call"
+        v-if="state.questions.length"
+    >
+      <image
+          class="width-96"
+          mode="widthFix"
+          src="/static/images/contact-customer.png"
+      />
+      <view class="mt-16 color-666 fs-28">电话客服</view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive} from "vue";
+import {body} from "@/utils/https"
+
+
+const initState = () => ({
+  questions: [] as any[],
+  servicerPhone: "",
+})
+
+const state = reactive(initState())
+
+const call = () => {
+  uni.makePhoneCall({
+    phoneNumber: state.servicerPhone,
+  });
+};
+
+const toggle = (index: number) => {
+  state.questions = state.questions.map((item, i) => {
+    return {
+      ...item,
+      open: item.open ? false : i === index,
+    };
+  });
+};
+
+onShow((options) => {
+  if (options) {
+    state.servicerPhone = options.service;
+  }
+  body(`faq/list`).then((res: any) => {
+    state.questions = res.list;
+  })
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.page {
+  min-height: 100vh;
+  background: #f6f7fa;
+  box-sizing: border-box;
+  padding: 40rpx 32rpx;
+}
+
+.contact {
+  height: 216rpx;
+  border-radius: 24rpx;
+  background: #fff;
+}
+
+.sheet {
+  box-sizing: border-box;
+  border-radius: 24rpx;
+  background: #fff;
+  overflow: hidden;
+
+  &_bar {
+    .head {
+      padding: 0rpx 24rpx;
+      box-sizing: border-box;
+
+      & > view {
+        border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+      }
+    }
+
+    .body {
+      box-sizing: border-box;
+      background-color: rgba(52, 125, 255, 0.06);
+      transition: all 0.3s;
+      padding: 0rpx 24rpx;
+    }
+
+    .body-hidden {
+      height: 0px;
+      overflow: hidden;
+    }
+
+    .body-open {
+      height: auto;
+      padding-top: 24rpx;
+      padding-bottom: 24rpx;
+    }
+
+    &:last-child {
+      .head {
+        & > view {
+          border-bottom: none;
+        }
+      }
+    }
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-user/fav/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-user/feedback/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-user/message/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-user/profile/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-wash/detail/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-wash/price/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>

+ 294 - 0
src/pages/sub-wash/process/index.vue

@@ -0,0 +1,294 @@
+<template>
+  <view class="pt-60 pb-20 flex-center">
+    <button
+        class="avatar"
+        open-type="chooseAvatar"
+        @chooseavatar="chooseAvatar"
+    >
+      <image class="avatar_image" :src="avatar" @error="errorHandle"></image>
+      <view class="avatar_text flex-center">编辑</view>
+    </button>
+  </view>
+
+  <view class="pl-50 pr-50">
+    <view
+        class="menu flex-align-center flex-between"
+        v-for="(item, index) in state.menuList"
+        :key="index"
+        @click="edit(index)"
+    >
+      <view class="fs-30">{{ item.title }}</view>
+      <view class="flex">
+        <view
+            :class="['fs-30', 'fw-500', `mr-${item.disabled ? '0' : '20'}`]"
+            style="color: rgba(0, 0, 0, 0.8)"
+        >{{ item.value }}
+        </view
+        >
+        <uni-icons
+            type="right"
+            size="12"
+            color="rgba(0,0,0,0.4)"
+            v-if="!item.disabled"
+        ></uni-icons>
+      </view>
+    </view>
+  </view>
+
+  <style-bottom-view>
+    <view class="pl-60 pr-60 pb-40">
+      <style-button type="primary" @click="logoutUser">退出登录</style-button>
+    </view>
+  </style-bottom-view>
+</template>
+
+<script setup lang="ts">
+import {onHide, onShow} from "@dcloudio/uni-app";
+import {reactive, ref} from "vue";
+import {body, get, upload} from "@/utils/https"
+
+const avatar = ref<string>();
+const menu = ref<any[]>([]);
+const MENU_TEMPLATE = [
+  {
+    title: "昵称",
+    key: "nickname",
+    value: "",
+  },
+  {
+    title: "电话",
+    key: "",
+    disabled: true,
+  },
+  {
+    title: "车牌号",
+    key: "defaultPlateNo",
+    value: "",
+  },
+  {
+    title: "VIN码",
+    key: "vin",
+    value: "",
+  },
+  // {
+  //   title: "充电卡",
+  //   key: "",
+  //   value: "",
+  // },
+];
+
+
+const initState = () => ({
+  user: {},
+  menuList: []
+})
+
+const state = reactive(initState())
+
+const refresh = () => {
+  const _menu = [...MENU_TEMPLATE];
+  get(`user/profile`).then(res => {
+    getApp<any>().globalData.user = res;
+  })
+  // fetchProfile().then(() => {
+  //   const user = getApp<any>().globalData.user;
+  //   if (user) {
+  //     _menu[0].value = user.nickname;
+  //     _menu[1].value = user.mobilePhone;
+  //     _menu[2].value = user.defaultPlateNo;
+  //     _menu[3].value = user.vin;
+  //     // _menu[4].value = user.card_no;
+  //     avatar.value =
+  //         user.avatar ||
+  //         "https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0";
+  //     menu.value = _menu;
+  //   }
+  // });
+};
+
+const save = (form: Record<string, any>) => {
+  uni.showLoading({
+    title: "保存中",
+  });
+  return body(`user/updateProfile`, form)
+      .then((res) => {
+        uni.hideLoading();
+        uni.showToast({
+          icon: "success",
+          title: "保存成功",
+        });
+        refresh();
+        return res;
+      })
+      .catch((err) => {
+        uni.hideLoading();
+        uni.showModal({
+          content: `${err.errMsg},请重试`,
+        });
+      });
+};
+
+const chooseAvatar = (e: any) => {
+  if (e.detail.avatarUrl) {
+    uni.showLoading({
+      title: "上传中",
+    });
+    let params = {
+      url: `file/upload`,
+      filePath: e.detail.avatarUrl,
+      success: (res) => {
+        body(`user/updateAvatar`, {
+          avatar: res.url,
+        })
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                title: "已更新",
+                icon: "success",
+              });
+              avatar.value = res.url;
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    }
+    upload(params)
+  } else {
+    uni.showModal({
+      content: `${e.detail.errMsg},请重试`,
+    });
+  }
+};
+const edit = (index: number) => {
+  const menuItem = menu.value[index];
+  if (menuItem.disabled) {
+    return;
+  }
+  if (!menuItem.key) {
+    uni.showToast({
+      icon: "none",
+      title: "暂不支持修改",
+    });
+    return;
+  }
+  if (/车牌/.test(menuItem.title)) {
+    uni.chooseLicensePlate({
+      success: (res) => {
+        save({
+          defaultPlateNo: res.plateNumber,
+        });
+      },
+      fail: (err) => {
+        console.log(err);
+      },
+    });
+    return;
+  }
+  uni.navigateTo({
+    url: `/pages-user/profile-edit/profile-edit?key=${menuItem.key}&title=${
+        menuItem.title
+    }${menuItem.value ? `&value=${encodeURIComponent(menuItem.value)}` : ""}`,
+  });
+};
+
+const logoutUser = () => {
+  uni.showModal({
+    title: "温馨提示",
+    content: "确定退出登录吗?",
+    confirmColor: "#2d9e95",
+    confirmText: "确定退出",
+    cancelText: "手滑了",
+    success: (res) => {
+      if (res.confirm) {
+        uni.showLoading({
+          title: "退出中",
+        });
+        logout()
+            .then(() => {
+              uni.hideLoading();
+              uni.showToast({
+                icon: "success",
+                title: "已退出",
+              });
+              clearToken();
+              setTimeout(() => {
+                uni.reLaunch({
+                  url: "/pages/map/map",
+                });
+              }, 1500);
+            })
+            .catch((err) => {
+              uni.hideLoading();
+              uni.showModal({
+                content: `${err.errMsg},请重试`,
+              });
+            });
+      }
+    },
+  });
+};
+
+const errorHandle = (e: any) => {
+  console.log(e);
+};
+
+onShow(() => {
+  if (getApp<any>().globalData.lastData.profile) {
+    const {key, value} = getApp<any>().globalData.lastData.profile;
+    save({
+      [key]: value,
+    }).then(() => {
+      getApp<any>().globalData.lastData.profile = undefined;
+    });
+  }
+});
+
+onHide(() => {
+  Object.assign(state, initState());
+})
+</script>
+
+<style lang="scss">
+.avatar {
+  position: relative;
+  height: 116rpx !important;
+  width: 116rpx !important;
+  border-radius: 50%;
+  border: 2rpx solid rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+
+  &_image {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    border-radius: 50%;
+  }
+
+  &_text {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40rpx;
+    background: rgba(0, 0, 0, 0.5);
+    color: #fff;
+    font-size: 24rpx;
+  }
+}
+
+.menu {
+  background-color: #fff;
+  height: 120rpx;
+  border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+</style>