Browse Source

弹窗登录

lifanagju_citu 8 months ago
parent
commit
36a849543f

+ 586 - 0
components/ui/ct-popup/index.vue

@@ -0,0 +1,586 @@
+<template>
+  <view
+    v-if="showPopup"
+    class="uni-popup"
+    :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']"
+    :style="[{ zIndex: zIndex }]"
+    @touchmove.stop.prevent="clear"
+  >
+    <view @touchstart="touchstart">
+      <uni-transition
+        key="1"
+        v-if="maskShow"
+        name="mask"
+        mode-class="fade"
+        :styles="maskClass"
+        :duration="duration"
+        :show="showTrans"
+        @click="onTap"
+      />
+      <uni-transition
+        key="2"
+        :mode-class="ani"
+        name="content"
+        :styles="{ ...transClass, ...borderRadius }"
+        :duration="duration"
+        :show="showTrans"
+        @click="onTap"
+      >
+        <view
+          v-if="showPopup"
+          class="uni-popup__wrapper"
+          :style="[{ backgroundColor: bg }, borderRadius]"
+          :class="[popupstyle]"
+          @click="clear"
+        >
+          <uni-icons
+            v-if="showClose"
+            class="close-icon"
+            color="#F6F6F6"
+            type="closeempty"
+            size="32"
+            @click="close"
+          ></uni-icons>
+          <slot />
+        </view>
+      </uni-transition>
+    </view>
+    <!-- #ifdef H5 -->
+    <keypress v-if="maskShow" @esc="onTap" />
+    <!-- #endif -->
+  </view>
+  <!-- #ifdef MP -->
+  <view v-else style="display: none">
+    <slot></slot>
+  </view>
+  <!-- #endif -->
+</template>
+
+<script>
+  // #ifdef H5
+  import keypress from './keypress.js';
+  // #endif
+
+  /**
+   * PopUp 弹出层
+   * @description 弹出层组件,为了解决遮罩弹层的问题
+   * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+   * @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
+   * 	@value top 顶部弹出
+   * 	@value center 中间弹出
+   * 	@value bottom 底部弹出
+   * 	@value left		左侧弹出
+   * 	@value right  右侧弹出
+   * 	@value message 消息提示
+   * 	@value dialog 对话框
+   * 	@value share 底部分享示例
+   * @property {Boolean} animation = [true|false] 是否开启动画
+   * @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
+   * @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
+   * @property {String}  backgroundColor 主窗口背景色
+   * @property {String}  maskBackgroundColor 蒙版颜色
+   * @property {Boolean} safeArea		   是否适配底部安全区
+   * @event {Function} change 打开关闭弹窗触发,e={show: false}
+   * @event {Function} maskClick 点击遮罩触发
+   */
+
+  export default {
+    name: 'SuPopup',
+    components: {
+      // #ifdef H5
+      keypress,
+      // #endif
+    },
+    emits: ['change', 'maskClick', 'close'],
+    props: {
+      // 开启状态
+      show: {
+        type: Boolean,
+        default: false,
+      },
+      // 顶部,底部时有效
+      space: {
+        type: Number,
+        default: 0,
+      },
+      // 默认圆角
+      round: {
+        type: [String, Number],
+        default: 0,
+      },
+      // 是否显示关闭
+      showClose: {
+        type: Boolean,
+        default: false,
+      },
+      // 开启动画
+      animation: {
+        type: Boolean,
+        default: true,
+      },
+      // 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+      // message: 消息提示 ; dialog : 对话框
+      type: {
+        type: String,
+        default: 'bottom',
+      },
+      // maskClick
+      isMaskClick: {
+        type: Boolean,
+        default: null,
+      },
+      // TODO 2 个版本后废弃属性 ,使用 isMaskClick
+      maskClick: {
+        type: Boolean,
+        default: null,
+      },
+      // 可设置none
+      backgroundColor: {
+        type: String,
+        default: '#ffffff',
+      },
+      backgroundImage: {
+        type: String,
+        default: '',
+      },
+      safeArea: {
+        type: Boolean,
+        default: true,
+      },
+      maskBackgroundColor: {
+        type: String,
+        default: 'rgba(0, 0, 0, 0.4)',
+      },
+      zIndex: {
+        type: [String, Number],
+        default: 10075,
+      },
+    },
+
+    watch: {
+      show: {
+        handler: function (newValue, oldValue) {
+          if (typeof oldValue === 'undefined' && !newValue) {
+            return;
+          }
+          if (newValue) {
+            this.open();
+          } else {
+            this.close();
+          }
+        },
+        immediate: true,
+      },
+      /**
+       * 监听type类型
+       */
+      type: {
+        handler: function (type) {
+          if (!this.config[type]) return;
+          this[this.config[type]](true);
+        },
+        immediate: true,
+      },
+      isDesktop: {
+        handler: function (newVal) {
+          if (!this.config[newVal]) return;
+          this[this.config[this.type]](true);
+        },
+        immediate: true,
+      },
+      /**
+       * 监听遮罩是否可点击
+       * @param {Object} val
+       */
+      maskClick: {
+        handler: function (val) {
+          this.mkclick = val;
+        },
+        immediate: true,
+      },
+      isMaskClick: {
+        handler: function (val) {
+          this.mkclick = val;
+        },
+        immediate: true,
+      },
+      // H5 下禁止底部滚动
+      showPopup(show) {
+        // #ifdef H5
+        // fix by mehaotian 处理 h5 滚动穿透的问题
+        document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible';
+        // #endif
+      },
+    },
+    data() {
+      return {
+        duration: 300,
+        ani: [],
+        showPopup: false,
+        showTrans: false,
+        popupWidth: 0,
+        popupHeight: 0,
+        config: {
+          top: 'top',
+          bottom: 'bottom',
+          center: 'center',
+          left: 'left',
+          right: 'right',
+          message: 'top',
+          dialog: 'center',
+          share: 'bottom',
+        },
+        maskClass: {
+          position: 'fixed',
+          bottom: 0,
+          top: 0,
+          left: 0,
+          right: 0,
+          backgroundColor: 'rgba(0, 0, 0, 0.4)',
+        },
+        transClass: {
+          position: 'fixed',
+          left: 0,
+          right: 0,
+        },
+        maskShow: true,
+        mkclick: true,
+        popupstyle: this.isDesktop ? 'fixforpc-top' : 'top',
+      };
+    },
+    computed: {
+      isDesktop() {
+        return this.popupWidth >= 500 && this.popupHeight >= 500;
+      },
+      bg() {
+        if (this.backgroundColor === '' || this.backgroundColor === 'none') {
+          return 'transparent';
+        }
+        return this.backgroundColor;
+      },
+      borderRadius() {
+        if (this.round) {
+          if (this.type === 'bottom') {
+            return {
+              'border-top-left-radius': parseFloat(this.round) + 'px',
+              'border-top-right-radius': parseFloat(this.round) + 'px',
+            };
+          }
+          if (this.type === 'center') {
+            return {
+              'border-top-left-radius': parseFloat(this.round) + 'px',
+              'border-top-right-radius': parseFloat(this.round) + 'px',
+              'border-bottom-left-radius': parseFloat(this.round) + 'px',
+              'border-bottom-right-radius': parseFloat(this.round) + 'px',
+            };
+          }
+          if (this.type === 'top') {
+            return {
+              'border-bottom-left-radius': parseFloat(this.round) + 'px',
+              'border-bottom-right-radius': parseFloat(this.round) + 'px',
+            };
+          }
+        }
+      },
+    },
+    mounted() {
+      const fixSize = () => {
+        const { windowWidth, windowHeight, windowTop, safeArea, screenHeight, safeAreaInsets } = uni.getSystemInfoSync() || {} // sheep.$platform.device;
+        this.popupWidth = windowWidth;
+        this.popupHeight = windowHeight + (windowTop || 0);
+        // TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
+        if (safeArea && this.safeArea) {
+          // #ifdef MP-WEIXIN
+          this.safeAreaInsets = screenHeight - safeArea.bottom;
+          // #endif
+          // #ifndef MP-WEIXIN
+          this.safeAreaInsets = safeAreaInsets.bottom;
+          // #endif
+        } else {
+          this.safeAreaInsets = 0;
+        }
+      };
+      fixSize();
+      // #ifdef H5
+      // window.addEventListener('resize', fixSize)
+      // this.$once('hook:beforeDestroy', () => {
+      // 	window.removeEventListener('resize', fixSize)
+      // })
+      // #endif
+    },
+    // #ifndef VUE3
+    // TODO vue2
+    destroyed() {
+      this.setH5Visible();
+    },
+    // #endif
+    // #ifdef VUE3
+    // TODO vue3
+    unmounted() {
+      this.setH5Visible();
+    },
+    // #endif
+    created() {
+      // this.mkclick =  this.isMaskClick || this.maskClick
+      if (this.isMaskClick === null && this.maskClick === null) {
+        this.mkclick = true;
+      } else {
+        this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick;
+      }
+      if (this.animation) {
+        this.duration = 300;
+      } else {
+        this.duration = 0;
+      }
+      // TODO 处理 message 组件生命周期异常的问题
+      this.messageChild = null;
+      // TODO 解决头条冒泡的问题
+      this.clearPropagation = false;
+      this.maskClass.backgroundColor = this.maskBackgroundColor;
+    },
+    methods: {
+      setH5Visible() {
+        // #ifdef H5
+        // fix by mehaotian 处理 h5 滚动穿透的问题
+        document.getElementsByTagName('body')[0].style.overflow = 'visible';
+        // #endif
+      },
+      /**
+       * 公用方法,不显示遮罩层
+       */
+      closeMask() {
+        this.maskShow = false;
+      },
+      /**
+       * 公用方法,遮罩层禁止点击
+       */
+      disableMask() {
+        this.mkclick = false;
+      },
+      // TODO nvue 取消冒泡
+      clear(e) {
+        // #ifndef APP-NVUE
+        e.stopPropagation();
+        // #endif
+        this.clearPropagation = true;
+      },
+
+      open(direction) {
+        // fix by mehaotian 处理快速打开关闭的情况
+        if (this.showPopup) {
+          clearTimeout(this.timer);
+          this.showPopup = false;
+        }
+        let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share'];
+        if (!(direction && innerType.indexOf(direction) !== -1)) {
+          direction = this.type;
+        }
+        if (!this.config[direction]) {
+          console.error('缺少类型:', direction);
+          return;
+        }
+        this[this.config[direction]]();
+        this.$emit('change', {
+          show: true,
+          type: direction,
+        });
+      },
+      close(type) {
+        this.showTrans = false;
+        this.$emit('change', {
+          show: false,
+          type: this.type,
+        });
+        this.$emit('close');
+        clearTimeout(this.timer);
+        // // 自定义关闭事件
+        // this.customOpen && this.customClose()
+        this.timer = setTimeout(() => {
+          this.showPopup = false;
+        }, 300);
+      },
+      // TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+      touchstart() {
+        this.clearPropagation = false;
+      },
+
+      onTap() {
+        if (this.clearPropagation) {
+          // fix by mehaotian 兼容 nvue
+          this.clearPropagation = false;
+          return;
+        }
+        this.$emit('maskClick');
+        if (!this.mkclick) return;
+        this.close();
+      },
+      /**
+       * 顶部弹出样式处理
+       */
+      top(type) {
+        this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top';
+        this.ani = ['slide-top'];
+        this.transClass = {
+          position: 'fixed',
+          left: 0,
+          right: 0,
+          top: this.space + 'px',
+          backgroundColor: this.bg,
+        };
+        // TODO 兼容 type 属性 ,后续会废弃
+        if (type) return;
+        this.showPopup = true;
+        this.showTrans = true;
+        this.$nextTick(() => {
+          if (this.messageChild && this.type === 'message') {
+            this.messageChild.timerClose();
+          }
+        });
+      },
+      /**
+       * 底部弹出样式处理
+       */
+      bottom(type) {
+        this.popupstyle = 'bottom';
+        this.ani = ['slide-bottom'];
+        this.transClass = {
+          position: 'fixed',
+          left: 0,
+          right: 0,
+          bottom: 0,
+          paddingBottom: this.safeAreaInsets + this.space + 'px',
+          backgroundColor: this.bg,
+        };
+        // TODO 兼容 type 属性 ,后续会废弃
+        if (type) return;
+        this.showPopup = true;
+        this.showTrans = true;
+      },
+      /**
+       * 中间弹出样式处理
+       */
+      center(type) {
+        this.popupstyle = 'center';
+        this.ani = ['zoom-out', 'fade'];
+        this.transClass = {
+          position: 'fixed',
+          /* #ifndef APP-NVUE */
+          display: 'flex',
+          flexDirection: 'column',
+          /* #endif */
+          bottom: 0,
+          left: 0,
+          right: 0,
+          top: 0,
+          justifyContent: 'center',
+          alignItems: 'center',
+        };
+        // TODO 兼容 type 属性 ,后续会废弃
+        if (type) return;
+        this.showPopup = true;
+        this.showTrans = true;
+      },
+      left(type) {
+        this.popupstyle = 'left';
+        this.ani = ['slide-left'];
+        this.transClass = {
+          position: 'fixed',
+          left: 0,
+          bottom: 0,
+          top: 0,
+          backgroundColor: this.bg,
+          /* #ifndef APP-NVUE */
+          display: 'flex',
+          flexDirection: 'column',
+          /* #endif */
+        };
+        // TODO 兼容 type 属性 ,后续会废弃
+        if (type) return;
+        this.showPopup = true;
+        this.showTrans = true;
+      },
+      right(type) {
+        this.popupstyle = 'right';
+        this.ani = ['slide-right'];
+        this.transClass = {
+          position: 'fixed',
+          bottom: 0,
+          right: 0,
+          top: 0,
+          backgroundColor: this.bg,
+          /* #ifndef APP-NVUE */
+          display: 'flex',
+          flexDirection: 'column',
+          /* #endif */
+        };
+        // TODO 兼容 type 属性 ,后续会废弃
+        if (type) return;
+        this.showPopup = true;
+        this.showTrans = true;
+      },
+    },
+  };
+</script>
+<style lang="scss">
+  // 关闭icon
+  .close-icon {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: -80rpx;
+    z-index: 100;
+  }
+
+  .uni-popup {
+    position: fixed;
+    /* #ifndef APP-NVUE */
+    z-index: 99;
+
+    /* #endif */
+    &.top,
+    &.left,
+    &.right {
+      /* #ifdef H5 */
+      top: var(--window-top);
+      /* #endif */
+      /* #ifndef H5 */
+      top: 0;
+      /* #endif */
+    }
+
+    .uni-popup__wrapper {
+      /* #ifndef APP-NVUE */
+      display: block;
+      /* #endif */
+      position: relative;
+      background: v-bind(backgroundImage) no-repeat;
+      background-size: 100% 100%;
+
+      /* iphonex 等安全区设置,底部安全区适配 */
+      /* #ifndef APP-NVUE */
+      // padding-bottom: constant(safe-area-inset-bottom);
+      // padding-bottom: env(safe-area-inset-bottom);
+      /* #endif */
+      &.left,
+      &.right {
+        /* #ifdef H5 */
+        padding-top: var(--window-top);
+        /* #endif */
+        /* #ifndef H5 */
+        padding-top: 0;
+        /* #endif */
+        flex: 1;
+      }
+    }
+  }
+
+  .fixforpc-z-index {
+    /* #ifndef APP-NVUE */
+    z-index: 999;
+    /* #endif */
+  }
+
+  .fixforpc-top {
+    top: 0;
+  }
+</style>

