shuishen
2025-09-29 1681cdaa1074f57d49579a6ae2adb0140a166851
feat:移动端基础配置调整、登录页基础配置调整
9 files modified
1 files added
616 ■■■■■ changed files
env/.env 2 ●●● patch | view | raw | blame | history
env/.env.development 2 ●●● patch | view | raw | blame | history
env/.env.production 2 ●●● patch | view | raw | blame | history
env/.env.test 2 ●●● patch | view | raw | blame | history
package.json 4 ●●● patch | view | raw | blame | history
pnpm-lock.yaml 14 ●●●●● patch | view | raw | blame | history
src/api/user/index.js 45 ●●●●● patch | view | raw | blame | history
src/config/website.js 83 ●●●●● patch | view | raw | blame | history
src/pages/login/index.vue 259 ●●●● patch | view | raw | blame | history
src/utils/request/interceptors.js 203 ●●●● patch | view | raw | blame | history
env/.env
@@ -5,7 +5,7 @@
VITE_APP_ENV=development
# 接口地址
VITE_API_BASE_URL=http://localhost:8080
VITE_API_BASE_URL=https://wrj.shuixiongit.com/api
# 端口号
VITE_APP_PORT=9527
env/.env.development
@@ -2,7 +2,7 @@
VITE_APP_ENV=development
# 接口地址
VITE_API_BASE_URL=http://localhost:8080
VITE_API_BASE_URL=https://wrj.shuixiongit.com/api
# 删除console
VITE_DROP_CONSOLE=false
env/.env.production
@@ -2,7 +2,7 @@
VITE_APP_ENV=production
# 接口地址
VITE_API_BASE_URL=http://localhost:8080/prod
VITE_API_BASE_URL=https://wrj.shuixiongit.com/api
# 删除console
VITE_DROP_CONSOLE=true
env/.env.test
@@ -2,7 +2,7 @@
VITE_APP_ENV=staging
# 接口地址
VITE_API_BASE_URL=http://localhost:8080/staging
VITE_API_BASE_URL=https://wrj.shuixiongit.com/api
# 删除console
VITE_DROP_CONSOLE=true
package.json
@@ -84,7 +84,9 @@
    "uview-plus": "^3.5.41",
    "vue": "3.4.21",
    "vue-i18n": "9.1.9",
    "z-paging": "^2.8.8"
    "z-paging": "^2.8.8",
    "js-md5": "^0.7.3",
    "js-base64": "^3.7.4"
  },
  "devDependencies": {
    "@antfu/eslint-config": "5.0.0",
pnpm-lock.yaml
@@ -59,6 +59,12 @@
  dayjs:
    specifier: ^1.11.18
    version: 1.11.18
  js-base64:
    specifier: ^3.7.4
    version: 3.7.4
  js-md5:
    specifier: ^0.7.3
    version: 0.7.3
  leaflet:
    specifier: ^1.9.4
    version: 1.9.4
@@ -7795,6 +7801,14 @@
    resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==}
    dev: false
  /js-base64@3.7.4:
    resolution: {integrity: sha512-wpM/wi20Tl+3ifTyi0RdDckS4YTD4Lf953mBRrpG8547T7hInHNPEj8+ck4gB8VDcGyeAWFK++Wb/fU1BeavKQ==}
    dev: false
  /js-md5@0.7.3:
    resolution: {integrity: sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==}
    dev: false
  /js-tokens@4.0.0:
    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
src/api/user/index.js
@@ -1,14 +1,51 @@
import { get, post } from "@/utils/request"
import {
  get,
  post,
  request
} from "@/utils/request"
import website from '@/config/website'
/** 获取用户信息 */
export const profile = params => get("/user/profile", { params })
export const profile = params => get("/user/profile", {
  params
})
/** 登录 */
export const login = data =>
  post("/user/login", { data, custom: { auth: false } })
  post("/user/login", {
    data,
    custom: {
      auth: false
    }
  })
