feat: 全面升级 Wot UI v2 并修复暗黑模式适配

- 修复 TabBar 切换激活状态不同步问题
- 修复大量 Wot UI 2.x 图标名称不匹配
- 修复暗黑模式下主题变量错误注入导致亮色模式显示异常
- 优化个人中心 UI(头像标记、图标背景色、菜单结构)
- 重构首页图表时间选择器为自定义分段选择器
- 修复登录弹窗文字丢失及验证顺序
- 修复设置页面布局、退出登录按钮及 cell 点击态
This commit is contained in:
Ray.Hao
2026-05-17 23:54:28 +08:00
parent e5cd415f66
commit 4465c4d613
25 changed files with 331 additions and 240 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "youlai-app",
"version": "0.1.2",
"version": "1.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",

View File

@@ -24,7 +24,7 @@
:model-value="isChecked(item.value)"
:indeterminate="isIndeterminate(item.value)"
:disabled="item.disabled"
shape="square"
type="square"
/>
</view>

View File

@@ -33,7 +33,7 @@ export interface TabbarItem {
/** 默认 TabBar 配置 */
const DEFAULT_TABBAR_ITEMS: Omit<TabbarItem, "active">[] = [
{ name: "home", value: null, title: "首页", icon: "home" },
{ name: "work", value: null, title: "工作台", icon: "laptop" },
{ name: "work", value: null, title: "工作台", icon: "apps" },
{ name: "mine", value: null, title: "我的", icon: "user" },
];

View File

@@ -60,23 +60,12 @@ export const menuConfig: WorkMenuGroup[] = [
url: "/pages/work/notice/index",
perm: "sys:notice:list",
},
],
},
{
title: "系统监控",
children: [
{
icon: "/static/icons/log.svg",
title: "系统日志",
url: "/pages/work/log/index",
perm: "sys:log:list",
},
{
icon: "/static/icons/dashboard.svg",
title: "应用监控",
url: "https://www.baidu.com",
perm: "",
},
],
},
{

View File

@@ -17,7 +17,7 @@ export default {
</script>
<template>
<wd-config-provider :theme-vars="themeVars" :custom-class="theme" :theme="theme">
<wd-config-provider :theme-vars="themeVars" :custom-class="theme === 'dark' ? 'dark' : ''" :theme="theme === 'dark' ? 'dark' : ''">
<slot />
<wd-notify />
<wd-toast />

View File

@@ -1,13 +1,14 @@
<template>
<wd-config-provider :theme-vars="themeVars" :custom-class="theme" :theme="theme">
<slot />
<wd-tabbar
:model-value="activeTabbar.name"
bordered
safe-area-inset-bottom
fixed
@change="handleTabbarChange"
>
<view :class="`wot-theme-${theme}`" :style="themeCSSVars">
<wd-config-provider :theme-vars="themeVars" :theme="theme === 'dark' ? 'dark' : ''">
<slot />
<wd-tabbar
:model-value="activeTabbar.name"
bordered
safe-area-inset-bottom
fixed
@change="handleTabbarChange"
>
<wd-tabbar-item
v-for="(item, index) in tabbarList"
:key="index"
@@ -17,41 +18,63 @@
:icon="item.icon"
/>
</wd-tabbar>
<wd-notify />
<wd-toast />
<wd-dialog />
</wd-config-provider>
<wd-notify />
<wd-toast />
<wd-dialog />
</wd-config-provider>
</view>
</template>
<script setup lang="ts">
import { useThemeStore } from "@/store";
import { useTabbar } from "@/composables/useTabbar";
import { useRouter, useRoute } from "uni-mini-router";
import { useRouter } from "uni-mini-router";
import { storeToRefs } from "pinia";
const router = useRouter();
const route = useRoute();
const themeStore = useThemeStore();
const { themeVars, theme } = storeToRefs(themeStore);
const { themeVars, theme, themeCSSVars } = storeToRefs(themeStore);
const { activeTabbar, getTabbarItemValue, setTabbarItemActive, tabbarList } = useTabbar();
/** 页面路径 -> tabbar name 映射(兼容带/不带前缀) */
const ROUTE_TO_TABBAR_NAME: Record<string, string> = {
"/pages/index/index": "home",
"/pages/work/index": "work",
"/pages/mine/index": "mine",
"pages/index/index": "home",
"pages/work/index": "work",
"pages/mine/index": "mine",
};
function handleTabbarChange({ value }: { value: string }) {
setTabbarItemActive(value);
router.pushTab({ name: value });
}
// 监听路由变化更新 tabbar 激活状态
watch(
() => route.name,
(name) => {
if (name && name !== activeTabbar.value.name) {
setTabbarItemActive(name);
}
},
{ immediate: true }
);
/** 根据当前页面路径同步 tabbar 激活状态 */
function syncTabbarActive() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentRoute = (currentPage?.route as string) || "";
if (!currentRoute) return;
const tabName = ROUTE_TO_TABBAR_NAME[currentRoute];
if (tabName && tabName !== activeTabbar.value.name) {
setTabbarItemActive(tabName);
}
}
onMounted(() => {
syncTabbarActive();
// 小程序 switchTab 时 layout 组件被复用onMounted 不会再次触发
// 使用定时轮询兜底,确保 tabbar 激活状态始终与当前页面一致
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU || MP-LARK || MP-XHS || MP-JD
const timer = setInterval(syncTabbarActive, 300);
onUnmounted(() => clearInterval(timer));
// #endif
// #ifdef APP-PLUS
uni.hideTabBar();
// #endif
@@ -62,7 +85,7 @@ onMounted(() => {
export default {
options: {
addGlobalClass: true,
virtualHost: true,
virtualHost: false,
styleIsolation: "shared",
},
};

View File

@@ -13,7 +13,6 @@
"animationDuration": 300
},
"pages": [
// GENERATED BY UNI-PAGES, PLATFORM: MP-WEIXIN
{
"path": "pages/index/index",
"type": "home",
@@ -223,7 +222,6 @@
"backgroundColor": "@tabBgColor",
"borderStyle": "@tabBorderStyle",
"list": [
// GENERATED BY UNI-PAGES, PLATFORM: MP-WEIXIN
{
"pagePath": "pages/index/index"
},

View File

@@ -13,7 +13,7 @@
v-for="(item, index) in quickNavList"
:key="index"
use-slot
@itemclick="handleNavClickWithGuard(item)"
@click="handleNavClickWithGuard(item)"
>
<view class="nav-item">
<image class="nav-item__icon" :src="item.icon" mode="aspectFit" />
@@ -27,7 +27,7 @@
<view class="m-24rpx">
<view class="notice-bar" @click="handleNoticeClick">
<view class="notice-bar__icon">
<wd-icon name="check-outline" size="32rpx" color="var(--color-success)" />
<wd-icon name="check" size="32rpx" color="var(--color-success)" />
</view>
<view class="notice-bar__content">
<text class="notice-bar__text">{{ noticeText || "暂无通知公告" }}</text>
@@ -62,20 +62,27 @@
</view>
<!-- 访问趋势图表 -->
<view class="mt-24rpx">
<view class="m-24rpx">
<wd-card custom-class="chart-card">
<template #title>
<view class="flex-between">
<text class="text-28rpx font-semibold">访问趋势</text>
<wd-radio-group
v-model="recentDaysRange"
shape="button"
inline
@change="handleDataRangeChange"
>
<wd-radio :value="7">近7天</wd-radio>
<wd-radio :value="15">近15天</wd-radio>
</wd-radio-group>
<view class="chart-header">
<text class="chart-header__title">访问趋势</text>
<view class="segment-control">
<view
class="segment-control__item"
:class="{ 'is-active': recentDaysRange === 7 }"
@click="switchRange(7)"
>
近7天
</view>
<view
class="segment-control__item"
:class="{ 'is-active': recentDaysRange === 15 }"
@click="switchRange(15)"
>
近15天
</view>
</view>
</view>
</template>
@@ -238,8 +245,8 @@ async function loadVisitTrendData() {
JSON.stringify({
categories: (data.dates || []).map((d) => dayjs(d).format("MM-DD")),
series: [
{ name: "访客数(UV)", data: data.uvList || [] },
{ name: "浏览量(PV)", data: data.pvList || [] },
{ name: "访客数", data: data.uvList || [] },
{ name: "浏览量", data: data.pvList || [] },
],
})
);
@@ -260,7 +267,8 @@ function handleNoticeClick() {
router.push({ path: "/pages/work/notice/index" });
}
function handleDataRangeChange({ value }: { value: number }) {
function switchRange(value: number) {
if (recentDaysRange.value === value) return;
recentDaysRange.value = value;
loadVisitTrendData();
}
@@ -309,6 +317,42 @@ onShow(() => {
height: 420rpx;
}
.chart-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
}
.chart-header__title {
font-size: 28rpx;
font-weight: 600;
color: var(--color-text);
}
.segment-control {
display: inline-flex;
align-items: center;
padding: 4rpx;
background: var(--color-bg-tertiary);
border: 1rpx solid var(--color-border);
border-radius: 12rpx;
}
.segment-control__item {
padding: 8rpx 20rpx;
font-size: 24rpx;
font-weight: 500;
color: var(--color-text-secondary);
border-radius: 8rpx;
transition: all 0.2s ease;
}
.segment-control__item.is-active {
color: var(--color-text-inverse);
background: var(--color-primary);
}
.section--overlay {
position: relative;
z-index: var(--z-sticky);

View File

@@ -37,7 +37,7 @@
<view v-if="loginMode !== 'WECHAT'" class="login__form">
<!-- 用户名/手机号 -->
<view class="login__form-item login__field">
<wd-icon name="person" size="20" color="var(--color-text-placeholder)" />
<wd-icon name="user" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.username"
class="login__field-input"
@@ -61,7 +61,7 @@
<!-- 图形验证码密码登录时显示 -->
<view v-if="loginMode === 'PASSWORD'" class="login__form-item login__field">
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
<wd-icon name="code-square" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.captchaCode"
class="login__field-input"
@@ -80,7 +80,7 @@
<!-- 短信验证码 -->
<view v-else class="login__form-item login__field">
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="formData.code"
class="login__field-input"
@@ -163,7 +163,7 @@
<!-- 协议勾选 -->
<view class="login__policy">
<wd-checkbox v-model="isAgreePolicy" shape="square" size="32rpx">
<wd-checkbox v-model="isAgreePolicy" type="square" size="32rpx">
<text class="login__policy-text">
我已阅读并同意
<text class="login__policy-link" @click.stop="navigateToAgreement('user')">
@@ -204,7 +204,7 @@
</view>
<view class="login__field">
<wd-icon name="shield" size="20" color="var(--color-text-placeholder)" />
<wd-icon name="lock" size="20" color="var(--color-text-placeholder)" />
<input
v-model="bindMobileForm.code"
class="login__field-input"
@@ -236,17 +236,7 @@
</wd-popup>
<!-- 协议确认弹窗 -->
<wd-dialog selector="policy-box" root-portal>
<view class="policy-dialog__content">
请阅读并同意有来技术
<text class="policy-dialog__link" @click.stop="navigateToAgreement('user')">
用户协议
</text>
<text class="policy-dialog__link" @click.stop="navigateToAgreement('privacy')">
隐私政策
</text>
</view>
</wd-dialog>
<wd-dialog selector="policy-box" root-portal />
<wd-toast />
</view>
@@ -367,8 +357,11 @@ const toggleLoginMode = () => {
const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode = "") => {
pendingLoginAction.value = action;
pendingWechatPhoneCode.value = phoneCode;
message
.confirm({ title: "提示" })
dialog
.confirm({
title: "提示",
msg: "请阅读并同意《用户协议》与《隐私政策》",
})
.then(async () => {
isAgreePolicy.value = true;
const act = pendingLoginAction.value;
@@ -386,12 +379,7 @@ const openPolicyDialog = (action: "FORM" | "WECHAT_PHONE", phoneCode = "") => {
// 表单登录
async function doFormLogin() {
if (!canSubmit.value) {
toast.error(
loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码"
);
return;
}
if (isLoading.value) return;
if (isLoading.value) return;
isLoading.value = true;
try {
@@ -420,6 +408,14 @@ async function doFormLogin() {
}
const handleLogin = async () => {
// 先校验表单必填项
if (!canSubmit.value) {
toast.error(
loginMode.value === "PASSWORD" ? "请输入用户名和密码" : "请输入正确的手机号和验证码"
);
return;
}
// 再校验隐私协议
if (!isAgreePolicy.value) {
openPolicyDialog("FORM");
return;
@@ -893,17 +889,6 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
color: var(--color-primary);
}
.policy-dialog__content {
font-size: 26rpx;
line-height: 1.625;
color: var(--color-text-secondary);
text-align: center;
}
.policy-dialog__link {
color: var(--color-primary);
}
.login__bind-panel {
padding: 48rpx;
}

View File

@@ -27,9 +27,6 @@
lazy-load
/>
<view v-if="isLogin" class="profile-card__online-dot" />
<view v-if="genderIconName" class="profile-card__gender" :class="genderIconClass">
<wd-icon :name="genderIconName" size="12" color="var(--color-text-inverse)" />
</view>
</view>
<view class="profile-card__main" @click="isLogin ? openProfile() : navigateToLogin()">
@@ -46,7 +43,7 @@
</view>
<view class="profile-tag">
<wd-icon
:name="isLogin && deptNameText ? 'home' : 'secured'"
:name="isLogin && deptNameText ? 'home' : 'safe'"
size="12"
color="var(--color-text-inverse)"
/>
@@ -65,13 +62,6 @@
>
<wd-icon name="notification" size="16" color="var(--color-text-inverse)" />
</view>
<view
class="profile-card__action-btn"
aria-label="主题设置"
@click.stop="openThemeSettings"
>
<wd-icon name="setting1" size="16" color="var(--color-text-inverse)" />
</view>
</view>
<view v-if="!isLogin" class="profile-card__action" @click.stop="navigateToLogin()">
@@ -101,7 +91,7 @@
<text class="community-card__desc">开源更新实战内容交流群入口统一在这里查看</text>
</view>
<view class="community-card__arrow">
<wd-icon name="arrow-right" size="16" color="var(--color-text-secondary)" />
<wd-icon name="right" size="16" color="var(--color-text-secondary)" />
</view>
</view>
@@ -117,7 +107,7 @@
</view>
<view class="quick-card" @click="openAccount">
<view class="quick-card__icon quick-card__icon--success">
<wd-icon name="secured" size="28" color="var(--color-success-dark)" />
<wd-icon name="safe" size="28" color="var(--color-success)" />
</view>
<view class="quick-card__body">
<text class="quick-card__title">账号安全</text>
@@ -134,24 +124,24 @@
<view class="menu-list menu-list--flat">
<view class="menu-row" @click="openNetworkTest">
<view class="menu-row__icon menu-row__icon--warning">
<wd-icon name="server" size="18" color="var(--color-warning-dark)" />
<wd-icon name="tool" size="18" color="var(--color-primary)" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">网络检测</text>
<text class="menu-row__desc">检测接口连通性</text>
</view>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
<wd-icon name="right" size="16" color="var(--color-text-placeholder)" />
</view>
<view class="menu-row" @click="handleClearCache">
<view class="menu-row__icon menu-row__icon--danger">
<wd-icon name="delete-thin" size="18" color="var(--color-danger-dark)" />
<wd-icon name="delete" size="18" color="var(--color-danger)" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">清理缓存</text>
<text class="menu-row__desc">显示当前缓存大小</text>
</view>
<text class="menu-row__value">{{ cacheSize }}</text>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
<wd-icon name="right" size="16" color="var(--color-text-placeholder)" />
</view>
</view>
</view>
@@ -161,34 +151,34 @@
<view class="menu-list menu-list--flat">
<view class="menu-row" @click="openSettings">
<view class="menu-row__icon menu-row__icon--primary">
<wd-icon name="setting" size="18" :color="`rgba(77, 128, 240, 0.12)`" />
<wd-icon name="settings" size="18" color="var(--color-primary)" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">系统设置</text>
<text class="menu-row__desc">主题语言通知等设置</text>
</view>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
<wd-icon name="right" size="16" color="var(--color-text-placeholder)" />
</view>
<view class="menu-row" @click="openUserAgreement">
<view class="menu-row__icon menu-row__icon--success">
<wd-icon name="secured" size="18" :color="`rgba(52, 209, 157, 0.12)`" />
<wd-icon name="safe" size="18" color="var(--color-success)" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">用户协议</text>
<text class="menu-row__desc">了解产品使用规则</text>
</view>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
<wd-icon name="right" size="16" color="var(--color-text-placeholder)" />
</view>
<view class="menu-row" @click="openAbout">
<view class="menu-row__icon menu-row__icon--teal">
<wd-icon name="info-circle" size="18" :color="`rgba(52, 209, 157, 0.12)`" />
<view class="menu-row__icon menu-row__icon--info">
<wd-icon name="info-circle" size="18" color="var(--color-info)" />
</view>
<view class="menu-row__main">
<text class="menu-row__title">关于系统</text>
<text class="menu-row__desc">产品介绍与联系方式</text>
</view>
<text class="menu-row__value">v{{ appVersion }}</text>
<wd-icon name="arrow-right" size="16" color="var(--color-text-placeholder)" />
<wd-icon name="right" size="16" color="var(--color-text-placeholder)" />
</view>
</view>
</view>
@@ -250,30 +240,12 @@ const hasUserProfile = computed(() => {
return !!(info && (info.userId || info.username || info.nickname));
});
const genderValue = computed(() => userInfo.value?.gender);
const normalizedGender = computed(() => {
const v = genderValue.value;
const n = typeof v === "number" ? v : Number(v);
return Number.isFinite(n) ? n : 0;
});
const deptNameText = computed(() => {
if (!isLogin.value) return "";
return userInfo.value?.deptName || "";
});
const genderIconName = computed(() => {
if (!isLogin.value) return "";
if (normalizedGender.value === 1) return "gender-male";
if (normalizedGender.value === 2) return "gender-female";
return "";
});
const genderIconClass = computed(() => {
if (normalizedGender.value === 1) return "profile-card__gender--male";
if (normalizedGender.value === 2) return "profile-card__gender--female";
return "";
});
const fetchUserInfoIfNeeded = async () => {
if (!isLogin.value || hasUserProfile.value) return;
@@ -481,8 +453,7 @@ const openOfficialAccount = () => {
letter-spacing: 2rpx;
}
.profile-tag,
.profile-card__gender {
.profile-tag {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -514,6 +485,8 @@ const openOfficialAccount = () => {
.profile-card__left {
position: relative;
flex-shrink: 0;
width: 120rpx;
height: 120rpx;
}
.profile-card__avatar {
@@ -525,27 +498,15 @@ const openOfficialAccount = () => {
box-shadow: var(--shadow-sm);
}
.profile-card__gender {
position: absolute;
right: -4rpx;
bottom: 2rpx;
width: 36rpx;
height: 36rpx;
border: 2rpx solid var(--color-text-inverse);
border-radius: 50%;
box-shadow: var(--shadow-sm);
}
.profile-card__online-dot {
position: absolute;
right: 6rpx;
bottom: 6rpx;
width: 18rpx;
height: 18rpx;
right: -4rpx;
bottom: -4rpx;
width: 16rpx;
height: 16rpx;
background: var(--color-success);
border: 3rpx solid var(--color-border-glass-strong);
border: 2rpx solid var(--color-bg);
border-radius: 50%;
box-shadow: var(--shadow-sm);
}
.profile-card__actions {
@@ -592,13 +553,7 @@ const openOfficialAccount = () => {
border-radius: 999rpx;
}
.profile-card__gender--male {
background: var(--color-primary);
}
.profile-card__gender--female {
background: var(--color-danger);
}
.profile-card__main {
flex: 1;
@@ -856,11 +811,11 @@ const openOfficialAccount = () => {
border-radius: 22rpx;
&--primary {
background: rgba(77, 128, 240, 0.12);
background: var(--color-primary-alpha-15);
}
&--warning {
background: var(--color-warning-light);
background: var(--color-primary-alpha-15);
}
&--danger {
@@ -868,11 +823,11 @@ const openOfficialAccount = () => {
}
&--success {
background: rgba(52, 209, 157, 0.12);
background: var(--color-success-light);
}
&--teal {
background: rgba(52, 209, 157, 0.12);
&--info {
background: var(--color-info-light);
}
}

View File

@@ -67,7 +67,7 @@
<!-- 性别 -->
<view class="profile-form__section">
<view class="profile-form__label">性别</view>
<wd-radio-group v-model="profileForm.gender" shape="button" class="gender-group">
<wd-radio-group v-model="profileForm.gender" type="button" class="gender-group">
<wd-radio :value="1" class="gender-radio"></wd-radio>
<wd-radio :value="2" class="gender-radio"></wd-radio>
</wd-radio-group>

View File

@@ -61,7 +61,7 @@
:rules="rules.nickname"
/>
<wd-cell title="性别" title-width="160rpx" center prop="gender" :rules="rules.gender">
<wd-radio-group v-model="userProfileForm.gender" shape="button" class="ef-radio-group">
<wd-radio-group v-model="userProfileForm.gender" type="button" class="ef-radio-group">
<wd-radio :value="1"></wd-radio>
<wd-radio :value="2"></wd-radio>
</wd-radio-group>

View File

@@ -1,27 +1,32 @@
<template>
<view class="page">
<wd-cell-group>
<wd-cell title="主题设置" icon="setting1" is-link @click="navigateToTheme" />
<wd-cell title="用户协议" icon="user" is-link @click="navigateToUserAgreement" />
<wd-cell title="关于我们" icon="info-circle" is-link @click="navigateToAbout" />
</wd-cell-group>
<wd-cell-group custom-style="margin-top:40rpx">
<wd-cell title="网络测试" icon="wifi" is-link @click="navigateToNetworkTest" />
<wd-cell
title="清空缓存"
icon="delete1"
:value="cacheSize"
clickable
@click="handleClearCache"
/>
</wd-cell-group>
<view v-if="isLogin" class="logout-section">
<wd-button custom-class="logout-btn" plain @click="handleLogout">退出登录</wd-button>
<view class="page settings-page">
<view class="settings-group-wrap">
<wd-cell-group>
<wd-cell title="主题设置" icon="settings" is-link @click="navigateToTheme" />
<wd-cell title="用户协议" icon="user" is-link @click="navigateToUserAgreement" />
<wd-cell title="关于我们" icon="info-circle" is-link @click="navigateToAbout" />
</wd-cell-group>
</view>
<view class="settings-group-wrap">
<wd-cell-group custom-style="margin-top:24rpx">
<wd-cell title="网络测试" icon="wifi" is-link @click="navigateToNetworkTest" />
<wd-cell
title="清空缓存"
icon="delete"
:value="cacheSize"
clickable
@click="handleClearCache"
/>
</wd-cell-group>
</view>
<view v-if="isLogin" class="logout-section">
<wd-button type="danger" block custom-class="logout-btn" @click="handleLogout">
退出登录
</wd-button>
</view>
<!-- 使用wot-design-uni的Loading组件 -->
<wd-loading
v-if="isClearing"
v-model="isClearing"
@@ -35,11 +40,10 @@
<script lang="ts" setup>
import { useUserStore } from "@/store";
import { onLoad } from "@dcloudio/uni-app";
import { useToast, useDialog } from "@wot-ui/ui";
import { useToast } from "@wot-ui/ui";
const userStore = useUserStore();
const toast = useToast();
const { messageBox } = useDialog();
const isLogin = computed(() => !!userStore.userInfo);
// 主题设置
@@ -149,14 +153,21 @@ const handleClearCache = async () => {
};
// 退出登录
const handleLogout = async () => {
try {
await messageBox({ title: "提示", msg: "确定要退出登录吗?", type: "warning" });
userStore.logout();
toast.success("已退出登录");
} catch {
// 用户取消
}
const handleLogout = () => {
uni.showModal({
title: "提示",
content: "确定要退出登录吗?",
confirmColor: "#ff4757",
success: (res) => {
if (res.confirm) {
userStore.logout();
toast.success("已退出登录");
setTimeout(() => {
uni.reLaunch({ url: "/pages/mine/index" });
}, 800);
}
},
});
};
// 检查登录状态
@@ -182,17 +193,27 @@ onLoad(() => {
margin-top: 60rpx;
}
:deep(.logout-btn) {
width: 80%;
height: 80rpx;
font-size: 32rpx;
font-weight: bold;
border-radius: 40rpx;
.settings-page {
padding: 24rpx;
}
&:active {
opacity: 0.8;
transform: scale(0.98);
}
.settings-group-wrap {
margin-bottom: 24rpx;
}
.logout-section {
margin-top: 48rpx;
}
:deep(.wd-cell:active) {
background-color: var(--color-bg-tertiary) !important;
}
:deep(.logout-btn) {
height: 88rpx;
font-size: 32rpx;
font-weight: 500;
border-radius: 24rpx;
}
.loading-center {

View File

@@ -12,7 +12,7 @@
<!-- 预设颜色选择 -->
<wd-cell title="预设主题色">
<template #default>
<view class="flex gap-16rpx">
<view class="flex flex-wrap gap-12rpx">
<view
v-for="(color, index) in themeColorOptions"
:key="index"
@@ -54,7 +54,7 @@
<wd-cell-group title="效果预览" border custom-class="mt-24rpx">
<wd-cell>
<template #default>
<view class="flex gap-24rpx py-12rpx">
<view class="flex items-center justify-center gap-24rpx py-12rpx">
<wd-button type="primary" size="small">主要按钮</wd-button>
<text class="text-28rpx font-500" :style="{ color: currentThemeColor }">
主题色文本
@@ -67,7 +67,9 @@
<!-- 操作按钮 -->
<view class="mt-32rpx">
<wd-button type="info" size="large" block @click="handleResetTheme">重置为默认主题</wd-button>
<wd-button type="primary" plain size="large" block @click="handleResetTheme">
重置为默认主题
</wd-button>
</view>
<!-- 自定义颜色输入弹窗 -->
@@ -88,7 +90,12 @@
<text class="text-28rpx color-text-secondary">请输入十六进制颜色值</text>
<view class="flex items-center gap-16rpx mt-16rpx">
<view class="color-dot-lg" :style="{ backgroundColor: customColor || '#FF4757' }" />
<wd-input v-model="customColor" placeholder="例如: #FF4757" :maxlength="7" />
<wd-input
v-model="customColor"
placeholder="例如: #FF4757"
:maxlength="7"
custom-class="theme-input"
/>
</view>
<text class="text-24rpx color-text-placeholder mt-12rpx">支持格式#RGB #RRGGBB</text>
</view>
@@ -280,4 +287,9 @@ onShow(() => {
padding: 24rpx 32rpx;
border-top: 1rpx solid var(--color-border);
}
:deep(.theme-input) {
background-color: var(--color-bg-secondary) !important;
color: var(--color-text) !important;
}
</style>

View File

@@ -71,7 +71,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeDictDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeDictDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitDictForm">保存</wd-button>
</view>
</view>

View File

@@ -76,7 +76,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeItemDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeItemDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitItemForm">保存</wd-button>
</view>
</view>

View File

@@ -8,7 +8,7 @@
v-for="(child, childIndex) in item.children"
:key="childIndex"
use-slot
@itemclick="handleNavClick(child)"
@click="handleNavClick(child)"
>
<view class="work-grid__icon p-2">
<image class="w-72rpx h-72rpx rounded-8rpx" :src="child.icon" />

View File

@@ -93,7 +93,7 @@
<wd-cell v-if="logDetail.errorMsg" title="错误信息" :value="logDetail.errorMsg" />
</wd-cell-group>
<view class="popup-actions">
<wd-button type="info" plain block @click="closeLogDetail">关闭</wd-button>
<wd-button type="info" variant="plain" block @click="closeLogDetail">关闭</wd-button>
</view>
</view>
</wd-popup>

View File

@@ -65,7 +65,7 @@
/>
<wd-input v-model="formData.name" label="菜单名称" required />
<wd-cell title="菜单类型" required>
<wd-radio-group v-model="formData.type" size="smalll" shape="button" cell>
<wd-radio-group v-model="formData.type" size="small" type="button">
<wd-radio value="C">目录</wd-radio>
<wd-radio value="M">菜单</wd-radio>
<wd-radio value="B">按钮</wd-radio>
@@ -100,7 +100,7 @@
</wd-form>
</scroll-view>
<view class="popup-actions">
<wd-button type="info" plain @click="closeMenuDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeMenuDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitMenuForm">保存</wd-button>
</view>
</view>

View File

@@ -90,7 +90,7 @@
<rich-text :nodes="noticeDetail.content" class="text-28rpx" />
</view>
<view class="popup-actions">
<wd-button type="info" plain block @click="closeNoticeDetail">关闭</wd-button>
<wd-button type="info" variant="plain" block @click="closeNoticeDetail">关闭</wd-button>
</view>
</view>
</wd-popup>
@@ -110,14 +110,14 @@
<wd-cell-group border>
<wd-input v-model="formData.title" label="标题" required placeholder="请输入通知标题" />
<wd-cell title="优先级">
<wd-radio-group v-model="formData.level" shape="button">
<wd-radio-group v-model="formData.level" type="button">
<wd-radio value="L"></wd-radio>
<wd-radio value="M"></wd-radio>
<wd-radio value="H"></wd-radio>
</wd-radio-group>
</wd-cell>
<wd-cell title="目标类型">
<wd-radio-group v-model="formData.targetType" shape="button">
<wd-radio-group v-model="formData.targetType" type="button">
<wd-radio :value="1">全体</wd-radio>
<wd-radio :value="2">指定用户</wd-radio>
</wd-radio-group>
@@ -132,7 +132,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeNoticeForm">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeNoticeForm">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitNoticeForm">
保存
</wd-button>

View File

@@ -9,8 +9,8 @@
<view class="action-bar">
<view class="action-bar__left">
<wd-button size="small" plain @click="handleSelectAll">全选</wd-button>
<wd-button size="small" plain @click="handleClearAll">取消全选</wd-button>
<wd-button size="small" variant="plain" @click="handleSelectAll">全选</wd-button>
<wd-button size="small" variant="plain" @click="handleClearAll">取消全选</wd-button>
</view>
</view>
</view>
@@ -69,7 +69,7 @@
<!-- 固定底部操作栏 -->
<view class="bottom-bar">
<wd-button type="info" plain @click="handleCancel">取消</wd-button>
<wd-button type="info" variant="plain" @click="handleCancel">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="handleSubmit">保存</wd-button>
</view>
</view>

View File

@@ -89,7 +89,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeRoleDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeRoleDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitRoleForm">保存</wd-button>
</view>
</view>

View File

@@ -33,7 +33,7 @@
/>
<cu-date-query v-model="queryParams.createTime" label="创建时间" />
<view class="popup-actions">
<wd-button type="info" plain @click="resetUserFilter">重置</wd-button>
<wd-button type="info" variant="plain" @click="resetUserFilter">重置</wd-button>
<wd-button type="primary" @click="applyUserFilter">查询</wd-button>
</view>
</view>
@@ -148,7 +148,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeUserDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeUserDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitUserForm">保存</wd-button>
</view>
</view>
@@ -184,7 +184,7 @@
</wd-cell-group>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="resetPwdDialog.visible = false">取消</wd-button>
<wd-button type="info" variant="plain" @click="resetPwdDialog.visible = false">取消</wd-button>
<wd-button
type="primary"
:loading="resetPwdDialog.isSubmitting"

View File

@@ -26,8 +26,10 @@ export const useThemeStore = defineStore("theme", () => {
Storage.get<ThemeColorOption>(THEME_COLOR_KEY, themeColorOptions[0])
);
/** 主题变量(响应式对象) */
const themeVars = reactive({
/**
* 深色主题变量(仅在 dark 模式下注入)
*/
const darkThemeVars = {
darkBackground: "#1f2937",
darkBackground2: "#111827",
darkBackground3: "#1e293b",
@@ -38,7 +40,45 @@ export const useThemeStore = defineStore("theme", () => {
darkColor: "#f9fafb",
darkColor2: "#9ca3af",
darkColor3: "#6b7280",
colorTheme: currentThemeColor.value.primary,
filledOppo: "#1f2937",
cardBg: "#1f2937",
cardBorderColor: "#374151",
cardTitleColor: "#f9fafb",
cardContentColor: "#9ca3af",
gridItemBg: "#1f2937",
gridItemTextColor: "#f9fafb",
gridItemIconColor: "#9ca3af",
cellBg: "#1f2937",
popupBg: "#1f2937",
searchBg: "#1f2937",
searchInputBg: "#374151",
inputBg: "#374151",
textareaBg: "#374151",
pickerBg: "#1f2937",
dialogBg: "#1f2937",
actionSheetBg: "#1f2937",
tabsNavBg: "#1f2937",
tabbarBg: "#1f2937",
navbarBg: "#1f2937",
buttonNormalBg: "#374151",
borderMain: "#374151",
borderLight: "#4b5563",
textMain: "#f9fafb",
textSecondary: "#9ca3af",
textPlaceholder: "#6b7280",
iconMain: "#f9fafb",
iconSecondary: "#9ca3af",
baseBlack: "#1f2937",
baseWhite: "#f9fafb",
};
/** 主题变量(计算属性,根据模式动态切换) */
const themeVars = computed(() => {
const base = { colorTheme: currentThemeColor.value.primary };
if (theme.value === "dark") {
return { ...base, ...darkThemeVars };
}
return base;
});
// ==========================================================================
@@ -48,6 +88,27 @@ export const useThemeStore = defineStore("theme", () => {
/** 是否为暗黑模式 */
const isDark = computed(() => theme.value === "dark");
/**
* 将 themeVars 转换为 CSS 变量内联样式字符串
* light 模式下只注入主题色,避免覆盖 Wot UI 默认亮色样式
* dark 模式下注入完整的深色变量覆盖
*/
const kebabCase = (str: string) =>
str
.replace(/^./, (s) => s.toLowerCase())
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/(\d)/g, "-$1")
.toLowerCase();
const themeCSSVars = computed(() => {
if (theme.value === "light") {
return `--wot-color-theme:${themeVars.value.colorTheme};`;
}
return Object.entries(themeVars.value)
.map(([key, val]) => `--wot-${kebabCase(key)}:${val}`)
.join(";");
});
// ==========================================================================
// 方法
// ==========================================================================
@@ -79,17 +140,12 @@ export const useThemeStore = defineStore("theme", () => {
const setCurrentThemeColor = (color: ThemeColorOption) => {
currentThemeColor.value = color;
Storage.set(THEME_COLOR_KEY, color);
themeVars.colorTheme = color.primary;
};
/**
* 初始化主题
*/
const initTheme = () => {
// 更新主题变量中的颜色
themeVars.colorTheme = currentThemeColor.value.primary;
// 设置导航栏颜色
nextTick(() => {
setNavigationBarColor();
});
@@ -107,6 +163,7 @@ export const useThemeStore = defineStore("theme", () => {
// 计算属性
isDark,
themeCSSVars,
// 方法
toggleTheme,

View File

@@ -33,6 +33,8 @@ page {
--color-warning-light: #fff4e6;
--color-danger: #ff4757;
--color-danger-light: #fff1f2;
--color-info: #3b82f6;
--color-info-light: #dbeafe;
--color-wx-green: #22c55e;
// -----------------------------------------------------------------------
@@ -130,6 +132,8 @@ page {
--color-warning-light: #78350f;
--color-danger: #f87171;
--color-danger-light: #7f1d1d;
--color-info: #60a5fa;
--color-info-light: #1e3a5f;
--color-wx-green: #22c55e;
// -----------------------------------------------------------------------
@@ -192,4 +196,7 @@ page {
--wot-color-text-secondary: var(--color-text-secondary);
--wot-color-text-placeholder: var(--color-text-placeholder);
--wot-color-border: var(--color-border);
--wot-radio-button-bg: var(--color-bg-tertiary);
--wot-radio-button-checked-bg: var(--color-primary);
--wot-radio-label-color: var(--color-text);
}