+ 45 - 0
components/ui/ct-popup/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  mounted() {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del'],
+    };
+    const listener = ($event) => {
+      if (this.disable) {
+        return;
+      }
+      const keyName = Object.keys(keyNames).find((key) => {
+        const keyName = $event.key;
+        const value = keyNames[key];
+        return value === keyName || (Array.isArray(value) && value.includes(keyName));
+      });
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {});
+        }, 0);
+      }
+    };
+    document.addEventListener('keyup', listener);
+    // this.$once('hook:beforeDestroy', () => {
+    //   document.removeEventListener('keyup', listener)
+    // })
+  },
+  render: () => {},
+};
+// #endif

+ 59 - 0
hooks/useModal.js

@@ -0,0 +1,59 @@
+import { modalStore } from '@/store/modal'; const modal = modalStore()
+
+// 隐藏TabBar
+const hideBar = () => {
+  uni.hideTabBar({
+    success: () => {},
+    fail: () => {} // 捕获报错,防止没有tabbar页面调用后控制台报错
+  }) 
+}
+// 显示TabBar
+const showBar = () => {
+  uni.showTabBar({
+    success: () => {},
+    fail: () => {} // 捕获报错,防止没有tabbar页面调用后控制台报错
+  }) 
+}
+
+// 打开授权弹框
+export function showAuthModal(type = 'login') {
+  if (modal.auth !== '') {
+    // 注意:延迟修改,保证下面的 closeAuthModal 先执行掉
+    setTimeout(() => {
+      hideBar()
+      modal.$patch((state) => {
+        state.auth = type
+      })
+    }, 500)
+    closeAuthModal()
+  } else {
+    hideBar()
+    modal.$patch((state) => {
+      state.auth = type
+    })
+  }
+}
+
+// 关闭授权弹框
+export function closeAuthModal() {
+  showBar()
+  modal.$patch((state) => {
+    state.auth = ''
+  })
+}
+
+// 打开分享弹框
+export function showShareModal() {
+  hideBar()
+  modal.$patch((state) => {
+    state.share = true
+  })
+}
+
+// 关闭分享弹框
+export function closeShareModal() {
+  showBar()
+  modal.$patch((state) => {
+    state.share = false
+  })
+}