/** 验证码登录 */
export const loginByCode = data => post("/user/loginByCode", { data })
export const loginByCode = data => post("/user/loginByCode", {
  data
})
/** 退出登录 */
export const logout = () => post("/user/logout")
// 用户登录接口
export const loginByUsername = (tenantId, deptId, roleId, username, password, type, key, code) =>
  request({
    url: '/blade-auth/oauth/token',
    method: 'post',
    header: {
      'Tenant-Id': tenantId,
      'Dept-Id': website.switchMode ? deptId : '',
      'Role-Id': website.switchMode ? roleId : '',
      'Captcha-Key': key,
      'Captcha-Code': code,
    },
    params: {
      tenantId,
      username,
      password,
      grant_type: 'password',
      scope: 'all',
      type,
    },
  })
src/config/website.js
New file
@@ -0,0 +1,83 @@
/**
 * 全局配置文件
 */
export default {
    title: '',
    logo: 'S',
    key: 'saber', //配置主键,目前用于存储
    indexTitle: 'BladeX 微服务平台',
    clientId: 'drone', // 客户端id
    clientSecret: 'drone_secret', // 客户端密钥
    tenantMode: false, // 是否开启租户模式
    tenantId: '000000', // 管理组租户编号
    captchaMode: false, // 是否开启验证码模式
    switchMode: false, // 是否开启登录切换角色部门
    lockPage: '/lock',
    tokenTime: 3000,
    tokenHeader: 'Blade-Auth',
    //HTTP状态码白名单
    statusWhiteList: [],
    //配置首页不可关闭
    setting: {
        sidebar: 'vertical',
        tag: true,
        debug: true,
        collapse: true,
        search: true,
        color: true,
        lock: true,
        screenshot: true,
        fullscreen: true,
        theme: true,
        menu: true,
    },
    //首页配置
    fistPage: {
        // name: '首页',
        // path: '/wel/index',
        name: '门户页',
        path: '/wel/gatewayPage',
    },
    //配置菜单的属性
    menu: {
        iconDefault: 'icon-caidan',
        label: 'name',
        path: 'path',
        icon: 'source',
        children: 'children',
        query: 'query',
        href: 'path',
        meta: 'meta',
    },
    //水印配置
    watermark: {
        mode: false,
        text: 'BladeX',
    },
    //oauth2配置
    oauth2: {
        // 是否开启注册功能
        registerMode: true,
        // 使用后端工程 @org.springblade.test.Sm2KeyGenerator 获取
        publicKey: '请配置国密sm2公钥',
        // 第三方系统授权地址
        authUrl: 'http://localhost/blade-auth/oauth/render',
        // 单点登录系统认证
        ssoMode: false, // 是否开启单点登录功能
        ssoBaseUrl: 'http://localhost:8100', // 单点登录系统地址(cloud端口为8100,boot端口为80)
        ssoAuthUrl: '/oauth/authorize?client_id=saber3&response_type=code&redirect_uri=', // 单点登录授权地址
        ssoLogoutUrl: '/oauth/authorize/logout?redirect_uri=', // 单点登录退出地址
        redirectUri: 'http://localhost:2888/login', // 单点登录回调地址(Saber服务的登录界面地址)
    },
    //设计器配置
    design: {
        // 流程设计器类型(true->nutflow,false->flowable)
        designMode: true,
        // 流程设计器地址(flowable模式)
        designUrl: 'http://localhost:9999',
        // 报表设计器地址(cloud端口为8108,boot端口为80)
        reportUrl: 'http://localhost:8108/ureport',
        // 规则设计引擎地址
        edgeUrl: 'http://localhost:1880',
    },
}
src/pages/login/index.vue
@@ -3,19 +3,9 @@
  <view>
    <view class="login-form-wrap">
      <view class="title"> 欢迎登录 </view>
      <input
        v-model="tel"
        class="u-border-bottom"
        type="number"
        placeholder="请输入手机号"
      />
      <input v-model="tel" class="u-border-bottom" type="number" placeholder="请输入手机号" />
      <view class="u-border-bottom my-40rpx flex">
        <input
          v-model="code"
          class="flex-1"
          type="number"
          placeholder="请输入验证码"
        />
        <input v-model="code" class="flex-1" type="number" placeholder="请输入验证码" />
        <view>
          <u-code ref="uCodeRef" @change="codeChange" />
          <u-button :text="tips" type="success" size="mini" @click="getCode" />
