Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
E
EMS
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
hejie
EMS
Commits
2c3c723a
提交
2c3c723a
authored
4月 28, 2025
作者:
hejie
浏览文件
操作
浏览文件
下载
差异文件
feat: 合并代码
上级
9ee0f0de
d04abf2f
全部展开
显示空白字符变更
内嵌
并排
正在显示
34 个修改的文件
包含
1451 行增加
和
138 行删除
+1451
-138
LoginPhone.vue
src/views/login/components/LoginPhone.vue
+53
-2
LoginQrCode.vue
src/views/login/components/LoginQrCode.vue
+15
-0
LoginRegist.vue
src/views/login/components/LoginRegist.vue
+88
-6
LoginUpdate.vue
src/views/login/components/LoginUpdate.vue
+69
-5
index.vue
src/views/login/index.vue
+0
-0
enums.ts
src/views/login/utils/enums.ts
+27
-0
motion.ts
src/views/login/utils/motion.ts
+16
-1
rule.ts
src/views/login/utils/rule.ts
+80
-1
static.ts
src/views/login/utils/static.ts
+11
-0
verifyCode.ts
src/views/login/utils/verifyCode.ts
+37
-0
form.vue
src/views/systems/dep/form.vue
+50
-13
hooks.ts
src/views/systems/dep/hooks.ts
+20
-2
index.vue
src/views/systems/dep/index.vue
+33
-1
hook.tsx
src/views/systems/dep/utils/hook.tsx
+96
-6
rule.ts
src/views/systems/dep/utils/rule.ts
+43
-7
types.ts
src/views/systems/dep/utils/types.ts
+57
-0
hooks.ts
src/views/systems/hooks.ts
+26
-1
form.vue
src/views/systems/menu/form.vue
+0
-0
index.vue
src/views/systems/menu/index.vue
+48
-0
enums.ts
src/views/systems/menu/utils/enums.ts
+77
-44
hook.tsx
src/views/systems/menu/utils/hook.tsx
+73
-8
rule.ts
src/views/systems/menu/utils/rule.ts
+14
-1
types.ts
src/views/systems/menu/utils/types.ts
+36
-0
form.vue
src/views/systems/role/form.vue
+22
-0
index.vue
src/views/systems/role/index.vue
+87
-0
rule.ts
src/views/systems/role/utils/rule.ts
+9
-1
types.ts
src/views/systems/role/utils/types.ts
+18
-5
index.vue
src/views/systems/user/form/index.vue
+56
-4
role.vue
src/views/systems/user/form/role.vue
+18
-0
index.vue
src/views/systems/user/index.vue
+82
-0
tree.vue
src/views/systems/user/tree.vue
+98
-19
hook.tsx
src/views/systems/user/utils/hook.tsx
+2
-1
rule.ts
src/views/systems/user/utils/rule.ts
+43
-7
types.ts
src/views/systems/user/utils/types.ts
+47
-3
没有找到文件。
src/views/login/components/LoginPhone.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入 vue-i18n 的 useI18n 函数,用于实现国际化支持
import
{
useI18n
}
from
"vue-i18n"
;
// 从 Vue 引入 ref 和 reactive 函数,用于创建响应式数据
import
{
ref
,
reactive
}
from
"vue"
;
// 引入自定义动画组件
import
Motion
from
"../utils/motion"
;
// 引入自定义消息提示工具
import
{
message
}
from
"@/utils/message"
;
// 引入手机号登录表单验证规则
import
{
phoneRules
}
from
"../utils/rule"
;
// 引入 Element Plus 的 FormInstance 类型,用于表单实例的类型定义
import
type
{
FormInstance
}
from
"element-plus"
;
// 引入自定义国际化相关函数
import
{
$t
,
transformI18n
}
from
"@/plugins/i18n"
;
// 引入验证码相关的自定义 hook
import
{
useVerifyCode
}
from
"../utils/verifyCode"
;
// 引入用户状态管理 hook
import
{
useUserStoreHook
}
from
"@/store/modules/user"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入手机图标
import
Iphone
from
"~icons/ep/iphone"
;
// 引入钥匙孔图标
import
Keyhole
from
"~icons/ri/shield-keyhole-line"
;
// 获取国际化实例
const
{
t
}
=
useI18n
();
// 定义登录加载状态的响应式引用
const
loading
=
ref
(
false
);
// 定义手机号登录表单数据的响应式对象
const
ruleForm
=
reactive
({
phone
:
""
,
verifyCode
:
""
phone
:
""
,
// 手机号,初始为空字符串
verifyCode
:
""
// 验证码,初始为空字符串
});
// 定义表单实例的响应式引用
const
ruleFormRef
=
ref
<
FormInstance
>
();
// 从 useVerifyCode hook 中解构出按钮禁用状态和倒计时文本
const
{
isDisabled
,
text
}
=
useVerifyCode
();
/**
* 处理手机号登录操作
* @param formEl - 表单实例
*/
const
onLogin
=
async
(
formEl
:
FormInstance
|
undefined
)
=>
{
// 设置加载状态为 true,表示正在登录
loading
.
value
=
true
;
// 如果表单实例不存在,直接返回
if
(
!
formEl
)
return
;
// 对表单进行验证
await
formEl
.
validate
(
valid
=>
{
if
(
valid
)
{
// 模拟登录请求,需根据实际开发进行修改
setTimeout
(()
=>
{
// 显示登录成功的消息提示
message
(
transformI18n
(
$t
(
"login.pureLoginSuccess"
)),
{
type
:
"success"
});
// 登录完成,设置加载状态为 false
loading
.
value
=
false
;
},
2000
);
}
else
{
// 表单验证失败,设置加载状态为 false
loading
.
value
=
false
;
}
});
};
/**
* 处理返回操作
* 结束验证码倒计时,并将当前页面设置为 0
*/
function
onBack
()
{
// 调用 useVerifyCode 的 end 方法结束验证码倒计时
useVerifyCode
().
end
();
// 调用用户状态管理的方法,将当前页面设置为 0
useUserStoreHook
().
SET_CURRENTPAGE
(
0
);
}
</
script
>
<
template
>
<!-- 手机号登录表单,绑定表单实例、表单数据和验证规则 -->
<el-form
ref=
"ruleFormRef"
:model=
"ruleForm"
:rules=
"phoneRules"
size=
"large"
>
<!-- 包裹手机号输入项的动画组件,设置默认延迟时间 -->
<Motion>
<!-- 手机号输入项,绑定验证规则中的 phone 属性 -->
<el-form-item
prop=
"phone"
>
<!-- 手机号输入框,绑定手机号数据,可清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.phone"
clearable
...
...
@@ -58,20 +95,26 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹验证码输入项的动画组件,设置延迟时间为 100 毫秒 -->
<Motion
:delay=
"100"
>
<!-- 验证码输入项,绑定验证规则中的 verifyCode 属性 -->
<el-form-item
prop=
"verifyCode"
>
<!-- 包含验证码输入框和获取验证码按钮的容器 -->
<div
class=
"w-full flex justify-between"
>
<!-- 验证码输入框,绑定验证码数据,可清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.verifyCode"
clearable
:placeholder=
"t('login.pureSmsVerifyCode')"
:prefix-icon=
"useRenderIcon(Keyhole)"
/>
<!-- 获取验证码按钮,根据 isDisabled 状态禁用按钮,点击触发获取验证码操作 -->
<el-button
:disabled=
"isDisabled"
class=
"ml-2!"
@
click=
"useVerifyCode().start(ruleFormRef, 'phone')"
>
<!-- 根据倒计时文本显示不同内容 -->
{{
text
.
length
>
0
?
text
+
t
(
"login.pureInfo"
)
...
...
@@ -82,8 +125,11 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹登录按钮的动画组件,设置延迟时间为 150 毫秒 -->
<Motion
:delay=
"150"
>
<!-- 登录按钮项 -->
<el-form-item>
<!-- 登录按钮,设置为全宽,主按钮样式,根据 loading 状态显示加载状态,点击触发登录操作 -->
<el-button
class=
"w-full"
size=
"default"
...
...
@@ -91,14 +137,19 @@ function onBack() {
:loading=
"loading"
@
click=
"onLogin(ruleFormRef)"
>
<!-- 显示登录按钮文本 -->
{{
t
(
"login.pureLogin"
)
}}
</el-button>
</el-form-item>
</Motion>
<!-- 包裹返回按钮的动画组件,设置延迟时间为 200 毫秒 -->
<Motion
:delay=
"200"
>
<!-- 返回按钮项 -->
<el-form-item>
<!-- 返回按钮,设置为全宽,点击触发返回操作 -->
<el-button
class=
"w-full"
size=
"default"
@
click=
"onBack"
>
<!-- 显示返回按钮文本 -->
{{
t
(
"login.pureBack"
)
}}
</el-button>
</el-form-item>
...
...
src/views/login/components/LoginQrCode.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入 vue-i18n 的 useI18n 函数,用于实现国际化支持
import
{
useI18n
}
from
"vue-i18n"
;
// 引入自定义动画组件
import
Motion
from
"../utils/motion"
;
// 引入自定义二维码组件
import
ReQrcode
from
"@/components/ReQrcode"
;
// 引入用户状态管理 hook
import
{
useUserStoreHook
}
from
"@/store/modules/user"
;
// 获取国际化实例,用于翻译文本
const
{
t
}
=
useI18n
();
</
script
>
<
template
>
<!-- 使用 Motion 动画组件包裹二维码,设置顶部和底部外边距为 -2 -->
<Motion
class=
"-mt-2 -mb-2"
>
<!-- 渲染自定义二维码组件,通过 :text 属性绑定国际化后的二维码内容 -->
<ReQrcode
:text=
"t('login.pureTest')"
/>
</Motion>
<!-- 使用 Motion 动画组件包裹分割线,设置动画延迟时间为 100 毫秒 -->
<Motion
:delay=
"100"
>
<!-- 渲染 Element Plus 的分割线组件 -->
<el-divider>
<!-- 在分割线中间显示提示文本,设置文本颜色为灰色,字号为 xs -->
<p
class=
"text-gray-500 text-xs"
>
{{
t
(
"login.pureTip"
)
}}
</p>
</el-divider>
</Motion>
<!-- 使用 Motion 动画组件包裹返回按钮,设置动画延迟时间为 150 毫秒 -->
<Motion
:delay=
"150"
>
<!-- 渲染 Element Plus 的按钮组件,设置按钮宽度为全宽,顶部外边距为 4 -->
<el-button
class=
"w-full mt-4!"
@
click=
"useUserStoreHook().SET_CURRENTPAGE(0)"
>
<!-- 显示国际化后的返回按钮文本 -->
{{
t
(
"login.pureBack"
)
}}
</el-button>
</Motion>
...
...
src/views/login/components/LoginRegist.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入 vue-i18n 的 useI18n 函数,用于实现国际化支持
import
{
useI18n
}
from
"vue-i18n"
;
// 从 Vue 引入 ref 和 reactive 函数,用于创建响应式数据
import
{
ref
,
reactive
}
from
"vue"
;
// 引入自定义动画组件
import
Motion
from
"../utils/motion"
;
// 引入自定义消息提示工具
import
{
message
}
from
"@/utils/message"
;
// 引入注册表单验证规则
import
{
updateRules
}
from
"../utils/rule"
;
// 引入 Element Plus 的 FormInstance 类型,用于表单实例的类型定义
import
type
{
FormInstance
}
from
"element-plus"
;
// 引入验证码相关的自定义 hook
import
{
useVerifyCode
}
from
"../utils/verifyCode"
;
// 引入自定义国际化相关函数
import
{
$t
,
transformI18n
}
from
"@/plugins/i18n"
;
// 引入用户状态管理 hook
import
{
useUserStoreHook
}
from
"@/store/modules/user"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入锁图标
import
Lock
from
"~icons/ri/lock-fill"
;
// 引入手机图标
import
Iphone
from
"~icons/ep/iphone"
;
// 引入用户图标
import
User
from
"~icons/ri/user-3-fill"
;
// 引入钥匙孔图标
import
Keyhole
from
"~icons/ri/shield-keyhole-line"
;
// 获取国际化实例
const
{
t
}
=
useI18n
();
// 定义是否勾选隐私协议的响应式引用,初始值为 false
const
checked
=
ref
(
false
);
// 定义注册加载状态的响应式引用,初始值为 false
const
loading
=
ref
(
false
);
// 定义注册表单数据的响应式对象
const
ruleForm
=
reactive
({
username
:
""
,
phone
:
""
,
verifyCode
:
""
,
password
:
""
,
repeatPassword
:
""
username
:
""
,
// 用户名,初始为空字符串
phone
:
""
,
// 手机号,初始为空字符串
verifyCode
:
""
,
// 验证码,初始为空字符串
password
:
""
,
// 密码,初始为空字符串
repeatPassword
:
""
// 重复密码,初始为空字符串
});
// 定义表单实例的响应式引用
const
ruleFormRef
=
ref
<
FormInstance
>
();
// 从 useVerifyCode hook 中解构出按钮禁用状态和倒计时文本
const
{
isDisabled
,
text
}
=
useVerifyCode
();
// 定义重复密码的验证规则
const
repeatPasswordRule
=
[
{
/**
* 重复密码验证函数
* @param rule - 当前验证规则
* @param value - 输入的重复密码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 重复密码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordSureReg"
))));
}
else
if
(
ruleForm
.
password
!==
value
)
{
// 重复密码与密码不一致时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordDifferentReg"
)))
);
}
else
{
// 重复密码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
];
/**
* 处理注册操作
* @param formEl - 表单实例
*/
const
onUpdate
=
async
(
formEl
:
FormInstance
|
undefined
)
=>
{
// 设置加载状态为 true,表示正在注册
loading
.
value
=
true
;
// 如果表单实例不存在,直接返回
if
(
!
formEl
)
return
;
// 对表单进行验证
await
formEl
.
validate
(
valid
=>
{
if
(
valid
)
{
if
(
checked
.
value
)
{
//
模拟
请求,需根据实际开发进行修改
//
表单验证通过且勾选了隐私协议,模拟注册
请求,需根据实际开发进行修改
setTimeout
(()
=>
{
// 显示注册成功的消息提示
message
(
transformI18n
(
$t
(
"login.pureRegisterSuccess"
)),
{
type
:
"success"
});
// 注册完成,设置加载状态为 false
loading
.
value
=
false
;
},
2000
);
}
else
{
// 未勾选隐私协议,设置加载状态为 false,并显示提示消息
loading
.
value
=
false
;
message
(
transformI18n
(
$t
(
"login.pureTickPrivacy"
)),
{
type
:
"warning"
});
}
}
else
{
// 表单验证失败,设置加载状态为 false
loading
.
value
=
false
;
}
});
};
/**
* 处理返回操作
* 结束验证码倒计时,并将当前页面设置为 0
*/
function
onBack
()
{
// 调用 useVerifyCode 的 end 方法结束验证码倒计时
useVerifyCode
().
end
();
// 调用用户状态管理的方法,将当前页面设置为 0
useUserStoreHook
().
SET_CURRENTPAGE
(
0
);
}
</
script
>
<
template
>
<!-- 注册表单,绑定表单实例、表单数据和验证规则,设置表单尺寸为 large -->
<el-form
ref=
"ruleFormRef"
:model=
"ruleForm"
:rules=
"updateRules"
size=
"large"
>
<!-- 包裹用户名输入项的动画组件,设置默认延迟时间 -->
<Motion>
<!-- 用户名输入项,绑定验证规则 -->
<el-form-item
:rules=
"[
{
...
...
@@ -92,6 +144,7 @@ function onBack() {
]"
prop="username"
>
<!-- 用户名输入框,绑定用户名数据,可清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.username"
clearable
...
...
@@ -101,8 +154,11 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹手机号输入项的动画组件,设置延迟时间为 100 毫秒 -->
<Motion
:delay=
"100"
>
<!-- 手机号输入项,绑定验证规则中的 phone 属性 -->
<el-form-item
prop=
"phone"
>
<!-- 手机号输入框,绑定手机号数据,可清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.phone"
clearable
...
...
@@ -112,20 +168,26 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹验证码输入项的动画组件,设置延迟时间为 150 毫秒 -->
<Motion
:delay=
"150"
>
<!-- 验证码输入项,绑定验证规则中的 verifyCode 属性 -->
<el-form-item
prop=
"verifyCode"
>
<!-- 包含验证码输入框和获取验证码按钮的容器 -->
<div
class=
"w-full flex justify-between"
>
<!-- 验证码输入框,绑定验证码数据,可清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.verifyCode"
clearable
:placeholder=
"t('login.pureSmsVerifyCode')"
:prefix-icon=
"useRenderIcon(Keyhole)"
/>
<!-- 获取验证码按钮,根据 isDisabled 状态禁用按钮,点击触发获取验证码操作 -->
<el-button
:disabled=
"isDisabled"
class=
"ml-2!"
@
click=
"useVerifyCode().start(ruleFormRef, 'phone')"
>
<!-- 根据倒计时文本显示不同内容 -->
{{
text
.
length
>
0
?
text
+
t
(
"login.pureInfo"
)
...
...
@@ -136,8 +198,11 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹密码输入项的动画组件,设置延迟时间为 200 毫秒 -->
<Motion
:delay=
"200"
>
<!-- 密码输入项,绑定验证规则中的 password 属性 -->
<el-form-item
prop=
"password"
>
<!-- 密码输入框,绑定密码数据,可清空,显示密码可见按钮,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.password"
clearable
...
...
@@ -148,8 +213,11 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹重复密码输入项的动画组件,设置延迟时间为 250 毫秒 -->
<Motion
:delay=
"250"
>
<!-- 重复密码输入项,绑定重复密码验证规则 -->
<el-form-item
:rules=
"repeatPasswordRule"
prop=
"repeatPassword"
>
<!-- 重复密码输入框,绑定重复密码数据,可清空,显示密码可见按钮,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.repeatPassword"
clearable
...
...
@@ -160,19 +228,28 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 包裹隐私协议勾选和隐私政策链接的动画组件,设置延迟时间为 300 毫秒 -->
<Motion
:delay=
"300"
>
<!-- 隐私协议项 -->
<el-form-item>
<!-- 隐私协议勾选框,绑定勾选状态 -->
<el-checkbox
v-model=
"checked"
>
<!-- 显示隐私协议勾选提示文本 -->
{{
t
(
"login.pureReadAccept"
)
}}
</el-checkbox>
<!-- 隐私政策链接按钮,设置为链接样式,主按钮类型 -->
<el-button
link
type=
"primary"
>
<!-- 显示隐私政策链接文本 -->
{{
t
(
"login.purePrivacyPolicy"
)
}}
</el-button>
</el-form-item>
</Motion>
<!-- 包裹注册按钮的动画组件,设置延迟时间为 350 毫秒 -->
<Motion
:delay=
"350"
>
<!-- 注册按钮项 -->
<el-form-item>
<!-- 注册按钮,设置为全宽,默认尺寸,主按钮样式,根据 loading 状态显示加载状态,点击触发注册操作 -->
<el-button
class=
"w-full"
size=
"default"
...
...
@@ -180,14 +257,19 @@ function onBack() {
:loading=
"loading"
@
click=
"onUpdate(ruleFormRef)"
>
<!-- 显示注册按钮文本 -->
{{
t
(
"login.pureDefinite"
)
}}
</el-button>
</el-form-item>
</Motion>
<!-- 包裹返回按钮的动画组件,设置延迟时间为 400 毫秒 -->
<Motion
:delay=
"400"
>
<!-- 返回按钮项 -->
<el-form-item>
<!-- 返回按钮,设置为全宽,默认尺寸,点击触发返回操作 -->
<el-button
class=
"w-full"
size=
"default"
@
click=
"onBack"
>
<!-- 显示返回按钮文本 -->
{{
t
(
"login.pureBack"
)
}}
</el-button>
</el-form-item>
...
...
src/views/login/components/LoginUpdate.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入 vue-i18n 的 useI18n 函数,用于实现国际化支持
import
{
useI18n
}
from
"vue-i18n"
;
// 从 Vue 引入 ref 和 reactive 函数,用于创建响应式数据
import
{
ref
,
reactive
}
from
"vue"
;
// 引入自定义动画组件
import
Motion
from
"../utils/motion"
;
// 引入自定义消息提示工具
import
{
message
}
from
"@/utils/message"
;
// 引入重置密码表单验证规则
import
{
updateRules
}
from
"../utils/rule"
;
// 引入 Element Plus 的 FormInstance 类型,用于表单实例的类型定义
import
type
{
FormInstance
}
from
"element-plus"
;
// 引入验证码相关的自定义 hook
import
{
useVerifyCode
}
from
"../utils/verifyCode"
;
// 引入自定义国际化相关函数
import
{
$t
,
transformI18n
}
from
"@/plugins/i18n"
;
// 引入用户状态管理 hook
import
{
useUserStoreHook
}
from
"@/store/modules/user"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入锁图标
import
Lock
from
"~icons/ri/lock-fill"
;
// 引入手机图标
import
Iphone
from
"~icons/ep/iphone"
;
// 引入钥匙孔图标
import
Keyhole
from
"~icons/ri/shield-keyhole-line"
;
// 获取国际化实例
const
{
t
}
=
useI18n
();
// 定义重置密码操作的加载状态,初始为 false
const
loading
=
ref
(
false
);
// 定义重置密码表单的数据,使用响应式对象
const
ruleForm
=
reactive
({
phone
:
""
,
verifyCode
:
""
,
password
:
""
,
repeatPassword
:
""
phone
:
""
,
// 手机号,初始为空字符串
verifyCode
:
""
,
// 验证码,初始为空字符串
password
:
""
,
// 新密码,初始为空字符串
repeatPassword
:
""
// 重复新密码,初始为空字符串
});
// 定义表单实例的引用
const
ruleFormRef
=
ref
<
FormInstance
>
();
// 从验证码 hook 中解构出按钮禁用状态和倒计时文本
const
{
isDisabled
,
text
}
=
useVerifyCode
();
// 定义重复密码的验证规则
const
repeatPasswordRule
=
[
{
/**
* 验证重复密码是否符合要求
* @param rule - 当前验证规则对象
* @param value - 用户输入的重复密码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 若重复密码为空,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordSureReg"
))));
}
else
if
(
ruleForm
.
password
!==
value
)
{
// 若重复密码与新密码不一致,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordDifferentReg"
)))
);
}
else
{
// 验证通过
callback
();
}
},
// 触发验证的时机为失去焦点时
trigger
:
"blur"
}
];
/**
* 处理重置密码操作
* @param formEl - 表单实例
*/
const
onUpdate
=
async
(
formEl
:
FormInstance
|
undefined
)
=>
{
// 开始重置操作,设置加载状态为 true
loading
.
value
=
true
;
// 若表单实例不存在,直接返回
if
(
!
formEl
)
return
;
// 对表单进行验证
await
formEl
.
validate
(
valid
=>
{
if
(
valid
)
{
// 模拟请求,
需根据实际开发进行修改
// 模拟请求,
实际开发中需替换为真实接口调用
setTimeout
(()
=>
{
// 显示密码重置成功的消息
message
(
transformI18n
(
$t
(
"login.purePassWordUpdateReg"
)),
{
type
:
"success"
});
// 重置操作完成,设置加载状态为 false
loading
.
value
=
false
;
},
2000
);
}
else
{
// 表单验证失败,设置加载状态为 false
loading
.
value
=
false
;
}
});
};
/**
* 处理返回操作
* 结束验证码倒计时,并将当前页面设置为 0
*/
function
onBack
()
{
// 调用验证码 hook 的 end 方法结束倒计时
useVerifyCode
().
end
();
// 调用用户状态管理的方法,将当前页面设置为 0
useUserStoreHook
().
SET_CURRENTPAGE
(
0
);
}
</
script
>
<
template
>
<!-- 重置密码表单,绑定表单实例、表单数据、验证规则,并设置表单尺寸为 large -->
<el-form
ref=
"ruleFormRef"
:model=
"ruleForm"
:rules=
"updateRules"
size=
"large"
>
<!-- 手机号输入项,使用动画组件包裹,设置默认延迟 -->
<Motion>
<el-form-item
prop=
"phone"
>
<!-- 手机号输入框,绑定手机号数据,支持清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.phone"
clearable
...
...
@@ -82,20 +131,25 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 验证码输入项,使用动画组件包裹,设置延迟为 100 毫秒 -->
<Motion
:delay=
"100"
>
<el-form-item
prop=
"verifyCode"
>
<!-- 包含验证码输入框和获取验证码按钮的容器 -->
<div
class=
"w-full flex justify-between"
>
<!-- 验证码输入框,绑定验证码数据,支持清空,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.verifyCode"
clearable
:placeholder=
"t('login.pureSmsVerifyCode')"
:prefix-icon=
"useRenderIcon(Keyhole)"
/>
<!-- 获取验证码按钮,根据 isDisabled 状态禁用按钮,点击触发获取验证码操作 -->
<el-button
:disabled=
"isDisabled"
class=
"ml-2!"
@
click=
"useVerifyCode().start(ruleFormRef, 'phone')"
>
<!-- 根据倒计时文本显示不同内容 -->
{{
text
.
length
>
0
?
text
+
t
(
"login.pureInfo"
)
...
...
@@ -106,8 +160,10 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 新密码输入项,使用动画组件包裹,设置延迟为 150 毫秒 -->
<Motion
:delay=
"150"
>
<el-form-item
prop=
"password"
>
<!-- 新密码输入框,绑定新密码数据,支持清空和显示密码,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.password"
clearable
...
...
@@ -118,8 +174,10 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 重复新密码输入项,使用动画组件包裹,设置延迟为 200 毫秒 -->
<Motion
:delay=
"200"
>
<el-form-item
:rules=
"repeatPasswordRule"
prop=
"repeatPassword"
>
<!-- 重复新密码输入框,绑定重复新密码数据,支持清空和显示密码,设置占位提示和前缀图标 -->
<el-input
v-model=
"ruleForm.repeatPassword"
clearable
...
...
@@ -130,8 +188,10 @@ function onBack() {
</el-form-item>
</Motion>
<!-- 重置密码按钮项,使用动画组件包裹,设置延迟为 250 毫秒 -->
<Motion
:delay=
"250"
>
<el-form-item>
<!-- 重置密码按钮,设置为全宽,默认尺寸,主按钮样式,根据加载状态显示加载动画,点击触发重置密码操作 -->
<el-button
class=
"w-full"
size=
"default"
...
...
@@ -139,14 +199,18 @@ function onBack() {
:loading=
"loading"
@
click=
"onUpdate(ruleFormRef)"
>
<!-- 显示重置密码按钮文本 -->
{{
t
(
"login.pureDefinite"
)
}}
</el-button>
</el-form-item>
</Motion>
<!-- 返回按钮项,使用动画组件包裹,设置延迟为 300 毫秒 -->
<Motion
:delay=
"300"
>
<el-form-item>
<!-- 返回按钮,设置为全宽,默认尺寸,点击触发返回操作 -->
<el-button
class=
"w-full"
size=
"default"
@
click=
"onBack"
>
<!-- 显示返回按钮文本 -->
{{
t
(
"login.pureBack"
)
}}
</el-button>
</el-form-item>
...
...
src/views/login/index.vue
浏览文件 @
2c3c723a
差异被折叠。
点击展开。
src/views/login/utils/enums.ts
浏览文件 @
2c3c723a
/**
* 引入国际化函数 $t,用于多语言支持
* 该函数从 @/plugins/i18n 模块导出,可将键值转换为对应语言的文本
*/
import
{
$t
}
from
"@/plugins/i18n"
;
/**
* 定义登录操作列表
* 包含手机号登录、二维码登录和注册三个操作,每个操作有对应的国际化标题
*/
const
operates
=
[
{
/** 手机号登录操作的国际化标题 */
title
:
$t
(
"login.purePhoneLogin"
)
},
{
/** 二维码登录操作的国际化标题 */
title
:
$t
(
"login.pureQRCodeLogin"
)
},
{
/** 注册操作的国际化标题 */
title
:
$t
(
"login.pureRegister"
)
}
];
/**
* 定义第三方登录列表
* 包含微信、支付宝、QQ 和微博四种第三方登录方式,每种方式有对应的国际化标题和图标标识
*/
const
thirdParty
=
[
{
/** 微信登录的国际化标题 */
title
:
$t
(
"login.pureWeChatLogin"
),
/** 微信登录的图标标识 */
icon
:
"wechat"
},
{
/** 支付宝登录的国际化标题 */
title
:
$t
(
"login.pureAlipayLogin"
),
/** 支付宝登录的图标标识 */
icon
:
"alipay"
},
{
/** QQ 登录的国际化标题 */
title
:
$t
(
"login.pureQQLogin"
),
/** QQ 登录的图标标识 */
icon
:
"qq"
},
{
/** 微博登录的国际化标题 */
title
:
$t
(
"login.pureWeiBoLogin"
),
/** 微博登录的图标标识 */
icon
:
"weibo"
}
];
/**
* 导出登录操作列表和第三方登录列表
* 方便其他模块引入使用这两个常量
*/
export
{
operates
,
thirdParty
};
src/views/login/utils/motion.ts
浏览文件 @
2c3c723a
import
{
h
,
defineComponent
,
withDirectives
,
resolveDirective
}
from
"vue"
;
/** 封装@vueuse/motion动画库中的自定义指令v-motion */
/**
* 封装 @vueuse/motion 动画库中的自定义指令 v-motion
* 该组件用于为包裹的内容添加动画效果,通过 props 可设置动画延迟时间
*/
export
default
defineComponent
({
// 组件名称,方便调试和识别
name
:
"Motion"
,
// 定义组件接收的 props
props
:
{
// 动画延迟时间,类型为数字,默认值为 50 毫秒
delay
:
{
type
:
Number
,
default
:
50
}
},
// 组件的渲染函数
render
()
{
// 从 props 中解构出 delay 属性
const
{
delay
}
=
this
;
// 解析 v-motion 指令
const
motion
=
resolveDirective
(
"motion"
);
// 使用 withDirectives 函数为元素添加指令
return
withDirectives
(
// 创建一个 div 元素
h
(
"div"
,
{},
{
// 默认插槽,渲染子组件或内容
default
:
()
=>
[
this
.
$slots
.
default
()]
}
),
...
...
@@ -24,10 +36,13 @@ export default defineComponent({
[
motion
,
{
// 初始状态,元素透明度为 0,在 y 轴向下偏移 100px
initial
:
{
opacity
:
0
,
y
:
100
},
// 进入动画状态,元素透明度变为 1,回到 y 轴初始位置
enter
:
{
opacity
:
1
,
y
:
0
,
// 设置动画过渡效果,包含延迟时间
transition
:
{
delay
}
...
...
src/views/login/utils/rule.ts
浏览文件 @
2c3c723a
// 从 Vue 引入 reactive 函数,用于创建响应式对象
import
{
reactive
}
from
"vue"
;
// 从 @pureadmin/utils 引入 isPhone 工具函数,用于验证手机号格式
import
{
isPhone
}
from
"@pureadmin/utils"
;
// 从 Element Plus 引入 FormRules 类型,用于定义表单验证规则
import
type
{
FormRules
}
from
"element-plus"
;
// 从 @/plugins/i18n 引入 $t 和 transformI18n 函数,用于国际化文本处理
import
{
$t
,
transformI18n
}
from
"@/plugins/i18n"
;
// 从 @/store/modules/user 引入 useUserStoreHook 函数,用于获取用户状态管理实例
import
{
useUserStoreHook
}
from
"@/store/modules/user"
;
/** 6位数字验证码正则 */
export
const
REGEXP_SIX
=
/^
\d{6}
$/
;
/** 密码正则(密码格式应为8
-
18位数字、字母、符号的任意两种组合) */
/** 密码正则(密码格式应为8
-
18位数字、字母、符号的任意两种组合) */
export
const
REGEXP_PWD
=
/^
(?![
0-9
]
+$
)(?![
a-z
]
+$
)(?![
A-Z
]
+$
)(?!([^
(0-9a-zA-Z)
]
|
[
()
])
+$
)(?!
^.*
[\u
4E00-
\u
9FA5
]
.*$
)([^
(0-9a-zA-Z)
]
|
[
()
]
|
[
a-z
]
|
[
A-Z
]
|
[
0-9
]){8,18}
$/
;
...
...
@@ -15,31 +20,51 @@ export const REGEXP_PWD =
const
loginRules
=
reactive
<
FormRules
>
({
password
:
[
{
/**
* 密码验证函数
* @param rule - 当前验证规则
* @param value - 输入的密码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 密码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordReg"
))));
}
else
if
(
!
REGEXP_PWD
.
test
(
value
))
{
// 密码不符合正则规则时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordRuleReg"
))));
}
else
{
// 密码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
],
verifyCode
:
[
{
/**
* 验证码验证函数
* @param rule - 当前验证规则
* @param value - 输入的验证码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 验证码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeReg"
))));
}
else
if
(
useUserStoreHook
().
verifyCode
!==
value
)
{
// 验证码与存储的验证码不一致时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeCorrectReg"
)))
);
}
else
{
// 验证码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
]
...
...
@@ -49,29 +74,49 @@ const loginRules = reactive<FormRules>({
const
phoneRules
=
reactive
<
FormRules
>
({
phone
:
[
{
/**
* 手机号验证函数
* @param rule - 当前验证规则
* @param value - 输入的手机号值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 手机号为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePhoneReg"
))));
}
else
if
(
!
isPhone
(
value
))
{
// 手机号格式不正确时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePhoneCorrectReg"
))));
}
else
{
// 手机号验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
],
verifyCode
:
[
{
/**
* 手机登录验证码验证函数
* @param rule - 当前验证规则
* @param value - 输入的验证码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 验证码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeReg"
))));
}
else
if
(
!
REGEXP_SIX
.
test
(
value
))
{
// 验证码不是6位数字时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeSixReg"
))));
}
else
{
// 验证码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
]
...
...
@@ -81,46 +126,80 @@ const phoneRules = reactive<FormRules>({
const
updateRules
=
reactive
<
FormRules
>
({
phone
:
[
{
/**
* 忘记密码时手机号验证函数
* @param rule - 当前验证规则
* @param value - 输入的手机号值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 手机号为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePhoneReg"
))));
}
else
if
(
!
isPhone
(
value
))
{
// 手机号格式不正确时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePhoneCorrectReg"
))));
}
else
{
// 手机号验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
],
verifyCode
:
[
{
/**
* 忘记密码时验证码验证函数
* @param rule - 当前验证规则
* @param value - 输入的验证码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 验证码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeReg"
))));
}
else
if
(
!
REGEXP_SIX
.
test
(
value
))
{
// 验证码不是6位数字时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.pureVerifyCodeSixReg"
))));
}
else
{
// 验证码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
],
password
:
[
{
/**
* 忘记密码时新密码验证函数
* @param rule - 当前验证规则
* @param value - 输入的新密码值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 新密码为空时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordReg"
))));
}
else
if
(
!
REGEXP_PWD
.
test
(
value
))
{
// 新密码不符合正则规则时,返回错误信息
callback
(
new
Error
(
transformI18n
(
$t
(
"login.purePassWordRuleReg"
))));
}
else
{
// 新密码验证通过
callback
();
}
},
// 失去焦点时触发验证
trigger
:
"blur"
}
]
});
/**
* 导出登录、手机登录和忘记密码的表单验证规则
* 方便在登录相关组件中使用这些验证规则
*/
export
{
loginRules
,
phoneRules
,
updateRules
};
src/views/login/utils/static.ts
浏览文件 @
2c3c723a
/**
* 从项目资源目录导入登录页面所需的静态资源
* @description 导入登录背景图、用户头像和插图 SVG 组件,后续会将这些资源导出供其他组件使用
*/
// 导入登录背景图,路径为 @/assets/login/bg.png
import
bg
from
"@/assets/login/bg.png"
;
// 导入用户头像 SVG 组件,路径为 @/assets/login/avatar.svg,并通过 ?component 标识为组件
import
avatar
from
"@/assets/login/avatar.svg?component"
;
// 导入登录插图 SVG 组件,路径为 @/assets/login/illustration.svg,并通过 ?component 标识为组件
import
illustration
from
"@/assets/login/illustration.svg?component"
;
/**
* 导出登录页面所需的静态资源
* @description 将导入的背景图、用户头像和插图 SVG 组件导出,方便其他组件引入使用
*/
export
{
bg
,
avatar
,
illustration
};
src/views/login/utils/verifyCode.ts
浏览文件 @
2c3c723a
// 从 element-plus 库中引入 FormInstance 和 FormItemProp 类型,用于处理表单实例和表单项属性
import
type
{
FormInstance
,
FormItemProp
}
from
"element-plus"
;
// 从 @pureadmin/utils 工具库中引入 clone 函数,用于深拷贝对象
import
{
clone
}
from
"@pureadmin/utils"
;
// 从 Vue 中引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 创建一个响应式引用,用于记录按钮是否处于禁用状态,初始值为 false
const
isDisabled
=
ref
(
false
);
// 创建一个响应式引用,用于存储定时器的 ID,初始值为 null
const
timer
=
ref
(
null
);
// 创建一个响应式引用,用于显示倒计时的文本,初始值为空字符串
const
text
=
ref
(
""
);
/**
* 自定义 hook,用于处理验证码倒计时逻辑
* @returns {Object} 包含 isDisabled、timer、text、start 和 end 方法的对象
*/
export
const
useVerifyCode
=
()
=>
{
/**
* 开始验证码倒计时
* @param {FormInstance | undefined} formEl - 表单实例,用于验证表单字段
* @param {FormItemProp} props - 需要验证的表单项属性
* @param {number} [time=60] - 倒计时的总秒数,默认值为 60 秒
*/
const
start
=
async
(
formEl
:
FormInstance
|
undefined
,
props
:
FormItemProp
,
time
=
60
)
=>
{
// 如果表单实例不存在,直接返回
if
(
!
formEl
)
return
;
// 深拷贝初始的倒计时时间,用于后续重置倒计时
const
initTime
=
clone
(
time
,
true
);
// 异步验证指定的表单字段
await
formEl
.
validateField
(
props
,
isValid
=>
{
if
(
isValid
)
{
// 验证通过,清除之前可能存在的定时器
clearInterval
(
timer
.
value
);
// 禁用按钮,防止重复点击
isDisabled
.
value
=
true
;
// 显示初始的倒计时时间
text
.
value
=
`
${
time
}
`
;
// 设置定时器,每秒更新一次倒计时
timer
.
value
=
setInterval
(()
=>
{
if
(
time
>
0
)
{
// 倒计时未结束,时间减 1 并更新显示文本
time
-=
1
;
text
.
value
=
`
${
time
}
`
;
}
else
{
// 倒计时结束,清空显示文本
text
.
value
=
""
;
// 启用按钮
isDisabled
.
value
=
false
;
// 清除定时器
clearInterval
(
timer
.
value
);
// 重置倒计时时间
time
=
initTime
;
}
},
1000
);
...
...
@@ -34,6 +62,10 @@ export const useVerifyCode = () => {
});
};
/**
* 结束验证码倒计时
* 清空显示文本,启用按钮,并清除定时器
*/
const
end
=
()
=>
{
text
.
value
=
""
;
isDisabled
.
value
=
false
;
...
...
@@ -41,10 +73,15 @@ export const useVerifyCode = () => {
};
return
{
/** 按钮是否禁用的响应式引用 */
isDisabled
,
/** 存储定时器 ID 的响应式引用 */
timer
,
/** 显示倒计时文本的响应式引用 */
text
,
/** 开始验证码倒计时的方法 */
start
,
/** 结束验证码倒计时的方法 */
end
};
};
src/views/systems/dep/form.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 vue 导入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 导入自定义组件 ReCol
import
ReCol
from
"@/components/ReCol"
;
// 从当前目录的 utils/rule 文件导入表单验证规则
import
{
formRules
}
from
"./utils/rule"
;
// 从当前目录的 utils/types 文件导入表单属性类型
import
{
FormProps
}
from
"./utils/types"
;
// 从当前目录的 hooks 文件导入公共 Hook
import
{
usePublicHooks
}
from
"./hooks"
;
/**
* 定义组件的 props,使用 withDefaults 设置默认值
* @property {FormItemProps} formInline - 表单内联数据,包含部门相关信息
*/
const
props
=
withDefaults
(
defineProps
<
FormProps
>
(),
{
formInline
:
()
=>
({
higherDeptOptions
:
[],
parentId
:
0
,
name
:
""
,
principal
:
""
,
phone
:
""
,
email
:
""
,
sort
:
0
,
status
:
1
,
remark
:
""
higherDeptOptions
:
[],
// 上级部门选项列表,默认为空数组
parentId
:
0
,
// 上级部门 ID,默认为 0
name
:
""
,
// 部门名称,默认为空字符串
principal
:
""
,
// 部门负责人,默认为空字符串
phone
:
""
,
// 联系电话,默认为空字符串
email
:
""
,
// 邮箱地址,默认为空字符串
sort
:
0
,
// 排序值,默认为 0
status
:
1
,
// 部门状态,默认为 1(启用)
remark
:
""
// 备注信息,默认为空字符串
})
});
// 创建一个 ref 引用,用于获取表单实例
const
ruleFormRef
=
ref
();
// 调用公共 Hook 获取 switch 样式
const
{
switchStyle
}
=
usePublicHooks
();
// 创建一个 ref 引用,用于存储表单数据
const
newFormInline
=
ref
(
props
.
formInline
);
/**
* 获取表单实例的引用
* @returns {unknown} 表单实例的引用
*/
function
getRef
()
{
return
ruleFormRef
.
value
;
}
// 暴露 getRef 方法,供父组件调用
defineExpose
({
getRef
});
</
script
>
<
template
>
<!-- 定义一个 Element Plus 表单,绑定表单引用、表单数据和验证规则 -->
<el-form
ref=
"ruleFormRef"
:model=
"newFormInline"
:rules=
"formRules"
label-width=
"82px"
>
<!-- 定义一个行布局,设置列间距为 30px -->
<el-row
:gutter=
"30"
>
<!-- 上级部门表单项 -->
<re-col>
<el-form-item
label=
"上级部门"
>
<!-- 级联选择器,用于选择上级部门 -->
<el-cascader
v-model=
"newFormInline.parentId"
class=
"w-full"
:options=
"newFormInline.higherDeptOptions"
:props=
"
{
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: true
value: 'id',
// 选项的值字段
label: 'name',
// 选项的显示文本字段
emitPath: false,
// 不返回选中项的完整路径
checkStrictly: true
// 可单独选择任意一级选项
}"
clearable
filterable
placeholder="请选择上级部门"
>
<!-- 自定义选项显示内容 -->
<template
#
default=
"
{ node, data }">
<span>
{{
data
.
name
}}
</span>
<!-- 如果不是叶子节点,显示子节点数量 -->
<span
v-if=
"!node.isLeaf"
>
(
{{
data
.
children
.
length
}}
)
</span>
</
template
>
</el-cascader>
</el-form-item>
</re-col>
<!-- 部门名称表单项,添加了验证规则 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"部门名称"
prop=
"name"
>
<!-- 输入框,用于输入部门名称 -->
<el-input
v-model=
"newFormInline.name"
clearable
...
...
@@ -71,8 +96,10 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<!-- 部门负责人表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"部门负责人"
>
<!-- 输入框,用于输入部门负责人姓名 -->
<el-input
v-model=
"newFormInline.principal"
clearable
...
...
@@ -81,8 +108,10 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!-- 手机号表单项,添加了验证规则 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"手机号"
prop=
"phone"
>
<!-- 输入框,用于输入手机号 -->
<el-input
v-model=
"newFormInline.phone"
clearable
...
...
@@ -90,8 +119,10 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<!-- 邮箱表单项,添加了验证规则 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"邮箱"
prop=
"email"
>
<!-- 输入框,用于输入邮箱地址 -->
<el-input
v-model=
"newFormInline.email"
clearable
...
...
@@ -100,8 +131,10 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!-- 排序表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"排序"
>
<!-- 数字输入框,用于输入排序值 -->
<el-input-number
v-model=
"newFormInline.sort"
class=
"w-full!"
...
...
@@ -111,8 +144,10 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<!-- 部门状态表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"部门状态"
>
<!-- 开关组件,用于切换部门状态 -->
<el-switch
v-model=
"newFormInline.status"
inline-prompt
...
...
@@ -125,8 +160,10 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!-- 备注表单项 -->
<re-col>
<el-form-item
label=
"备注"
>
<!-- 文本域输入框,用于输入备注信息 -->
<el-input
v-model=
"newFormInline.remark"
placeholder=
"请输入备注信息"
...
...
src/views/systems/dep/hooks.ts
浏览文件 @
2c3c723a
// 抽离可公用的工具函数等用于系统管理页面逻辑
// 从 vue 库导入 computed 函数,用于创建计算属性
import
{
computed
}
from
"vue"
;
// 从 @pureadmin/utils 库导入 useDark 函数,用于获取当前是否为深色模式
import
{
useDark
}
from
"@pureadmin/utils"
;
/**
* 用于系统管理页面的公共 Hook
* 该 Hook 主要用于处理深色模式相关的样式计算
* @returns 包含深色模式状态、开关样式和标签样式的对象
*/
export
function
usePublicHooks
()
{
// 调用 useDark 函数获取当前是否为深色模式的状态
const
{
isDark
}
=
useDark
();
/**
* 计算 `el-switch` 组件的样式
* 根据深色模式状态设置开关开启和关闭时的颜色
*/
const
switchStyle
=
computed
(()
=>
{
return
{
"--el-switch-on-color"
:
"#6abe39"
,
"--el-switch-off-color"
:
"#e84749"
"--el-switch-on-color"
:
"#6abe39"
,
// 开关开启时的颜色
"--el-switch-off-color"
:
"#e84749"
// 开关关闭时的颜色
};
});
/**
* 计算 `el-tag` 组件的样式
* 根据传入的状态和深色模式状态设置标签的文本颜色、背景颜色和边框颜色
*/
const
tagStyle
=
computed
(()
=>
{
return
(
status
:
number
)
=>
{
return
status
===
1
?
{
// 状态为 1 时的标签样式
"--el-tag-text-color"
:
isDark
.
value
?
"#6abe39"
:
"#389e0d"
,
"--el-tag-bg-color"
:
isDark
.
value
?
"#172412"
:
"#f6ffed"
,
"--el-tag-border-color"
:
isDark
.
value
?
"#274a17"
:
"#b7eb8f"
}
:
{
// 状态不为 1 时的标签样式
"--el-tag-text-color"
:
isDark
.
value
?
"#e84749"
:
"#cf1322"
,
"--el-tag-bg-color"
:
isDark
.
value
?
"#2b1316"
:
"#fff1f0"
,
"--el-tag-border-color"
:
isDark
.
value
?
"#58191c"
:
"#ffa39e"
...
...
src/views/systems/dep/index.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 导入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 从当前目录的 utils/hook 文件导入 useDept 自定义 Hook
import
{
useDept
}
from
"./utils/hook"
;
// 导入自定义组件 PureTableBar
import
{
PureTableBar
}
from
"@/components/RePureTableBar"
;
// 从 ReIcon 组件的 hooks 文件导入 useRenderIcon 函数,用于渲染图标
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 导入不同的图标组件
import
Delete
from
"~icons/ep/delete"
;
import
EditPen
from
"~icons/ep/edit-pen"
;
import
Refresh
from
"~icons/ep/refresh"
;
import
AddFill
from
"~icons/ri/add-circle-line"
;
import
{
Column
}
from
"element-plus"
;
/**
* 定义组件选项,设置组件名称为 SystemDept
*/
defineOptions
({
name
:
"SystemDept"
});
// 创建表单引用和表格引用
const
formRef
=
ref
();
const
tableRef
=
ref
();
// 调用 useDept Hook,获取部门管理相关的数据和方法
const
{
form
,
loading
,
...
...
@@ -27,20 +39,26 @@ const {
handleSelectionChange
}
=
useDept
();
/**
* 处理全屏操作,重置表格高度
*/
function
onFullscreen
()
{
// 重置表格高度
//
调用表格引用的 setAdaptive 方法
重置表格高度
tableRef
.
value
.
setAdaptive
();
}
</
script
>
<
template
>
<!-- 主容器 -->
<div
class=
"main"
>
<!-- 搜索表单 -->
<el-form
ref=
"formRef"
:inline=
"true"
:model=
"form"
class=
"search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<!-- 部门名称搜索项 -->
<el-form-item
label=
"部门名称:"
prop=
"name"
>
<el-input
v-model=
"form.name"
...
...
@@ -49,6 +67,7 @@ function onFullscreen() {
class=
"w-[180px]!"
/>
</el-form-item>
<!-- 状态搜索项 -->
<el-form-item
label=
"状态:"
prop=
"status"
>
<el-select
v-model=
"form.status"
...
...
@@ -60,6 +79,7 @@ function onFullscreen() {
<el-option
label=
"停用"
:value=
"0"
/>
</el-select>
</el-form-item>
<!-- 搜索和重置按钮项 -->
<el-form-item>
<el-button
type=
"primary"
...
...
@@ -75,6 +95,7 @@ function onFullscreen() {
</el-form-item>
</el-form>
<!-- 自定义表格栏组件 -->
<PureTableBar
title=
"部门管理(仅演示,操作后不生效)"
:columns=
"columns"
...
...
@@ -82,6 +103,7 @@ function onFullscreen() {
@
refresh=
"onSearch"
@
fullscreen=
"onFullscreen"
>
<!-- 自定义按钮插槽 -->
<template
#
buttons
>
<el-button
type=
"primary"
...
...
@@ -91,7 +113,9 @@ function onFullscreen() {
新增部门
</el-button>
</
template
>
<!-- 自定义表格内容插槽 -->
<
template
v-slot=
"{ size, dynamicColumns }"
>
<!-- 自定义表格组件 -->
<pure-table
ref=
"tableRef"
adaptive
...
...
@@ -111,7 +135,9 @@ function onFullscreen() {
}"
@selection-change="handleSelectionChange"
>
<!-- 自定义操作列插槽 -->
<template
#
operation=
"
{ row }">
<!-- 修改按钮 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -122,6 +148,7 @@ function onFullscreen() {
>
修改
</el-button>
<!-- 新增子部门按钮 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -132,10 +159,12 @@ function onFullscreen() {
>
新增
</el-button>
<!-- 删除确认弹窗 -->
<el-popconfirm
:title=
"`是否确认删除部门名称为$
{row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<!-- 触发确认弹窗的按钮 -->
<template
#
reference
>
<el-button
class=
"reset-margin"
...
...
@@ -156,14 +185,17 @@ function onFullscreen() {
</template>
<
style
lang=
"scss"
scoped
>
/* 移除表格内滚动条底部的线 */
:deep
(
.el-table__inner-wrapper
::before
)
{
height
:
0
;
}
/* 主内容区域样式 */
.main-content
{
margin
:
24px
24px
0
!
important
;
}
/* 搜索表单样式 */
.search-form
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12px
;
...
...
src/views/systems/dep/utils/hook.tsx
浏览文件 @
2c3c723a
...
...
@@ -10,23 +10,50 @@ import type { FormItemProps } from "../utils/types";
import
{
cloneDeep
,
isAllEmpty
,
deviceDetection
}
from
"@pureadmin/utils"
;
// import { cloneDeep, deviceDetection } from "@pureadmin/utils";
/**
* 使用部门管理相关功能的自定义 Hook
* @returns 包含部门管理相关方法和数据的对象
*/
export
function
useDept
()
{
// 定义搜索表单的数据
const
form
=
reactive
({
name
:
""
,
status
:
null
});
// 编辑表单的引用
const
formRef
=
ref
();
// 存储部门列表数据
const
dataList
=
ref
([]);
// 表格加载状态
const
loading
=
ref
(
true
);
// 获取公共 Hook 中的标签样式方法
const
{
tagStyle
}
=
usePublicHooks
();
const
columns
:
TableColumnList
=
[
/**
* 表格列配置类型
* @typedef {Object} TableColumn
* @property {string} label - 列标题
* @property {string} [prop] - 对应数据的字段名
* @property {number} [width] - 列宽度
* @property {number} [minWidth] - 列最小宽度
* @property {string} [align] - 列内容对齐方式
* @property {string} [fixed] - 列固定位置
* @property {string} [slot] - 自定义插槽名称
* @property {Function} [cellRenderer] - 单元格渲染函数
* @property {Function} [formatter] - 数据格式化函数
*/
/**
* 表格列配置列表
* @type {TableColumn[]}
*/
const
columns
=
[
{
label
:
"部门名称"
,
prop
:
"name"
,
width
:
180
,
align
:
"left"
align
:
"left"
as
"center"
|
"left"
|
"right"
},
{
label
:
"排序"
,
...
...
@@ -37,6 +64,14 @@ export function useDept() {
label
:
"状态"
,
prop
:
"status"
,
minWidth
:
100
,
align
:
"center"
as
"center"
|
"left"
|
"right"
,
/**
* 状态列单元格渲染函数
* @param {Object} param - 包含行数据和属性的对象
* @param {Object} param.row - 行数据
* @param {Object} param.props - 列属性
* @returns {JSX.Element} 渲染后的标签元素
*/
cellRenderer
:
({
row
,
props
})
=>
(
<
el
-
tag
size=
{
props
.
size
}
style=
{
tagStyle
.
value
(
row
.
status
)
}
>
{
row
.
status
===
1
?
"启用"
:
"停用"
}
...
...
@@ -47,6 +82,12 @@ export function useDept() {
label
:
"创建时间"
,
minWidth
:
200
,
prop
:
"createTime"
,
/**
* 时间格式化函数
* @param {Object} param - 包含创建时间的对象
* @param {string} param.createTime - 创建时间
* @returns {string} 格式化后的时间字符串
*/
formatter
:
({
createTime
})
=>
dayjs
(
createTime
).
format
(
"YYYY-MM-DD HH:mm:ss"
)
},
...
...
@@ -63,39 +104,63 @@ export function useDept() {
}
];
/**
* 处理表格选择变化事件
* @param {Array} val - 选中的行数据数组
*/
function
handleSelectionChange
(
val
)
{
console
.
log
(
"handleSelectionChange"
,
val
);
}
/**
* 重置搜索表单并重新搜索
* @param {Object} formEl - 表单引用对象
*/
function
resetForm
(
formEl
)
{
if
(
!
formEl
)
return
;
formEl
.
resetFields
();
onSearch
();
}
/**
* 执行搜索操作
*/
async
function
onSearch
()
{
// loading.value = true;
// 定义搜索参数
const
params
=
{
pageNum
:
1
,
pageSize
:
200
};
// 获取部门列表数据
const
{
data
}
=
await
getDeptList
(
params
);
// 获取记录数据
let
newData
=
(
data
as
any
).
records
;
if
(
!
isAllEmpty
(
form
.
name
))
{
// 前端搜索部门名称
newData
=
newData
.
filter
(
item
=>
item
.
name
.
includes
(
form
.
name
));
newData
=
newData
.
filter
((
item
:
{
name
:
string
})
=>
item
.
name
.
includes
(
form
.
name
)
);
}
if
(
!
isAllEmpty
(
form
.
status
))
{
// 前端搜索状态
newData
=
newData
.
filter
(
item
=>
item
.
status
===
form
.
status
);
newData
=
newData
.
filter
(
(
item
:
{
status
:
number
|
null
})
=>
item
.
status
===
form
.
status
);
}
dataList
.
value
=
handleTree
(
newData
);
// 处理成树结构
// 处理数据为树结构
dataList
.
value
=
handleTree
(
newData
);
console
.
log
(
"dataList"
,
dataList
.
value
);
setTimeout
(()
=>
{
loading
.
value
=
false
;
},
500
);
}
/**
* 格式化上级部门选项,根据状态添加禁用属性
* @param {Array} treeList - 部门树列表数据
* @returns {Array} 格式化后的部门树列表数据
*/
function
formatHigherDeptOptions
(
treeList
)
{
// 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
if
(
!
treeList
||
!
treeList
.
length
)
return
;
...
...
@@ -108,6 +173,11 @@ export function useDept() {
return
newTreeList
;
}
/**
* 打开新增或编辑部门对话框
* @param {string} [title="新增"] - 对话框标题
* @param {FormItemProps} [row] - 要编辑的部门数据
*/
function
openDialog
(
title
=
"新增"
,
row
?:
FormItemProps
)
{
addDialog
({
title
:
`
${
title
}
部门`
,
...
...
@@ -129,10 +199,23 @@ export function useDept() {
fullscreen
:
deviceDetection
(),
fullscreenIcon
:
true
,
closeOnClickModal
:
false
,
/**
* 对话框内容渲染函数
* @returns {JSX.Element} 渲染后的编辑表单元素
*/
contentRenderer
:
()
=>
h
(
editForm
,
{
ref
:
formRef
,
formInline
:
null
}),
/**
* 对话框确认前的回调函数
* @param {Function} done - 完成回调函数,用于关闭对话框
* @param {Object} param - 包含对话框选项的对象
* @param {Object} param.options - 对话框选项
*/
beforeSure
:
(
done
,
{
options
})
=>
{
const
FormRef
=
formRef
.
value
.
getRef
();
const
curData
=
options
.
props
.
formInline
as
FormItemProps
;
/**
* 处理成功后的杂项操作
*/
function
chores
()
{
message
(
`您
${
title
}
了部门名称为
${
curData
.
name
}
的这条数据`
,
{
type
:
"success"
...
...
@@ -153,7 +236,7 @@ export function useDept() {
}
});
}
else
{
if
(
!
row
.
id
)
{
if
(
!
row
?
.
id
)
{
message
(
"id不能为空"
,
{
type
:
"error"
});
return
;
}
...
...
@@ -172,6 +255,12 @@ export function useDept() {
});
}
/**
* 处理删除部门操作
* @param {Object} row - 要删除的部门数据
* @param {number} row.id - 部门 ID
* @param {string} row.name - 部门名称
*/
function
handleDelete
(
row
)
{
console
.
log
(
"handleDelete"
,
row
.
id
);
deleteDept
({
id
:
row
.
id
}).
then
(
res
=>
{
...
...
@@ -182,6 +271,7 @@ export function useDept() {
});
}
// 组件挂载后执行搜索操作
onMounted
(()
=>
{
onSearch
();
});
...
...
src/views/systems/dep/utils/rule.ts
浏览文件 @
2c3c723a
// 从 vue 库导入 reactive 函数,用于创建响应式对象
import
{
reactive
}
from
"vue"
;
// 从 element-plus 库导入 FormRules 类型,用于定义表单验证规则
import
type
{
FormRules
}
from
"element-plus"
;
// 从 @pureadmin/utils 库导入 isPhone 和 isEmail 函数,用于验证手机号和邮箱格式
import
{
isPhone
,
isEmail
}
from
"@pureadmin/utils"
;
/** 自定义表单规则校验 */
/**
* 自定义表单规则校验
* 此对象包含了部门表单中各个字段的验证规则,使用 reactive 函数将其转换为响应式对象。
*/
export
const
formRules
=
reactive
(
<
FormRules
>
{
name
:
[{
required
:
true
,
message
:
"部门名称为必填项"
,
trigger
:
"blur"
}],
// 部门名称字段的验证规则
name
:
[
// 验证部门名称是否为必填项,若为空则提示 "部门名称为必填项",在失去焦点时触发验证
{
required
:
true
,
message
:
"部门名称为必填项"
,
trigger
:
"blur"
}
],
// 联系电话字段的验证规则
phone
:
[
{
/**
* 自定义验证函数,用于验证联系电话格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
// 若联系电话为空,则直接通过验证
if
(
value
===
""
)
{
callback
();
}
else
if
(
!
isPhone
(
value
))
{
}
// 若联系电话格式不正确,则返回错误信息
else
if
(
!
isPhone
(
value
))
{
callback
(
new
Error
(
"请输入正确的手机号码格式"
));
}
else
{
}
// 若联系电话格式正确,则通过验证
else
{
callback
();
}
},
// 在失去焦点时触发验证
trigger
:
"blur"
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
// 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
// trigger: "click"
}
],
// 邮箱字段的验证规则
email
:
[
{
/**
* 自定义验证函数,用于验证邮箱格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator
:
(
rule
,
value
,
callback
)
=>
{
// 若邮箱为空,则直接通过验证
if
(
value
===
""
)
{
callback
();
}
else
if
(
!
isEmail
(
value
))
{
}
// 若邮箱格式不正确,则返回错误信息
else
if
(
!
isEmail
(
value
))
{
callback
(
new
Error
(
"请输入正确的邮箱格式"
));
}
else
{
}
// 若邮箱格式正确,则通过验证
else
{
callback
();
}
},
...
...
src/views/systems/dep/utils/types.ts
浏览文件 @
2c3c723a
/**
* 部门表单单项属性接口
* 定义了部门表单中每个字段的类型和属性,用于规范部门相关表单数据的结构。
*/
interface
FormItemProps
{
/**
* 上级部门选项列表
* 每个选项是一个键值对对象,键为字符串类型,值为任意类型。
*/
higherDeptOptions
:
Record
<
string
,
unknown
>
[];
/**
* 上级部门 ID
* 用于标识当前部门所属的上级部门,为数字类型。
*/
parentId
:
number
;
/**
* 部门名称
* 用于存储部门的具体名称,为字符串类型。
*/
name
:
string
;
/**
* 部门负责人
* 记录部门负责人的姓名,为字符串类型。
*/
principal
:
string
;
/**
* 联系电话
* 可以是字符串或数字类型,用于存储部门的联系电话。
*/
phone
:
string
|
number
;
/**
* 邮箱地址
* 用于存储部门的联系邮箱,为字符串类型。
*/
email
:
string
;
/**
* 排序值
* 用于对部门进行排序,为数字类型。
*/
sort
:
number
;
/**
* 部门状态
* 用数字表示部门的状态,如启用、停用等。
*/
status
:
number
;
/**
* 备注信息
* 用于存储关于部门的额外说明信息,为字符串类型。
*/
remark
:
string
;
/**
* 部门 ID
* 可选属性,用于唯一标识一个部门,为数字类型。
*/
id
?:
number
;
}
/**
* 部门表单属性接口
* 包含一个部门表单单项属性对象,用于规范整个部门表单的数据结构。
*/
interface
FormProps
{
/**
* 表单内联数据
* 包含部门表单各项属性的对象,类型为 FormItemProps。
*/
formInline
:
FormItemProps
;
}
/**
* 导出部门表单单项属性和表单属性类型
* 方便在其他文件中引用这些类型,确保数据类型的一致性。
*/
export
type
{
FormItemProps
,
FormProps
};
src/views/systems/hooks.ts
浏览文件 @
2c3c723a
//
抽离可公用的工具函数等用于系统管理页面逻辑
//
从 Vue 中引入 computed 函数,用于创建计算属性
import
{
computed
}
from
"vue"
;
// 从 @pureadmin/utils 包中引入 useDark 函数,用于检测当前是否为暗黑模式
import
{
useDark
}
from
"@pureadmin/utils"
;
/**
* 定义一个组合式函数 usePublicHooks,用于获取系统管理页面所需的公共状态和样式
* 该函数封装了暗黑模式检测、el-switch 组件样式和 el-tag 组件样式的逻辑
* @returns 包含 isDark、switchStyle 和 tagStyle 的对象
*/
export
function
usePublicHooks
()
{
// 调用 useDark 函数获取当前是否为暗黑模式的状态
const
{
isDark
}
=
useDark
();
/**
* 计算 el-switch 组件的样式
* 根据设计需求,设置开启和关闭状态的颜色
*/
const
switchStyle
=
computed
(()
=>
{
return
{
// 开启状态的颜色
"--el-switch-on-color"
:
"#6abe39"
,
// 关闭状态的颜色
"--el-switch-off-color"
:
"#e84749"
};
});
/**
* 计算 el-tag 组件的样式
* 根据传入的 status 参数和当前的暗黑模式状态,动态设置标签的文本颜色、背景颜色和边框颜色
* @param status - 状态值,根据不同的值应用不同的样式
* @returns 包含 CSS 变量的对象,用于设置 el-tag 组件的样式
*/
const
tagStyle
=
computed
(()
=>
{
return
(
status
:
number
)
=>
{
return
status
===
1
?
{
// 状态为 1 时,根据暗黑模式设置标签的文本颜色
"--el-tag-text-color"
:
isDark
.
value
?
"#6abe39"
:
"#389e0d"
,
// 状态为 1 时,根据暗黑模式设置标签的背景颜色
"--el-tag-bg-color"
:
isDark
.
value
?
"#172412"
:
"#f6ffed"
,
// 状态为 1 时,根据暗黑模式设置标签的边框颜色
"--el-tag-border-color"
:
isDark
.
value
?
"#274a17"
:
"#b7eb8f"
}
:
{
// 状态不为 1 时,根据暗黑模式设置标签的文本颜色
"--el-tag-text-color"
:
isDark
.
value
?
"#e84749"
:
"#cf1322"
,
// 状态不为 1 时,根据暗黑模式设置标签的背景颜色
"--el-tag-bg-color"
:
isDark
.
value
?
"#2b1316"
:
"#fff1f0"
,
// 状态不为 1 时,根据暗黑模式设置标签的边框颜色
"--el-tag-border-color"
:
isDark
.
value
?
"#58191c"
:
"#ffa39e"
};
};
...
...
src/views/systems/menu/form.vue
浏览文件 @
2c3c723a
差异被折叠。
点击展开。
src/views/systems/menu/index.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 引入菜单相关的自定义 hook
import
{
useMenu
}
from
"./utils/hook"
;
// 引入国际化转换函数
import
{
transformI18n
}
from
"@/plugins/i18n"
;
// 引入自定义表格栏组件
import
{
PureTableBar
}
from
"@/components/RePureTableBar"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入删除图标
import
Delete
from
"~icons/ep/delete"
;
// 引入编辑图标
import
EditPen
from
"~icons/ep/edit-pen"
;
// 引入刷新图标
import
Refresh
from
"~icons/ep/refresh"
;
// 引入添加图标
import
AddFill
from
"~icons/ri/add-circle-line"
;
/**
* 定义组件选项
* 设置组件名称为 SystemMenu
*/
defineOptions
({
name
:
"SystemMenu"
});
// 创建表单引用
const
formRef
=
ref
();
// 创建表格引用
const
tableRef
=
ref
();
/**
* 从 useMenu hook 中解构出所需的状态和方法
* form: 搜索表单数据
* loading: 加载状态
* columns: 表格列配置
* dataList: 表格数据列表
* onSearch: 执行搜索操作的方法
* resetForm: 重置表单并重新搜索的方法
* openDialog: 打开新增或修改对话框的方法
* handleDelete: 处理删除操作的方法
* handleSelectionChange: 处理表格选择变化的方法
*/
const
{
form
,
loading
,
...
...
@@ -28,6 +55,10 @@ const {
handleSelectionChange
}
=
useMenu
();
/**
* 处理全屏操作
* 重置表格高度
*/
function
onFullscreen
()
{
// 重置表格高度
tableRef
.
value
.
setAdaptive
();
...
...
@@ -36,12 +67,14 @@ function onFullscreen() {
<
template
>
<div
class=
"main"
>
<!-- 搜索表单 -->
<el-form
ref=
"formRef"
:inline=
"true"
:model=
"form"
class=
"search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<!-- 菜单名称输入项 -->
<el-form-item
label=
"菜单名称:"
prop=
"title"
>
<el-input
v-model=
"form.name"
...
...
@@ -51,6 +84,7 @@ function onFullscreen() {
/>
</el-form-item>
<el-form-item>
<!-- 搜索按钮,点击触发搜索操作 -->
<el-button
type=
"primary"
:icon=
"useRenderIcon('ri/search-line')"
...
...
@@ -59,12 +93,14 @@ function onFullscreen() {
>
搜索
</el-button>
<!-- 重置按钮,点击触发重置表单并重新搜索 -->
<el-button
:icon=
"useRenderIcon(Refresh)"
@
click=
"resetForm(formRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<!-- 自定义表格栏组件 -->
<PureTableBar
title=
"菜单管理(仅演示,操作后不生效)"
:columns=
"columns"
...
...
@@ -73,7 +109,9 @@ function onFullscreen() {
@
refresh=
"onSearch"
@
fullscreen=
"onFullscreen"
>
<!-- 自定义按钮插槽 -->
<template
#
buttons
>
<!-- 新增菜单按钮,点击打开新增对话框 -->
<el-button
type=
"primary"
:icon=
"useRenderIcon(AddFill)"
...
...
@@ -82,7 +120,9 @@ function onFullscreen() {
新增菜单
</el-button>
</
template
>
<!-- 表格插槽 -->
<
template
v-slot=
"{ size, dynamicColumns }"
>
<!-- 自定义表格组件 -->
<pure-table
ref=
"tableRef"
adaptive
...
...
@@ -101,7 +141,9 @@ function onFullscreen() {
}"
@selection-change="handleSelectionChange"
>
<!-- 操作列插槽 -->
<template
#
operation=
"
{ row }">
<!-- 修改按钮,点击打开修改对话框 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -112,6 +154,7 @@ function onFullscreen() {
>
修改
</el-button>
<!-- 新增子菜单按钮,仅当菜单类型不为 3 时显示 -->
<el-button
v-show=
"row.menuType !== 3"
class=
"reset-margin"
...
...
@@ -123,11 +166,13 @@ function onFullscreen() {
>
新增
</el-button>
<!-- 删除确认弹窗 -->
<el-popconfirm
:title=
"`是否确认删除菜单名称为$
{transformI18n(row.title)}的这条数据${row?.children?.length > 0 ? '。注意下级菜单也会一并删除,请谨慎操作' : ''}`"
@confirm="handleDelete(row)"
>
<template
#
reference
>
<!-- 删除按钮 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -147,14 +192,17 @@ function onFullscreen() {
</template>
<
style
lang=
"scss"
scoped
>
/* 隐藏表格内部包装器的底部边框 */
:deep
(
.el-table__inner-wrapper
::before
)
{
height
:
0
;
}
/* 主内容区域样式 */
.main-content
{
margin
:
24px
24px
0
!
important
;
}
/* 搜索表单样式 */
.search-form
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12px
;
...
...
src/views/systems/menu/utils/enums.ts
浏览文件 @
2c3c723a
// 从 @/components/ReSegmented 导入 OptionsType 类型,用于规范选项数据的结构
import
type
{
OptionsType
}
from
"@/components/ReSegmented"
;
/**
* 菜单类型选项列表
* 定义了不同菜单类型的选项,每个选项包含标签和对应的值
*/
const
menuTypeOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"菜单"
,
value
:
1
label
:
"菜单"
,
// 菜单类型的显示标签
value
:
1
// 菜单类型对应的值
},
{
label
:
"目录"
,
value
:
2
label
:
"目录"
,
// 目录类型的显示标签
value
:
2
// 目录类型对应的值
},
{
label
:
"外链"
,
value
:
3
label
:
"外链"
,
// 外链类型的显示标签
value
:
3
// 外链类型对应的值
},
{
label
:
"按钮"
,
value
:
4
label
:
"按钮"
,
// 按钮类型的显示标签
value
:
4
// 按钮类型对应的值
}
];
/**
* 显示链接选项列表
* 定义了菜单是否显示链接的选项,每个选项包含标签、提示信息和对应的值
*/
const
showLinkOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"显示"
,
tip
:
"会在菜单中显示"
,
value
:
true
label
:
"显示"
,
// 显示链接的显示标签
tip
:
"会在菜单中显示"
,
// 显示链接的提示信息
value
:
true
// 显示链接对应的值
},
{
label
:
"隐藏"
,
tip
:
"不会在菜单中显示"
,
value
:
false
label
:
"隐藏"
,
// 隐藏链接的显示标签
tip
:
"不会在菜单中显示"
,
// 隐藏链接的提示信息
value
:
false
// 隐藏链接对应的值
}
];
/**
* 固定标签选项列表
* 定义了菜单标签是否固定显示的选项,每个选项包含标签、提示信息和对应的值
*/
const
fixedTagOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"固定"
,
tip
:
"当前菜单名称固定显示在标签页且不可关闭"
,
value
:
true
label
:
"固定"
,
// 固定标签的显示标签
tip
:
"当前菜单名称固定显示在标签页且不可关闭"
,
// 固定标签的提示信息
value
:
true
// 固定标签对应的值
},
{
label
:
"不固定"
,
tip
:
"当前菜单名称不固定显示在标签页且可关闭"
,
value
:
false
label
:
"不固定"
,
// 不固定标签的显示标签
tip
:
"当前菜单名称不固定显示在标签页且可关闭"
,
// 不固定标签的提示信息
value
:
false
// 不固定标签对应的值
}
];
/**
* 缓存选项列表
* 定义了菜单页面是否缓存的选项,每个选项包含标签、提示信息和对应的值
*/
const
keepAliveOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"缓存"
,
tip
:
"会保存该页面的整体状态,刷新后会清空状态"
,
value
:
true
label
:
"缓存"
,
// 缓存的显示标签
tip
:
"会保存该页面的整体状态,刷新后会清空状态"
,
// 缓存的提示信息
value
:
true
// 缓存对应的值
},
{
label
:
"不缓存"
,
tip
:
"不会保存该页面的整体状态"
,
value
:
false
label
:
"不缓存"
,
// 不缓存的显示标签
tip
:
"不会保存该页面的整体状态"
,
// 不缓存的提示信息
value
:
false
// 不缓存对应的值
}
];
/**
* 隐藏标签选项列表
* 定义了菜单名称或自定义信息是否允许添加到标签页的选项,每个选项包含标签、提示信息和对应的值
*/
const
hiddenTagOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"允许"
,
tip
:
"当前菜单名称或自定义信息允许添加到标签页"
,
value
:
false
label
:
"允许"
,
// 允许添加到标签页的显示标签
tip
:
"当前菜单名称或自定义信息允许添加到标签页"
,
// 允许添加到标签页的提示信息
value
:
false
// 允许添加到标签页对应的值
},
{
label
:
"禁止"
,
tip
:
"当前菜单名称或自定义信息禁止添加到标签页"
,
value
:
true
label
:
"禁止"
,
// 禁止添加到标签页的显示标签
tip
:
"当前菜单名称或自定义信息禁止添加到标签页"
,
// 禁止添加到标签页的提示信息
value
:
true
// 禁止添加到标签页对应的值
}
];
/**
* 显示父级菜单选项列表
* 定义了是否显示父级菜单的选项,每个选项包含标签、提示信息和对应的值
*/
const
showParentOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"显示"
,
tip
:
"会显示父级菜单"
,
value
:
true
label
:
"显示"
,
// 显示父级菜单的显示标签
tip
:
"会显示父级菜单"
,
// 显示父级菜单的提示信息
value
:
true
// 显示父级菜单对应的值
},
{
label
:
"隐藏"
,
tip
:
"不会显示父级菜单"
,
value
:
false
label
:
"隐藏"
,
// 隐藏父级菜单的显示标签
tip
:
"不会显示父级菜单"
,
// 隐藏父级菜单的提示信息
value
:
false
// 隐藏父级菜单对应的值
}
];
/**
* 框架加载动画选项列表
* 定义了是否开启首次加载动画的选项,每个选项包含标签、提示信息和对应的值
*/
const
frameLoadingOptions
:
Array
<
OptionsType
>
=
[
{
label
:
"开启"
,
tip
:
"有首次加载动画"
,
value
:
true
label
:
"开启"
,
// 开启加载动画的显示标签
tip
:
"有首次加载动画"
,
// 开启加载动画的提示信息
value
:
true
// 开启加载动画对应的值
},
{
label
:
"关闭"
,
tip
:
"无首次加载动画"
,
value
:
false
label
:
"关闭"
,
// 关闭加载动画的显示标签
tip
:
"无首次加载动画"
,
// 关闭加载动画的提示信息
value
:
false
// 关闭加载动画对应的值
}
];
/**
* 导出所有选项列表
* 方便在其他文件中引用这些选项数据
*/
export
{
menuTypeOptions
,
showLinkOptions
,
...
...
src/views/systems/menu/utils/hook.tsx
浏览文件 @
2c3c723a
...
...
@@ -10,15 +10,29 @@ import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import
{
cloneDeep
,
isAllEmpty
,
deviceDetection
}
from
"@pureadmin/utils"
;
// import { c } from "node_modules/vite/dist/node/moduleRunnerTransport.d-CXw_Ws6P";
/**
* 自定义 Hook,用于管理菜单相关的状态和操作
* @returns 包含菜单相关状态和操作的对象
*/
export
function
useMenu
()
{
// 定义表单数据,使用 reactive 创建响应式对象,初始包含菜单名称字段
const
form
=
reactive
({
name
:
""
});
// 定义表单引用,用于获取表单实例
const
formRef
=
ref
();
// 定义菜单数据列表,使用 ref 创建响应式对象,初始为空数组
const
dataList
=
ref
([]);
// 定义加载状态,使用 ref 创建响应式对象,初始为 true 表示正在加载
const
loading
=
ref
(
true
);
/**
* 根据菜单类型返回对应的标签类型或文本描述
* @param type - 菜单类型,取值为 1, 2, 3, 4
* @param text - 是否返回文本描述,默认为 false
* @returns 标签类型或文本描述
*/
const
getMenuType
=
(
type
,
text
=
false
)
=>
{
switch
(
type
)
{
case
1
:
...
...
@@ -32,11 +46,13 @@ export function useMenu() {
}
};
const
columns
:
TableColumnList
=
[
// 定义表格列配置,用于渲染菜单表格
const
columns
=
[
{
label
:
"菜单名称"
,
prop
:
"name"
,
align
:
"left"
,
// 自定义单元格渲染函数,显示菜单图标和名称
cellRenderer
:
({
row
})
=>
(
<>
<
span
class=
"inline-block mr-1"
>
...
...
@@ -52,6 +68,7 @@ export function useMenu() {
label
:
"菜单类型"
,
prop
:
"type"
,
width
:
100
,
// 自定义单元格渲染函数,显示菜单类型标签
cellRenderer
:
({
row
,
props
})
=>
(
<
el
-
tag
size=
{
props
.
size
}
type=
{
getMenuType
(
row
.
type
)
}
effect=
"plain"
>
{
getMenuType
(
row
.
type
,
true
)
}
...
...
@@ -65,6 +82,7 @@ export function useMenu() {
{
label
:
"组件路径"
,
prop
:
"component"
,
// 自定义格式化函数,若组件路径为空则显示路由路径
formatter
:
({
path
,
component
})
=>
isAllEmpty
(
component
)
?
path
:
component
},
...
...
@@ -80,6 +98,7 @@ export function useMenu() {
{
label
:
"隐藏"
,
prop
:
"visible"
,
// 自定义格式化函数,将布尔值转换为中文描述
formatter
:
({
visible
})
=>
(
visible
?
"是"
:
"否"
),
width
:
100
},
...
...
@@ -91,46 +110,75 @@ export function useMenu() {
}
];
/**
* 处理表格选中项变化的事件
* @param val - 选中项的值
*/
function
handleSelectionChange
(
val
)
{
console
.
log
(
"handleSelectionChange"
,
val
);
}
/**
* 重置表单数据并重新搜索菜单
* @param formEl - 表单实例
*/
function
resetForm
(
formEl
)
{
if
(
!
formEl
)
return
;
// 手动清空菜单名称
form
.
name
=
""
;
// 重置表单字段
formEl
.
resetFields
();
// 重新搜索菜单
onSearch
();
}
/**
* 搜索菜单数据
*/
async
function
onSearch
()
{
// 设置加载状态为正在加载
loading
.
value
=
true
;
// 请求菜单列表数据
const
{
data
}
=
await
getMenuList
({
name
:
""
,
pageNum
:
1
,
pageSize
:
100
});
// 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
// 获取返回数据中的记录列表
let
newData
=
(
data
as
any
).
records
;
if
(
!
isAllEmpty
(
form
.
name
))
{
// 前端搜索菜单名称
// 前端搜索菜单名称
,过滤出包含搜索关键词的菜单
newData
=
newData
.
filter
(
item
=>
transformI18n
(
item
.
name
).
includes
(
form
.
name
)
);
}
dataList
.
value
=
handleTree
(
newData
);
// 处理成树结构
console
.
log
(
"dataList"
,
dataList
.
value
);
// 将一维数组数据处理成树结构
dataList
.
value
=
handleTree
(
newData
);
// 模拟加载延迟,500ms 后设置加载状态为完成
setTimeout
(()
=>
{
loading
.
value
=
false
;
},
500
);
}
/**
* 格式化上级菜单选项,将菜单数据转换为可用于下拉选择的选项
* @param treeList - 菜单树状数据
* @returns 格式化后的菜单选项数组
*/
function
formatHigherMenuOptions
(
treeList
)
{
if
(
!
treeList
||
!
treeList
.
length
)
return
;
const
newTreeList
=
[];
for
(
let
i
=
0
;
i
<
treeList
.
length
;
i
++
)
{
// 对菜单名称进行国际化处理
treeList
[
i
].
name
=
transformI18n
(
treeList
[
i
].
name
);
// 递归处理子菜单
formatHigherMenuOptions
(
treeList
[
i
].
children
);
newTreeList
.
push
(
treeList
[
i
]);
}
return
newTreeList
;
}
/**
* 打开新增或编辑菜单的对话框
* @param title - 对话框标题,默认为 "新增"
* @param row - 要编辑的菜单数据,可选
*/
function
openDialog
(
title
=
"新增"
,
row
?:
FormItemProps
)
{
addDialog
({
title
:
`
${
title
}
菜单`
,
...
...
@@ -168,10 +216,17 @@ export function useMenu() {
fullscreen
:
deviceDetection
(),
fullscreenIcon
:
true
,
closeOnClickModal
:
false
,
// 对话框内容渲染函数,渲染编辑表单
contentRenderer
:
()
=>
h
(
editForm
,
{
ref
:
formRef
,
formInline
:
null
}),
// 对话框确认前的回调函数
beforeSure
:
(
done
,
{
options
})
=>
{
// 获取表单实例
const
FormRef
=
formRef
.
value
.
getRef
();
// 获取表单数据
const
curData
=
options
.
props
.
formInline
as
FormItemProps
;
/**
* 处理成功操作后的通用任务,如提示消息、关闭对话框、刷新表格数据
*/
function
chores
()
{
message
(
`您
${
title
}
了菜单名称为
${
transformI18n
(
curData
.
name
)}
的这条数据`
,
...
...
@@ -179,15 +234,19 @@ export function useMenu() {
type
:
"success"
}
);
done
();
// 关闭弹框
onSearch
();
// 刷新表格数据
// 关闭对话框
done
();
// 刷新表格数据
onSearch
();
}
// 验证表单
FormRef
.
validate
(
valid
=>
{
if
(
valid
)
{
// 表单规则校验通过
if
(
title
===
"新增"
)
{
curData
.
visible
=
curData
.
visible
||
curData
.
visible
===
0
?
1
:
0
;
// 调用新增菜单接口
addMenu
(
curData
).
then
(
res
=>
{
if
((
res
as
any
).
code
===
"0"
)
{
chores
();
...
...
@@ -201,12 +260,12 @@ export function useMenu() {
curData
.
id
=
row
?.
id
;
curData
.
visible
=
curData
.
visible
||
curData
.
visible
===
0
?
1
:
0
;
// 调用更新菜单接口
updateMenu
(
curData
).
then
(
res
=>
{
if
((
res
as
any
).
code
===
"0"
)
{
chores
();
}
});
chores
();
}
}
});
...
...
@@ -214,17 +273,22 @@ export function useMenu() {
});
}
/**
* 处理删除菜单的操作
* @param row - 要删除的菜单数据
*/
function
handleDelete
(
row
)
{
console
.
log
(
"handleDelete"
,
row
.
id
);
if
(
!
row
.
id
)
{
message
(
"id不能为空"
,
{
type
:
"error"
});
return
;
}
// 调用删除菜单接口
deleteMenu
({
id
:
row
.
id
}).
then
(
res
=>
{
if
((
res
as
any
).
code
===
"0"
)
{
message
(
`您删除了菜单名称为
${
transformI18n
(
row
.
title
)}
的这条数据`
,
{
type
:
"success"
});
// 刷新表格数据
onSearch
();
}
else
{
message
((
res
as
any
).
msg
,
{
type
:
"error"
});
...
...
@@ -232,6 +296,7 @@ export function useMenu() {
});
}
// 组件挂载后执行搜索操作
onMounted
(()
=>
{
onSearch
();
});
...
...
src/views/systems/menu/utils/rule.ts
浏览文件 @
2c3c723a
/**
* 从 Vue 导入 reactive 函数,用于创建响应式对象
*/
import
{
reactive
}
from
"vue"
;
/**
* 从 element-plus 导入 FormRules 类型,用于定义表单验证规则
*/
import
type
{
FormRules
}
from
"element-plus"
;
/** 自定义表单规则校验 */
/**
* 自定义表单规则校验
* 该常量使用 reactive 函数创建一个响应式的表单验证规则对象,
* 包含对菜单名称、路由路径和权限标识的必填项验证。
*/
export
const
formRules
=
reactive
(
<
FormRules
>
{
// 菜单名称的验证规则,要求该项为必填项,失去焦点时触发验证
name
:
[{
required
:
true
,
message
:
"菜单名称为必填项"
,
trigger
:
"blur"
}],
// 路由路径的验证规则,要求该项为必填项,失去焦点时触发验证
path
:
[{
required
:
true
,
message
:
"路由路径为必填项"
,
trigger
:
"blur"
}],
// 权限标识的验证规则,要求该项为必填项,失去焦点时触发验证
perm
:
[{
required
:
true
,
message
:
"权限标识为必填项"
,
trigger
:
"blur"
}]
});
src/views/systems/menu/utils/types.ts
浏览文件 @
2c3c723a
/**
* 定义表单项的属性接口
*/
interface
FormItemProps
{
/** 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮)*/
type
:
number
;
/** 上级菜单选项数组,每个选项是一个键值对对象 */
higherMenuOptions
:
Record
<
string
,
unknown
>
[];
/** 父菜单的ID */
parentId
:
number
;
/** 菜单名称 */
name
:
string
;
/** 菜单路径 */
path
:
string
;
/** 菜单对应的组件 */
component
:
string
;
/** 菜单权限 */
perm
:
string
;
/** 菜单是否可见,值可以是数字或布尔类型 */
visible
:
number
|
boolean
;
/** 菜单排序 */
sort
:
number
;
/** 菜单图标 */
icon
:
string
;
/** iframe 菜单的源地址 */
frameSrc
:
string
;
/** 菜单ID,可选属性 */
id
?:
any
;
// /** 菜单标题 */
// title: string;
// /** 菜单层级 */
// rank: number;
// /** 菜单重定向路径 */
// redirect: string;
// /** 额外图标 */
// extraIcon: string;
// /** 进入过渡动画 */
// enterTransition: string;
// /** 离开过渡动画 */
// leaveTransition: string;
// /** 激活路径 */
// activePath: string;
// /** 菜单权限列表 */
// auths: string;
// /** iframe 菜单加载状态 */
// frameLoading: boolean;
// /** 是否缓存组件 */
// keepAlive: boolean;
// /** 是否隐藏标签页 */
// hiddenTag: boolean;
// /** 是否固定标签页 */
// fixedTag: boolean;
// /** 是否显示链接 */
// showLink: boolean;
// /** 是否显示父菜单 */
// showParent: boolean;
}
/**
* 定义表单属性接口
*/
interface
FormProps
{
/** 内联表单,其属性遵循 FormItemProps 接口 */
formInline
:
FormItemProps
;
}
/**
* 导出 FormItemProps 和 FormProps 类型,供其他模块使用
*/
export
type
{
FormItemProps
,
FormProps
};
src/views/systems/role/form.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 引入当前目录下 utils 文件夹里 rule.ts 文件定义的表单验证规则
import
{
formRules
}
from
"./utils/rule"
;
// 引入当前目录下 utils 文件夹里 types.ts 文件定义的表单属性类型
import
{
FormProps
}
from
"./utils/types"
;
/**
* 定义组件的 props
* 使用 withDefaults 函数为 props 设置默认值,确保在父组件未传递 props 时,组件仍能正常工作。
* @property formInline - 表单内联数据,包含角色名称、角色标识和备注信息,默认初始值为空字符串。
*/
const
props
=
withDefaults
(
defineProps
<
FormProps
>
(),
{
formInline
:
()
=>
({
name
:
""
,
...
...
@@ -11,24 +19,34 @@ const props = withDefaults(defineProps<FormProps>(), {
})
});
// 创建一个响应式引用,用于获取表单实例
const
ruleFormRef
=
ref
();
// 创建一个响应式引用,存储表单内联数据,初始值来自 props 传入的数据
const
newFormInline
=
ref
(
props
.
formInline
);
/**
* 获取表单实例
* @returns 返回表单实例,如果实例存在则返回,不存在则返回 undefined。
*/
function
getRef
()
{
return
ruleFormRef
.
value
;
}
// 将 getRef 方法暴露给父组件,使得父组件可以调用该方法获取表单实例
defineExpose
({
getRef
});
</
script
>
<
template
>
<!-- 定义一个 Element Plus 的表单组件 -->
<el-form
ref=
"ruleFormRef"
:model=
"newFormInline"
:rules=
"formRules"
label-width=
"82px"
>
<!-- 角色名称表单项 -->
<el-form-item
label=
"角色名称"
prop=
"name"
>
<!-- 输入框组件,绑定角色名称数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.name"
clearable
...
...
@@ -36,7 +54,9 @@ defineExpose({ getRef });
/>
</el-form-item>
<!-- 角色标识表单项 -->
<el-form-item
label=
"角色标识"
prop=
"code"
>
<!-- 输入框组件,绑定角色标识数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.code"
clearable
...
...
@@ -44,7 +64,9 @@ defineExpose({ getRef });
/>
</el-form-item>
<!-- 备注表单项 -->
<el-form-item
label=
"备注"
>
<!-- 文本域输入框组件,绑定备注信息数据,有占位提示 -->
<el-input
v-model=
"newFormInline.remark"
placeholder=
"请输入备注信息"
...
...
src/views/systems/role/index.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入当前目录下 utils 文件夹里的 useRole hook,用于处理角色管理相关逻辑
import
{
useRole
}
from
"./utils/hook"
;
// 从 Vue 引入响应式 API 和生命周期钩子
import
{
ref
,
computed
,
nextTick
,
onMounted
}
from
"vue"
;
// 引入自定义表格栏组件
import
{
PureTableBar
}
from
"@/components/RePureTableBar"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入工具函数
import
{
delay
,
subBefore
,
...
...
@@ -10,6 +15,7 @@ import {
useResizeObserver
}
from
"@pureadmin/utils"
;
// 引入图标
// import Database from "~icons/ri/database-2-line";
// import More from "~icons/ep/more-filled";
import
Delete
from
"~icons/ep/delete"
;
...
...
@@ -20,10 +26,18 @@ import AddFill from "~icons/ri/add-circle-line";
import
Close
from
"~icons/ep/close"
;
import
Check
from
"~icons/ep/check"
;
/**
* 定义组件选项
* 设置组件名称为 SystemRole,方便调试和组件识别
*/
defineOptions
({
name
:
"SystemRole"
});
/**
* 计算图标类名
* 返回一个包含多个 CSS 类名的数组,用于设置图标的样式
*/
const
iconClass
=
computed
(()
=>
{
return
[
"w-[22px]"
,
...
...
@@ -41,12 +55,46 @@ const iconClass = computed(() => {
];
});
// 创建树形组件引用
const
treeRef
=
ref
();
// 创建表单引用
const
formRef
=
ref
();
// 创建表格引用
const
tableRef
=
ref
();
// 创建内容区域引用
const
contentRef
=
ref
();
// 创建树形组件高度的响应式变量
const
treeHeight
=
ref
();
/**
* 从 useRole hook 中解构出所需的状态和方法
* form: 搜索表单数据
* isShow: 是否显示菜单权限弹窗
* curRow: 当前选中的角色行数据
* loading: 表格加载状态
* columns: 表格列配置
* rowStyle: 表格行样式方法
* dataList: 表格数据列表
* treeData: 树形菜单数据
* treeProps: 树形组件属性配置
* isLinkage: 是否联动操作
* pagination: 分页配置
* isExpandAll: 是否展开所有树形节点
* isSelectAll: 是否全选所有树形节点
* treeSearchValue: 树形搜索框的值
* onSearch: 执行搜索操作的方法
* resetForm: 重置表单并重新搜索的方法
* openDialog: 打开新增或修改对话框的方法
* handleMenu: 处理菜单权限操作的方法
* handleSave: 保存菜单权限的方法
* handleDelete: 处理删除角色操作的方法
* filterMethod: 树形组件过滤方法
* transformI18n: 国际化转换函数
* onQueryChanged: 处理搜索框内容变化的方法
* handleSizeChange: 处理每页显示数量变化的方法
* handleCurrentChange: 处理当前页码变化的方法
* handleSelectionChange: 处理表格选择变化的方法
*/
const
{
form
,
isShow
,
...
...
@@ -78,10 +126,17 @@ const {
handleSelectionChange
}
=
useRole
(
treeRef
);
/**
* 组件挂载后执行的操作
* 监听内容区域的大小变化,动态设置树形组件的高度
*/
onMounted
(()
=>
{
useResizeObserver
(
contentRef
,
async
()
=>
{
// 等待下一个 DOM 更新周期
await
nextTick
();
// 延迟 60 毫秒后执行
delay
(
60
).
then
(()
=>
{
// 获取表格包装器的高度,并转换为数字赋值给树形组件高度变量
treeHeight
.
value
=
parseFloat
(
subBefore
(
tableRef
.
value
.
getTableDoms
().
tableWrapper
.
style
.
height
,
"px"
)
);
...
...
@@ -92,12 +147,14 @@ onMounted(() => {
<
template
>
<div
class=
"main"
>
<!-- 搜索表单 -->
<el-form
ref=
"formRef"
:inline=
"true"
:model=
"form"
class=
"search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<!-- 角色名称输入项 -->
<el-form-item
label=
"角色名称:"
prop=
"name"
>
<el-input
v-model=
"form.name"
...
...
@@ -106,6 +163,7 @@ onMounted(() => {
class=
"w-[180px]!"
/>
</el-form-item>
<!-- 角色标识输入项 -->
<el-form-item
label=
"角色标识:"
prop=
"code"
>
<el-input
v-model=
"form.code"
...
...
@@ -114,6 +172,7 @@ onMounted(() => {
class=
"w-[180px]!"
/>
</el-form-item>
<!-- 状态选择项 -->
<el-form-item
label=
"状态:"
prop=
"status"
>
<el-select
v-model=
"form.status"
...
...
@@ -125,7 +184,9 @@ onMounted(() => {
<el-option
label=
"已停用"
value=
"0"
/>
</el-select>
</el-form-item>
<!-- 操作按钮项 -->
<el-form-item>
<!-- 搜索按钮,点击触发搜索操作 -->
<el-button
type=
"primary"
:icon=
"useRenderIcon('ri/search-line')"
...
...
@@ -134,16 +195,19 @@ onMounted(() => {
>
搜索
</el-button>
<!-- 重置按钮,点击触发重置表单并重新搜索 -->
<el-button
:icon=
"useRenderIcon(Refresh)"
@
click=
"resetForm(formRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<!-- 内容区域 -->
<div
ref=
"contentRef"
:class=
"['flex', deviceDetection() ? 'flex-wrap' : '']"
>
<!-- 自定义表格栏组件 -->
<PureTableBar
:class=
"[isShow && !deviceDetection() ? 'w-[60vw]!' : 'w-full']"
style=
"transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1)"
...
...
@@ -151,7 +215,9 @@ onMounted(() => {
:columns=
"columns"
@
refresh=
"onSearch"
>
<!-- 自定义按钮插槽 -->
<template
#
buttons
>
<!-- 新增角色按钮,点击打开新增对话框 -->
<el-button
type=
"primary"
:icon=
"useRenderIcon(AddFill)"
...
...
@@ -160,7 +226,9 @@ onMounted(() => {
新增角色
</el-button>
</
template
>
<!-- 表格插槽 -->
<
template
v-slot=
"{ size, dynamicColumns }"
>
<!-- 自定义表格组件 -->
<pure-table
ref=
"tableRef"
align-whole=
"center"
...
...
@@ -182,7 +250,9 @@ onMounted(() => {
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<!-- 操作列插槽 -->
<template
#
operation=
"
{ row }">
<!-- 修改按钮,点击打开修改对话框 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -193,11 +263,13 @@ onMounted(() => {
>
修改
</el-button>
<!-- 删除确认弹窗 -->
<el-popconfirm
:title=
"`是否确认删除角色名称为$
{row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<template
#
reference
>
<!-- 删除按钮 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -209,6 +281,7 @@ onMounted(() => {
</el-button>
</
template
>
</el-popconfirm>
<!-- 权限按钮,点击处理菜单权限操作 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -219,6 +292,7 @@ onMounted(() => {
>
权限
</el-button>
<!-- 注释掉的下拉菜单,可用于扩展功能 -->
<!-- <el-dropdown>
<el-button
class="ml-3 mt-[2px]"
...
...
@@ -261,12 +335,15 @@ onMounted(() => {
</template>
</PureTableBar>
<!-- 菜单权限弹窗,当 isShow 为 true 时显示 -->
<div
v-if=
"isShow"
class=
"min-w-[calc(100vw-60vw-268px)]! w-full mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto"
>
<!-- 弹窗头部 -->
<div
class=
"flex justify-between w-full px-3 pt-5 pb-4"
>
<div
class=
"flex"
>
<!-- 关闭图标,点击隐藏菜单权限弹窗 -->
<span
:class=
"iconClass"
>
<IconifyIconOffline
v-tippy=
"{
...
...
@@ -279,6 +356,7 @@ onMounted(() => {
@
click=
"handleMenu"
/>
</span>
<!-- 保存图标,点击保存菜单权限 -->
<span
:class=
"[iconClass, 'ml-2']"
>
<IconifyIconOffline
v-tippy=
"{
...
...
@@ -292,11 +370,13 @@ onMounted(() => {
/>
</span>
</div>
<!-- 弹窗标题 -->
<p
class=
"font-bold truncate"
>
菜单权限
{{ `${curRow?.name ? `(${curRow.name})` : ""}` }}
</p>
</div>
<!-- 树形搜索框 -->
<el-input
v-model=
"treeSearchValue"
placeholder=
"请输入菜单进行搜索"
...
...
@@ -304,11 +384,13 @@ onMounted(() => {
clearable
@
input=
"onQueryChanged"
/>
<!-- 操作复选框 -->
<div
class=
"flex flex-wrap"
>
<el-checkbox
v-model=
"isExpandAll"
label=
"展开/折叠"
/>
<el-checkbox
v-model=
"isSelectAll"
label=
"全选/全不选"
/>
<el-checkbox
v-model=
"isLinkage"
label=
"父子联动"
/>
</div>
<!-- 树形组件 -->
<el-tree-v2
ref=
"treeRef"
show-checkbox
...
...
@@ -318,7 +400,9 @@ onMounted(() => {
:check-strictly=
"!isLinkage"
:filter-method=
"filterMethod"
>
<!-- 树形节点默认插槽 -->
<
template
#
default=
"{ node }"
>
<!-- 显示树形节点标签,进行国际化转换 -->
<span>
{{
transformI18n
(
node
.
label
)
}}
</span>
</
template
>
</el-tree-v2>
...
...
@@ -328,14 +412,17 @@ onMounted(() => {
</template>
<
style
lang=
"scss"
scoped
>
/* 设置下拉菜单中图标元素的外边距为 0 */
:deep
(
.el-dropdown-menu__item
i
)
{
margin
:
0
;
}
/* 设置主内容区域的外边距 */
.main-content
{
margin
:
24px
24px
0
!
important
;
}
/* 设置搜索表单内表单项的底部外边距 */
.search-form
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12px
;
...
...
src/views/systems/role/utils/rule.ts
浏览文件 @
2c3c723a
// 从 Vue 中引入 reactive 函数,用于创建响应式对象
import
{
reactive
}
from
"vue"
;
// 从 Element Plus 引入 FormRules 类型,用于定义表单验证规则
import
type
{
FormRules
}
from
"element-plus"
;
/** 自定义表单规则校验 */
/**
* 自定义表单规则校验
* 此常量包含了角色表单中各个字段的验证规则,使用 reactive 函数使其变为响应式对象,
* 方便在表单验证过程中动态更新规则。
*/
export
const
formRules
=
reactive
(
<
FormRules
>
{
// 角色名称字段的验证规则
name
:
[{
required
:
true
,
message
:
"角色名称为必填项"
,
trigger
:
"blur"
}],
// 角色标识字段的验证规则
code
:
[{
required
:
true
,
message
:
"角色标识为必填项"
,
trigger
:
"blur"
}]
});
src/views/systems/role/utils/types.ts
浏览文件 @
2c3c723a
// 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了
/**
* 定义表单单项属性的接口
* 该接口描述了角色表单中单个表单项的属性结构
*/
interface
FormItemProps
{
/** 角色名称 */
/** 角色名称
,必填字段,字符串类型
*/
name
:
string
;
/** 角色编号 */
/** 角色编号
,必填字段,字符串类型
*/
code
:
string
;
/** 备注 */
/** 备注
信息,可选字段,字符串类型
*/
remark
:
string
;
/** 角色 ID,可选字段,字符串类型 */
id
?:
string
;
}
/**
* 定义表单属性的接口
* 该接口描述了整个角色表单的属性结构,包含一个 FormItemProps 类型的表单内联对象
*/
interface
FormProps
{
/** 表单内联对象,包含角色表单的各项属性 */
formInline
:
FormItemProps
;
}
/**
* 导出定义的类型
* 导出 FormItemProps 和 FormProps 接口,方便在其他文件中使用这些类型定义
*/
export
type
{
FormItemProps
,
FormProps
};
src/views/systems/user/form/index.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 引入自定义列组件
import
ReCol
from
"@/components/ReCol"
;
// 引入表单验证规则
import
{
formRules
}
from
"../utils/rule"
;
// 引入表单属性类型定义
import
{
FormProps
}
from
"../utils/types"
;
// 引入公共 hook
import
{
usePublicHooks
}
from
"../../hooks"
;
/**
* 定义组件的 props
* 使用 withDefaults 函数为 props 设置默认值,确保在父组件未传递 props 时,组件能正常初始化。
* @property formInline - 表单内联数据,包含用户相关信息,如昵称、用户名等。
*/
const
props
=
withDefaults
(
defineProps
<
FormProps
>
(),
{
formInline
:
()
=>
({
title
:
"新增"
,
...
...
@@ -22,6 +32,10 @@ const props = withDefaults(defineProps<FormProps>(), {
})
});
/**
* 性别选项数组
* 包含男性和女性两个选项,每个选项有对应的值和显示标签。
*/
const
sexOptions
=
[
{
value
:
0
,
...
...
@@ -32,27 +46,40 @@ const sexOptions = [
label
:
"女"
}
];
// 创建一个响应式引用,用于获取表单实例
const
ruleFormRef
=
ref
();
// 从公共 hook 中获取开关样式
const
{
switchStyle
}
=
usePublicHooks
();
// 创建一个响应式引用,存储表单内联数据,初始值来自 props 传入的数据
const
newFormInline
=
ref
(
props
.
formInline
);
/**
* 获取表单实例
* @returns 返回表单实例,如果实例存在则返回,不存在则返回 undefined。
*/
function
getRef
()
{
return
ruleFormRef
.
value
;
}
// 将 getRef 方法暴露给父组件,使得父组件可以调用该方法获取表单实例
defineExpose
({
getRef
});
</
script
>
<
template
>
<!-- 定义一个 Element Plus 的表单组件 -->
<el-form
ref=
"ruleFormRef"
:model=
"newFormInline"
:rules=
"formRules"
label-width=
"82px"
>
<!-- 定义一个行布局,设置列间距为 30px -->
<el-row
:gutter=
"30"
>
<!-- 用户昵称表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户昵称"
prop=
"nickname"
>
<!-- 输入框组件,绑定用户昵称数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.nickname"
clearable
...
...
@@ -60,8 +87,11 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<!-- 用户名称表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户名称"
prop=
"username"
>
<!-- 输入框组件,绑定用户名称数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.username"
clearable
...
...
@@ -70,7 +100,9 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!--
<re-col
<!-- 用户密码表单项,仅在表单标题为“新增”时显示 -->
<!--
<re-col
v-if=
"newFormInline.title === '新增'"
:value=
"12"
:xs=
"24"
...
...
@@ -83,9 +115,13 @@ defineExpose({ getRef });
placeholder=
"请输入用户密码"
/>
</el-form-item>
</re-col>
-->
</re-col>
-->
<!-- 手机号表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"手机号"
prop=
"mobile"
>
<!-- 输入框组件,绑定手机号数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.mobile"
clearable
...
...
@@ -94,8 +130,10 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!-- 邮箱表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"邮箱"
prop=
"email"
>
<!-- 输入框组件,绑定邮箱数据,支持清空输入内容,有占位提示 -->
<el-input
v-model=
"newFormInline.email"
clearable
...
...
@@ -103,14 +141,18 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<!-- 用户性别表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"用户性别"
>
<!-- 下拉选择框组件,绑定用户性别数据,支持清空选择,有占位提示 -->
<el-select
v-model=
"newFormInline.gender"
placeholder=
"请选择用户性别"
class=
"w-full"
clearable
>
<!-- 循环渲染性别选项 -->
<el-option
v-for=
"(item, index) in sexOptions"
:key=
"index"
...
...
@@ -121,8 +163,10 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<!-- 归属部门表单项 -->
<re-col
:value=
"12"
:xs=
"24"
:sm=
"24"
>
<el-form-item
label=
"归属部门"
>
<!-- 级联选择器组件,绑定归属部门父 ID 数据,支持清空选择、过滤选项,有占位提示 -->
<el-cascader
v-model=
"newFormInline.parentId"
class=
"w-full"
...
...
@@ -137,14 +181,19 @@ defineExpose({ getRef });
filterable
placeholder="请选择归属部门"
>
<!-- 自定义级联选择器节点显示内容 -->
<template
#
default=
"
{ node, data }">
<span>
{{
data
.
name
}}
</span>
<!-- 非叶子节点显示子节点数量 -->
<span
v-if=
"!node.isLeaf"
>
(
{{
data
.
children
.
length
}}
)
</span>
</
template
>
</el-cascader>
</el-form-item>
</re-col>
<!-- <re-col
<!-- 用户状态表单项,仅在表单标题为“新增”时显示 -->
<!--
<re-col
v-if="newFormInline.title === '新增'"
:value="12"
:xs="24"
...
...
@@ -161,10 +210,13 @@ defineExpose({ getRef });
:style="switchStyle"
/>
</el-form-item>
</re-col> -->
</re-col>
-->
<!-- 备注表单项 -->
<re-col>
<el-form-item
label=
"备注"
>
<!-- 文本域输入框组件,绑定备注信息数据,有占位提示 -->
<el-input
v-model=
"newFormInline.remark"
placeholder=
"请输入备注信息"
...
...
src/views/systems/user/form/role.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 引入自定义列组件
import
ReCol
from
"@/components/ReCol"
;
// 引入角色表单属性类型定义
import
{
RoleFormProps
}
from
"../utils/types"
;
/**
* 定义组件的 props
* 使用 withDefaults 函数为 props 设置默认值,确保在父组件未传递 props 时,组件能正常初始化。
* @property formInline - 表单内联数据,包含用户名、昵称、角色选项和所选角色 ID 列表。
*/
const
props
=
withDefaults
(
defineProps
<
RoleFormProps
>
(),
{
formInline
:
()
=>
({
username
:
""
,
...
...
@@ -12,24 +20,32 @@ const props = withDefaults(defineProps<RoleFormProps>(), {
})
});
// 创建一个响应式引用,存储表单内联数据,初始值来自 props 传入的数据
const
newFormInline
=
ref
(
props
.
formInline
);
</
script
>
<
template
>
<!-- 定义一个 Element Plus 的表单组件,绑定表单数据 -->
<el-form
:model=
"newFormInline"
>
<!-- 定义一个行布局,设置列间距为 30px -->
<el-row
:gutter=
"30"
>
<!-- 注释掉的用户名称表单项 -->
<!--
<re-col>
<el-form-item
label=
"用户名称"
prop=
"username"
>
<el-input
disabled
v-model=
"newFormInline.username"
/>
</el-form-item>
</re-col>
-->
<!-- 用户昵称表单项 -->
<re-col>
<el-form-item
label=
"用户昵称"
prop=
"nickname"
>
<!-- 输入框组件,绑定用户昵称数据,设置为禁用状态 -->
<el-input
v-model=
"newFormInline.nickname"
disabled
/>
</el-form-item>
</re-col>
<!-- 角色列表表单项 -->
<re-col>
<el-form-item
label=
"角色列表"
prop=
"ids"
>
<!-- 下拉选择框组件,支持多选,绑定所选角色 ID 列表,有占位提示,支持清空选择 -->
<el-select
v-model=
"newFormInline.ids"
placeholder=
"请选择"
...
...
@@ -37,12 +53,14 @@ const newFormInline = ref(props.formInline);
clearable
multiple
>
<!-- 循环渲染角色选项 -->
<el-option
v-for=
"(item, index) in newFormInline.roleOptions"
:key=
"index"
:value=
"item.id"
:label=
"item.name"
>
<!-- 显示角色名称 -->
{{
item
.
name
}}
</el-option>
</el-select>
...
...
src/views/systems/user/index.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 从 Vue 引入 ref 函数,用于创建响应式引用
import
{
ref
}
from
"vue"
;
// 引入部门树组件
import
tree
from
"./tree.vue"
;
// 引入用户管理相关的自定义 hook
import
{
useUser
}
from
"./utils/hook"
;
// 引入自定义表格栏组件
import
{
PureTableBar
}
from
"@/components/RePureTableBar"
;
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 引入上传图标
import
Upload
from
"~icons/ri/upload-line"
;
// 引入角色图标
import
Role
from
"~icons/ri/admin-line"
;
// 引入密码图标
import
Password
from
"~icons/ri/lock-password-line"
;
// 引入更多操作图标
import
More
from
"~icons/ep/more-filled"
;
// 引入删除图标
import
Delete
from
"~icons/ep/delete"
;
// 引入编辑图标
import
EditPen
from
"~icons/ep/edit-pen"
;
// 引入刷新图标
import
Refresh
from
"~icons/ep/refresh"
;
// 引入添加图标
import
AddFill
from
"~icons/ri/add-circle-line"
;
/**
* 定义组件选项
* 设置组件名称为 SystemUser,方便调试和识别
*/
defineOptions
({
name
:
"SystemUser"
});
// 创建部门树组件的引用
const
treeRef
=
ref
();
// 创建表单的引用
const
formRef
=
ref
();
// 创建表格的引用
const
tableRef
=
ref
();
/**
* 从 useUser hook 中解构出所需的状态和方法
* form: 搜索表单数据
* loading: 表格加载状态
* columns: 表格列配置
* dataList: 表格数据列表
* treeData: 部门树数据
* treeLoading: 部门树加载状态
* selectedNum: 表格中选中的记录数量
* pagination: 分页配置
* buttonClass: 按钮样式类
* deviceDetection: 设备检测方法
* onSearch: 执行搜索操作的方法
* resetForm: 重置表单并重新搜索的方法
* onbatchDel: 批量删除操作的方法
* openDialog: 打开新增或编辑用户对话框的方法
* onTreeSelect: 部门树节点选择的处理方法
* handleUpdate: 更新用户信息的方法
* handleDelete: 删除单个用户的方法
* handleUpload: 上传用户头像的方法
* handleReset: 重置用户密码的方法
* handleRole: 分配用户角色的方法
* handleSizeChange: 处理每页显示数量变化的方法
* onSelectionCancel: 取消表格选中的方法
* handleCurrentChange: 处理当前页码变化的方法
* handleSelectionChange: 处理表格选择变化的方法
*/
const
{
form
,
loading
,
...
...
@@ -51,7 +98,9 @@ const {
</
script
>
<
template
>
<!-- 主容器,根据设备检测结果进行不同布局 -->
<div
:class=
"['flex', 'justify-between', deviceDetection() && 'flex-wrap']"
>
<!-- 部门树组件 -->
<tree
ref=
"treeRef"
:class=
"['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']"
...
...
@@ -59,15 +108,19 @@ const {
:treeLoading=
"treeLoading"
@
tree-select=
"onTreeSelect"
/>
<!-- 右侧内容区域,根据设备检测结果调整宽度 -->
<div
:class=
"[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']"
>
<!-- 搜索表单 -->
<el-form
ref=
"formRef"
:inline=
"true"
:model=
"form"
class=
"search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<!-- 用户名称输入项 -->
<el-form-item
label=
"用户名称:"
prop=
"username"
>
<el-input
v-model=
"form.username"
...
...
@@ -76,6 +129,8 @@ const {
class=
"w-[180px]!"
/>
</el-form-item>
<!-- 手机号码输入项 -->
<el-form-item
label=
"手机号码:"
prop=
"mobile"
>
<el-input
v-model=
"form.mobile"
...
...
@@ -84,6 +139,8 @@ const {
class=
"w-[180px]!"
/>
</el-form-item>
<!-- 用户状态选择项 -->
<el-form-item
label=
"状态:"
prop=
"status"
>
<el-select
v-model=
"form.status"
...
...
@@ -95,7 +152,10 @@ const {
<el-option
label=
"已关闭"
value=
"0"
/>
</el-select>
</el-form-item>
<!-- 操作按钮项 -->
<el-form-item>
<!-- 搜索按钮,点击触发搜索操作 -->
<el-button
type=
"primary"
:icon=
"useRenderIcon('ri/search-line')"
...
...
@@ -104,17 +164,20 @@ const {
>
搜索
</el-button>
<!-- 重置按钮,点击触发重置表单并重新搜索 -->
<el-button
:icon=
"useRenderIcon(Refresh)"
@
click=
"resetForm(formRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<!-- 自定义表格栏组件 -->
<PureTableBar
title=
"用户管理(仅演示,操作后不生效)"
:columns=
"columns"
@
refresh=
"onSearch"
>
<!-- 自定义按钮插槽,放置新增用户按钮 -->
<template
#
buttons
>
<el-button
type=
"primary"
...
...
@@ -124,23 +187,29 @@ const {
新增用户
</el-button>
</
template
>
<!-- 表格内容插槽 -->
<
template
v-slot=
"{ size, dynamicColumns }"
>
<!-- 当有选中记录时显示提示信息和批量操作按钮 -->
<div
v-if=
"selectedNum > 0"
v-motion-fade
class=
"bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div
class=
"flex-auto"
>
<!-- 显示已选记录数量 -->
<span
style=
"font-size: var(--el-font-size-base)"
class=
"text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选
{{
selectedNum
}}
项
</span>
<!-- 取消选择按钮,点击触发取消选择操作 -->
<el-button
type=
"primary"
text
@
click=
"onSelectionCancel"
>
取消选择
</el-button>
</div>
<!-- 删除确认弹窗,确认后触发批量删除操作 -->
<el-popconfirm
title=
"是否确认删除?"
@
confirm=
"onbatchDel"
>
<template
#
reference
>
<el-button
type=
"danger"
text
class=
"mr-1!"
>
...
...
@@ -149,6 +218,8 @@ const {
</
template
>
</el-popconfirm>
</div>
<!-- 自定义表格组件 -->
<pure-table
ref=
"tableRef"
row-key=
"id"
...
...
@@ -169,7 +240,9 @@ const {
@
page-size-change=
"handleSizeChange"
@
page-current-change=
"handleCurrentChange"
>
<!-- 操作列插槽,放置针对每条记录的操作按钮 -->
<
template
#
operation=
"{ row }"
>
<!-- 编辑按钮,点击打开编辑用户对话框 -->
<el-button
class=
"reset-margin"
link
...
...
@@ -180,6 +253,8 @@ const {
>
修改
</el-button>
<!-- 删除确认弹窗,确认后触发删除单个用户操作 -->
<!--
<el-popconfirm
:title=
"`是否确认删除用户编号为$
{row.id}的这条数据`"
@confirm="handleDelete(row)"
...
...
@@ -208,6 +283,7 @@ const {
/>
<
template
#
dropdown
>
<el-dropdown-menu>
<!-- 上传头像菜单项,点击触发上传头像操作 -->
<el-dropdown-item>
<el-button
:class=
"buttonClass"
...
...
@@ -220,6 +296,7 @@ const {
上传头像
</el-button>
</el-dropdown-item>
<!-- 重置密码菜单项,点击触发重置密码操作 -->
<el-dropdown-item>
<el-button
:class=
"buttonClass"
...
...
@@ -232,6 +309,7 @@ const {
重置密码
</el-button>
</el-dropdown-item>
<!-- 分配角色菜单项,点击触发分配角色操作 -->
<el-dropdown-item>
<el-button
:class=
"buttonClass"
...
...
@@ -256,18 +334,22 @@ const {
</template>
<
style
lang=
"scss"
scoped
>
/* 设置下拉菜单中图标元素的外边距为 0 */
:deep
(
.el-dropdown-menu__item
i
)
{
margin
:
0
;
}
/* 设置按钮获得焦点时不显示轮廓 */
:deep
(
.el-button
:focus-visible
)
{
outline
:
none
;
}
/* 主内容区域样式,设置外边距 */
.main-content
{
margin
:
24px
24px
0
!
important
;
}
/* 搜索表单样式,设置内部表单项的底部外边距 */
.search-form
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12px
;
...
...
src/views/systems/user/tree.vue
浏览文件 @
2c3c723a
<
script
setup
lang=
"ts"
>
// 引入图标渲染 hook
import
{
useRenderIcon
}
from
"@/components/ReIcon/src/hooks"
;
// 从 Vue 引入响应式 API、监听 API 和获取当前实例的函数
import
{
ref
,
computed
,
watch
,
getCurrentInstance
}
from
"vue"
;
// 引入图标组件
import
Dept
from
"~icons/ri/git-branch-line"
;
// import Reset from "~icons/ri/restart-line";
import
More2Fill
from
"~icons/ri/more-2-fill?width=18&height=18"
;
...
...
@@ -10,29 +13,56 @@ import LocationCompany from "~icons/ep/add-location";
import
ExpandIcon
from
"./svg/expand.svg?component"
;
import
UnExpandIcon
from
"./svg/unexpand.svg?component"
;
/**
* 定义树形结构节点的接口
* 描述了树形结构中每个节点的属性结构
*/
interface
Tree
{
id
:
number
;
name
:
string
;
highlight
?:
boolean
;
children
?:
Tree
[];
id
:
number
;
// 节点唯一标识
name
:
string
;
// 节点显示名称
highlight
?:
boolean
;
// 节点是否高亮,可选属性
children
?:
Tree
[];
// 子节点数组,可选属性
}
/**
* 定义组件的 props
* 接收父组件传递的部门树加载状态和部门树数据
*/
defineProps
({
treeLoading
:
Boolean
,
treeData
:
Array
treeLoading
:
Boolean
,
// 部门树是否正在加载
treeData
:
Array
// 部门树数据数组
});
/**
* 定义组件的自定义事件
* 当部门树节点被选择时触发 `tree-select` 事件
*/
const
emit
=
defineEmits
([
"tree-select"
]);
// 创建树形组件的引用
const
treeRef
=
ref
();
// 记录树形组件是否全部展开的状态
const
isExpand
=
ref
(
true
);
// 记录搜索框的输入值
const
searchValue
=
ref
(
""
);
// 记录节点高亮状态的映射对象
const
highlightMap
=
ref
({});
// 获取当前组件实例
const
{
proxy
}
=
getCurrentInstance
();
/**
* 定义树形组件的默认属性
* 指定树形组件中表示子节点和显示标签的字段名
*/
const
defaultProps
=
{
children
:
"children"
,
label
:
"name"
children
:
"children"
,
// 表示子节点的字段名
label
:
"name"
// 表示显示标签的字段名
};
/**
* 计算按钮的样式类
* 返回一个包含多个 CSS 类名的数组,用于设置按钮的样式
*/
const
buttonClass
=
computed
(()
=>
{
return
[
"h-[20px]!"
,
...
...
@@ -44,13 +74,26 @@ const buttonClass = computed(() => {
];
});
/**
* 过滤树形节点的方法
* 根据搜索框输入值过滤树形节点,包含输入值的节点将显示
* @param value - 搜索框输入的过滤值
* @param data - 当前树形节点的数据
* @returns 如果节点包含过滤值或过滤值为空则返回 true,否则返回 false
*/
const
filterNode
=
(
value
:
string
,
data
:
Tree
)
=>
{
if
(
!
value
)
return
true
;
return
data
.
name
.
includes
(
value
);
if
(
!
value
)
return
true
;
// 过滤值为空,显示所有节点
return
data
.
name
.
includes
(
value
);
// 节点名称包含过滤值则显示
};
/**
* 处理树形节点点击事件
* 切换点击节点的高亮状态,并将选中状态通过事件传递给父组件
* @param value - 点击的节点数据
*/
function
nodeClick
(
value
)
{
const
nodeId
=
value
.
$treeNodeId
;
const
nodeId
=
value
.
$treeNodeId
;
// 获取节点的唯一标识
// 切换当前节点的高亮状态
highlightMap
.
value
[
nodeId
]
=
highlightMap
.
value
[
nodeId
]?.
highlight
?
Object
.
assign
({
id
:
nodeId
},
highlightMap
.
value
[
nodeId
],
{
highlight
:
false
...
...
@@ -58,11 +101,13 @@ function nodeClick(value) {
:
Object
.
assign
({
id
:
nodeId
},
highlightMap
.
value
[
nodeId
],
{
highlight
:
true
});
// 取消其他节点的高亮状态
Object
.
values
(
highlightMap
.
value
).
forEach
((
v
:
Tree
)
=>
{
if
(
v
.
id
!==
nodeId
)
{
v
.
highlight
=
false
;
}
});
// 触发 `tree-select` 事件,传递选中节点的信息
emit
(
"tree-select"
,
highlightMap
.
value
[
nodeId
]?.
highlight
...
...
@@ -71,35 +116,54 @@ function nodeClick(value) {
);
}
/**
* 展开或折叠所有树形节点
* 根据传入的状态设置所有节点的展开状态
* @param status - 展开或折叠状态,true 为展开,false 为折叠
*/
function
toggleRowExpansionAll
(
status
)
{
isExpand
.
value
=
status
;
const
nodes
=
(
proxy
.
$refs
[
"treeRef"
]
as
any
).
store
.
_getAllNodes
();
isExpand
.
value
=
status
;
// 更新展开状态记录
const
nodes
=
(
proxy
.
$refs
[
"treeRef"
]
as
any
).
store
.
_getAllNodes
();
// 获取所有节点
for
(
let
i
=
0
;
i
<
nodes
.
length
;
i
++
)
{
nodes
[
i
].
expanded
=
status
;
nodes
[
i
].
expanded
=
status
;
// 设置节点的展开状态
}
}
/** 重置部门树状态(选中状态、搜索框值、树初始化) */
/**
* 重置部门树状态
* 清空节点高亮状态、搜索框内容,并展开所有节点
*/
function
onTreeReset
()
{
highlightMap
.
value
=
{};
searchValue
.
value
=
""
;
toggleRowExpansionAll
(
true
);
highlightMap
.
value
=
{};
// 清空节点高亮状态
searchValue
.
value
=
""
;
// 清空搜索框内容
toggleRowExpansionAll
(
true
);
// 展开所有节点
}
/**
* 监听搜索框输入值的变化
* 当搜索框输入值变化时,触发树形组件的过滤方法
*/
watch
(
searchValue
,
val
=>
{
treeRef
.
value
!
.
filter
(
val
);
treeRef
.
value
!
.
filter
(
val
);
// 调用树形组件的过滤方法
});
/**
* 将重置部门树状态的方法暴露给父组件
* 允许父组件调用 `onTreeReset` 方法重置部门树状态
*/
defineExpose
({
onTreeReset
});
</
script
>
<
template
>
<!-- 容器元素,根据 `treeLoading` 显示加载状态,设置背景、高度等样式 -->
<div
v-loading=
"treeLoading"
class=
"h-full bg-bg_color overflow-hidden relative"
:style=
"
{ minHeight: `calc(100vh - 141px)` }"
>
<!-- 搜索框和操作按钮容器 -->
<div
class=
"flex items-center h-[34px]"
>
<!-- 搜索框组件,绑定搜索框输入值,设置占位提示和清空按钮 -->
<el-input
v-model=
"searchValue"
class=
"ml-2"
...
...
@@ -107,6 +171,7 @@ defineExpose({ onTreeReset });
placeholder=
"请输入部门名称"
clearable
>
<!-- 搜索框后缀图标,输入为空时显示搜索图标 -->
<template
#
suffix
>
<el-icon
class=
"el-input__icon"
>
<IconifyIconOffline
...
...
@@ -116,10 +181,14 @@ defineExpose({ onTreeReset });
</el-icon>
</
template
>
</el-input>
<!-- 下拉菜单组件,提供展开/折叠和重置状态操作 -->
<el-dropdown
:hide-on-click=
"false"
>
<!-- 下拉菜单触发图标 -->
<More2Fill
class=
"w-[28px] cursor-pointer outline-hidden"
/>
<!-- 下拉菜单内容 -->
<
template
#
dropdown
>
<el-dropdown-menu>
<!-- 展开/折叠菜单项 -->
<el-dropdown-item>
<el-button
:class=
"buttonClass"
...
...
@@ -128,9 +197,11 @@ defineExpose({ onTreeReset });
:icon=
"useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)"
@
click=
"toggleRowExpansionAll(isExpand ? false : true)"
>
<!-- 根据展开状态显示不同的文本 -->
{{
isExpand
?
"折叠全部"
:
"展开全部"
}}
</el-button>
</el-dropdown-item>
<!-- 注释掉的重置状态菜单项 -->
<!--
<el-dropdown-item>
<el-button
:class=
"buttonClass"
...
...
@@ -146,8 +217,11 @@ defineExpose({ onTreeReset });
</
template
>
</el-dropdown>
</div>
<!-- 分割线 -->
<el-divider
/>
<!-- 滚动条组件,设置高度 -->
<el-scrollbar
height=
"calc(90vh - 88px)"
>
<!-- 树形组件,绑定数据、属性和事件 -->
<el-tree
ref=
"treeRef"
:data=
"treeData"
...
...
@@ -159,6 +233,7 @@ defineExpose({ onTreeReset });
:filter-node-method=
"filterNode"
@
node-click=
"nodeClick"
>
<!-- 树形节点默认内容模板 -->
<
template
#
default=
"{ node, data }"
>
<div
:class=
"[
...
...
@@ -181,6 +256,7 @@ defineExpose({ onTreeReset });
: 'transparent'
}"
>
<!-- 根据节点类型显示不同的图标 -->
<IconifyIconOffline
:icon=
"
data.type === 1
...
...
@@ -190,6 +266,7 @@ defineExpose({ onTreeReset });
: Dept
"
/>
<!-- 显示节点名称,超出部分用省略号表示 -->
<span
class=
"w-[120px]! truncate!"
:title=
"node.label"
>
{{
node
.
label
}}
</span>
...
...
@@ -201,10 +278,12 @@ defineExpose({ onTreeReset });
</template>
<
style
lang=
"scss"
scoped
>
/* 设置分割线的外边距为 0 */
:deep
(
.el-divider
)
{
margin
:
0
;
}
/* 设置树形组件节点悬停时的背景色为透明 */
:deep
(
.el-tree
)
{
--el-tree-node-hover-bg-color
:
transparent
;
}
...
...
src/views/systems/user/utils/hook.tsx
浏览文件 @
2c3c723a
...
...
@@ -52,7 +52,8 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
mobile
:
null
,
status
:
null
,
pageNum
:
1
,
pageSize
:
10
pageSize
:
10
,
deptId
:
""
});
const
formRef
=
ref
();
const
ruleFormRef
=
ref
();
...
...
src/views/systems/user/utils/rule.ts
浏览文件 @
2c3c723a
// 从 Vue 中引入 reactive 函数,用于创建响应式对象
import
{
reactive
}
from
"vue"
;
// 从 Element Plus 引入 FormRules 类型,该类型用于定义表单验证规则
import
type
{
FormRules
}
from
"element-plus"
;
// 从 @pureadmin/utils 包中引入 isPhone 和 isEmail 工具函数,分别用于验证手机号和邮箱格式
import
{
isPhone
,
isEmail
}
from
"@pureadmin/utils"
;
/** 自定义表单规则校验 */
export
const
formRules
=
reactive
(
<
FormRules
>
{
nickname
:
[{
required
:
true
,
message
:
"用户昵称为必填项"
,
trigger
:
"blur"
}],
username
:
[{
required
:
true
,
message
:
"用户名称为必填项"
,
trigger
:
"blur"
}],
password
:
[{
required
:
true
,
message
:
"用户密码为必填项"
,
trigger
:
"blur"
}],
/**
* 自定义表单规则校验
* 该常量定义了用户表单中各个字段的验证规则,使用 reactive 函数将其转换为响应式对象,
* 方便在表单验证过程中动态更新规则。
*/
export
const
formRules
=
reactive
<
FormRules
>
({
// 昵称字段验证规则
nickname
:
[
{
required
:
true
,
// 该字段为必填项
message
:
"用户昵称为必填项"
,
// 验证失败时的提示信息
trigger
:
"blur"
// 触发验证的时机为失去焦点时
}
],
// 用户名字段验证规则
username
:
[
{
required
:
true
,
// 该字段为必填项
message
:
"用户名称为必填项"
,
// 验证失败时的提示信息
trigger
:
"blur"
// 触发验证的时机为失去焦点时
}
],
// 密码字段验证规则
password
:
[
{
required
:
true
,
// 该字段为必填项
message
:
"用户密码为必填项"
,
// 验证失败时的提示信息
trigger
:
"blur"
// 触发验证的时机为失去焦点时
}
],
// 手机号字段验证规则
phone
:
[
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 如果手机号为空,则不进行格式验证,直接通过
callback
();
}
else
if
(
!
isPhone
(
value
))
{
// 如果手机号不为空且格式不正确,返回错误信息
callback
(
new
Error
(
"请输入正确的手机号码格式"
));
}
else
{
// 如果手机号不为空且格式正确,通过验证
callback
();
}
},
trigger
:
"blur"
trigger
:
"blur"
// 触发验证的时机为失去焦点时
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
}
],
// 邮箱字段验证规则
email
:
[
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
""
)
{
// 如果邮箱为空,则不进行格式验证,直接通过
callback
();
}
else
if
(
!
isEmail
(
value
))
{
// 如果邮箱不为空且格式不正确,返回错误信息
callback
(
new
Error
(
"请输入正确的邮箱格式"
));
}
else
{
// 如果邮箱不为空且格式正确,通过验证
callback
();
}
},
trigger
:
"blur"
trigger
:
"blur"
// 触发验证的时机为失去焦点时
}
]
});
src/views/systems/user/utils/types.ts
浏览文件 @
2c3c723a
/**
* 定义用户表单单项属性的接口
* 此接口描述了用户表单中单个表单项的属性结构
*/
interface
FormItemProps
{
/** 用户 ID,可选字段,类型为数字 */
id
?:
number
;
/** 用于判断是
`新增`还是`修改`
*/
/** 用于判断是
`新增` 还是 `修改` 操作,类型为字符串
*/
title
:
string
;
/** 上级部门选项数组,每个选项是一个键值对对象,类型为 Record<string, unknown> 数组 */
higherDeptOptions
:
Record
<
string
,
unknown
>
[];
/** 父部门的 ID,类型为数字 */
parentId
:
number
;
/** 用户昵称,类型为字符串 */
nickname
:
string
;
/** 用户名,类型为字符串 */
username
:
string
;
/** 用户密码,类型为字符串 */
password
:
string
;
/** 用户手机号,类型可以是字符串或数字 */
mobile
:
string
|
number
;
/** 用户邮箱,类型为字符串 */
email
:
string
;
/** 用户性别,类型可以是字符串或数字 */
gender
:
string
|
number
;
/** 用户状态,类型为数字 */
status
:
number
;
/** 用户所属部门信息,可选字段,包含部门 ID 和部门名称 */
dept
?:
{
/** 部门 ID,可选字段,类型为数字 */
id
?:
number
;
/** 部门名称,可选字段,类型为字符串 */
name
?:
string
;
};
/** 备注信息,类型为字符串 */
remark
:
string
;
/** 名称,可选字段,类型为字符串 */
name
?:
string
;
/** 部门 ID,可选字段,类型为数字 */
deptId
?:
number
;
/** 用户头像地址,可选字段,类型为字符串 */
avatar
?:
string
;
}
/**
* 定义用户表单属性的接口
* 此接口描述了整个用户表单的属性结构,包含一个 FormItemProps 类型的表单内联对象
*/
interface
FormProps
{
/** 表单内联对象,包含用户表单的各项属性 */
formInline
:
FormItemProps
;
}
/**
* 定义角色表单单项属性的接口
* 此接口描述了角色表单中单个表单项的属性结构
*/
interface
RoleFormItemProps
{
/** 用户名,类型为字符串 */
username
:
string
;
/** 用户昵称,类型为字符串 */
nickname
:
string
;
/** 角色列表 */
/** 角色列表
,类型为任意类型的数组
*/
roleOptions
:
any
[];
/** 选中的角色列表 */
/** 选中的角色列表
,每个元素是一个以数字为键的键值对对象数组
*/
ids
:
Record
<
number
,
unknown
>
[];
}
/**
* 定义角色表单属性的接口
* 此接口描述了整个角色表单的属性结构,包含一个 RoleFormItemProps 类型的表单内联对象
*/
interface
RoleFormProps
{
/** 表单内联对象,包含角色表单的各项属性 */
formInline
:
RoleFormItemProps
;
}
/**
* 导出定义的类型
* 导出 FormItemProps、FormProps、RoleFormItemProps 和 RoleFormProps 接口,
* 方便在其他文件中使用这些类型定义
*/
export
type
{
FormItemProps
,
FormProps
,
RoleFormItemProps
,
RoleFormProps
};
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论