+ 25 - 0
layout/components/auth-modal.vue

@@ -0,0 +1,25 @@
+<!--  -->
+<template>
+  <ct-popup :show="authType !== ''" round="10" :showClose="true" @close="closeAuthModal">
+    <login></login>
+  </ct-popup>
+</template>
+
+<script setup>
+import { closeAuthModal } from '@/hooks/useModal'
+import ctPopup from '@/components/ui/ct-popup'
+import { modalStore } from '@/store/modal';
+import login from './authModal/login'
+
+
+const modal = modalStore()
+import { computed } from 'vue'
+
+
+// 授权弹窗类型
+const authType = computed(() => modal.auth)
+
+
+</script>
+<style lang="scss" scoped>
+</style>

+ 136 - 0
layout/components/authModal/login/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <view class="ss-p-30 head-box">
+    <view class="head-title">欢迎来到门墩儿招聘</view>
+    <uni-segmented-control class="ss-m-t-60" :current="current" :values="items" style-type="text" active-color="#00897B" @clickItem="onClickItem" />
+    <view class="head-subtitle ss-m-t-10">未注册的手机号,验证后自动注册账号</view>
+
+    <view class="ss-m-t-30">
+      <!-- 短信验证码登录 -->
+      <uni-forms
+        v-if="current === 0"
+        ref="smsLoginRef"
+        v-model="state.sms"
+        :rules="state.smsRules"
+        validateTrigger="bind"
+        labelWidth="140"
+        labelAlign="center"
+      >
+        <uni-forms-item name="phone" label="手机号">
+          <uni-easyinput placeholder="请输入手机号" v-model="state.sms.phone" :inputBorder="false" type="number">
+            <template v-slot:right>
+              <button class="login-code" :disabled="state.isMobileEnd" :class="{ 'code-btn-end': state.isMobileEnd }" @tap="handleCode">
+                {{ getSmsTimer('smsLogin') }}
+              </button>
+            </template>
+          </uni-easyinput>
+        </uni-forms-item>
+
+        <uni-forms-item name="code" label="验证码">
+          <uni-easyinput placeholder="请输入验证码" v-model="state.sms.code" :inputBorder="false" type="number" maxlength="6"></uni-easyinput>
+        </uni-forms-item>
+      </uni-forms>
+
+      <!-- 账号密码登录 -->
+      <uni-forms
+        v-else
+        ref="accountLoginRef"
+        v-model="state.account"
+        :rules="state.rules"
+        validateTrigger="bind"
+        labelWidth="140"
+        labelAlign="center"
+      >
+        <uni-forms-item name="phone" label="账号">
+          <uni-easyinput placeholder="请输入账号" v-model="state.account.phone" :inputBorder="false"></uni-easyinput>
+        </uni-forms-item>
+
+        <uni-forms-item name="password" label="密码">
+          <uni-easyinput type="password" placeholder="请输入密码" v-model="state.account.password" :inputBorder="false"></uni-easyinput>
+        </uni-forms-item>
+      </uni-forms>
+
+      <button class="send-button" @tap="handleLogin"> 登录/注册 </button>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, unref } from 'vue'
+import { mobile, password, code } from '@/utils/validate'
+import { getSmsCode, getSmsTimer } from '@/utils/code'
+import { userStore } from '@/store/user'
+
+const useUserStore = userStore()
+const items = ['短信登录', '账号登录']
+const current = ref(1)
+const accountLoginRef = ref()
+const smsLoginRef = ref()
+const state = ref({
+  isMobileEnd: false, // 手机号输入完毕
+  codeText: '获取验证码',
+  sms: {
+    phone: '13229740092',
+    code: ''
+  },
+  account: {
+    phone: '13229740091',
+    password: 'Citu123'
+  },
+  rules: {
+    phone: mobile,
+    password,
+  },
+  smsRules: {
+    code,
+    phone: mobile
+  }
+})
+
+// 登录成功后的返回页面
+// const backUrl = getCurrentPages().length ? getCurrentPages()[getCurrentPages().length - 2].route : ''
+
+const onClickItem = (e) => {
+  current.value = e.currentIndex
+}
+
+// 获取验证码
+const handleCode = () => {
+  if (!state.value.sms.phone) {
+    uni.showToast({
+      title: '请输入手机号',
+      icon: 'none',
+      duration: 2000
+    })
+    return
+  }
+  getSmsCode('smsLogin', state.value.sms.phone)
+}
+
+// 登录
+const handleLogin = async () => {
+  const validate = await unref(current.value === 0 ? smsLoginRef : accountLoginRef).validate()
+  if (!validate) return
+  await useUserStore.handleSmsLogin(current.value === 0 ? state.value.sms : state.value.account, current.value === 0 ? true : false)
+}
+</script>
+
+<style scoped lang="scss">
+.login-code {
+  width: 73px;
+  min-width: 73px;
+  color: #00897B;
+  text-align: center; 
+  font-size: 12px; 
+  cursor: pointer;
+  border: 1px dashed #00897B;
+  border-radius: 26px;
+  padding: 0;
+}
+.head-title {
+  font-size: 40rpx;
+  text-align: center;
+  color: #00897B;
+  margin-top: 30rpx;
+  margin-bottom: 100rpx;
+}
+</style>

