zuy před 1 rokem
rodič
revize
bb7cabb037

+ 14 - 0
src/components/tab-bar/index.vue

@@ -0,0 +1,14 @@
+<template>
+  <uv-tabbar :value="value" @change="index=>value = index" :border="false"  :fixed="true">
+    <uv-tabbar-item text="首页" icon="home"></uv-tabbar-item>
+    <uv-tabbar-item text="优惠" icon="photo"></uv-tabbar-item>
+    <uv-tabbar-item text="" icon="scan" :iconSize="40"></uv-tabbar-item>
+    <uv-tabbar-item text="订单" icon="play-right"></uv-tabbar-item>
+    <uv-tabbar-item text="我的" icon="account"></uv-tabbar-item>
+  </uv-tabbar>
+</template>
+<script setup lang="ts" name="TabBar">
+import { ref } from 'vue'
+const value = ref(0)
+
+</script>

+ 294 - 0
src/pages-common/account/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-common/contact/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>

+ 153 - 0
src/pages-common/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-common/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-common/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>

+ 14 - 5
src/pages.json

@@ -14,17 +14,26 @@
       }
     },
     {
-      "path": "pages/map/map",
+      "path": "pages/coupon/index",
       "style": {
         "disableScroll": true
       }
     },
     {
-      "path": "pages/user/user"
+      "path": "pages/map/index",
+      "style": {
+        "disableScroll": true
+      }
+    },
+    {
+      "path": "pages/order/index"
+    },
+    {
+      "path": "pages/user/index"
     }
   ],
