fix: 升级 Wot UI v2 — 弹窗/表单/暗黑模式/FAB 兼容修复

This commit is contained in:
Ray.Hao
2026-05-21 11:04:02 +08:00
parent 13ea3eef28
commit 6be527fce1
17 changed files with 311 additions and 279 deletions

1
components.d.ts vendored
View File

@@ -38,6 +38,7 @@ declare module 'vue' {
WdFab: typeof import('@wot-ui/ui/components/wd-fab/wd-fab.vue')['default']
WdForm: typeof import('@wot-ui/ui/components/wd-form/wd-form.vue')['default']
WdFormItem: typeof import('@wot-ui/ui/components/wd-form-item/wd-form-item.vue')['default']
WdGap: typeof import('@wot-ui/ui/components/wd-gap/wd-gap.vue')['default']
WdGrid: typeof import('@wot-ui/ui/components/wd-grid/wd-grid.vue')['default']
WdGridItem: typeof import('@wot-ui/ui/components/wd-grid-item/wd-grid-item.vue')['default']
WdIcon: typeof import('@wot-ui/ui/components/wd-icon/wd-icon.vue')['default']

View File

@@ -9,3 +9,7 @@ onLaunch(() => {
onShow(() => {});
</script>
<style lang="scss">
@use "@wot-ui/ui/styles/theme/index.scss" as *;
</style>

View File

@@ -7,7 +7,10 @@
"backgroundTextStyle": "@bgTxtStyle",
"backgroundColorTop": "@bgColorTop",
"backgroundColorBottom": "@bgColorBottom",
"enablePullDownRefresh": false
"enablePullDownRefresh": false,
"onReachBottomDistance": 50,
"animationType": "pop-in",
"animationDuration": 300
},
"pages": [
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
@@ -212,6 +215,8 @@
"subPackages": [],
"tabBar": {
"custom": true,
"customize": true,
"overlay": true,
"height": "0",
"color": "@tabColor",
"selectedColor": "@tabSelectedColor",

View File

@@ -720,18 +720,18 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
display: flex;
flex-direction: column;
align-items: center;
margin-top: 16rpx;
margin-bottom: 72rpx;
margin-top: 8rpx;
margin-bottom: 40rpx;
}
.login__logo {
width: 160rpx;
height: 160rpx;
margin-bottom: 32rpx;
width: 120rpx;
height: 120rpx;
margin-bottom: 20rpx;
}
.login__brand-name {
font-size: 40rpx;
font-size: 36rpx;
font-weight: 600;
letter-spacing: 0.05em;
color: var(--color-text);
@@ -739,8 +739,9 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
.login__card {
width: 100%;
padding: 64rpx;
padding: 44rpx;
background-color: var(--color-bg);
border: 2rpx solid var(--color-border-light);
border-radius: 48rpx;
box-shadow: 0 20rpx 50rpx -10rpx rgba(0, 0, 0, 0.1);
@@ -753,25 +754,25 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
// 卡片头部
.login__card-head {
margin-bottom: 64rpx;
margin-bottom: 36rpx;
text-align: center;
}
.login__card-title {
font-size: 48rpx;
font-size: 40rpx;
font-weight: 700;
color: var(--color-text);
}
.login__card-subtitle {
display: block;
margin-top: 16rpx;
font-size: 28rpx;
margin-top: 8rpx;
font-size: 26rpx;
color: var(--color-text-secondary);
}
.login__form-item {
margin-top: 32rpx;
margin-top: 28rpx;
&:first-child {
margin-top: 0;
@@ -781,7 +782,7 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
// 覆盖 wd-form-item 默认样式,仅用于校验和错误提示
:deep(.wd-form-item) {
padding: 0 !important;
margin-top: 32rpx;
margin-top: 28rpx;
background: transparent;
&:first-child {
@@ -816,7 +817,7 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
.login__field {
display: flex;
align-items: center;
height: 96rpx;
height: 88rpx;
padding: 0 32rpx;
background-color: var(--color-bg-secondary);
border-radius: 24rpx;
@@ -842,7 +843,7 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
align-items: center;
justify-content: center;
width: 100%;
height: 96rpx;
height: 88rpx;
font-size: 32rpx;
font-weight: 600;
color: var(--color-text-inverse);
@@ -884,7 +885,7 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
.login__mode-switch {
display: flex;
justify-content: center;
padding-top: 32rpx;
padding-top: 24rpx;
}
.login__mode-switch-text {
@@ -903,7 +904,7 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
.login__divider {
display: flex;
align-items: center;
margin: 48rpx 0;
margin: 32rpx 0;
}
.login__divider-line {
@@ -925,8 +926,8 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
}
.login__wx-icon {
width: 80rpx;
height: 80rpx;
width: 72rpx;
height: 72rpx;
&:active {
transform: scale(0.95);
@@ -937,8 +938,8 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 32rpx;
margin-top: 48rpx;
padding-top: 24rpx;
margin-top: 32rpx;
border-top: 2rpx solid var(--color-border-light);
}
@@ -979,3 +980,28 @@ onShow(() => uni.setNavigationBarTitle({ title: "" }));
border-radius: 8rpx;
}
</style>
<style lang="scss">
/* 暗黑模式覆盖 - 非 scoped因 .wot-theme-dark 位于页面根元素 */
.wot-theme-dark {
.login__card {
background-color: #252d3a;
border-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 20rpx 50rpx -10rpx rgba(0, 0, 0, 0.4);
}
.login__divider-line {
background-color: rgba(255, 255, 255, 0.12);
}
.login__field {
background-color: rgba(255, 255, 255, 0.06);
}
.login__navbar-btn {
background-color: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.08);
}
}
</style>

View File

@@ -51,22 +51,27 @@
<view class="popup-title">
{{ formData.id ? "编辑配置" : "新增配置" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.configName" label="配置名称" required />
<wd-input v-model="formData.configKey" label="配置键名" required />
<wd-input v-model="formData.configValue" label="配置键" required />
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="configName" title="配置名称" required>
<wd-input v-model="formData.configName" placeholder="请输入配置名称" />
</wd-form-item>
<wd-form-item prop="configKey" title="配置键" required>
<wd-input v-model="formData.configKey" placeholder="请输入配置键名" />
</wd-form-item>
<wd-form-item prop="configValue" title="配置键值" required>
<wd-input v-model="formData.configValue" placeholder="请输入配置键值" />
</wd-form-item>
<wd-form-item prop="remark" title="描述">
<wd-textarea
v-model="formData.remark"
label="描述"
placeholder="请输入配置描述"
:maxlength="100"
show-word-limit
/>
</wd-cell-group>
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeConfigDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeConfigDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitConfigForm">
保存
</wd-button>
@@ -77,6 +82,8 @@
<!-- 浮动新增按钮 -->
<wd-fab
v-if="hasPermission('sys:config:create') && !dialog.visible"
:expandable="false"
:gap="{ bottom: 32 }"
@click="openConfigDialog()"
/>
@@ -98,7 +105,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import ConfigAPI, { type ConfigPageQuery, ConfigItem, ConfigForm } from "@/api/config";
import { hasPermission } from "@/utils/permission";
@@ -128,11 +135,11 @@ const initialFormData: ConfigForm = {
const formData = reactive<ConfigForm>({ ...initialFormData });
const rules: FormRules = {
const rules = toFormSchema({
configName: [{ required: true, message: "请输入配置名称" }],
configKey: [{ required: true, message: "请输入配置键名" }],
configValue: [{ required: true, message: "请输入配置键值" }],
};
});
// 搜索触发
const handleSearch = () => loadConfigList();
@@ -198,17 +205,18 @@ function closeConfigDialog() {
}
// 操作菜单分发
function handleActionSelect({ value }: { value: string }) {
const item = currentActionItem.value;
function handleActionSelect({ item }: { item: any }) {
const value = item.name;
const configItem = currentActionItem.value;
if (value === "编辑") {
openConfigDialog(item.id);
openConfigDialog(configItem.id);
} else if (value === "删除") {
messageBox({
title: "确认删除",
msg: `确定要删除配置「${item.configName}」吗?`,
msg: `确定要删除配置「${configItem.configName}」吗?`,
type: "warning",
}).then(async () => {
await ConfigAPI.deleteById(item.id!);
await ConfigAPI.deleteById(configItem.id!);
toast.success("删除成功");
loadConfigList();
});

View File

@@ -43,36 +43,38 @@
<view class="popup-title">
{{ formData.id ? "编辑部门" : "新增部门" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="parentId" title="上级部门" required>
<wd-cascader
v-model="parentSelected"
label="上级部门"
:columns="parentColumns"
:column-change="handleParentColumnChange"
required
:display-format="displayParentFormat"
@confirm="handleParentConfirm"
/>
<wd-input v-model="formData.name" label="部门名称" required />
<wd-input v-model="formData.code" label="部门编号" required />
<wd-cell title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-cell>
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
</wd-cell-group>
</wd-form-item>
<wd-form-item prop="name" title="部门名称" required>
<wd-input v-model="formData.name" placeholder="请输入部门名称" />
</wd-form-item>
<wd-form-item prop="code" title="部门编号" required>
<wd-input v-model="formData.code" placeholder="请输入部门编号" />
</wd-form-item>
<wd-form-item prop="sort" title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-form-item>
<wd-form-item prop="status" title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" plain @click="closeDeptDialog">取消</wd-button>
<wd-button type="info" variant="plain" @click="closeDeptDialog">取消</wd-button>
<wd-button type="primary" :loading="isSubmitting" @click="submitDeptForm">保存</wd-button>
</view>
</view>
</wd-popup>
<!-- 浮动新增按钮 -->
<wd-fab v-if="hasPermission('sys:dept:create') && !dialog.visible" @click="openDeptDialog()" />
<wd-fab v-if="hasPermission('sys:dept:create') && !dialog.visible" :expandable="false" :gap="{ bottom: 32 }" @click="openDeptDialog()" />
<!-- 操作菜单 -->
<wd-action-sheet
@@ -91,7 +93,7 @@ definePage({
});
import { onLoad } from "@dcloudio/uni-app";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import DeptAPI, { type DeptQuery, DeptItem, DeptForm } from "@/api/dept";
import { hasPermission } from "@/utils/permission";
@@ -184,11 +186,11 @@ const handleParentConfirm = ({ value }: any) => {
formData.parentId = Number(value[value.length - 1]) || 0;
};
const rules: FormRules = {
const rules = toFormSchema({
name: [{ required: true, message: "请输入部门名称" }],
code: [{ required: true, message: "请输入部门编号" }],
parentId: [{ required: true, message: "请选择上级部门" }],
};
});
// 搜索触发
const handleSearch = () => loadDeptList();
@@ -205,7 +207,8 @@ function loadDeptList() {
}
// 操作菜单分发
function handleActionSelect({ value }: { value: string }) {
function handleActionSelect({ item }: { item: any }) {
const value = item.name;
const dept = currentActionItem.value;
if (value === "新增子部门") {
handleAddChild(dept);

View File

@@ -23,7 +23,7 @@
</view>
<text class="text-24rpx color-text-secondary">字典编码{{ item.dictCode }}</text>
</view>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" variant="plain">
{{ item.status === 1 ? "启用" : "禁用" }}
</wd-tag>
</view>
@@ -54,21 +54,24 @@
<view class="popup-title">
{{ formData.id ? "编辑字典" : "新增字典" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.name" label="字典名称" required />
<wd-input v-model="formData.dictCode" label="字典编码" required />
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="name" title="字典名称" required>
<wd-input v-model="formData.name" placeholder="请输入字典名称" />
</wd-form-item>
<wd-form-item prop="dictCode" title="字典编码" required>
<wd-input v-model="formData.dictCode" placeholder="请输入字典编码" />
</wd-form-item>
<wd-form-item prop="status" title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-form-item>
<wd-form-item prop="remark" title="备注">
<wd-textarea
v-model="formData.remark"
label="备注"
placeholder="请输入备注"
:maxlength="100"
show-word-limit
/>
</wd-cell-group>
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="closeDictDialog">取消</wd-button>
@@ -84,7 +87,7 @@
@select="handleActionSelect"
/>
<wd-fab v-if="hasPermission('sys:dict:create') && !dialog.visible" @click="openDictDialog()" />
<wd-fab v-if="hasPermission('sys:dict:create') && !dialog.visible" :expandable="false" :gap="{ bottom: 32 }" @click="openDictDialog()" />
</view>
</template>
@@ -97,7 +100,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { useRouter } from "uni-mini-router";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import DictAPI, { type DictTypeForm, type DictTypePageQuery, type DictTypeItem } from "@/api/dict";
import { hasPermission } from "@/utils/permission";
@@ -124,10 +127,10 @@ const initialFormData: DictTypeForm = {
const formData = reactive<DictTypeForm>({ ...initialFormData });
const rules: FormRules = {
const rules = toFormSchema({
name: [{ required: true, message: "请输入字典名称" }],
dictCode: [{ required: true, message: "请输入字典编码" }],
};
});
const handleSearch = () => loadDictTypeList();
@@ -241,8 +244,8 @@ function showDictActions(item: DictTypeItem) {
actionSheetVisible.value = true;
}
function handleActionSelect({ value }: { value: string }) {
pendingAction.value[value]?.();
function handleActionSelect({ item }: { item: any }) {
pendingAction.value[item.name]?.();
}
onReachBottom(() => {

View File

@@ -25,7 +25,7 @@
字典值{{ item.value }} · 排序{{ item.sort }}
</text>
</view>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" variant="plain">
{{ item.status === 1 ? "启用" : "禁用" }}
</wd-tag>
</view>
@@ -56,24 +56,27 @@
<view class="popup-title">
{{ formData.id ? "编辑字典数据" : "新增字典数据" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.label" label="字典项标签" required />
<wd-input v-model="formData.value" label="字典项值" required />
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
<wd-cell title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-cell>
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="label" title="字典项标签" required>
<wd-input v-model="formData.label" placeholder="请输入字典项标签" />
</wd-form-item>
<wd-form-item prop="value" title="字典项值" required>
<wd-input v-model="formData.value" placeholder="请输入字典项值" />
</wd-form-item>
<wd-form-item prop="status" title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-form-item>
<wd-form-item prop="sort" title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-form-item>
<wd-form-item prop="remark" title="备注">
<wd-textarea
v-model="formData.remark"
label="备注"
placeholder="请输入备注"
:maxlength="100"
show-word-limit
/>
</wd-cell-group>
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="closeItemDialog">取消</wd-button>
@@ -91,6 +94,8 @@
<wd-fab
v-if="hasPermission('sys:dict-item:create') && !dialog.visible"
:expandable="false"
:gap="{ bottom: 32 }"
@click="openItemDialog()"
/>
</view>
@@ -104,7 +109,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import DictAPI, { type DictItemForm, type DictItemPageQuery, type DictDataItem } from "@/api/dict";
import { hasPermission } from "@/utils/permission";
@@ -136,10 +141,10 @@ const initialFormData: DictItemForm = {
const formData = reactive<DictItemForm>({ ...initialFormData });
const rules: FormRules = {
const rules = toFormSchema({
label: [{ required: true, message: "请输入字典项标签" }],
value: [{ required: true, message: "请输入字典项值" }],
};
});
const handleSearch = () => loadItemList();
@@ -259,8 +264,8 @@ function showItemActions(item: DictDataItem) {
actionSheetVisible.value = true;
}
function handleActionSelect({ value }: { value: string }) {
pendingAction.value[value]?.();
function handleActionSelect({ item }: { item: any }) {
pendingAction.value[item.name]?.();
}
onReachBottom(() => {

View File

@@ -52,51 +52,44 @@
{{ formData.id ? "编辑菜单" : "新增菜单" }}
</view>
<scroll-view scroll-y class="max-h-60vh">
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="parentId" title="上级菜单" required>
<wd-cascader
v-model="parentSelected"
label="上级菜单"
:columns="parentColumns"
:column-change="handleParentColumnChange"
required
:display-format="displayParentFormat"
@confirm="handleParentConfirm"
/>
<wd-input v-model="formData.name" label="菜单名称" required />
<wd-cell title="菜单类型" required>
<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>
</wd-radio-group>
</wd-cell>
<wd-input
v-if="formData.type !== 'B'"
v-model="formData.routePath"
label="路由路径"
placeholder="system 或 /system"
/>
<wd-input
v-if="formData.type === 'M'"
v-model="formData.component"
label="组件路径"
placeholder="system/menu/index"
/>
<wd-input
v-if="formData.type === 'B'"
v-model="formData.perm"
label="权限标识"
placeholder="sys:menu:create"
/>
<wd-input v-model="formData.icon" label="图标" />
<wd-cell title="排序">
<wd-input-number v-model="formData.sort!" :min="0" />
</wd-cell>
<wd-cell title="状态">
<wd-switch v-model="formData.visible" :active-value="1" :inactive-value="0" />
</wd-cell>
</wd-cell-group>
</wd-form-item>
<wd-form-item prop="name" title="菜单名称" required>
<wd-input v-model="formData.name" placeholder="请输入菜单名称" />
</wd-form-item>
<wd-form-item prop="type" title="菜单类型" required>
<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>
</wd-radio-group>
</wd-form-item>
<wd-form-item v-if="formData.type !== 'B'" prop="routePath" title="路由路径">
<wd-input v-model="formData.routePath" placeholder="system 或 /system" />
</wd-form-item>
<wd-form-item v-if="formData.type === 'M'" prop="component" title="组件路径">
<wd-input v-model="formData.component" placeholder="system/menu/index" />
</wd-form-item>
<wd-form-item v-if="formData.type === 'B'" prop="perm" title="权限标识">
<wd-input v-model="formData.perm" placeholder="sys:menu:create" />
</wd-form-item>
<wd-form-item prop="icon" title="图标">
<wd-input v-model="formData.icon" placeholder="请输入图标名" />
</wd-form-item>
<wd-form-item prop="sort" title="排序">
<wd-input-number v-model="formData.sort!" :min="0" />
</wd-form-item>
<wd-form-item prop="visible" title="状态">
<wd-switch v-model="formData.visible" :active-value="1" :inactive-value="0" />
</wd-form-item>
</wd-form>
</scroll-view>
<view class="popup-actions">
@@ -107,7 +100,7 @@
</wd-popup>
<!-- 浮动新增按钮 -->
<wd-fab v-if="hasPermission('sys:menu:create') && !dialog.visible" @click="openMenuDialog()" />
<wd-fab v-if="hasPermission('sys:menu:create') && !dialog.visible" :expandable="false" :gap="{ bottom: 32 }" @click="openMenuDialog()" />
<!-- 操作菜单 -->
<wd-action-sheet
@@ -126,7 +119,7 @@ definePage({
});
import { onLoad } from "@dcloudio/uni-app";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import MenuAPI, { type MenuQuery, MenuItem, MenuForm } from "@/api/menu";
import { hasPermission } from "@/utils/permission";
@@ -250,11 +243,11 @@ const handleParentConfirm = ({ value }: any) => {
formData.parentId = String(value[value.length - 1]) || "0";
};
const rules: FormRules = {
const rules = toFormSchema({
name: [{ required: true, message: "请输入菜单名称" }],
type: [{ required: true, message: "请选择菜单类型" }],
parentId: [{ required: true, message: "请选择上级菜单" }],
};
});
const handleSearch = () => loadMenuList();
@@ -265,7 +258,8 @@ function loadMenuList() {
}
// 操作菜单分发
function handleActionSelect({ value }: { value: string }) {
function handleActionSelect({ item }: { item: any }) {
const value = item.name;
const menu = currentActionItem.value;
if (value === "新增子菜单") {
handleAddChild(menu);

View File

@@ -27,7 +27,7 @@
{{ item.publisherName || "系统管理员" }}
</text>
</view>
<wd-tag :type="getStatusType(item.publishStatus)" plain>
<wd-tag :type="getStatusType(item.publishStatus)" variant="plain">
{{ getStatusText(item.publishStatus) }}
</wd-tag>
</view>
@@ -106,30 +106,31 @@
<view class="popup-title">
{{ formData.id ? "编辑通知" : "新增通知" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="formRules">
<wd-cell-group border>
<wd-input v-model="formData.title" label="标题" required placeholder="请输入通知标题" />
<wd-cell title="优先级">
<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" type="button">
<wd-radio :value="1">全体</wd-radio>
<wd-radio :value="2">指定用户</wd-radio>
</wd-radio-group>
</wd-cell>
<wd-form ref="formRef" :model="formData" :schema="formRules">
<wd-form-item prop="title" title="标题" required>
<wd-input v-model="formData.title" placeholder="请输入通知标题" />
</wd-form-item>
<wd-form-item prop="level" title="优先级">
<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-form-item>
<wd-form-item prop="targetType" title="目标类型">
<wd-radio-group v-model="formData.targetType" type="button">
<wd-radio :value="1">全体</wd-radio>
<wd-radio :value="2">指定用户</wd-radio>
</wd-radio-group>
</wd-form-item>
<wd-form-item prop="content" title="内容">
<wd-textarea
v-model="formData.content"
label="内容"
placeholder="请输入通知内容"
:maxlength="500"
show-word-limit
/>
</wd-cell-group>
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="closeNoticeForm">取消</wd-button>
@@ -149,6 +150,8 @@
<wd-fab
v-if="hasPermission('sys:notice:create') && !formDialog.visible && !detailDialog.visible"
:expandable="false"
:gap="{ bottom: 32 }"
@click="openNoticeForm()"
/>
</view>
@@ -162,7 +165,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import NoticeAPI, {
type NoticePageQuery,
@@ -197,9 +200,9 @@ const initialFormData: NoticeForm = {
};
const formData = reactive<NoticeForm>({ ...initialFormData });
const formRules: FormRules = {
const formRules = toFormSchema({
title: [{ required: true, message: "请输入通知标题" }],
};
});
// 获取状态样式
const getStatusType = (
@@ -387,8 +390,8 @@ function showNoticeActions(item: NoticeItem) {
actionSheetVisible.value = true;
}
function handleActionSelect({ value }: { value: string }) {
pendingAction.value[value]?.();
function handleActionSelect({ item }: { item: any }) {
pendingAction.value[item.name]?.();
}
onReachBottom(() => {

View File

@@ -25,7 +25,7 @@
</view>
<text class="role-card__code">{{ item.code }}</text>
</view>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" variant="plain">
{{ item.status === 1 ? "正常" : "禁用" }}
</wd-tag>
</view>
@@ -70,23 +70,25 @@
<view class="popup-title">
{{ formData.id ? "编辑角色" : "新增角色" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-input v-model="formData.name" label="角色名称" required />
<wd-input v-model="formData.code" label="角色编码" required />
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="name" title="角色名称" required>
<wd-input v-model="formData.name" placeholder="请输入角色名称" />
</wd-form-item>
<wd-form-item prop="code" title="角色编码" required>
<wd-input v-model="formData.code" placeholder="请输入角色编码" />
</wd-form-item>
<wd-form-item prop="dataScope" title="数据权限" required>
<wd-select-picker
v-model="formData.dataScope"
label="数据权限"
:columns="dataScopeOptions"
required
/>
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
<wd-cell title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-cell>
</wd-cell-group>
</wd-form-item>
<wd-form-item prop="status" title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-form-item>
<wd-form-item prop="sort" title="排序">
<wd-input-number v-model="formData.sort" :min="0" />
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="closeRoleDialog">取消</wd-button>
@@ -96,7 +98,7 @@
</wd-popup>
<!-- 浮动新增按钮 -->
<wd-fab v-if="hasPermission('sys:role:create') && !dialog.visible" @click="openRoleDialog()" />
<wd-fab v-if="hasPermission('sys:role:create') && !dialog.visible" :expandable="false" :gap="{ bottom: 32 }" @click="openRoleDialog()" />
<!-- 操作菜单 -->
<wd-action-sheet
@@ -116,7 +118,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useToast, useDialog } from "@wot-ui/ui";
import RoleAPI, { type RolePageQuery, RoleItem, RoleForm } from "@/api/role";
import { hasPermission } from "@/utils/permission";
@@ -155,11 +157,11 @@ const dataScopeOptions = ref<Record<string, any>[]>([
{ label: "自定义部门数据", value: 5 },
]);
const rules: FormRules = {
const rules = toFormSchema({
name: [{ required: true, message: "请输入角色名称" }],
code: [{ required: true, message: "请输入角色编码" }],
dataScope: [{ required: true, message: "请选择数据权限" }],
};
});
// 搜索触发
const handleSearch = () => loadRoleList();
@@ -225,16 +227,17 @@ function closeRoleDialog() {
}
// 操作菜单分发
function handleActionSelect({ value }: { value: string }) {
const item = currentActionItem.value;
function handleActionSelect({ item }: { item: any }) {
const value = item.name;
const role = currentActionItem.value;
if (value === "编辑") {
openRoleDialog(item.id);
openRoleDialog(role.id);
} else if (value === "分配权限") {
handleAssignPerm(item.id);
handleAssignPerm(role.id);
} else if (value === "删除") {
messageBox({
title: "确认删除",
msg: `确定要删除角色「${item.name}」吗?`,
msg: `确定要删除角色「${role.name}」吗?`,
type: "warning",
}).then(async () => {
await RoleAPI.deleteByIds(String(item.id));

View File

@@ -71,7 +71,7 @@
</view>
<text class="user-card__role">{{ item.roleNames }} · {{ item.deptName }}</text>
</view>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" plain>
<wd-tag :type="item.status === 1 ? 'success' : 'danger'" variant="plain">
{{ item.status === 1 ? "正常" : "禁用" }}
</wd-tag>
</view>
@@ -116,36 +116,41 @@
<view class="popup-title">
{{ formData.id ? "编辑用户" : "新增用户" }}
</view>
<wd-form ref="formRef" :model="formData" :rules="rules">
<wd-cell-group border>
<wd-form ref="formRef" :model="formData" :schema="rules">
<wd-form-item prop="username" title="用户名" required>
<wd-input
v-model="formData.username"
label="用户名"
placeholder="请输入用户名"
:readonly="!!formData.id"
required
/>
<wd-input v-model="formData.nickname" label="昵称" required />
</wd-form-item>
<wd-form-item prop="nickname" title="昵称" required>
<wd-input v-model="formData.nickname" placeholder="请输入昵称" />
</wd-form-item>
<wd-form-item prop="deptId" title="部门" required>
<wd-cascader
v-model="deptSelected"
label="部门"
:columns="deptColumns"
:column-change="handleDeptColumnChange"
required
:display-format="displayDeptFormat"
@confirm="handleDeptConfirm"
/>
</wd-form-item>
<wd-form-item prop="roleIds" title="角色" required>
<wd-select-picker
v-model="formData.roleIds"
label="角色"
:columns="roleOptions"
required
/>
<wd-input v-model="formData.mobile" label="手机号" />
<wd-input v-model="formData.email" label="邮箱" />
<wd-cell title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-cell>
</wd-cell-group>
</wd-form-item>
<wd-form-item prop="mobile" title="手机号">
<wd-input v-model="formData.mobile" placeholder="请输入手机号" />
</wd-form-item>
<wd-form-item prop="email" title="邮箱">
<wd-input v-model="formData.email" placeholder="请输入邮箱" />
</wd-form-item>
<wd-form-item prop="status" title="状态">
<wd-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="closeUserDialog">取消</wd-button>
@@ -155,7 +160,7 @@
</wd-popup>
<!-- 浮动新增按钮 -->
<wd-fab v-if="hasPermission('sys:user:create') && !dialog.visible" @click="openUserDialog()" />
<wd-fab :expandable="false" :gap="{ bottom: 100 }" @click="openUserDialog()" />
<!-- 操作菜单 -->
<wd-action-sheet
@@ -170,18 +175,12 @@
<view class="p-4">
<view class="popup-title">重置密码</view>
<wd-form ref="resetPwdFormRef" :model="resetPwdForm">
<wd-cell-group border>
<wd-form-item prop="password" title="新密码" required>
<wd-input
v-model="resetPwdForm.password"
label="新密码"
placeholder="请输入新密码至少6位"
prop="password"
:rules="[
{ required: true, message: '请输入新密码' },
{ pattern: /^.{6,}$/, message: '密码至少需要6位字符' },
]"
/>
</wd-cell-group>
</wd-form-item>
</wd-form>
<view class="popup-actions">
<wd-button type="info" variant="plain" @click="resetPwdDialog.visible = false">取消</wd-button>
@@ -206,7 +205,7 @@ definePage({
import { onLoad, onReachBottom } from "@dcloudio/uni-app";
import { LoadMoreState } from "@wot-ui/ui/components/wd-loadmore/types";
import { FormRules } from "@wot-ui/ui/components/wd-form/types";
import { toFormSchema } from "@/utils/form";
import { useQueue, useToast, useDialog } from "@wot-ui/ui";
import UserAPI, { type UserPageQuery, UserItem, UserForm } from "@/api/user";
import RoleAPI from "@/api/role";
@@ -288,12 +287,12 @@ const handleDeptConfirm = ({ value, selectedItems }: any) => {
formData.deptId = value[value.length - 1];
};
const rules: FormRules = {
const rules = toFormSchema({
username: [{ required: true, message: "请输入用户名" }],
nickname: [{ required: true, message: "请输入昵称" }],
roleIds: [{ required: true, message: "请选择角色" }],
deptId: [{ required: true, message: "请选择部门" }],
};
});
// 排序切换
const handleSortChange = ({ value }: { value: string | number }) => {
@@ -478,8 +477,8 @@ function showUserActions(item: UserItem) {
actionSheetVisible.value = true;
}
function handleActionSelect({ value }: { value: string }) {
pendingAction.value[value]?.();
function handleActionSelect({ item }: { item: any }) {
pendingAction.value[item.name]?.();
}
// 打开重置密码弹窗

View File

@@ -29,49 +29,11 @@ export const useThemeStore = defineStore("theme", () => {
/**
* 深色主题变量(仅在 dark 模式下注入)
* 注意Wot UI v2 内置了完整的暗黑模式,不需要手动覆盖各个组件变量。
* 只需在 ConfigProvider 上设置 theme="dark" + 引入官方主题 SCSS 即可。
* 这里只保留颜色名映射,供自定义 CSS 类使用。
*/
const darkThemeVars = {
darkBackground: "#1f2937",
darkBackground2: "#111827",
darkBackground3: "#1e293b",
darkBackground4: "#374151",
darkBackground5: "#4b5563",
darkBackground6: "#6b7280",
darkBackground7: "#9ca3af",
darkColor: "#f9fafb",
darkColor2: "#9ca3af",
darkColor3: "#6b7280",
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 darkThemeVars = {};
/** 主题变量(计算属性,根据模式动态切换) */
const themeVars = computed<ConfigProviderThemeVars>(() => {

View File

@@ -118,6 +118,7 @@ body {
font-weight: bold;
font-size: 32rpx;
margin-bottom: 16rpx;
color: var(--wot-text-main);
}

View File

@@ -19,7 +19,6 @@ page {
--z-dialog: 500;
--z-toast: 600;
--z-navbar: 700;
--z-fab: 800;
// -----------------------------------------------------------------------
// 主题色
@@ -84,19 +83,10 @@ page {
--shadow-lg: 0 20rpx 30rpx rgba(0, 0, 0, 0.1);
// -----------------------------------------------------------------------
// wot-design-uni 组件库变量
// 以下只保留主色调自定义,其余交给 Wot UI 官方主题
// -----------------------------------------------------------------------
--wot-color-theme: var(--color-primary);
--wot-color-success: var(--color-success);
--wot-color-warning: var(--color-warning);
--wot-color-danger: var(--color-danger);
--wot-button-normal-bg: var(--color-bg);
--wot-color-bg: var(--color-bg);
--wot-color-bg-light: var(--color-bg-secondary);
--wot-color-text: var(--color-text);
--wot-color-text-secondary: var(--color-text-secondary);
--wot-color-text-placeholder: var(--color-text-placeholder);
--wot-color-border: var(--color-border);
--wot-fab-z-index: 999;
// -----------------------------------------------------------------------
// 间距规范8rpx 网格基数)
@@ -183,20 +173,7 @@ page {
--shadow-lg: 0 20rpx 30rpx rgba(0, 0, 0, 0.4);
// -----------------------------------------------------------------------
// wot-design-uni 组件库变量
// 以下只保留主色调自定义,其余交给 Wot UI 官方暗黑模式
// -----------------------------------------------------------------------
--wot-color-theme: var(--color-primary);
--wot-color-success: var(--color-success);
--wot-color-warning: var(--color-warning);
--wot-color-danger: var(--color-danger);
--wot-button-normal-bg: var(--color-bg-secondary);
--wot-color-bg: var(--color-bg);
--wot-color-bg-light: var(--color-bg-secondary);
--wot-color-text: var(--color-text);
--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);
}

View File

@@ -136,6 +136,7 @@ declare global {
const theme: typeof import('../composables/types/theme')['theme']
const themeColorOptions: typeof import('../composables/useTheme')['themeColorOptions']
const themeVars: typeof import('../composables/types/theme')['themeVars']
const toFormSchema: typeof import('../utils/form')['toFormSchema']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
@@ -337,6 +338,7 @@ declare module 'vue' {
readonly store: UnwrapRef<typeof import('../store/index')['store']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly themeColorOptions: UnwrapRef<typeof import('../composables/useTheme')['themeColorOptions']>
readonly toFormSchema: UnwrapRef<typeof import('../utils/form')['toFormSchema']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>

36
src/utils/form.ts Normal file
View File

@@ -0,0 +1,36 @@
import type { FormSchema } from "@wot-ui/ui/components/wd-form/types";
/**
* v1 FormRules → v2 FormSchema 适配器
* 将旧版 rules 格式转换为 v2 的 schema 格式
*/
export function toFormSchema(
rules: Record<string, { required?: boolean; message?: string; pattern?: RegExp }[]>
): FormSchema {
return {
validate(model: Record<string, any>) {
const issues: { path: Array<string | number>; message: string }[] = [];
for (const [field, fieldRules] of Object.entries(rules)) {
const value = model[field];
for (const rule of fieldRules) {
if (rule.required) {
const isEmpty =
value === undefined ||
value === null ||
(typeof value === "string" && value.trim() === "") ||
(Array.isArray(value) && value.length === 0);
if (isEmpty) {
issues.push({ path: [field], message: rule.message || `${field} 为必填项` });
break;
}
}
if (rule.pattern && typeof value === "string" && !rule.pattern.test(value)) {
issues.push({ path: [field], message: rule.message || `${field} 格式不正确` });
break;
}
}
}
return issues;
},
};
}