+ 52 - 0
layout/index.vue

@@ -0,0 +1,52 @@
+<!--  -->
+<template>
+  <view class="page-app" >
+    <view class="page-main">
+      <view class="page-body">
+        <!-- 页面内容插槽 -->
+        <slot />
+      </view>
+    </view>
+    
+    <view class="page-modal">
+      <!-- 全局授权弹窗 -->
+      <authModal />
+      <!-- 全局分享弹窗 -->
+      <!-- <s-share-modal :shareInfo="shareInfo" /> -->
+      <!-- 全局快捷入口 -->
+      <!-- <s-menu-tools /> -->
+    </view>
+  </view>
+</template>
+
+<script setup>
+import authModal from './components/auth-modal.vue'
+
+</script>
+<style lang="scss" scoped>
+.page-app {
+  position: relative;
+  // color: var(--ui-TC);
+  // background-color: var(--ui-BG-1) !important;
+  z-index: 2;
+  display: flex;
+  width: 100%;
+  height: 100vh;
+
+  .page-main {
+    position: absolute;
+    z-index: 1;
+    width: 100%;
+    min-height: 100%;
+    display: flex;
+    flex-direction: column;
+
+    .page-body {
+      width: 100%;
+      position: relative;
+      z-index: 1;
+      flex: 1;
+    }
+  }
+}
+</style>