-  "subPackages": {
-    "root": "pages-common",
+  "subPackages": [{
+    "root": "pages-common/",
     "pages": [
       {
         "path": "profile/index",
@@ -59,7 +68,7 @@
         }
       }
     ]
-  },
+  }],
   "globalStyle": {
     "navigationBarTextStyle": "black",
     "navigationBarTitleText": "uni-app",

+ 51 - 0
src/pages/coupon/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <view class="content">
+    <image class="logo" src="/static/logo.png" />
+    <view class="text-area">
+      <text class="title">{{ title }}</text>
+    </view>
+    <uv-button type="primary" text="确定"></uv-button>
+    <uv-calendars insert @change="change" />
+    <uv-image src="https://cdn.uviewui.com/uview/album/1.jpg" mode="widthFix"></uv-image>
+    <uv-qrcode ref="qrcode" size="300px" value="https://h5.uvui.cn"></uv-qrcode>
+    <uv-switch v-model="open" loading></uv-switch>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+const title = ref('Hello')
+const open = ref(false)
+
+const change = () => {
+  console.log("change")
+}
+</script>
+
+<style>
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.logo {
+  height: 200rpx;
+  width: 200rpx;
+  margin-top: 200rpx;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 50rpx;
+}
+
+.text-area {
+  display: flex;
+  justify-content: center;
+}
+
+.title {
+  font-size: 36rpx;
+  color: #8f8f94;
+}
+</style>

+ 7 - 3
src/pages/index/index.vue

@@ -1,19 +1,23 @@
 <template>
   <view class="content">
-    <image class="logo" src="/static/logo.png" />
+    <image class="logo" src="/static/logo.png"/>
     <view class="text-area">
       <text class="title">{{ title }}</text>
     </view>
     <uv-button type="primary" text="确定"></uv-button>
-    <uv-calendars insert @change="change" />
+    <uv-calendars insert @change="change"/>
     <uv-image src="https://cdn.uviewui.com/uview/album/1.jpg" mode="widthFix"></uv-image>
     <uv-qrcode ref="qrcode" size="300px" value="https://h5.uvui.cn"></uv-qrcode>
     <uv-switch v-model="open" loading></uv-switch>
+
+    <tab-bar></tab-bar>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import {ref} from 'vue'
+import TabBar from "@/components/tab-bar";
+
 const title = ref('Hello')
 const open = ref(false)
 

+ 51 - 0
src/pages/map/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <view class="content">
+    <image class="logo" src="/static/logo.png" />
+    <view class="text-area">
+      <text class="title">{{ title }}</text>
+    </view>
+    <uv-button type="primary" text="确定"></uv-button>
+    <uv-calendars insert @change="change" />
+    <uv-image src="https://cdn.uviewui.com/uview/album/1.jpg" mode="widthFix"></uv-image>
+    <uv-qrcode ref="qrcode" size="300px" value="https://h5.uvui.cn"></uv-qrcode>
+    <uv-switch v-model="open" loading></uv-switch>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+const title = ref('Hello')
+const open = ref(false)
+
+const change = () => {
+  console.log("change")
+}
+</script>
+
+<style>
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.logo {
+  height: 200rpx;
+  width: 200rpx;
+  margin-top: 200rpx;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 50rpx;
+}
+
+.text-area {
+  display: flex;
+  justify-content: center;
+}
+
+.title {
+  font-size: 36rpx;
+  color: #8f8f94;
+}
+</style>

+ 51 - 0
src/pages/order/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <view class="content">
+    <image class="logo" src="/static/logo.png" />
+    <view class="text-area">
+      <text class="title">{{ title }}</text>
+    </view>
+    <uv-button type="primary" text="确定"></uv-button>
+    <uv-calendars insert @change="change" />
+    <uv-image src="https://cdn.uviewui.com/uview/album/1.jpg" mode="widthFix"></uv-image>
+    <uv-qrcode ref="qrcode" size="300px" value="https://h5.uvui.cn"></uv-qrcode>
+    <uv-switch v-model="open" loading></uv-switch>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+const title = ref('Hello')
+const open = ref(false)
+
+const change = () => {
+  console.log("change")
+}
+</script>
+
+<style>
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.logo {
+  height: 200rpx;
+  width: 200rpx;
+  margin-top: 200rpx;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 50rpx;
+}
+
+.text-area {
+  display: flex;
+  justify-content: center;
+}
+
+.title {
+  font-size: 36rpx;
+  color: #8f8f94;
+}
+</style>

+ 51 - 0
src/pages/user/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <view class="content">
+    <image class="logo" src="/static/logo.png" />
+    <view class="text-area">
+      <text class="title">{{ title }}</text>
+    </view>
+    <uv-button type="primary" text="确定"></uv-button>
+    <uv-calendars insert @change="change" />
+    <uv-image src="https://cdn.uviewui.com/uview/album/1.jpg" mode="widthFix"></uv-image>
+    <uv-qrcode ref="qrcode" size="300px" value="https://h5.uvui.cn"></uv-qrcode>
+    <uv-switch v-model="open" loading></uv-switch>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+const title = ref('Hello')
+const open = ref(false)
+
+const change = () => {
+  console.log("change")
+}
+</script>
+
+<style>
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.logo {
+  height: 200rpx;
+  width: 200rpx;
+  margin-top: 200rpx;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 50rpx;
+}
+
+.text-area {
+  display: flex;
+  justify-content: center;
+}
+
+.title {
+  font-size: 36rpx;
+  color: #8f8f94;
+}
+</style>

+ 13 - 13
src/utils/https.js → src/utils/https.ts

@@ -1,4 +1,4 @@
-const env = process.env.NODE_ENV==="development"?"dev":"prd";
+const env = process?.env.NODE_ENV === "development" ? "dev" : "prd";
 let apis = {
     dev: {
         serverUrl: "http://localhost:10086/cms/",
@@ -51,7 +51,7 @@ source = 9;
 source = 8;
 // #endif
 
-const isEmptyOrNull = function (exp) {
+const isEmptyOrNull = function (exp: any) {
     return !exp || typeof (exp) == "undefined" || exp.length === 0 || exp === '' || JSON.stringify(exp) === "{}";
 };
 
@@ -61,11 +61,12 @@ const isEmptyOrNull = function (exp) {
  * @param url
  * @param param
  */
-const get = (url, param = {}) => {
+const get = (url: string, param = {}) => {
     let token = uni.getStorageSync(cfg.key.token) || "";
     if (!isEmptyOrNull(param)) {
         var params = [];
         for (var key in param) {
+            // @ts-ignore
             params.push(encodeURIComponent(key) + "=" + encodeURIComponent(param[key]));
         }
         param = params.join("&")
@@ -77,15 +78,14 @@ const get = (url, param = {}) => {
         header: {
             "Accept": "application/json",
             "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
-            "X-Token": token,
-            "X-Client-Type": source
+            "token": token,
         },
     };
     return request(options)
 };
 
 
-const post = (url, param = {}) => {
+const body = (url: string, param = {}) => {
     let token = uni.getStorageSync(cfg.key.token) || "";
     let options = {
         url: fillUrl(url),
@@ -95,8 +95,7 @@ const post = (url, param = {}) => {
             'X-Requested-With': 'XMLHttpRequest',
             "Accept": "application/json",
             "Content-Type": "application/json; charset=UTF-8",
-            "X-Token": token,
-            "X-Client-Type": source
+            "token": token,
         },
         dataType: 'json'
     };
@@ -104,7 +103,7 @@ const post = (url, param = {}) => {
 };
 
 
-const request = (options) => {
+const request = (options: any) => {
     return new Promise((resolve, reject) => {
         uni.request({
             url: options.url,
@@ -113,6 +112,7 @@ const request = (options) => {
             header: options.header,
             dataType: options.dataType
         }).then(res => {
+            // @ts-ignore
             let response = res[1].data;
             if (response.code !== 200) {
                 if (response.code == 92213 || response.code == 92305) {
@@ -149,7 +149,7 @@ const request = (options) => {
     });
 };
 
-const upload = opt => {
+const upload = (opt: any) => {
     opt = opt || {};
     opt.url = opt.url || '';
     opt.filePath = opt.filePath || null;//要上传文件资源的路径。
@@ -177,7 +177,7 @@ const upload = opt => {
 };
 
 
-const fillUrl = function (url) {
+const fillUrl = function (url: string) {
     if (url.indexOf("http") === 0) {
         return url;
     } else {
@@ -185,7 +185,7 @@ const fillUrl = function (url) {
     }
 };
 
-const formatUrl = v => {
+const formatUrl = (v: string) => {
     if (v == null || v == "") {
         return "/static/missing-face.png";
     }
@@ -196,5 +196,5 @@ const formatUrl = v => {
 };
 
 export {
-    get, post, upload, cfg, serverUrl, fileUrl,formatUrl
+    get, body, upload, cfg, serverUrl, fileUrl, formatUrl
 }