@@ -55,133 +45,166 @@
</template>
<script setup>
import {
  HOME_PATH,
  isTabBarPath,
  LOGIN_PATH,
  removeQueryString,
} from "@/router";
import { setToken } from "@/utils/auth";
// import { useUserStore } from '@/store';
  import md5 from 'js-md5'
  import {
    loginByUsername
  } from '@/api/user/index.js'
  import {
    HOME_PATH,
    isTabBarPath,
    LOGIN_PATH,
    removeQueryString,
  } from "@/router";
  import {
    setToken
  } from "@/utils/auth";
  // import { useUserStore } from '@/store';
// const userStore = useUserStore();
const tel = ref("18502811111");
const code = ref("1234");
const tips = ref();
const uCodeRef = ref(null);
let redirect = HOME_PATH;
  // const userStore = useUserStore();
  const tel = ref("18502811111");
  const code = ref("1234");
  const tips = ref();
  const uCodeRef = ref(null);
  let redirect = HOME_PATH;
const inputStyle = computed(() => {
  const style = {};
  if (tel.value && code.value) {
    style.color = "#fff";
    style.backgroundColor = uni.$u.color.warning;
  const inputStyle = computed(() => {
    const style = {};
    if (tel.value && code.value) {
      style.color = "#fff";
      style.backgroundColor = uni.$u.color.warning;
    }
    return style;
  });
  function codeChange(text) {
    tips.value = text;
  }
  return style;
});
function codeChange(text) {
  tips.value = text;
}
function getCode() {
  if (uCodeRef.value?.canGetCode) {
    // 模拟向后端请求验证码
    uni.showLoading({
      title: "正在获取验证码",
    });
  function getCode() {
    if (uCodeRef.value?.canGetCode) {
      // 模拟向后端请求验证码
      uni.showLoading({
        title: "正在获取验证码",
      });
      setTimeout(() => {
        uni.hideLoading();
        uni.$u.toast("验证码已发送");
        // 通知验证码组件内部开始倒计时
        uCodeRef.value?.start();
      }, 1000);
    } else {
      uni.$u.toast("倒计时结束后再发送");
    }
  }
  async function submit() {
    if (!uni.$u.test.mobile(Number(tel.value))) {
      uni.$u.toast("请输入正确的手机号");
      return;
    }
    if (!code.value) {
      uni.$u.toast("请输入验证码");
      return;
    }
    // 登录请求
    // const res = await userStore.login({ phone: tel.value, code: code.value }).catch(() => {
    //   uni.$u.toast('登录失败');
    // });
    // if (!res) return;
    setToken("1234567890");
    setTimeout(() => {
      uni.hideLoading();
      uni.$u.toast("验证码已发送");
      // 通知验证码组件内部开始倒计时
      uCodeRef.value?.start();
    }, 1000);
  } else {
    uni.$u.toast("倒计时结束后再发送");
  }
}
async function submit() {
  if (!uni.$u.test.mobile(Number(tel.value))) {
    uni.$u.toast("请输入正确的手机号");
    return;
  }
  if (!code.value) {
    uni.$u.toast("请输入验证码");
    return;
  }
  // 登录请求
  // const res = await userStore.login({ phone: tel.value, code: code.value }).catch(() => {
  //   uni.$u.toast('登录失败');
  // });
  // if (!res) return;
  setToken("1234567890");
  setTimeout(() => {
    uni.$u.route({
      type: isTabBarPath(redirect) ? "switchTab" : "redirectTo",
      url: redirect,
    });
  }, 800);
}
      uni.$u.route({
        type: isTabBarPath(redirect) ? "switchTab" : "redirectTo",
        url: redirect,
      });
    }, 800);