+ 40 - 40
pages/index/my.vue

@@ -1,42 +1,44 @@
 <template>
-  <view class="ss-p-b-30">
-    <view class="text-center">
-      <img :src="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)" alt="" class="img-box">
-      <view v-if="!useUserStore.isLogin" class="font-weight-bold font-size-20" @tap="handleLogin">点击登录</view>
-      <view v-else class="font-weight-bold font-size-20">{{ baseInfo?.name || userInfo?.phone }}</view>
-    </view>
-    <view class="d-flex" style="margin-top: 80rpx;">
-        <view v-for="(item, index) in itemList" :key="index" @tap="handleToLink(item)" class="parent">
-					<view class="d-flex justify-space-between">
-						<view>
-							<view class="colors">
-								<span>查看更多</span>
-								<uni-icons color="#c8c5c4" type="right" size="15" class="ml"/>
+	<layout-page>
+		<view class="ss-p-b-30">
+			<view class="text-center">
+				<img :src="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)" alt="" class="img-box">
+				<view v-if="!useUserStore.isLogin" class="font-weight-bold font-size-20" @tap="handleLogin">点击登录</view>
+				<view v-else class="font-weight-bold font-size-20">{{ baseInfo?.name || userInfo?.phone }}</view>
+			</view>
+			<view class="d-flex" style="margin-top: 80rpx;">
+					<view v-for="(item, index) in itemList" :key="index" @tap="handleToLink(item)" class="parent">
+						<view class="d-flex justify-space-between">
+							<view>
+								<view class="colors">
+									<span>查看更多</span>
+									<uni-icons color="#c8c5c4" type="right" size="15" class="ml"/>
+								</view>
+								<view class="size-16">{{ item.title }}</view>
+							</view>
+							<view>
+								<uni-icons color="#00897B" :type="item.icon" size="45"/>
 							</view>
