refactor: 更新应用配置与样式依赖
This commit is contained in:
@@ -7,7 +7,8 @@ VITE_APP_PORT = 4096
|
|||||||
VITE_APP_BASE_API = '/dev-api'
|
VITE_APP_BASE_API = '/dev-api'
|
||||||
|
|
||||||
# API 服务器的 URL
|
# API 服务器的 URL
|
||||||
VITE_APP_API_URL = http://localhost:8000
|
# VITE_APP_API_URL = http://localhost:8000
|
||||||
|
VITE_APP_API_URL = https://api.youlai.tech
|
||||||
|
|
||||||
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
# WebSocket 服务器的 URL,不配置默认关闭 WebSocket
|
||||||
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"stylelint-config-recommended-scss": "^14.1.0",
|
"stylelint-config-recommended-scss": "^14.1.0",
|
||||||
"stylelint-config-recommended-vue": "^1.6.1",
|
"stylelint-config-recommended-vue": "^1.6.1",
|
||||||
"stylelint-prettier": "^5.0.3",
|
"stylelint-prettier": "^5.0.3",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^5.7.0",
|
||||||
"typescript-eslint": "^8.46.0",
|
"typescript-eslint": "^8.46.0",
|
||||||
"uni-mini-router": "^0.1.6",
|
"uni-mini-router": "^0.1.6",
|
||||||
"unocss": "^0.62.4",
|
"unocss": "^0.62.4",
|
||||||
@@ -141,6 +141,6 @@
|
|||||||
"unplugin-auto-import": "^0.18.6",
|
"unplugin-auto-import": "^0.18.6",
|
||||||
"vite": "6.3.2",
|
"vite": "6.3.2",
|
||||||
"vue-eslint-parser": "^9.4.3",
|
"vue-eslint-parser": "^9.4.3",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default defineUniPages({
|
|||||||
globalStyle: {
|
globalStyle: {
|
||||||
navigationBarBackgroundColor: "@navBgColor",
|
navigationBarBackgroundColor: "@navBgColor",
|
||||||
navigationBarTextStyle: "@navTxtStyle",
|
navigationBarTextStyle: "@navTxtStyle",
|
||||||
navigationBarTitleText: "vue-uniapp-template",
|
navigationBarTitleText: "youlai-app",
|
||||||
backgroundColor: "@bgColor",
|
backgroundColor: "@bgColor",
|
||||||
backgroundTextStyle: "@bgTxtStyle",
|
backgroundTextStyle: "@bgTxtStyle",
|
||||||
backgroundColorTop: "@bgColorTop",
|
backgroundColorTop: "@bgColorTop",
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onLaunch, onShow } from "@dcloudio/uni-app";
|
||||||
|
import { useThemeStore } from "@/store";
|
||||||
|
|
||||||
|
onLaunch(() => {
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
themeStore.initTheme();
|
||||||
|
});
|
||||||
|
|
||||||
onShow(() => {});
|
onShow(() => {});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getToken } from "@/utils/storage";
|
import { getAccessToken } from "@/utils/auth";
|
||||||
import { ApiCode } from "@/enums/api-code-enum";
|
import { ApiCode } from "@/enums/api-code-enum";
|
||||||
|
|
||||||
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径
|
// H5 使用 VITE_APP_BASE_API 作为代理路径,其他平台使用 VITE_APP_API_URL 作为请求路径
|
||||||
@@ -25,7 +25,7 @@ const FileAPI = {
|
|||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
name: "file",
|
name: "file",
|
||||||
header: {
|
header: {
|
||||||
Authorization: getToken() ? `Bearer ${getToken()}` : "",
|
Authorization: getAccessToken() ? `Bearer ${getAccessToken()}` : "",
|
||||||
},
|
},
|
||||||
formData: {},
|
formData: {},
|
||||||
success: (response) => {
|
success: (response) => {
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title: "",
|
title: "",
|
||||||
titleColor: "#333333",
|
titleColor: "var(--color-text)",
|
||||||
bgColor: "#ffffff",
|
bgColor: "var(--color-bg)",
|
||||||
showBack: true,
|
showBack: true,
|
||||||
showHome: false,
|
showHome: false,
|
||||||
backIcon: "arrow-left",
|
backIcon: "arrow-left",
|
||||||
backIconSize: "20px",
|
backIconSize: "20px",
|
||||||
iconColor: "#333333",
|
iconColor: "var(--color-text)",
|
||||||
fixed: true,
|
fixed: true,
|
||||||
placeholder: false,
|
placeholder: false,
|
||||||
navBarHeight: 44,
|
navBarHeight: 44,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, type Ref, type ComputedRef } from "vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信小程序导航栏高度计算
|
* 微信小程序导航栏高度计算
|
||||||
@@ -175,6 +175,15 @@ export function useNavbar(options: UseNavbarOptions = {}): UseNavbarReturn {
|
|||||||
}, delayMs);
|
}, delayMs);
|
||||||
} else {
|
} else {
|
||||||
console.warn("getMenuButtonBoundingClientRect 返回值无效,使用默认值");
|
console.warn("getMenuButtonBoundingClientRect 返回值无效,使用默认值");
|
||||||
|
// 兜底:设置合理的默认胶囊按钮尺寸,避免导航栏高度为 0
|
||||||
|
menuButton.value = {
|
||||||
|
width: 87,
|
||||||
|
height: 32,
|
||||||
|
top: statusBarHeight.value + 6,
|
||||||
|
right: windowWidth.value - 10,
|
||||||
|
bottom: statusBarHeight.value + 38,
|
||||||
|
left: windowWidth.value - 97,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("获取胶囊按钮位置失败,使用默认值", e);
|
console.warn("获取胶囊按钮位置失败,使用默认值", e);
|
||||||
@@ -208,10 +217,8 @@ export function useNavbar(options: UseNavbarOptions = {}): UseNavbarReturn {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 在 onMounted 中调用,确保页面渲染完成
|
// setup 阶段同步调用,避免首次渲染闪烁
|
||||||
onMounted(() => {
|
init();
|
||||||
init();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 高度信息
|
// 高度信息
|
||||||
@@ -236,7 +243,3 @@ export function useNavbar(options: UseNavbarOptions = {}): UseNavbarReturn {
|
|||||||
init,
|
init,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 类型导出
|
|
||||||
type Ref<T> = import("vue").Ref<T>;
|
|
||||||
type ComputedRef<T> = import("vue").ComputedRef;
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-uniapp-template",
|
"name": "youlai-app",
|
||||||
"appid": "",
|
"appid": "",
|
||||||
"description": "有来移动端跨端解决方案开发模板",
|
"description": "有来移动端跨端解决方案开发模板",
|
||||||
"versionName": "1.0.0",
|
"versionName": "1.0.0",
|
||||||
@@ -44,7 +44,9 @@
|
|||||||
"mp-weixin": {
|
"mp-weixin": {
|
||||||
"appid": "wx99a151dc43d2637b",
|
"appid": "wx99a151dc43d2637b",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": false
|
"urlCheck": false,
|
||||||
|
"ignoreDevUnusedFiles": false,
|
||||||
|
"ignoreUploadUnusedFiles": false
|
||||||
},
|
},
|
||||||
"usingComponents": true,
|
"usingComponents": true,
|
||||||
"darkMode": true,
|
"darkMode": true,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"type": "page",
|
"type": "page",
|
||||||
"name": "work",
|
"name": "work",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "工作台"
|
"navigationBarTitleText": "工作台"
|
||||||
},
|
},
|
||||||
"layout": "tabbar"
|
"layout": "tabbar"
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
"navigationBarBackgroundColor": "@navBgColor",
|
"navigationBarBackgroundColor": "@navBgColor",
|
||||||
"navigationBarTextStyle": "@navTxtStyle",
|
"navigationBarTextStyle": "@navTxtStyle",
|
||||||
"navigationBarTitleText": "vue-uniapp-template",
|
"navigationBarTitleText": "youlai-app",
|
||||||
"backgroundColor": "@bgColor",
|
"backgroundColor": "@bgColor",
|
||||||
"backgroundTextStyle": "@bgTxtStyle",
|
"backgroundTextStyle": "@bgTxtStyle",
|
||||||
"backgroundColorTop": "@bgColorTop",
|
"backgroundColorTop": "@bgColorTop",
|
||||||
|
|||||||
@@ -45,23 +45,23 @@
|
|||||||
<!-- 数据统计 -->
|
<!-- 数据统计 -->
|
||||||
<view class="m-24rpx">
|
<view class="m-24rpx">
|
||||||
<view class="grid grid-cols-2 gap-16rpx">
|
<view class="grid grid-cols-2 gap-16rpx">
|
||||||
<view class="stat-card gradient-bg--success">
|
<view class="stat-card stat-card--uv">
|
||||||
<image class="stat-card__icon" src="/static/icons/visitor.svg" mode="aspectFit" />
|
<image class="stat-card__icon" src="/static/icons/uv.svg" mode="aspectFit" />
|
||||||
<view class="stat-card__header">
|
<view class="stat-card__header">
|
||||||
<text class="stat-card__label">访客数</text>
|
<text class="stat-card__label">访客数</text>
|
||||||
<view class="stat-card__dot stat-card__dot--green"></view>
|
<view class="stat-card__dot stat-card__dot--uv"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="stat-card__num stat-card__num--green">
|
<text class="stat-card__num stat-card__num--uv">
|
||||||
{{ visitOverviewData.todayUvCount }}
|
{{ visitOverviewData.todayUvCount }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-card gradient-bg--primary">
|
<view class="stat-card stat-card--pv">
|
||||||
<image class="stat-card__icon" src="/static/icons/browser.svg" mode="aspectFit" />
|
<image class="stat-card__icon" src="/static/icons/pv.svg" mode="aspectFit" />
|
||||||
<view class="stat-card__header">
|
<view class="stat-card__header">
|
||||||
<text class="stat-card__label">浏览量</text>
|
<text class="stat-card__label">浏览量</text>
|
||||||
<view class="stat-card__dot stat-card__dot--blue"></view>
|
<view class="stat-card__dot stat-card__dot--pv"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="stat-card__num stat-card__num--blue">
|
<text class="stat-card__num stat-card__num--pv">
|
||||||
{{ visitOverviewData.todayPvCount }}
|
{{ visitOverviewData.todayPvCount }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 访问趋势图表 -->
|
<!-- 访问趋势图表 -->
|
||||||
<view class="m-24rpx">
|
<view class="mt-24rpx">
|
||||||
<wd-card custom-class="chart-card">
|
<wd-card custom-class="chart-card">
|
||||||
<template #title>
|
<template #title>
|
||||||
<view class="flex-between">
|
<view class="flex-between">
|
||||||
@@ -133,7 +133,7 @@ const userStore = useUserStore();
|
|||||||
const current = ref(0);
|
const current = ref(0);
|
||||||
const recentDaysRange = ref(7);
|
const recentDaysRange = ref(7);
|
||||||
|
|
||||||
const swiperList = ref(["https://www.youlai.tech/storage/blog/banner9.png"]);
|
const swiperList = ref(["https://www.youlai.tech/storage/youlai/bg02.png" ,"https://www.youlai.tech/storage/blog/banner9.png" ]);
|
||||||
|
|
||||||
const visitOverviewData = ref<VisitOverviewVO>({
|
const visitOverviewData = ref<VisitOverviewVO>({
|
||||||
todayUvCount: 0,
|
todayUvCount: 0,
|
||||||
@@ -477,11 +477,11 @@ onShow(() => {
|
|||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -10rpx;
|
right: 4px;
|
||||||
bottom: -10rpx;
|
bottom: 4px;
|
||||||
width: 100rpx;
|
width: 72rpx;
|
||||||
height: 100rpx;
|
height: 72rpx;
|
||||||
opacity: 0.15;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__dot {
|
&__dot {
|
||||||
@@ -489,19 +489,16 @@ onShow(() => {
|
|||||||
height: 12rpx;
|
height: 12rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&--uv,
|
||||||
&--green {
|
&--green {
|
||||||
background: var(--color-success);
|
background: #34d19d;
|
||||||
box-shadow: 0 0 10rpx rgba(52, 209, 157, 0.4);
|
box-shadow: 0 0 10rpx rgba(52, 209, 157, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--pv,
|
||||||
&--blue {
|
&--blue {
|
||||||
background: var(--color-primary);
|
background: #4d80f0;
|
||||||
box-shadow: 0 0 10rpx rgba(37, 99, 235, 0.4);
|
box-shadow: 0 0 10rpx rgba(77, 128, 240, 0.3);
|
||||||
}
|
|
||||||
|
|
||||||
&--orange {
|
|
||||||
background: var(--color-warning);
|
|
||||||
box-shadow: 0 0 12rpx rgba(245, 158, 11, 0.4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,16 +510,14 @@ onShow(() => {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
letter-spacing: -1rpx;
|
letter-spacing: -1rpx;
|
||||||
|
|
||||||
|
&--uv,
|
||||||
&--green {
|
&--green {
|
||||||
color: var(--color-success);
|
color: #34d19d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--pv,
|
||||||
&--blue {
|
&--blue {
|
||||||
color: var(--color-primary);
|
color: #4d80f0;
|
||||||
}
|
|
||||||
|
|
||||||
&--orange {
|
|
||||||
color: var(--color-warning);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="login-page">
|
<view class="page login">
|
||||||
<!-- 背景装饰 -->
|
<!-- 背景装饰 -->
|
||||||
<view class="bg-decoration">
|
<view class="login__decoration">
|
||||||
<view class="bg-circle bg-circle-1" />
|
<view class="login__circle login__circle--1" />
|
||||||
<view class="bg-circle bg-circle-2" />
|
<view class="login__circle login__circle--2" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="login-nav" :style="{ paddingTop: `${statusBarHeight}px` }">
|
<!-- 导航栏 -->
|
||||||
<view class="login-nav__bar" :style="{ height: `${navBarHeight}px` }">
|
<view class="login__navbar" :style="{ paddingTop: `${statusBarHeight}px` }">
|
||||||
<view class="login-nav__action" hover-class="login-nav__action--active" @click="handleBack">
|
<view class="login__navbar-bar" :style="{ height: `${navBarHeight}px` }">
|
||||||
<text class="login-nav__back-icon">‹</text>
|
<view class="login__navbar-btn" hover-class="login__navbar-btn--active" @click="handleBack">
|
||||||
|
<text class="login__navbar-icon">‹</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="login-nav__title" />
|
<view class="login__navbar-title" />
|
||||||
<view class="login-nav__placeholder" />
|
<view class="login__navbar-placeholder" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 主内容 -->
|
<!-- 主内容 -->
|
||||||
<view class="login-main" :style="{ paddingTop: `${statusBarHeight + navBarHeight + 4}px` }">
|
<view class="login__body" :style="{ paddingTop: `${statusBarHeight + navBarHeight + 4}px` }">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<view class="login-logo">
|
<view class="login__brand">
|
||||||
<image class="logo-image" src="/static/logo.png" mode="aspectFit" />
|
<image class="login__logo" src="/static/logo.png" mode="aspectFit" />
|
||||||
<text class="logo-text">youlai-app</text>
|
<text class="login__brand-name">youlai-app</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 登录卡片 -->
|
<!-- 登录卡片 -->
|
||||||
<view class="login-card">
|
<view class="login__card">
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<view class="card-header">
|
<view class="login__card-head">
|
||||||
<text class="card-title">欢迎登录</text>
|
<text class="login__card-title">欢迎登录</text>
|
||||||
<text class="card-desc">{{ loginModeDesc }}</text>
|
<text class="login__card-subtitle">{{ loginModeDesc }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 表单区域 -->
|
<!-- 表单区域 -->
|
||||||
<view v-if="loginMode !== 'WECHAT'" class="form-area">
|
<view v-if="loginMode !== 'WECHAT'" class="login__form">
|
||||||
<!-- 用户名/手机号 -->
|
<!-- 用户名/手机号 -->
|
||||||
<view class="input-box">
|
<view class="login__field">
|
||||||
<wd-icon name="person" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="person" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="formData.username"
|
v-model="formData.username"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
|
:placeholder="loginMode === 'PASSWORD' ? '请输入用户名' : '请输入手机号'"
|
||||||
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
|
:maxlength="loginMode === 'PASSWORD' ? 50 : 11"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 密码 -->
|
<!-- 密码 -->
|
||||||
<view v-if="loginMode === 'PASSWORD'" class="input-box">
|
<view v-if="loginMode === 'PASSWORD'" class="login__field">
|
||||||
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="formData.password"
|
v-model="formData.password"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
:maxlength="50"
|
:maxlength="50"
|
||||||
password
|
password
|
||||||
@@ -59,38 +60,38 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 图形验证码(密码登录时显示) -->
|
<!-- 图形验证码(密码登录时显示) -->
|
||||||
<view v-if="loginMode === 'PASSWORD'" class="input-box">
|
<view v-if="loginMode === 'PASSWORD'" class="login__field">
|
||||||
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="formData.captchaCode"
|
v-model="formData.captchaCode"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
@confirm="handleLogin"
|
@confirm="handleLogin"
|
||||||
/>
|
/>
|
||||||
<image
|
<image
|
||||||
v-if="captchaBase64"
|
v-if="captchaBase64"
|
||||||
class="captcha-img"
|
class="login__captcha-img"
|
||||||
:src="captchaBase64"
|
:src="captchaBase64"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
@click="fetchCaptcha"
|
@click="fetchCaptcha"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 验证码 -->
|
<!-- 短信验证码 -->
|
||||||
<view v-else class="input-box">
|
<view v-else class="login__field">
|
||||||
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="formData.code"
|
v-model="formData.code"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
type="number"
|
type="number"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
@confirm="handleLogin"
|
@confirm="handleLogin"
|
||||||
/>
|
/>
|
||||||
<view
|
<view
|
||||||
class="sms-btn"
|
class="login__code-btn"
|
||||||
:class="smsCountdown > 0 ? 'sms-btn-disabled' : 'sms-btn-active'"
|
:class="smsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
|
||||||
@click="handleSendCode"
|
@click="handleSendCode"
|
||||||
>
|
>
|
||||||
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
|
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
|
||||||
@@ -98,8 +99,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 演示环境提示 -->
|
<!-- 演示环境提示 -->
|
||||||
<view v-if="loginMode === 'SMS'" class="demo-tip">
|
<view v-if="loginMode === 'SMS'" class="login__demo-hint">
|
||||||
<text class="demo-tip-text">演示环境验证码:123456</text>
|
<text class="login__demo-hint-text">演示环境验证码:123456</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 登录按钮 -->
|
<!-- 登录按钮 -->
|
||||||
@@ -108,11 +109,11 @@
|
|||||||
</wd-button>
|
</wd-button>
|
||||||
|
|
||||||
<!-- 切换登录方式 -->
|
<!-- 切换登录方式 -->
|
||||||
<view class="switch-mode" @click="toggleLoginMode">
|
<view class="login__mode-switch" @click="toggleLoginMode">
|
||||||
<text class="switch-text">
|
<text class="login__mode-switch-text">
|
||||||
{{ loginMode === "PASSWORD" ? "忘记密码?" : "记得密码?" }}
|
{{ loginMode === "PASSWORD" ? "忘记密码?" : "记得密码?" }}
|
||||||
</text>
|
</text>
|
||||||
<text class="switch-link">
|
<text class="login__mode-switch-link">
|
||||||
{{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
|
{{ loginMode === "PASSWORD" ? "验证码登录" : "密码登录" }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -120,51 +121,52 @@
|
|||||||
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<!-- 微信登录区域 -->
|
<!-- 微信登录区域 -->
|
||||||
<view v-else class="form-area">
|
<view v-else class="login__form">
|
||||||
<button
|
<button
|
||||||
class="btn-wechat"
|
class="login__wx-btn"
|
||||||
open-type="getPhoneNumber"
|
open-type="getPhoneNumber"
|
||||||
@getphonenumber="handleWechatPhoneLogin"
|
@getphonenumber="handleWechatPhoneLogin"
|
||||||
>
|
>
|
||||||
<image class="btn-wechat-icon" src="/static/icons/weixin.png" mode="aspectFit" />
|
<image class="login__wx-btn-icon" src="/static/icons/weixin.png" mode="aspectFit" />
|
||||||
微信一键登录
|
微信一键登录
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 其他登录方式 -->
|
<!-- 其他登录方式 -->
|
||||||
<view class="switch-mode" @click="loginMode = 'PASSWORD'">
|
<view class="login__mode-switch" @click="loginMode = 'PASSWORD'">
|
||||||
<text class="switch-text">其他登录方式</text>
|
<text class="login__mode-switch-text">其他登录方式</text>
|
||||||
<text class="switch-link">账号登录</text>
|
<text class="login__mode-switch-link">账号登录</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
<view v-if="loginMode !== 'WECHAT'" class="divider">
|
<view v-if="loginMode !== 'WECHAT'" class="login__divider">
|
||||||
<view class="divider-line" />
|
<view class="login__divider-line" />
|
||||||
<text class="divider-text">其他登录方式</text>
|
<text class="login__divider-text">其他登录方式</text>
|
||||||
<view class="divider-line" />
|
<view class="login__divider-line" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 微信登录入口 -->
|
<!-- 微信登录入口 -->
|
||||||
<view v-if="loginMode !== 'WECHAT'" class="wechat-entry">
|
<view v-if="loginMode !== 'WECHAT'" class="login__oauth-row">
|
||||||
<image
|
<image
|
||||||
class="wechat-icon-img"
|
class="login__wx-icon"
|
||||||
src="/static/icons/weixin.png"
|
src="/static/icons/weixin.png"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
@click="loginMode = 'WECHAT'"
|
@click="loginMode = 'WECHAT'"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="agreement">
|
<!-- 协议勾选 -->
|
||||||
|
<view class="login__policy">
|
||||||
<wd-checkbox v-model="isAgreePolicy" shape="square" size="16px">
|
<wd-checkbox v-model="isAgreePolicy" shape="square" size="16px">
|
||||||
<text class="agreement-text">
|
<text class="login__policy-text">
|
||||||
我已阅读并同意
|
我已阅读并同意
|
||||||
<text class="agreement-link" @click.stop="navigateToAgreement('user')">
|
<text class="login__policy-link" @click.stop="navigateToAgreement('user')">
|
||||||
《用户协议》
|
《用户协议》
|
||||||
</text>
|
</text>
|
||||||
与
|
与
|
||||||
<text class="agreement-link" @click.stop="navigateToAgreement('privacy')">
|
<text class="login__policy-link" @click.stop="navigateToAgreement('privacy')">
|
||||||
《隐私政策》
|
《隐私政策》
|
||||||
</text>
|
</text>
|
||||||
</text>
|
</text>
|
||||||
@@ -182,33 +184,33 @@
|
|||||||
custom-class="popup-bottom"
|
custom-class="popup-bottom"
|
||||||
@close="resetBindForm"
|
@close="resetBindForm"
|
||||||
>
|
>
|
||||||
<view class="bind-popup">
|
<view class="login__bind-panel">
|
||||||
<text class="bind-title">绑定手机号</text>
|
<text class="login__bind-panel-title">绑定手机号</text>
|
||||||
|
|
||||||
<view class="form-area">
|
<view class="login__form">
|
||||||
<view class="input-box">
|
<view class="login__field">
|
||||||
<wd-icon name="phone" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="phone" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="bindMobileForm.mobile"
|
v-model="bindMobileForm.mobile"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
placeholder="请输入手机号"
|
placeholder="请输入手机号"
|
||||||
type="number"
|
type="number"
|
||||||
:maxlength="11"
|
:maxlength="11"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="input-box">
|
<view class="login__field">
|
||||||
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
|
||||||
<input
|
<input
|
||||||
v-model="bindMobileForm.code"
|
v-model="bindMobileForm.code"
|
||||||
class="input-field"
|
class="login__field-input"
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
type="number"
|
type="number"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
/>
|
/>
|
||||||
<view
|
<view
|
||||||
class="sms-btn"
|
class="login__code-btn"
|
||||||
:class="bindSmsCountdown > 0 ? 'sms-btn-disabled' : 'sms-btn-active'"
|
:class="bindSmsCountdown > 0 ? 'login__code-btn--disabled' : 'login__code-btn--active'"
|
||||||
@click="handleSendBindCode"
|
@click="handleSendBindCode"
|
||||||
>
|
>
|
||||||
{{ bindSmsCountdown > 0 ? `${bindSmsCountdown}s` : "获取验证码" }}
|
{{ bindSmsCountdown > 0 ? `${bindSmsCountdown}s` : "获取验证码" }}
|
||||||
@@ -216,8 +218,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 演示环境提示 -->
|
<!-- 演示环境提示 -->
|
||||||
<view class="demo-tip">
|
<view class="login__demo-hint">
|
||||||
<text class="demo-tip-text">演示环境验证码:123456</text>
|
<text class="login__demo-hint-text">演示环境验证码:123456</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<wd-button type="primary" block :loading="bindLoading" @click="handleBindMobile">
|
<wd-button type="primary" block :loading="bindLoading" @click="handleBindMobile">
|
||||||
@@ -228,7 +230,7 @@
|
|||||||
</wd-popup>
|
</wd-popup>
|
||||||
|
|
||||||
<!-- 协议确认弹窗 -->
|
<!-- 协议确认弹窗 -->
|
||||||
<wd-message-box selector="policy-box">
|
<wd-message-box selector="policy-box" root-portal>
|
||||||
<view class="text-center text-sm text-gray-500 leading-relaxed">
|
<view class="text-center text-sm text-gray-500 leading-relaxed">
|
||||||
请阅读并同意有来技术
|
请阅读并同意有来技术
|
||||||
<text class="text-blue-500" @click.stop="navigateToAgreement('user')">《用户协议》</text>
|
<text class="text-blue-500" @click.stop="navigateToAgreement('user')">《用户协议》</text>
|
||||||
@@ -257,7 +259,7 @@ const toast = useToast();
|
|||||||
const message = useMessage("policy-box");
|
const message = useMessage("policy-box");
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 导航栏
|
// 导航栏尺寸
|
||||||
const statusBarHeight = ref(20);
|
const statusBarHeight = ref(20);
|
||||||
const navBarHeight = ref(44);
|
const navBarHeight = ref(44);
|
||||||
|
|
||||||
@@ -556,10 +558,10 @@ onUnload(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面容器
|
// ==========================================================================
|
||||||
.login-page {
|
// 页面背景
|
||||||
position: relative;
|
// ==========================================================================
|
||||||
min-height: 100vh;
|
.login {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
var(--color-bg-tertiary) 0%,
|
var(--color-bg-tertiary) 0%,
|
||||||
@@ -568,38 +570,42 @@ onUnload(() => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 背景装饰
|
// ==========================================================================
|
||||||
.bg-decoration {
|
// 背景装饰(模糊光晕圆)
|
||||||
position: absolute;
|
// ==========================================================================
|
||||||
|
.login__decoration {
|
||||||
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-circle {
|
.login__circle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
filter: blur(64px);
|
filter: blur(64px);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&--1 {
|
||||||
|
top: -80px;
|
||||||
|
left: -80px;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
background-color: rgba(96, 165, 250, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--2 {
|
||||||
|
right: -80px;
|
||||||
|
bottom: -80px;
|
||||||
|
width: 320px;
|
||||||
|
height: 320px;
|
||||||
|
background-color: rgba(59, 130, 246, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-circle-1 {
|
// ==========================================================================
|
||||||
top: -80px;
|
|
||||||
left: -80px;
|
|
||||||
width: 240px;
|
|
||||||
height: 240px;
|
|
||||||
background-color: rgba(96, 165, 250, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-circle-2 {
|
|
||||||
right: -80px;
|
|
||||||
bottom: -80px;
|
|
||||||
width: 320px;
|
|
||||||
height: 320px;
|
|
||||||
background-color: rgba(59, 130, 246, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导航栏
|
// 导航栏
|
||||||
.login-nav {
|
// ==========================================================================
|
||||||
|
.login__navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -608,15 +614,15 @@ onUnload(() => {
|
|||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__bar {
|
.login__navbar-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__action,
|
.login__navbar-btn,
|
||||||
.login-nav__placeholder {
|
.login__navbar-placeholder {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -627,22 +633,22 @@ onUnload(() => {
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__action {
|
.login__navbar-btn {
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: rgba(255, 255, 255, 0.18);
|
background-color: rgba(255, 255, 255, 0.18);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__action--active {
|
.login__navbar-placeholder {
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-nav__placeholder {
|
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__back-icon {
|
.login__navbar-icon {
|
||||||
margin-top: -4rpx;
|
margin-top: -4rpx;
|
||||||
font-size: 44rpx;
|
font-size: 44rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -650,25 +656,27 @@ onUnload(() => {
|
|||||||
color: rgba(15, 23, 42, 0.92);
|
color: rgba(15, 23, 42, 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-nav__title {
|
.login__navbar-title {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
color: rgba(15, 23, 42, 0.92);
|
color: rgba(15, 23, 42, 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主内容
|
// ==========================================================================
|
||||||
.login-main {
|
// 主内容区 —— 普通流式布局,不需要高 z-index
|
||||||
position: relative;
|
// ==========================================================================
|
||||||
z-index: var(--z-sticky);
|
.login__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 48px;
|
padding: 0 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logo
|
// ==========================================================================
|
||||||
.login-logo {
|
// 品牌 Logo
|
||||||
|
// ==========================================================================
|
||||||
|
.login__brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -676,21 +684,23 @@ onUnload(() => {
|
|||||||
margin-bottom: 36px;
|
margin-bottom: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-image {
|
.login__logo {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-text {
|
.login__brand-name {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 登录卡片
|
// 登录卡片
|
||||||
.login-card {
|
// ==========================================================================
|
||||||
|
.login__card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
@@ -700,26 +710,28 @@ onUnload(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 卡片头部
|
// 卡片头部
|
||||||
.card-header {
|
.login__card-head {
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.login__card-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-desc {
|
.login__card-subtitle {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 表单区域
|
// 表单区域
|
||||||
.form-area {
|
// ==========================================================================
|
||||||
|
.login__form {
|
||||||
> view,
|
> view,
|
||||||
> button {
|
> button {
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
@@ -728,8 +740,8 @@ onUnload(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输入框容器
|
// 输入框行
|
||||||
.input-box {
|
.login__field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
@@ -742,7 +754,7 @@ onUnload(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.login__field-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
@@ -750,14 +762,17 @@ onUnload(() => {
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.captcha-img {
|
// 图形验证码图片
|
||||||
|
.login__captcha-img {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 微信按钮
|
// ==========================================================================
|
||||||
.btn-wechat {
|
// 微信登录按钮
|
||||||
|
// ==========================================================================
|
||||||
|
.login__wx-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -775,14 +790,16 @@ onUnload(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-wechat-icon {
|
.login__wx-btn-icon {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 验证码按钮
|
// 验证码按钮
|
||||||
.sms-btn {
|
// ==========================================================================
|
||||||
|
.login__code-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -794,31 +811,33 @@ onUnload(() => {
|
|||||||
transition:
|
transition:
|
||||||
background-color 0.2s,
|
background-color 0.2s,
|
||||||
color 0.2s;
|
color 0.2s;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background-color: var(--color-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: var(--color-text-placeholder);
|
||||||
|
background-color: var(--color-bg-tertiary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sms-btn-active {
|
// ==========================================================================
|
||||||
color: var(--color-primary);
|
// 登录方式切换
|
||||||
background-color: var(--color-primary-light);
|
// ==========================================================================
|
||||||
}
|
.login__mode-switch {
|
||||||
|
|
||||||
.sms-btn-disabled {
|
|
||||||
color: var(--color-text-placeholder);
|
|
||||||
background-color: var(--color-bg-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换登录方式
|
|
||||||
.switch-mode {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-text {
|
.login__mode-switch-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-link {
|
.login__mode-switch-link {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -826,33 +845,37 @@ onUnload(() => {
|
|||||||
border-bottom: 1px solid var(--color-primary);
|
border-bottom: 1px solid var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 分割线
|
// 分割线
|
||||||
.divider {
|
// ==========================================================================
|
||||||
|
.login__divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-line {
|
.login__divider-line {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--color-border);
|
background-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-text {
|
.login__divider-text {
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-text-placeholder);
|
color: var(--color-text-placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 微信登录入口
|
// ==========================================================================
|
||||||
.wechat-entry {
|
// 第三方登录入口
|
||||||
|
// ==========================================================================
|
||||||
|
.login__oauth-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wechat-icon-img {
|
.login__wx-icon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|
||||||
@@ -861,8 +884,10 @@ onUnload(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 协议
|
// ==========================================================================
|
||||||
.agreement {
|
// 协议勾选
|
||||||
|
// ==========================================================================
|
||||||
|
.login__policy {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -871,22 +896,24 @@ onUnload(() => {
|
|||||||
border-top: 1px solid var(--color-border-light);
|
border-top: 1px solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-text {
|
.login__policy-text {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-link {
|
.login__policy-link {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 绑定手机号弹窗
|
// 绑定手机号弹窗
|
||||||
.bind-popup {
|
// ==========================================================================
|
||||||
|
.login__bind-panel {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bind-title {
|
.login__bind-panel-title {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@@ -895,29 +922,20 @@ onUnload(() => {
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
// 演示环境提示
|
// 演示环境提示
|
||||||
.demo-tip {
|
// ==========================================================================
|
||||||
|
.login__demo-hint {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-tip-text {
|
.login__demo-hint-text {
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
background-color: var(--color-warning-light);
|
background-color: var(--color-warning-light);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深度样式覆盖
|
|
||||||
::deep(.wd-checkbox__label) {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
::deep(.wd-popup__close) {
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -904,7 +904,7 @@ const openOfficialAccount = () => {
|
|||||||
.mine-body {
|
.mine-body {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: var(--z-base);
|
z-index: var(--z-base);
|
||||||
padding: 0 28rpx calc(env(safe-area-inset-bottom) + 40rpx);
|
padding: 0 28rpx 40rpx;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
background: var(--color-bg-secondary);
|
background: var(--color-bg-secondary);
|
||||||
border-top-left-radius: 48rpx;
|
border-top-left-radius: 48rpx;
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ onLoad(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 退出登录按钮样式 - 使用更具体的选择器替代 !important
|
// 退出登录按钮样式 - 使用更具体的选择器替代 !important
|
||||||
::deep(.wd-button.logout-btn) {
|
:deep(.wd-button.logout-btn) {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
@@ -194,7 +194,7 @@ onLoad(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::deep(.loading-center) {
|
:deep(.loading-center) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -203,11 +203,11 @@ onLoad(() => {
|
|||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
::deep(.loading-center .wd-loading__spinner) {
|
:deep(.loading-center .wd-loading__spinner) {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
::deep(.loading-center .wd-loading__text) {
|
:deep(.loading-center .wd-loading__text) {
|
||||||
margin-top: 20rpx;
|
margin-top: 20rpx;
|
||||||
color: var(--color-text-inverse);
|
color: var(--color-text-inverse);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page page--padding page--tabbar">
|
<custom-navbar title="工作台" :show-back="false" />
|
||||||
|
<view
|
||||||
|
class="page page--tabbar"
|
||||||
|
:style="{ padding: `${navbar.totalHeight.value + 8}px 32rpx 0` }"
|
||||||
|
>
|
||||||
<template v-for="(item, index) in visibleGridList" :key="index">
|
<template v-for="(item, index) in visibleGridList" :key="index">
|
||||||
<wd-card :title="item.title">
|
<wd-card :title="item.title">
|
||||||
<wd-grid clickable :column="4">
|
<wd-grid clickable :column="4">
|
||||||
@@ -23,13 +27,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useRouter } from "uni-mini-router";
|
import { useRouter } from "uni-mini-router";
|
||||||
|
import { useNavbar } from "@/composables/useNavbar";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
import { menuConfig } from "@/config/menu";
|
import { menuConfig } from "@/config/menu";
|
||||||
import { checkLogin, isLoggedIn } from "@/utils/auth";
|
import { checkLogin, isLoggedIn } from "@/utils/auth";
|
||||||
|
import { hasPermission as checkPermission } from "@/utils/permission";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const navbar = useNavbar({ hasTabbar: true });
|
||||||
|
|
||||||
// 用户权限列表
|
// 用户权限列表
|
||||||
const userPerms = computed(() => userStore.userInfo?.perms || []);
|
const userPerms = computed(() => userStore.userInfo?.perms || []);
|
||||||
|
|
||||||
@@ -38,8 +46,8 @@ const isLogged = computed(() => isLoggedIn());
|
|||||||
|
|
||||||
// 检查是否有权限
|
// 检查是否有权限
|
||||||
const hasPermission = (perm: string) => {
|
const hasPermission = (perm: string) => {
|
||||||
if (!perm) return true; // 无权限要求则显示
|
if (!perm) return true;
|
||||||
return userPerms.value.includes(perm) || userPerms.value.includes("*:*:*");
|
return checkPermission(perm);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据权限过滤后的菜单列表
|
// 根据权限过滤后的菜单列表
|
||||||
@@ -82,6 +90,7 @@ function handleNavClick(item: any) {
|
|||||||
{
|
{
|
||||||
"name": "work",
|
"name": "work",
|
||||||
"style": {
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
"navigationBarTitleText": "工作台"
|
"navigationBarTitleText": "工作台"
|
||||||
},
|
},
|
||||||
"layout": "tabbar"
|
"layout": "tabbar"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { pages, subPackages } from "virtual:uni-pages";
|
import { pages, subPackages } from "virtual:uni-pages";
|
||||||
import { getAccessToken, isLoggedIn } from "@/utils/auth";
|
import { getAccessToken } from "@/utils/auth";
|
||||||
import { createRouter } from "uni-mini-router";
|
import { createRouter } from "uni-mini-router";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ import { useUserStore } from "@/store";
|
|||||||
function generateRoutes() {
|
function generateRoutes() {
|
||||||
const routes = pages.map((page: { path: string; [key: string]: any }) => {
|
const routes = pages.map((page: { path: string; [key: string]: any }) => {
|
||||||
const newPath = `/${page.path}`;
|
const newPath = `/${page.path}`;
|
||||||
// 透传 meta 字段(如果 pages.json 中定义了)
|
|
||||||
const meta = page.meta ?? undefined;
|
const meta = page.meta ?? undefined;
|
||||||
return { ...page, path: newPath, meta };
|
return { ...page, path: newPath, meta };
|
||||||
});
|
});
|
||||||
@@ -33,37 +32,32 @@ const router = createRouter({
|
|||||||
|
|
||||||
// 全局前置守卫
|
// 全局前置守卫
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.meta && to.meta.requireAuth && !isLoggedIn()) {
|
|
||||||
const redirectPath = (to.path || "/pages/index/index") as string;
|
|
||||||
// 先取消本次导航,避免在异步对话框中遗漏 next 导致报错
|
|
||||||
next(false);
|
|
||||||
uni.showModal({
|
|
||||||
title: "提示",
|
|
||||||
content: "该功能需要登录后使用",
|
|
||||||
confirmText: "去登录",
|
|
||||||
cancelText: "返回",
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
router.push({
|
|
||||||
path: "/pages/login/index",
|
|
||||||
query: { redirect: encodeURIComponent(redirectPath) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to.meta && to.meta.requireAuth) {
|
if (to.meta && to.meta.requireAuth) {
|
||||||
const token = getAccessToken();
|
const token = getAccessToken();
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
|
// 无 token:弹窗提示登录
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
const redirectPath = (to.path || "/pages/index/index") as string;
|
||||||
next(false);
|
next(false);
|
||||||
router.push({ path: "/pages/login/index" });
|
uni.showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: "该功能需要登录后使用",
|
||||||
|
confirmText: "去登录",
|
||||||
|
cancelText: "返回",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
router.push({
|
||||||
|
path: "/pages/login/index",
|
||||||
|
query: { redirect: encodeURIComponent(redirectPath) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 有 token 但无用户信息:尝试获取
|
||||||
|
const userStore = useUserStore();
|
||||||
if (!userStore.userInfo) {
|
if (!userStore.userInfo) {
|
||||||
try {
|
try {
|
||||||
await userStore.getInfo();
|
await userStore.getInfo();
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="200" height="200" fill="#3a8ee6">
|
|
||||||
<rect x="4" y="24" width="8" height="20" rx="2"/>
|
|
||||||
<rect x="16" y="14" width="8" height="30" rx="2"/>
|
|
||||||
<rect x="28" y="20" width="8" height="24" rx="2"/>
|
|
||||||
<rect x="40" y="8" width="8" height="36" rx="2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 318 B |
1
src/static/icons/pv.svg
Normal file
1
src/static/icons/pv.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1733620744216" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13366" width="128" height="128"><path d="M512.956 741.549c24.671-8.545 73.221-25.811 120.072-46.486 12.131-5.394 24.087-12.036 47.412-25.31a109.078 109.078 0 0 0 37.94-29.11 83.344 83.344 0 0 0 12.227-23.053V219.281c-0.096-24.831-20.211-44.936-45.045-45.043-9.34 1.573-22.76 4.209-38.757 8.543 24.439 62.406 18.146 132.708-17.021 189.792-34.498 55.993-92.88 92.712-158.098 99.992-1.275 0.137-2.499 0.463-3.776 0.585v223.354c0.11 24.835 20.213 44.934 45.046 45.045zM378.672 741.549c24.833-0.108 44.939-20.21 45.046-45.047V472.779a213.556 213.556 0 0 1-158.787-101.461c-34.357-56.592-40.495-125.929-16.655-187.697a423.782 423.782 0 0 0-42.873-9.482c-24.833 0.098-44.938 20.214-45.047 45.047v398.308a82.748 82.748 0 0 0 12.242 23.039c10.5 12.524 23.704 22.495 38.591 29.219 23.801 13.273 35.756 20.011 47.424 25.311 46.84 20.673 95.405 37.942 120.059 46.486z" fill="#4D80F0" opacity=".4" p-id="13367"></path><path d="M744.827 708.729a57 57 0 0 0 32.919-11.275 53.974 53.974 0 0 0 17.343-27.226V271.907c-0.066-23.054-17.548-42.33-40.493-44.667 0.528 2.635 0.815 5.297 0.855 7.974v398.309a82.915 82.915 0 0 1-12.227 23.037 108.602 108.602 0 0 1-37.941 29.126c-22.944 12.799-34.805 19.428-46.568 24.752 12.528 0.003 49.706 0.761 86.112-1.709zM141.202 227.432c-22.861 2.335-40.317 21.49-40.495 44.475V670.23a53.882 53.882 0 0 0 17.362 27.4 56.9 56.9 0 0 0 32.902 11.291c36.421 2.471 73.585 1.984 86.112 1.711-11.764-5.324-23.61-11.958-46.565-24.751a108.597 108.597 0 0 1-37.928-29.128 82.787 82.787 0 0 1-12.242-23.036V235.404c0.041-2.676 0.327-5.338 0.854-7.972z" fill="#4D80F0" opacity=".4" p-id="13368"></path><path d="M629.782 372.569c35.172-57.083 41.463-127.383 17.023-189.792-0.007-0.026-0.016-0.066-0.029-0.096-51.497 13.939-127.844 45.508-165.674 117.207a189.132 189.132 0 0 0-13.288 31.87v141.388c1.307-0.123 2.569-0.447 3.872-0.585 65.218-7.28 123.598-44 158.096-99.992zM248.658 183.62c-23.842 61.769-17.701 131.105 16.639 187.697 34.357 56.608 93.013 94.084 158.8 101.461h0.099v-141.02a190.385 190.385 0 0 0-13.273-31.87c-36.989-70.079-110.861-101.855-162.265-116.268z" fill="#A3C4FD" opacity=".4" p-id="13369"></path><path d="M593.104 570.52v223.357c0.105 24.83 20.215 44.938 45.05 45.046 24.668-8.544 73.218-25.811 120.071-46.488 12.127-5.392 24.086-12.036 47.409-25.306a109.116 109.116 0 0 0 37.941-29.114 83.408 83.408 0 0 0 12.225-23.051v-398.31c-0.092-24.833-20.21-44.938-45.045-45.047-9.481 1.602-23.134 4.265-39.446 8.723 24.355 62.297 18.091 132.42-16.915 189.423a213.388 213.388 0 0 1-161.29 100.767zM330.601 271.513c-24.833 0.094-44.939 20.214-45.048 45.045v398.31a82.678 82.678 0 0 0 12.24 23.039c10.502 12.524 23.708 22.493 38.595 29.22 23.799 13.271 35.753 20.013 47.422 25.307 46.841 20.677 95.404 37.944 120.062 46.488 24.831-0.109 44.938-20.216 45.045-45.046v-223.72c-65.791-7.377-124.448-44.86-158.79-101.463-34.354-56.591-40.496-125.93-16.655-187.696a423.057 423.057 0 0 0-42.871-9.484z" fill="#4D80F0" p-id="13370"></path><path d="M868.422 753.937a108.626 108.626 0 0 1-37.943 29.126c-22.944 12.796-34.805 19.428-46.567 24.75 12.526 0 49.702 0.765 86.112-1.71a56.988 56.988 0 0 0 32.916-11.274 54.009 54.009 0 0 0 17.346-27.228v-398.32c-0.069-23.053-17.552-42.328-40.496-44.667 0.529 2.635 0.815 5.299 0.855 7.974v398.31a83.035 83.035 0 0 1-12.223 23.039zM266.399 324.804c-22.863 2.338-40.319 21.491-40.496 44.477v398.323a53.886 53.886 0 0 0 17.361 27.4 56.879 56.879 0 0 0 32.901 11.291c36.419 2.471 73.587 1.983 86.111 1.708-11.764-5.323-23.608-11.953-46.566-24.75a108.578 108.578 0 0 1-37.928-29.124 82.698 82.698 0 0 1-12.238-23.039V332.779c0.039-2.675 0.326-5.338 0.855-7.975z" fill="#4D80F0" p-id="13371"></path><path d="M771.97 280.058c-51.497 13.939-127.844 45.508-165.675 117.207a188.947 188.947 0 0 0-13.289 31.871v141.386c66.797-6.206 126.786-43.472 161.969-100.58 35.184-57.11 41.473-127.45 16.995-189.884z" fill="#4D80F0" p-id="13372"></path><path d="M771.97 280.058c-51.497 13.939-127.844 45.508-165.675 117.207a188.947 188.947 0 0 0-13.289 31.871v141.386c66.797-6.206 126.786-43.472 161.969-100.58 35.184-57.11 41.473-127.45 16.995-189.884z" fill="#A3C4FD" p-id="13373"></path><path d="M549.294 570.155h0.095V429.132a189.948 189.948 0 0 0-13.271-31.871c-36.992-70.081-110.863-101.856-162.266-116.268-23.839 61.769-17.701 131.105 16.642 187.696 34.352 56.608 93.012 94.089 158.8 101.466z" fill="#4D80F0" p-id="13374"></path><path d="M549.294 570.155h0.095V429.132a189.948 189.948 0 0 0-13.271-31.871c-36.992-70.081-110.863-101.856-162.266-116.268-23.839 61.769-17.701 131.105 16.642 187.696 34.352 56.608 93.012 94.089 158.8 101.466z" fill="#A3C4FD" p-id="13375"></path></svg>
|
||||||
1
src/static/icons/uv.svg
Normal file
1
src/static/icons/uv.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1733620604471" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1596" width="128" height="128"><path d="M604.766 304.391c55.072 0 101.33 31.359 125.567 76.137v-94.054c0-51.505-41.856-91.812-92.513-91.812H250.118c-50.659 0-92.518 40.307-92.518 91.812v423.255c0 49.268 41.859 89.574 92.518 89.574h171.824c-30.848-11.195-48.466-33.586-50.672-71.662-4.402-85.105 48.465-161.231 90.321-197.066 6.608-6.728 11.01-11.194 17.616-15.685-8.813-20.149-15.421-42.548-15.421-67.176 0-78.385 61.679-143.323 140.98-143.323z" fill="#D5F5ED" p-id="1597"></path><path d="M804.423 349.242c0-51.507-41.855-91.814-92.515-91.814H324.204c-50.658 0-92.514 40.308-92.514 91.814v423.254c0 49.268 41.855 89.576 92.514 89.576h171.828c-30.846-11.197-48.466-33.587-50.67-71.668-4.404-85.101 48.462-161.239 90.319-197.063 6.608-6.727 11.009-11.196 17.617-15.682-8.813-20.152-15.422-42.549-15.422-67.182 0-78.384 61.68-143.318 140.977-143.318 55.071 0 101.336 31.356 125.57 76.133v-94.05zM394.702 638.123h-66.081c-6.612 0-11.024-4.474-11.024-11.187 0-6.729 4.413-11.2 11.024-11.2h66.081c6.608 0 11.008 4.471 11.008 11.2 0 6.713-4.4 11.187-11.008 11.187z m66.081-89.572H328.62c-4.417 0-11.025-4.477-11.025-11.199 0-6.707 4.413-11.189 11.025-11.189h132.163c6.609 0 11.01 4.482 11.01 11.189 0.001 6.72-4.401 11.199-11.01 11.199z m0-89.577H328.62c-4.417 0-11.025-4.481-11.025-11.2s6.608-11.198 11.025-11.198h132.163c6.609 0 11.01 4.481 11.01 11.198 0.001 6.719-4.401 11.2-11.01 11.2z" fill="#34D19D" p-id="1598"></path><path d="M566.516 620.21c-33.043 26.875-72.692 85.096-70.484 152.288 2.193 67.177 72.686 67.177 176.216 67.177 103.535 0 174.03 0 176.228-67.177 2.205-67.192-39.651-123.183-70.485-152.288-24.239-22.396-44.066-26.868-50.672-26.868 28.64-15.683 46.256-47.039 46.256-82.861 0-53.743-41.841-96.294-94.722-96.294-52.863 0-94.707 42.551-92.511 96.294 0 35.822 17.628 64.951 46.253 82.861H621.59c-0.002 0-22.034 0-55.074 26.868z" fill="#34D19D" p-id="1599"></path></svg>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="200" height="200" fill="#2ab789">
|
|
||||||
<circle cx="24" cy="16" r="8"/>
|
|
||||||
<circle cx="10" cy="20" r="6"/>
|
|
||||||
<circle cx="38" cy="20" r="6"/>
|
|
||||||
<path d="M24 28c-8 0-14 4-14 8v6h28v-6c0-4-6-8-14-8z"/>
|
|
||||||
<path d="M10 28c-4 0-8 2-8 5v5h8v-5c0-2 1-4 3-5-1 0-2 0-3 0z"/>
|
|
||||||
<path d="M38 28c4 0 8 2 8 5v5h-8v-5c0-2-1-4-3-5 1 0 2 0 3 0z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 400 B |
@@ -28,16 +28,16 @@ export const useThemeStore = defineStore("theme", () => {
|
|||||||
|
|
||||||
/** 主题变量(响应式对象) */
|
/** 主题变量(响应式对象) */
|
||||||
const themeVars = reactive({
|
const themeVars = reactive({
|
||||||
darkBackground: "#0f0f0f",
|
darkBackground: "#1f2937",
|
||||||
darkBackground2: "#1a1a1a",
|
darkBackground2: "#111827",
|
||||||
darkBackground3: "#242424",
|
darkBackground3: "#1e293b",
|
||||||
darkBackground4: "#2f2f2f",
|
darkBackground4: "#374151",
|
||||||
darkBackground5: "#3d3d3d",
|
darkBackground5: "#4b5563",
|
||||||
darkBackground6: "#4a4a4a",
|
darkBackground6: "#6b7280",
|
||||||
darkBackground7: "#606060",
|
darkBackground7: "#9ca3af",
|
||||||
darkColor: "#ffffff",
|
darkColor: "#f9fafb",
|
||||||
darkColor2: "#e0e0e0",
|
darkColor2: "#9ca3af",
|
||||||
darkColor3: "#a0a0a0",
|
darkColor3: "#6b7280",
|
||||||
colorTheme: currentThemeColor.value.primary,
|
colorTheme: currentThemeColor.value.primary,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export const useThemeStore = defineStore("theme", () => {
|
|||||||
const setNavigationBarColor = () => {
|
const setNavigationBarColor = () => {
|
||||||
uni.setNavigationBarColor({
|
uni.setNavigationBarColor({
|
||||||
frontColor: theme.value === "light" ? "#000000" : "#ffffff",
|
frontColor: theme.value === "light" ? "#000000" : "#ffffff",
|
||||||
backgroundColor: theme.value === "light" ? "#ffffff" : "#000000",
|
backgroundColor: theme.value === "light" ? "#ffffff" : "#1f2937",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import AuthAPI, {
|
|||||||
} from "@/api/auth"
|
} from "@/api/auth"
|
||||||
import UserAPI, { type UserInfo } from "@/api/user"
|
import UserAPI, { type UserInfo } from "@/api/user"
|
||||||
import { setAccessToken, clearTokens } from "@/utils/auth"
|
import { setAccessToken, clearTokens } from "@/utils/auth"
|
||||||
import { getUserInfo, setUserInfo } from "@/utils/storage"
|
|
||||||
import { USER_INFO_KEY } from "@/constants"
|
|
||||||
import { Storage } from "@/utils/storage"
|
import { Storage } from "@/utils/storage"
|
||||||
|
import { USER_INFO_KEY } from "@/constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户状态管理 Store
|
* 用户状态管理 Store
|
||||||
@@ -27,7 +26,7 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
const userInfo = ref<UserInfo | undefined>(getUserInfo())
|
const userInfo = ref<UserInfo | undefined>(Storage.get<UserInfo>(USER_INFO_KEY))
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 登录方法
|
// 登录方法
|
||||||
@@ -101,7 +100,7 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
*/
|
*/
|
||||||
const getInfo = async () => {
|
const getInfo = async () => {
|
||||||
const data = await UserAPI.getUserInfo()
|
const data = await UserAPI.getUserInfo()
|
||||||
setUserInfo(data)
|
Storage.set(USER_INFO_KEY, data)
|
||||||
userInfo.value = data
|
userInfo.value = data
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ body {
|
|||||||
// 页面容器
|
// 页面容器
|
||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page--tabbar {
|
.page--tabbar {
|
||||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom));
|
padding-bottom: calc(50px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 筛选栏
|
// 筛选栏
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"light": {
|
"light": {
|
||||||
"bgColor": "#F8F8F8",
|
"bgColor": "#F5F5F5",
|
||||||
"bgColorBottom": "#F8F8F8",
|
"bgColorBottom": "#F5F5F5",
|
||||||
"bgColorTop": "#F8F8F8",
|
"bgColorTop": "#F5F5F5",
|
||||||
"bgTxtStyle": "dark",
|
"bgTxtStyle": "dark",
|
||||||
"navBgColor": "#FFF",
|
"navBgColor": "#FFFFFF",
|
||||||
"navTxtStyle": "black",
|
"navTxtStyle": "black",
|
||||||
"tabBgColor": "#ffffff",
|
"tabBgColor": "#ffffff",
|
||||||
"tabBorderStyle": "black",
|
"tabBorderStyle": "black",
|
||||||
"tabColor": "#bfbfbf",
|
"tabColor": "#9ca3af",
|
||||||
"tabSelectedColor": "#0165FF"
|
"tabSelectedColor": "#4d80f0"
|
||||||
},
|
},
|
||||||
"dark": {
|
"dark": {
|
||||||
"bgColor": "#000",
|
"bgColor": "#1f2937",
|
||||||
"bgColorBottom": "#000",
|
"bgColorBottom": "#1f2937",
|
||||||
"bgColorTop": "#000",
|
"bgColorTop": "#1f2937",
|
||||||
"bgTxtStyle": "light",
|
"bgTxtStyle": "light",
|
||||||
"navBgColor": "#000000",
|
"navBgColor": "#1f2937",
|
||||||
"navTxtStyle": "white",
|
"navTxtStyle": "white",
|
||||||
"tabBgColor": "#1a1a1a",
|
"tabBgColor": "#111827",
|
||||||
"tabBorderStyle": "white",
|
"tabBorderStyle": "white",
|
||||||
"tabColor": "#bfbfbf",
|
"tabColor": "#9ca3af",
|
||||||
"tabSelectedColor": "#0165FF"
|
"tabSelectedColor": "#3b82f6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/types/auto-imports.d.ts
vendored
4
src/types/auto-imports.d.ts
vendored
@@ -245,8 +245,6 @@ declare module 'vue' {
|
|||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
readonly getRefreshToken: UnwrapRef<typeof import('../utils/auth')['getRefreshToken']>
|
readonly getRefreshToken: UnwrapRef<typeof import('../utils/auth')['getRefreshToken']>
|
||||||
readonly getToken: UnwrapRef<typeof import('../utils/storage')['getToken']>
|
|
||||||
readonly getUserInfo: UnwrapRef<typeof import('../utils/storage')['getUserInfo']>
|
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly hasAllPermissions: UnwrapRef<typeof import('../utils/permission')['hasAllPermissions']>
|
readonly hasAllPermissions: UnwrapRef<typeof import('../utils/permission')['hasAllPermissions']>
|
||||||
readonly hasAllRoles: UnwrapRef<typeof import('../utils/permission')['hasAllRoles']>
|
readonly hasAllRoles: UnwrapRef<typeof import('../utils/permission')['hasAllRoles']>
|
||||||
@@ -318,8 +316,6 @@ declare module 'vue' {
|
|||||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
readonly setRefreshToken: UnwrapRef<typeof import('../utils/auth')['setRefreshToken']>
|
readonly setRefreshToken: UnwrapRef<typeof import('../utils/auth')['setRefreshToken']>
|
||||||
readonly setToken: UnwrapRef<typeof import('../utils/storage')['setToken']>
|
|
||||||
readonly setUserInfo: UnwrapRef<typeof import('../utils/storage')['setUserInfo']>
|
|
||||||
readonly setupStore: UnwrapRef<typeof import('../store/index')['setupStore']>
|
readonly setupStore: UnwrapRef<typeof import('../store/index')['setupStore']>
|
||||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
|||||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -22,7 +22,7 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
interface PageResult<T> {
|
interface PageResult<T> {
|
||||||
/** 数据列表 */
|
/** 数据列表 */
|
||||||
list: T;
|
list: T[];
|
||||||
/** 总数 */
|
/** 总数 */
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/uni.scss
26
src/uni.scss
@@ -15,26 +15,26 @@
|
|||||||
/* 颜色变量 */
|
/* 颜色变量 */
|
||||||
|
|
||||||
/* 行为相关颜色 */
|
/* 行为相关颜色 */
|
||||||
$uni-color-primary: #007aff;
|
$uni-color-primary: #4d80f0;
|
||||||
$uni-color-success: #4cd964;
|
$uni-color-success: #34d19d;
|
||||||
$uni-color-warning: #f0ad4e;
|
$uni-color-warning: #f0883a;
|
||||||
$uni-color-error: #dd524d;
|
$uni-color-error: #ff4757;
|
||||||
|
|
||||||
/* 文字基本颜色 */
|
/* 文字基本颜色 */
|
||||||
$uni-text-color: #333; // 基本色
|
$uni-text-color: #1f2937; // 基本色
|
||||||
$uni-text-color-inverse: #fff; // 反色
|
$uni-text-color-inverse: #ffffff; // 反色
|
||||||
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
|
$uni-text-color-grey: #6b7280; // 辅助灰色
|
||||||
$uni-text-color-placeholder: #808080;
|
$uni-text-color-placeholder: #9ca3af;
|
||||||
$uni-text-color-disable: #c0c0c0;
|
$uni-text-color-disable: #d1d5db;
|
||||||
|
|
||||||
/* 背景颜色 */
|
/* 背景颜色 */
|
||||||
$uni-bg-color: #fff;
|
$uni-bg-color: #ffffff;
|
||||||
$uni-bg-color-grey: #f8f8f8;
|
$uni-bg-color-grey: #f5f5f5;
|
||||||
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
|
$uni-bg-color-hover: #f3f4f6; // 点击状态颜色
|
||||||
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
||||||
|
|
||||||
/* 边框颜色 */
|
/* 边框颜色 */
|
||||||
$uni-border-color: #c8c7cc;
|
$uni-border-color: #e5e7eb;
|
||||||
|
|
||||||
/* 尺寸变量 */
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { getAccessToken, clearTokens } from "./auth";
|
import { getAccessToken, clearTokens } from "./auth";
|
||||||
|
|
||||||
|
// 401 跳转防抖锁,避免并发请求多次跳转登录页
|
||||||
|
let isRedirecting401 = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求错误类
|
* 请求错误类
|
||||||
*/
|
*/
|
||||||
@@ -7,13 +10,13 @@ export class RequestError extends Error {
|
|||||||
/** HTTP 状态码 */
|
/** HTTP 状态码 */
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
/** 业务错误码 */
|
/** 业务错误码 */
|
||||||
code: number;
|
code: string;
|
||||||
|
|
||||||
constructor(message: string, statusCode: number, code?: number) {
|
constructor(message: string, statusCode: number, code?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "RequestError";
|
this.name = "RequestError";
|
||||||
this.statusCode = statusCode;
|
this.statusCode = statusCode;
|
||||||
this.code = code ?? statusCode;
|
this.code = code ?? String(statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,26 +68,42 @@ function request<T = any>(options: RequestOptions): Promise<T> {
|
|||||||
timeout: options.timeout || 30000,
|
timeout: options.timeout || 30000,
|
||||||
responseType: options.responseType,
|
responseType: options.responseType,
|
||||||
success: (res: any) => {
|
success: (res: any) => {
|
||||||
|
const serverCode = res?.data?.code;
|
||||||
const serverMsg = res?.data?.msg || res?.data?.message;
|
const serverMsg = res?.data?.msg || res?.data?.message;
|
||||||
// 请求成功
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
// 令牌无效/过期(业务码 A023x 或 HTTP 401):清除 token 并跳转登录页(防抖)
|
||||||
resolve(res.data.data);
|
const isTokenError =
|
||||||
}
|
(res.statusCode >= 200 && res.statusCode < 300 && serverCode?.startsWith("A023")) ||
|
||||||
// 未授权错误:清除 token 并跳转登录页
|
res.statusCode === 401;
|
||||||
else if (res.statusCode === 401) {
|
if (isTokenError) {
|
||||||
clearTokens();
|
clearTokens();
|
||||||
uni.navigateTo({
|
if (!isRedirecting401) {
|
||||||
url: "/pages/login/index",
|
isRedirecting401 = true;
|
||||||
});
|
uni.navigateTo({
|
||||||
reject(new RequestError(serverMsg || "未授权,请重新登录", 401));
|
url: "/pages/login/index",
|
||||||
|
complete: () => {
|
||||||
|
isRedirecting401 = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reject(new RequestError(serverMsg || "未授权,请重新登录", 401, serverCode || "A0230"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// 其他错误
|
|
||||||
else {
|
// HTTP 成功:校验业务码
|
||||||
const errorCode = res?.data?.code || res.statusCode;
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
reject(
|
if (!serverCode || serverCode === "00000") {
|
||||||
new RequestError(serverMsg || `请求失败: ${res.statusCode}`, res.statusCode, errorCode)
|
resolve(res.data.data);
|
||||||
);
|
} else {
|
||||||
|
reject(new RequestError(serverMsg || "请求失败", res.statusCode, serverCode));
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 其他 HTTP 错误
|
||||||
|
reject(
|
||||||
|
new RequestError(serverMsg || `请求失败: ${res.statusCode}`, res.statusCode, serverCode)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
reject(new RequestError(err.errMsg || "网络请求失败", 0));
|
reject(new RequestError(err.errMsg || "网络请求失败", 0));
|
||||||
@@ -94,4 +113,3 @@ function request<T = any>(options: RequestOptions): Promise<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default request;
|
export default request;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
/**
|
import {
|
||||||
|
ACCESS_TOKEN_KEY,
|
||||||
|
REFRESH_TOKEN_KEY,
|
||||||
|
USER_INFO_KEY,
|
||||||
|
THEME_MODE_KEY,
|
||||||
|
THEME_COLOR_KEY,
|
||||||
|
} from "@/constants";
|
||||||
|
|
||||||
|
/**
|
||||||
* 存储工具类
|
* 存储工具类
|
||||||
* 提供localStorage和sessionStorage操作方法
|
* 提供uni-app Storage操作方法
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,42 +40,12 @@ export const Storage = {
|
|||||||
remove,
|
remove,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 为了向后兼容,导出具体的函数
|
|
||||||
import { ACCESS_TOKEN_KEY, USER_INFO_KEY, THEME_MODE_KEY, THEME_COLOR_KEY } from "@/constants";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取令牌
|
|
||||||
*/
|
|
||||||
export function getToken(): string | null {
|
|
||||||
return Storage.get<string>(ACCESS_TOKEN_KEY) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置令牌
|
|
||||||
*/
|
|
||||||
export function setToken(token: string): void {
|
|
||||||
Storage.set(ACCESS_TOKEN_KEY, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户信息
|
|
||||||
*/
|
|
||||||
export function getUserInfo<T = any>(): T | undefined {
|
|
||||||
return Storage.get<T>(USER_INFO_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置用户信息
|
|
||||||
*/
|
|
||||||
export function setUserInfo(userInfo: any): void {
|
|
||||||
Storage.set(USER_INFO_KEY, userInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除所有数据
|
* 清除所有数据
|
||||||
*/
|
*/
|
||||||
export function clearAll(): void {
|
export function clearAll(): void {
|
||||||
Storage.remove(ACCESS_TOKEN_KEY);
|
Storage.remove(ACCESS_TOKEN_KEY);
|
||||||
|
Storage.remove(REFRESH_TOKEN_KEY);
|
||||||
Storage.remove(USER_INFO_KEY);
|
Storage.remove(USER_INFO_KEY);
|
||||||
Storage.remove(THEME_MODE_KEY);
|
Storage.remove(THEME_MODE_KEY);
|
||||||
Storage.remove(THEME_COLOR_KEY);
|
Storage.remove(THEME_COLOR_KEY);
|
||||||
|
|||||||
Reference in New Issue
Block a user