onLoad((options) => {
  if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
    redirect = decodeURIComponent(options.redirect);
    let userInfo = {
      "tenantId": "000000",
      "deptId": "",
      "roleId": "",
      "username": "shuishen",
      "password": "Dashabi....",
      "type": "account",
      "code": "",
      "key": "",
      "image": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
    }
    loginByUsername(
      userInfo.tenantId,
      userInfo.deptId,
      userInfo.roleId,
      userInfo.username,
      md5(userInfo.password),
      userInfo.type,
      userInfo.key,
      userInfo.code
    ).then(res => {
      uni.switchTab({
        url: '/pages/map/index'
      })
    })
  }
});
  onLoad((options) => {
    if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
      redirect = decodeURIComponent(options.redirect);
    }
  });
</script>
<style lang="scss" scoped>
.login-form-wrap {
  @apply mt-80rpx mx-auto mb-0 w-600rpx;
  .login-form-wrap {
    @apply mt-80rpx mx-auto mb-0 w-600rpx;
  .title {
    @apply mb-100rpx text-60rpx text-left font-500;
  }
    .title {
      @apply mb-100rpx text-60rpx text-left font-500;
    }
  input {
    @apply pb-6rpx mb-10rpx text-left;
  }
    input {
      @apply pb-6rpx mb-10rpx text-left;
    }
  .tips {
    @apply mt-8rpx mb-60rpx;
    .tips {
      @apply mt-8rpx mb-60rpx;
    color: $u-info;
  }
      color: $u-info;
    }
  .login-btn {
    @apply flex items-center justify-center py-12rpx px-0 text-30rpx bg-#fdf3d0 border-none;
    .login-btn {
      @apply flex items-center justify-center py-12rpx px-0 text-30rpx bg-#fdf3d0 border-none;
    color: $u-tips-color;
      color: $u-tips-color;
    &::after {
      @apply border-none;
      &::after {
        @apply border-none;
      }
    }
    .alternative {
      @apply flex justify-between mt-30rpx;
      color: $u-tips-color;
    }
  }
  .alternative {
    @apply flex justify-between mt-30rpx;
  .login-type-wrap {
    @apply flex justify-between pt-350rpx px-150rpx pb-150rpx;
    .item {
      @apply flex items-center flex-col text-28rpx;
      color: $u-content-color;
    }
  }
  .hint {
    @apply px-40rpx py-20rpx text-24rpx;
    color: $u-tips-color;
    .link {
      color: $u-warning;
    }
  }
}
.login-type-wrap {
  @apply flex justify-between pt-350rpx px-150rpx pb-150rpx;
  .item {
    @apply flex items-center flex-col text-28rpx;
    color: $u-content-color;
  }
}
.hint {
  @apply px-40rpx py-20rpx text-24rpx;
  color: $u-tips-color;
  .link {
    color: $u-warning;
  }
}
</style>
src/utils/request/interceptors.js
@@ -1,8 +1,17 @@
import { useUserStore } from "@/store"
import { getToken } from "@/utils/auth"
import {
  useUserStore
} from "@/store"
import {
  getToken
} from "@/utils/auth"
import storage from "@/utils/storage"
import { showMessage } from "./status"
import {
  showMessage
} from "./status"
import website from '@/config/website'
import {
  Base64
} from 'js-base64'
// 重试队列,每一项将是一个待执行的函数形式
let requestQueue = []
@@ -10,10 +19,8 @@
const repeatSubmit = config => {
  const requestObj = {
    url: config.url,
    data:
      typeof config.data === "object"
        ? JSON.stringify(config.data)
        : config.data,
    data: typeof config.data === "object" ?
      JSON.stringify(config.data) : config.data,
    time: new Date().getTime()
  }
  const sessionObj = storage.getJSON("sessionObj")
@@ -66,106 +73,86 @@
  })
}
function requestInterceptors (http) {
  /**
   * 请求拦截
   * @param {object} http
   */
  http.interceptors.request.use(
    config => {
      // 可使用async await 做异步操作
      // 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
      config.data = config.data || {}
      // 自定义参数
      const custom = config?.custom
      // 是否需要设置 token
      const isToken = custom?.auth === false
      if (getToken() && !isToken && config.header) {
        // token设置
        config.header.token = getToken()
      }
      // 是否显示 loading
      if (custom?.loading) {
        uni.showLoading({
          title: "加载中",
          mask: true
        })
      }
      // 是否需要防止数据重复提交
      const isRepeatSubmit = custom?.repeatSubmit === false
      if (
        !isRepeatSubmit &&
        (config.method === "POST" || config.method === "UPLOAD")
      ) {
        repeatSubmit(config)
      }
      return config
    },
    (
      config // 可使用async await 做异步操作
    ) => Promise.reject(config)
  )
}
function responseInterceptors (http) {
  /**
   * 响应拦截
   * @param {object} http
   */
  http.interceptors.response.use(
    response => {
      /* 对响应成功做点什么 可使用async await 做异步操作 */
      const data = response.data
      // 配置参数
      const config = response.config
      // 自定义参数
      const custom = config?.custom
      // 登录状态失效,重新登录
      if (data.code === 401) {
        return refreshToken(http, config)
      }
      // 隐藏loading
      if (custom?.loading) {
        uni.hideLoading()
      }
      // 请求成功则返回结果
      if (data.code === 200) {
        return response || {}
      }
      // 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
      if (custom?.toast !== false) {
        uni.$u.toast(data.message)
      }
      // 请求失败则抛出错误
      return Promise.reject(data)
    },
    response => {
      // 自定义参数
      const custom = response.config?.custom
      // 隐藏loading
      if (custom?.loading !== false) {
        uni.hideLoading()
      }
      // 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
      if (custom?.toast !== false) {
        const message = response.statusCode
          ? showMessage(response.statusCode)
          : "网络连接异常,请稍后再试!"
        uni.$u.toast(message)
      }
      return Promise.reject(response)
function requestInterceptors(http) {
  http.interceptors.request.use((config) => { // 可使用async await 做异步操作
    // 假设有token值需要在头部需要携带
    let accessToken = uni.getStorageSync('accessToken');
    if (accessToken) {
      config.header['Blade-Auth'] = 'bearer ' + accessToken;
    }
  )
    // 安全请求header
    config.header['Blade-Requested-With'] = 'BladeHttpRequest';
    // 客户端认证参数
    config.header['Authorization'] = 'Basic ' + Base64.encode(website.clientId + ':' + website.clientSecret);
    // #ifndef H5
    let url = config.url
    if (process.env.NODE_ENV == 'development' && !url.startsWith("http")) {
      url = url.substring(url.indexOf('/api') + 4)
      config.url = url
    }
    // #endif
    // 额外参数
    // config.data = config.data || {};
    // config.data.pf = uni.getSystemInfoSync().platform;
    // config.data.sys = uni.getSystemInfoSync().system;
    // 演示custom 用处
    // if (config.custom.auth) {
    //   config.header.token = 'token'
    // }
    // if (config.custom.loading) {
    //  uni.showLoading()
    // }
    /**
     /* 演示
     if (!token) { // 如果token不存在,return Promise.reject(config) 会取消本次请求
        return Promise.reject(config)
      }
     **/
    return config
  }, config => { // 可使用async await 做异步操作
    return Promise.reject(config)
  })
}
export { requestInterceptors, responseInterceptors }
function responseInterceptors(http) {
  http.interceptors.response.use((response) => {
    console.log(response, 2222)
    // 若有数据返回则通过
    if (response.data.access_token || response.data.key) {
      return response.data
    }
    // 服务端返回的状态码不等于200,则reject()
    if (response.data.code !== 200) {
      uni.showToast({
        title: response.data.msg,
        icon: 'none'
      });
      return Promise.reject(response);
    }
    return response.data;
  }, (response) => {
    console.log(response, 111)
    /*  对响应错误做点什么 (statusCode !== 200)*/
    uni.showToast({
      title: response.data.msg,
      icon: 'none'
    });
    if (response.statusCode == 401) {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length - 1]
      uni.redirectTo({
        url: `/pages/login/login-account?redirect=/${currentPage.route}`
      })
    }
    return Promise.reject(response)
  })
}
export {
  requestInterceptors,
  responseInterceptors
}