-							<view class="size-16">{{ item.title }}</view>
-						</view>
-						<view>
-							<uni-icons color="#00897B" :type="item.icon" size="45"/>
 						</view>
 					</view>
 				</view>
-      </view>
-
-      <view style="height: 10rpx; background-color: #f8f8fa;"></view>
-
-      <view class="card">
-      <uni-list>
-        <uni-list-item v-for="item in list" :clickable="true" :key="item.title" :title="item.title" showArrow :rightText="item.rightTex || ''" @click="handleToLink(item)"></uni-list-item>
-      </uni-list>
-    </view>
-
-    <button v-if="useUserStore.isLogin" class="send-button" @tap="handleLogout">退出登录</button>
-
-    <uni-popup ref="popup" type="dialog">
-			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确定" title="系统提示" content="确认退出账号?" @confirm="handleLogoutConfirm"
-				@close="handleLogoutClose"></uni-popup-dialog>
-		</uni-popup>
-  </view>
+	
+				<view style="height: 10rpx; background-color: #f8f8fa;"></view>
+	
+				<view class="card">
+				<uni-list>
+					<uni-list-item v-for="item in list" :clickable="true" :key="item.title" :title="item.title" showArrow :rightText="item.rightTex || ''" @click="handleToLink(item)"></uni-list-item>
+				</uni-list>
+			</view>
+	
+			<button v-if="useUserStore.isLogin" class="send-button" @tap="handleLogout">退出登录</button>
+	
+			<uni-popup ref="popup" type="dialog">
+				<uni-popup-dialog type="warn" cancelText="取消" confirmText="确定" title="系统提示" content="确认退出账号?" @confirm="handleLogoutConfirm"
+					@close="handleLogoutClose"></uni-popup-dialog>
+			</uni-popup>
+		</view>
+	</layout-page>
 </template>
 
 <script setup>
@@ -44,6 +46,8 @@ import { ref, computed } from 'vue'
 import { userStore } from '@/store/user'
 import { getUserAvatar } from '@/utils/avatar'
 import { getAccessToken } from '@/utils/request'
+import layoutPage from '@/layout'
+import { showAuthModal } from '@/hooks/useModal'
 
 const useUserStore = userStore()
 const baseInfo = computed(() => useUserStore?.baseInfo)
@@ -75,9 +79,7 @@ const handleToLink = (item) => {
 		uni.showToast({
 			title: '请先登录'
 		})
-		uni.navigateTo({
-			url: '/pages/login/index'
-		})
+		showAuthModal()
 		return
 	}
 	uni.navigateTo({
@@ -87,9 +89,7 @@ const handleToLink = (item) => {
 
 // 登录
 const handleLogin = () => {
-  uni.navigateTo({
-    url: '/pages/login/index'
-  })
+	showAuthModal()
 }
 
 // 退出登录

+ 9 - 3
pagesB/positionDetail/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <view>
+  <layout-page>
     <scroll-view class="scrollBox" style="position:relative;">
       <view class="box">
         <view v-if="loading" class="vertical80-center">{{ loadingText }}</view>
@@ -141,7 +141,7 @@
 				@close="uploadPopup.close()"
       ></uni-popup-dialog>
 		</uni-popup>
-  </view>
+  </layout-page>
 </template>
 
 <script setup>
@@ -149,6 +149,7 @@ import { commissionCalculation } from '@/utils/position'
 import { timesTampChange } from '@/utils/date'
 import { preview } from '@/utils/preview'
 import { uploadFileTest } from '@/api/file'
+import layoutPage from '@/layout'
 import { ref } from 'vue';
 import {
   jobCvRelSend,
@@ -207,6 +208,7 @@ const deliveryCheck = async () => {
   }
 }
 
+import { showAuthModal } from '@/hooks/useModal'
 const popup = ref()
 const uploadPopup = ref()
 const resumeList = ref([])
@@ -214,7 +216,11 @@ const selectIndex = ref(null)
 const handleDelivery = async () => {
   // 未登录
   if (!getAccessToken()) {
-    uni.showToast({ title: '您还未上传过简历,请先上传简历', icon: 'none', duration: 2000, })
+    uni.showToast({
+      title:'请先登录',
+      icon: 'none'
+    })
+    showAuthModal()
     return
   }
   // 已投递

+ 1 - 0
store/modal.js

@@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
 export const modalStore = defineStore({
   id: 'modal',
   state: () => ({
+    auth: '', // 授权弹框 login|resetPassword|changeMobile|changePassword|changeUsername
     lastTimer: {
       // 短信验证码计时器,为了防止刷新请求做了持久化
       smsLogin: 0,

+ 5 - 3
store/user.js

@@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
 import { clone, cloneDeep } from 'lodash-es';
 import { getBaseInfo, getUserInfo } from '@/api/user';
 import { smsLogin, passwordLogin, logout } from '@/api/common'
+import { closeAuthModal } from '@/hooks/useModal'
 
 // 默认用户信息
 const defaultBaseInfo = {
@@ -50,6 +51,7 @@ export const userStore = defineStore({
       this.accountInfo = data
       this.getInfo()
       this.getUserInfo()
+      closeAuthModal()
 
       // 登录成功后的跳转地址
       // if (tabUrl.includes(route)) {
@@ -61,9 +63,9 @@ export const userStore = defineStore({
       //     url: '/' + route
       //   })
       // }
-      uni.switchTab({
-        url: '/pages/index/position'
-      })
+      // uni.switchTab({
+      //   url: '/pages/index/position'
+      // })
     },
     // 获取人才信息
     async getInfo() {

+ 3 - 6
utils/request.js

@@ -7,6 +7,7 @@ import Request from 'luch-request';
 import { refreshToken } from '@/api/common';
 import { userStore } from '@/store/user'
 import { baseUrl, tenantId, apiPath } from './config'
+import { showAuthModal } from '@/hooks/useModal'
 import qs from 'qs'
 
 const options = {
@@ -76,9 +77,7 @@ http.interceptors.request.use(
 				title:'请先登录',
 				icon: 'none'
 			})
-			uni.navigateTo({
-				url: '/pages/login/index'
-			})
+			showAuthModal()
 			return Promise.reject();
 		}
 
@@ -297,9 +296,7 @@ const handleAuthorized = () => {
 		title:'请先登录',
 		icon: 'none'
 	})
-	uni.navigateTo({
-		url: '/pages/login/index'
-	})
+	showAuthModal()
   // 登录超时
   return Promise.reject({
     code: 401,