feat: 添加部门管理
This commit is contained in:
67
src/views/account-settings/components/AccountSafe.vue
Normal file
67
src/views/account-settings/components/AccountSafe.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { deviceDetection, hideTextAtIndex } from "@pureadmin/utils";
|
||||
import { useUserInfo } from "../utils/hooks";
|
||||
defineOptions({
|
||||
name: "AccountSafe"
|
||||
});
|
||||
|
||||
const { handleReset, handlePhone, handleEmail, userInfo } = useUserInfo();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">账户安全</h3>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p>账号密码</p>
|
||||
<el-text class="mx-1" type="info" />
|
||||
</div>
|
||||
<el-button type="primary" text @click="handleReset(userInfo)"
|
||||
>修改</el-button
|
||||
>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p>绑定号码</p>
|
||||
<el-text class="mx-1" type="info">
|
||||
已经绑定手机:{{
|
||||
hideTextAtIndex(userInfo.phone, { start: 3, end: 6 })
|
||||
}}
|
||||
</el-text>
|
||||
</div>
|
||||
<el-button type="primary" text @click="handlePhone(userInfo)"
|
||||
>修改</el-button
|
||||
>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p>绑定邮箱</p>
|
||||
<el-text class="mx-1" type="info"
|
||||
>已绑定邮箱:{{
|
||||
hideTextAtIndex(userInfo.email, {
|
||||
start: 3,
|
||||
end: userInfo.email.indexOf("@") - 1
|
||||
})
|
||||
}}
|
||||
</el-text>
|
||||
</div>
|
||||
<el-button type="primary" text @click="handleEmail(userInfo)"
|
||||
>修改</el-button
|
||||
>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-divider--horizontal {
|
||||
border-top: 0.1px var(--el-border-color) var(--el-border-style);
|
||||
}
|
||||
</style>
|
||||
163
src/views/account-settings/components/Profile.vue
Normal file
163
src/views/account-settings/components/Profile.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { message } from "@/utils/message";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import ReCropperPreview from "@/components/ReCropperPreview";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
import uploadLine from "@iconify-icons/ri/upload-line";
|
||||
import { postUploadAvatarAPI, putUpdateBaseUserInfoAPI } from "@/api/user";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { userInfoKey } from "@/utils/auth";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { useUserInfo } from "../utils/hooks";
|
||||
defineOptions({
|
||||
name: "Profile"
|
||||
});
|
||||
|
||||
const { userInfo, getUserInfo } = useUserInfo();
|
||||
const imgSrc = ref("");
|
||||
const cropperBlob = ref();
|
||||
const cropRef = ref();
|
||||
const uploadRef = ref();
|
||||
const isShow = ref(false);
|
||||
const userInfoFormRef = ref<FormInstance>();
|
||||
|
||||
const disabledDate = (time: Date) => {
|
||||
return time.getTime() > Date.now();
|
||||
};
|
||||
|
||||
const onChange = uploadFile => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
imgSrc.value = e.target.result as string;
|
||||
isShow.value = true;
|
||||
};
|
||||
reader.readAsDataURL(uploadFile.raw);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
cropRef.value.hidePopover();
|
||||
uploadRef.value.clearFiles();
|
||||
isShow.value = false;
|
||||
};
|
||||
|
||||
const onCropper = ({ blob }) => (cropperBlob.value = blob);
|
||||
|
||||
const handleSubmitImage = async () => {
|
||||
const res = await postUploadAvatarAPI(userInfo.id, {
|
||||
file: cropperBlob.value
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message("更新头像成功", { type: "success" });
|
||||
userInfo.avatar = `/file/${res.data.id}`;
|
||||
const user = storageLocal().getItem<object>(userInfoKey);
|
||||
storageLocal().setItem(userInfoKey, {
|
||||
...user,
|
||||
avatar: `/file/${res.data.id}`
|
||||
});
|
||||
useUserStoreHook().SET_AVATAR(`/file/${res.data.id}`);
|
||||
handleClose();
|
||||
} else {
|
||||
message("更新头像失败", { type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新信息
|
||||
const onSubmit = async (formEl: FormInstance) => {
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
let updateForm = {
|
||||
name: userInfo.nickname,
|
||||
gender: userInfo.gender
|
||||
};
|
||||
const res = await putUpdateBaseUserInfoAPI(updateForm);
|
||||
if (res.code === 200) {
|
||||
message(res.msg, { type: "success" });
|
||||
await getUserInfo();
|
||||
} else {
|
||||
message(res.msg, { type: "error" });
|
||||
}
|
||||
} else {
|
||||
console.log("error submit!", fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">个人信息</h3>
|
||||
<el-form ref="userInfoFormRef" label-position="top" :model="userInfo">
|
||||
<el-form-item label="头像">
|
||||
<el-avatar :size="80" :src="userInfo.avatar" />
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
accept="image/*"
|
||||
action="#"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="onChange"
|
||||
>
|
||||
<el-button plain class="ml-4">
|
||||
<IconifyIconOffline :icon="uploadLine" />
|
||||
<span class="ml-2">更新头像</span>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="账号" prop="userId">
|
||||
<el-input
|
||||
v-model="userInfo.username"
|
||||
placeholder="请输入账号~"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="姓名"
|
||||
prop="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户姓名~',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
>
|
||||
<el-input v-model="userInfo.nickname" placeholder="请输入姓名~" />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="userInfo.gender" class="ml-4">
|
||||
<el-radio :value="1" size="default">男</el-radio>
|
||||
<el-radio :value="0" size="default">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(userInfoFormRef)">
|
||||
更新信息
|
||||
</el-button>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
v-model="isShow"
|
||||
width="40%"
|
||||
title="编辑头像"
|
||||
destroy-on-close
|
||||
:closeOnClickModal="false"
|
||||
:before-close="handleClose"
|
||||
:fullscreen="deviceDetection()"
|
||||
>
|
||||
<ReCropperPreview ref="cropRef" :imgSrc="imgSrc" @cropper="onCropper" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button bg text @click="handleClose">取消</el-button>
|
||||
<el-button bg text type="primary" @click="handleSubmitImage">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
176
src/views/account-settings/index.vue
Normal file
176
src/views/account-settings/index.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref, onBeforeMount, computed } from "vue";
|
||||
import { ReText } from "@/components/ReText";
|
||||
import Avatar from "@/assets/user.png";
|
||||
import Profile from "./components/Profile.vue";
|
||||
import AccountSafe from "./components/AccountSafe.vue";
|
||||
import { useGlobal, deviceDetection } from "@pureadmin/utils";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
import LaySidebarTopCollapse from "@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue";
|
||||
|
||||
import leftLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import ProfileIcon from "@iconify-icons/ri/user-3-line";
|
||||
import AccountSafeIcon from "@iconify-icons/ri/profile-line";
|
||||
|
||||
defineOptions({
|
||||
name: "AccountSettings"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const isOpen = ref(deviceDetection() ? false : true);
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||
onBeforeMount(() => {
|
||||
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
|
||||
});
|
||||
|
||||
/** 用户名 */
|
||||
const username = computed(() => {
|
||||
return useUserStoreHook()?.username;
|
||||
});
|
||||
/** 用户头像 */
|
||||
const userAvatar = computed(() => {
|
||||
console.log(useUserStoreHook()?.avatar);
|
||||
if (useUserStoreHook()?.avatar) {
|
||||
return useUserStoreHook()?.avatar;
|
||||
} else {
|
||||
}
|
||||
return Avatar;
|
||||
});
|
||||
/**用户账号 */
|
||||
const nickname = computed(() => {
|
||||
return useUserStoreHook()?.nickname;
|
||||
});
|
||||
const panes = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "个人信息",
|
||||
icon: ProfileIcon,
|
||||
component: Profile
|
||||
},
|
||||
{
|
||||
key: "accountSafe",
|
||||
label: "账户安全",
|
||||
icon: AccountSafeIcon,
|
||||
component: AccountSafe
|
||||
}
|
||||
];
|
||||
const witchPane = ref("profile");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container class="h-full">
|
||||
<el-aside
|
||||
v-if="isOpen"
|
||||
class="pure-account-settings overflow-hidden px-2 dark:!bg-[var(--el-bg-color)] border-r-[1px] border-[var(--pure-border-color)]"
|
||||
:width="deviceDetection() ? '180px' : '240px'"
|
||||
>
|
||||
<el-menu :default-active="witchPane" class="pure-account-settings-menu">
|
||||
<el-menu-item
|
||||
class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]"
|
||||
@click="router.go(-1)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<IconifyIconOffline :icon="leftLine" />
|
||||
<span class="ml-2">返回</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
<div class="flex items-center ml-8 mt-4 mb-4">
|
||||
<el-avatar :size="48" :src="userAvatar" />
|
||||
<div class="ml-4 flex flex-col max-w-[130px]">
|
||||
<ReText class="font-bold !self-baseline">
|
||||
{{ username }}
|
||||
</ReText>
|
||||
<ReText class="!self-baseline" type="info">
|
||||
{{ nickname }}
|
||||
</ReText>
|
||||
</div>
|
||||
</div>
|
||||
<el-menu-item
|
||||
v-for="item in panes"
|
||||
:key="item.key"
|
||||
:index="item.key"
|
||||
@click="
|
||||
() => {
|
||||
witchPane = item.key;
|
||||
if (deviceDetection()) {
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="flex items-center z-10">
|
||||
<el-icon><IconifyIconOffline :icon="item.icon" /></el-icon>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<LaySidebarTopCollapse
|
||||
v-if="deviceDetection()"
|
||||
class="px-0"
|
||||
:is-active="isOpen"
|
||||
@toggleClick="isOpen = !isOpen"
|
||||
/>
|
||||
<component
|
||||
:is="panes.find(item => item.key === witchPane).component"
|
||||
:class="[!deviceDetection() && 'ml-[120px]']"
|
||||
/>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.pure-account-settings {
|
||||
background: var(--pure-theme-menu-bg) !important;
|
||||
}
|
||||
|
||||
.pure-account-settings-menu {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
.el-menu-item {
|
||||
height: 48px !important;
|
||||
color: var(--pure-theme-menu-text);
|
||||
background-color: transparent !important;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: #fff !important;
|
||||
|
||||
&:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
inset: 0 8px;
|
||||
margin: 4px 0;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
body[layout] {
|
||||
.el-menu--vertical .is-active {
|
||||
color: #fff !important;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
349
src/views/account-settings/utils/hooks.tsx
Normal file
349
src/views/account-settings/utils/hooks.tsx
Normal file
@@ -0,0 +1,349 @@
|
||||
import { message } from "@/utils/message";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { reactive, ref, onMounted, watch } from "vue";
|
||||
import { ElForm, ElFormItem, ElInput, ElProgress } from "element-plus";
|
||||
import type { UserInfo } from "types/user";
|
||||
import { getUserInfoAPI } from "@/api/login";
|
||||
import {
|
||||
putUpdateEmailAPI,
|
||||
putUpdatePasswordAPI,
|
||||
putUpdatePhoneAPI
|
||||
} from "@/api/user";
|
||||
import { isAllEmpty, isEmail, isPhone, storageLocal } from "@pureadmin/utils";
|
||||
import { zxcvbn } from "@zxcvbn-ts/core";
|
||||
import { setUserInfo, userInfoKey } from "@/utils/auth";
|
||||
|
||||
export const useUserInfo = () => {
|
||||
/** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */
|
||||
const REGEXP_PWD =
|
||||
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
|
||||
|
||||
const pwdProgress = [
|
||||
{ color: "#e74242", text: "非常弱" },
|
||||
{ color: "#EFBD47", text: "弱" },
|
||||
{ color: "#ffa500", text: "一般" },
|
||||
{ color: "#1bbf1b", text: "强" },
|
||||
{ color: "#008000", text: "非常强" }
|
||||
];
|
||||
// 当前密码强度(0-4)
|
||||
const curScore = ref();
|
||||
const ruleFormRef = ref();
|
||||
/**密码表单 */
|
||||
const passwordForm = reactive({
|
||||
oldPassword: "",
|
||||
newPassword: ""
|
||||
});
|
||||
/**联系方式表单 */
|
||||
const phoneForm = reactive({
|
||||
password: "",
|
||||
phone: ""
|
||||
});
|
||||
/**邮箱*/
|
||||
const emailForm = reactive({
|
||||
password: "",
|
||||
email: ""
|
||||
});
|
||||
const userInfo = reactive<UserInfo>({
|
||||
id: "",
|
||||
username: "",
|
||||
gender: 1,
|
||||
avatar: "",
|
||||
email: "",
|
||||
nickname: "",
|
||||
phone: "",
|
||||
status: 0,
|
||||
create_time: "",
|
||||
update_time: "",
|
||||
roles: [],
|
||||
permissions: []
|
||||
});
|
||||
/**获取个人信息 */
|
||||
const getUserInfo = async () => {
|
||||
const res = await getUserInfoAPI();
|
||||
if (res.success) {
|
||||
Object.assign(userInfo, res.data);
|
||||
const user = storageLocal().getItem<object>(userInfoKey);
|
||||
storageLocal().setItem(userInfoKey, {
|
||||
...user,
|
||||
avatar: res.data.avatar,
|
||||
nickname: res.data.nickname
|
||||
});
|
||||
setUserInfo(res.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**组件挂载执行 */
|
||||
onMounted(async () => {
|
||||
await getUserInfo();
|
||||
});
|
||||
watch(
|
||||
passwordForm,
|
||||
({ newPassword }) =>
|
||||
(curScore.value = isAllEmpty(newPassword)
|
||||
? -1
|
||||
: zxcvbn(newPassword).score)
|
||||
);
|
||||
/**密码校验规则 */
|
||||
const passwordRule = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入新密码~"));
|
||||
} else if (!REGEXP_PWD.test(value)) {
|
||||
callback(
|
||||
new Error("密码格式应为8-18位数字、字母、符号的任意两种组合~")
|
||||
);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
];
|
||||
/**手机好校验规则 */
|
||||
const phoneRule = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入手机号码~"));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error("请输入正确的手机号码格式~"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
];
|
||||
/**邮箱校验规则 */
|
||||
const emailRule = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入邮箱~"));
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error("请输入正确的邮箱格式~"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
];
|
||||
/** 重置密码 */
|
||||
const handleReset = (row: UserInfo) => {
|
||||
addDialog({
|
||||
title: `重置 ${row.username}--${row.nickname} 的密码`,
|
||||
width: "30%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => (
|
||||
<div>
|
||||
<ElForm ref={ruleFormRef} model={passwordForm}>
|
||||
<ElFormItem
|
||||
prop="oldPassword"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入旧密码~",
|
||||
trigger: "blur"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={passwordForm.oldPassword}
|
||||
placeholder="请输入旧密码~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="newPassword" rules={passwordRule}>
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={passwordForm.newPassword}
|
||||
placeholder="请输入新密码~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<div class="mt-4 flex">
|
||||
{pwdProgress.map(({ color, text }, idx) => (
|
||||
<div
|
||||
class="w-[19vw]"
|
||||
style={{ marginLeft: idx !== 0 ? "4px" : 0 }}
|
||||
>
|
||||
<ElProgress
|
||||
striped
|
||||
striped-flow
|
||||
duration={curScore.value === idx ? 6 : 0}
|
||||
percentage={curScore.value >= idx ? 100 : 0}
|
||||
color={color}
|
||||
stroke-width={10}
|
||||
show-text={false}
|
||||
/>
|
||||
<p
|
||||
class="text-center"
|
||||
style={{ color: curScore.value === idx ? color : "" }}
|
||||
>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
closeCallBack: () => passwordForm,
|
||||
beforeSure: done => {
|
||||
ruleFormRef.value.validate(async (valid: any) => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
const res = await putUpdatePasswordAPI(passwordForm);
|
||||
if (res.code === 200) {
|
||||
done();
|
||||
message(res.msg, {
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
message(res.msg, {
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**更新手机号 */
|
||||
const handlePhone = (row: UserInfo) => {
|
||||
phoneForm.phone = row.phone;
|
||||
addDialog({
|
||||
title: `更新 ${row.username}--${row.nickname} 的联系号码`,
|
||||
width: "30%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => (
|
||||
<div>
|
||||
<ElForm ref={ruleFormRef} model={phoneForm}>
|
||||
<ElFormItem
|
||||
prop="password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码~",
|
||||
trigger: "blur"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={phoneForm.password}
|
||||
placeholder="请输入密码~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="phone" rules={phoneRule}>
|
||||
<ElInput
|
||||
clearable
|
||||
type="text"
|
||||
v-model={phoneForm.phone}
|
||||
placeholder="请输入手机号~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
),
|
||||
closeCallBack: () => phoneForm,
|
||||
beforeSure: done => {
|
||||
ruleFormRef.value.validate(async (valid: any) => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
const res = await putUpdatePhoneAPI(phoneForm);
|
||||
if (res.code === 200) {
|
||||
done();
|
||||
message(res.msg, {
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
message(res.msg, {
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
/**更新邮箱 */
|
||||
const handleEmail = (row: UserInfo) => {
|
||||
emailForm.email = row.email;
|
||||
addDialog({
|
||||
title: `更新 ${row.username}--${row.nickname} 的联系邮箱`,
|
||||
width: "30%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => (
|
||||
<div>
|
||||
<ElForm ref={ruleFormRef} model={emailForm}>
|
||||
<ElFormItem
|
||||
prop="password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码~",
|
||||
trigger: "blur"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<ElInput
|
||||
clearable
|
||||
show-password
|
||||
type="password"
|
||||
v-model={emailForm.password}
|
||||
placeholder="请输入密码~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="email" rules={emailRule}>
|
||||
<ElInput
|
||||
clearable
|
||||
type="text"
|
||||
v-model={emailForm.email}
|
||||
placeholder="请输入联系邮箱~"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
),
|
||||
closeCallBack: () => emailForm,
|
||||
beforeSure: done => {
|
||||
ruleFormRef.value.validate(async (valid: any) => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
const res = await putUpdateEmailAPI(emailForm);
|
||||
if (res.code === 200) {
|
||||
done();
|
||||
message(res.msg, {
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
message(res.msg, {
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
getUserInfo,
|
||||
handleReset,
|
||||
handlePhone,
|
||||
handleEmail
|
||||
};
|
||||
};
|
||||
@@ -27,7 +27,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
if (valid) {
|
||||
// 模拟登录请求,需根据实际开发进行修改
|
||||
setTimeout(() => {
|
||||
message(transformI18n($t("login.pureLoginSuccess")), {
|
||||
message(transformI18n($t("login:LoginSuccess")), {
|
||||
type: "success"
|
||||
});
|
||||
loading.value = false;
|
||||
@@ -51,7 +51,7 @@ function onBack() {
|
||||
<el-input
|
||||
v-model="ruleForm.phone"
|
||||
clearable
|
||||
:placeholder="t('login.purePhone')"
|
||||
:placeholder="t('login:Phone')"
|
||||
:prefix-icon="useRenderIcon(Iphone)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -63,7 +63,7 @@ function onBack() {
|
||||
<el-input
|
||||
v-model="ruleForm.verifyCode"
|
||||
clearable
|
||||
:placeholder="t('login.pureSmsVerifyCode')"
|
||||
:placeholder="t('login:SmsVerifyCode')"
|
||||
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
|
||||
/>
|
||||
<el-button
|
||||
@@ -73,8 +73,8 @@ function onBack() {
|
||||
>
|
||||
{{
|
||||
text.length > 0
|
||||
? text + t("login.pureInfo")
|
||||
: t("login.pureGetVerifyCode")
|
||||
? text + t("login:Info")
|
||||
: t("login:GetVerifyCode")
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -90,7 +90,7 @@ function onBack() {
|
||||
:loading="loading"
|
||||
@click="onLogin(ruleFormRef)"
|
||||
>
|
||||
{{ t("login.pureLogin") }}
|
||||
{{ t("login:Login") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
@@ -98,7 +98,7 @@ function onBack() {
|
||||
<Motion :delay="200">
|
||||
<el-form-item>
|
||||
<el-button class="w-full" size="default" @click="onBack">
|
||||
{{ t("login.pureBack") }}
|
||||
{{ t("login:Back") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
@@ -9,11 +9,11 @@ const { t } = useI18n();
|
||||
|
||||
<template>
|
||||
<Motion class="-mt-2 -mb-2">
|
||||
<ReQrcode :text="t('login.pureTest')" />
|
||||
<ReQrcode :text="t('login:Test')" />
|
||||
</Motion>
|
||||
<Motion :delay="100">
|
||||
<el-divider>
|
||||
<p class="text-gray-500 text-xs">{{ t("login.pureTip") }}</p>
|
||||
<p class="text-gray-500 text-xs">{{ t("login:Tip") }}</p>
|
||||
</el-divider>
|
||||
</Motion>
|
||||
<Motion :delay="150">
|
||||
@@ -21,7 +21,7 @@ const { t } = useI18n();
|
||||
class="w-full mt-4"
|
||||
@click="useUserStoreHook().SET_CURRENTPAGE(0)"
|
||||
>
|
||||
{{ t("login.pureBack") }}
|
||||
{{ t("login:Back") }}
|
||||
</el-button>
|
||||
</Motion>
|
||||
</template>
|
||||
|
||||
@@ -3,37 +3,70 @@ import { useI18n } from "vue-i18n";
|
||||
import { ref, reactive } from "vue";
|
||||
import Motion from "../utils/motion";
|
||||
import { message } from "@/utils/message";
|
||||
import { updateRules } from "../utils/rule";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { registerRules } from "../utils/rule";
|
||||
import type { FormInstance, FormItemProp } from "element-plus";
|
||||
import { useVerifyCode } from "../utils/verifyCode";
|
||||
import { clone } from "@pureadmin/utils";
|
||||
import { $t, transformI18n } from "@/plugins/i18n";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import Lock from "@iconify-icons/ri/lock-fill";
|
||||
import Iphone from "@iconify-icons/ep/iphone";
|
||||
import User from "@iconify-icons/ri/user-3-fill";
|
||||
import UserName from "@iconify-icons/ri/user-4-line";
|
||||
import Mail from "@iconify-icons/ri/mail-open-line";
|
||||
import { postGetCodeAPI, postRegisterAPI } from "@/api/login";
|
||||
|
||||
const { t } = useI18n();
|
||||
const checked = ref(false);
|
||||
const loading = ref(false);
|
||||
const timer = ref(null);
|
||||
const ruleForm = reactive({
|
||||
username: "",
|
||||
phone: "",
|
||||
verifyCode: "",
|
||||
gender: 1,
|
||||
email: "",
|
||||
nickname: "",
|
||||
department_id: "",
|
||||
code: "",
|
||||
password: "",
|
||||
repeatPassword: ""
|
||||
});
|
||||
/**当前选项 */
|
||||
const currentOption = ref<number>(1);
|
||||
/**
|
||||
* 点击返回
|
||||
*/
|
||||
const clickBack = () => {
|
||||
if (currentOption.value >= 1) {
|
||||
currentOption.value--;
|
||||
} else {
|
||||
currentOption.value = 1;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 点击下一步
|
||||
*/
|
||||
const clickNext = async () => {
|
||||
await ruleFormRef.value.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
if (currentOption.value <= 2) {
|
||||
currentOption.value++;
|
||||
} else {
|
||||
currentOption.value = 2;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const { isDisabled, text } = useVerifyCode();
|
||||
const repeatPasswordRule = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePassWordSureReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordSureReg"))));
|
||||
} else if (ruleForm.password !== value) {
|
||||
callback(
|
||||
new Error(transformI18n($t("login.purePassWordDifferentReg")))
|
||||
);
|
||||
callback(new Error(transformI18n($t("login:PassWordDifferentReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -45,19 +78,33 @@ const repeatPasswordRule = [
|
||||
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
await formEl.validate(valid => {
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
if (checked.value) {
|
||||
// 模拟请求,需根据实际开发进行修改
|
||||
setTimeout(() => {
|
||||
message(transformI18n($t("login.pureRegisterSuccess")), {
|
||||
const res = await postRegisterAPI({
|
||||
username: ruleForm.username,
|
||||
password: ruleForm.password,
|
||||
gender: ruleForm.gender,
|
||||
email: ruleForm.email,
|
||||
nickname: ruleForm.nickname,
|
||||
phone: ruleForm.phone,
|
||||
code: ruleForm.code,
|
||||
department_id: ruleForm.department_id
|
||||
});
|
||||
if (res.code === 200) {
|
||||
message(transformI18n($t("login:RegisterSuccess")), {
|
||||
type: "success"
|
||||
});
|
||||
loading.value = false;
|
||||
}, 2000);
|
||||
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||
} else {
|
||||
message(res.msg, {
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
loading.value = false;
|
||||
message(transformI18n($t("login.pureTickPrivacy")), {
|
||||
message(transformI18n($t("login:TickPrivacy")), {
|
||||
type: "warning"
|
||||
});
|
||||
}
|
||||
@@ -66,9 +113,42 @@ const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const start = async (
|
||||
formEl: FormInstance | undefined,
|
||||
props: FormItemProp,
|
||||
time = 120
|
||||
) => {
|
||||
if (!formEl) return;
|
||||
const initTime = clone(time, true);
|
||||
await formEl.validateField(props, async isValid => {
|
||||
if (isValid) {
|
||||
const res = await postGetCodeAPI({
|
||||
username: ruleForm.username,
|
||||
title: "注册",
|
||||
mail: ruleForm.email
|
||||
});
|
||||
if (res.code === 200) {
|
||||
clearInterval(timer.value);
|
||||
isDisabled.value = true;
|
||||
text.value = `${time}`;
|
||||
timer.value = setInterval(() => {
|
||||
if (time > 0) {
|
||||
time -= 1;
|
||||
text.value = `${time}`;
|
||||
} else {
|
||||
text.value = "";
|
||||
isDisabled.value = false;
|
||||
clearInterval(timer.value);
|
||||
time = initTime;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
message(res.msg, { type: "error" });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
function onBack() {
|
||||
useVerifyCode().end();
|
||||
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||
}
|
||||
</script>
|
||||
@@ -77,15 +157,15 @@ function onBack() {
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="ruleForm"
|
||||
:rules="updateRules"
|
||||
:rules="registerRules"
|
||||
size="large"
|
||||
>
|
||||
<Motion>
|
||||
<Motion v-if="currentOption === 1">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: transformI18n($t('login.pureUsernameReg')),
|
||||
message: transformI18n($t('login:UsernameReg')),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
@@ -94,101 +174,215 @@ function onBack() {
|
||||
<el-input
|
||||
v-model="ruleForm.username"
|
||||
clearable
|
||||
:placeholder="t('login.pureUsername')"
|
||||
:prefix-icon="useRenderIcon(User)"
|
||||
:placeholder="t('login:Username')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(UserName, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion v-if="currentOption === 1">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: transformI18n($t('login:NicknameReg')),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
prop="nickname"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm.nickname"
|
||||
clearable
|
||||
:placeholder="t('login:Nickname')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(User, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="100">
|
||||
<Motion v-if="currentOption === 1" :delay="100">
|
||||
<el-form-item prop="phone">
|
||||
<el-input
|
||||
v-model="ruleForm.phone"
|
||||
clearable
|
||||
:placeholder="t('login.purePhone')"
|
||||
:prefix-icon="useRenderIcon(Iphone)"
|
||||
:placeholder="t('login:Phone')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Iphone, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion v-if="currentOption === 2" :delay="100">
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
v-model="ruleForm.email"
|
||||
clearable
|
||||
:placeholder="t('login:Email')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Mail, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="150">
|
||||
<el-form-item prop="verifyCode">
|
||||
<Motion v-if="currentOption === 2" :delay="150">
|
||||
<el-form-item prop="code">
|
||||
<div class="w-full flex justify-between">
|
||||
<el-input
|
||||
v-model="ruleForm.verifyCode"
|
||||
v-model="ruleForm.code"
|
||||
clearable
|
||||
:placeholder="t('login.pureSmsVerifyCode')"
|
||||
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
|
||||
:placeholder="t('login:EmailVerifyCode')"
|
||||
:prefix-icon="
|
||||
useRenderIcon('ri:shield-keyhole-line', {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
<el-button
|
||||
:disabled="isDisabled"
|
||||
class="ml-2"
|
||||
@click="useVerifyCode().start(ruleFormRef, 'phone')"
|
||||
@click="start(ruleFormRef, 'email')"
|
||||
>
|
||||
{{
|
||||
text.length > 0
|
||||
? text + t("login.pureInfo")
|
||||
: t("login.pureGetVerifyCode")
|
||||
? text + t("login:Info")
|
||||
: t("login:GetVerifyCode")
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="200">
|
||||
<Motion v-if="currentOption === 2" :delay="200">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="ruleForm.password"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.purePassword')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
:placeholder="t('login:Password')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Lock, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="250">
|
||||
<Motion v-if="currentOption === 2" :delay="250">
|
||||
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
|
||||
<el-input
|
||||
v-model="ruleForm.repeatPassword"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.pureSure')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
:placeholder="t('login:Sure')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Lock, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion v-if="currentOption === 1" :delay="100">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="ruleForm.gender" class="ml-4">
|
||||
<el-radio :value="1" size="default">男</el-radio>
|
||||
<el-radio :value="0" size="default">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="300">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="checked">
|
||||
{{ t("login.pureReadAccept") }}
|
||||
{{ t("login:ReadAccept") }}
|
||||
</el-checkbox>
|
||||
<el-button link type="primary">
|
||||
{{ t("login.purePrivacyPolicy") }}
|
||||
{{ t("login:PrivacyPolicy") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="350">
|
||||
<Motion v-if="currentOption !== 1" :delay="350">
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="w-full"
|
||||
size="default"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="onUpdate(ruleFormRef)"
|
||||
>
|
||||
{{ t("login.pureDefinite") }}
|
||||
</el-button>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button
|
||||
class="w-[75%]"
|
||||
size="default"
|
||||
round
|
||||
:loading="loading"
|
||||
@click="clickBack"
|
||||
>
|
||||
{{ t("login:Laststep") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion v-if="currentOption !== 2" :delay="350">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button
|
||||
class="w-[75%]"
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
:loading="loading"
|
||||
@click="clickNext"
|
||||
>
|
||||
{{ t("login:Nextstep") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion v-if="currentOption === 2" :delay="350">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button
|
||||
class="w-[75%]"
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
:loading="loading"
|
||||
@click="onUpdate(ruleFormRef)"
|
||||
>
|
||||
{{ t("login:Register") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="400">
|
||||
<el-form-item>
|
||||
<el-button class="w-full" size="default" @click="onBack">
|
||||
{{ t("login.pureBack") }}
|
||||
</el-button>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button class="w-[75%]" round size="default" @click="onBack">
|
||||
{{ t("login:Back") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
</el-form>
|
||||
|
||||
@@ -4,19 +4,24 @@ import { ref, reactive } from "vue";
|
||||
import Motion from "../utils/motion";
|
||||
import { message } from "@/utils/message";
|
||||
import { updateRules } from "../utils/rule";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import type { FormInstance, FormItemProp } from "element-plus";
|
||||
import { useVerifyCode } from "../utils/verifyCode";
|
||||
import { $t, transformI18n } from "@/plugins/i18n";
|
||||
import { clone } from "@pureadmin/utils";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { postGetCodeAPI, postResetPasswordAPI } from "@/api/login";
|
||||
import Lock from "@iconify-icons/ri/lock-fill";
|
||||
import Iphone from "@iconify-icons/ep/iphone";
|
||||
import UserName from "@iconify-icons/ri/user-4-line";
|
||||
import Mail from "@iconify-icons/ri/mail-open-line";
|
||||
|
||||
const { t } = useI18n();
|
||||
const loading = ref(false);
|
||||
const timer = ref(null);
|
||||
const ruleForm = reactive({
|
||||
phone: "",
|
||||
verifyCode: "",
|
||||
username: "",
|
||||
email: "",
|
||||
code: "",
|
||||
password: "",
|
||||
repeatPassword: ""
|
||||
});
|
||||
@@ -26,11 +31,9 @@ const repeatPasswordRule = [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePassWordSureReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordSureReg"))));
|
||||
} else if (ruleForm.password !== value) {
|
||||
callback(
|
||||
new Error(transformI18n($t("login.purePassWordDifferentReg")))
|
||||
);
|
||||
callback(new Error(transformI18n($t("login:PassWordDifferentReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -42,23 +45,67 @@ const repeatPasswordRule = [
|
||||
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
await formEl.validate(valid => {
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
// 模拟请求,需根据实际开发进行修改
|
||||
setTimeout(() => {
|
||||
message(transformI18n($t("login.purePassWordUpdateReg")), {
|
||||
const res = await postResetPasswordAPI({
|
||||
username: ruleForm.username,
|
||||
password: ruleForm.password,
|
||||
code: ruleForm.code,
|
||||
mail: ruleForm.email
|
||||
});
|
||||
if (res.success) {
|
||||
message(res.msg, {
|
||||
type: "success"
|
||||
});
|
||||
loading.value = false;
|
||||
}, 2000);
|
||||
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||
} else {
|
||||
message(res.msg, {
|
||||
type: "error"
|
||||
});
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const start = async (
|
||||
formEl: FormInstance | undefined,
|
||||
props: FormItemProp,
|
||||
time = 120
|
||||
) => {
|
||||
if (!formEl) return;
|
||||
const initTime = clone(time, true);
|
||||
await formEl.validateField(props, async isValid => {
|
||||
if (isValid) {
|
||||
const res = await postGetCodeAPI({
|
||||
username: ruleForm.username,
|
||||
title: "重置",
|
||||
mail: ruleForm.email
|
||||
});
|
||||
if (res.code === 200) {
|
||||
clearInterval(timer.value);
|
||||
isDisabled.value = true;
|
||||
text.value = `${time}`;
|
||||
timer.value = setInterval(() => {
|
||||
if (time > 0) {
|
||||
time -= 1;
|
||||
text.value = `${time}`;
|
||||
} else {
|
||||
text.value = "";
|
||||
isDisabled.value = false;
|
||||
clearInterval(timer.value);
|
||||
time = initTime;
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
message(res.msg, { type: "error" });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
function onBack() {
|
||||
useVerifyCode().end();
|
||||
useUserStoreHook().SET_CURRENTPAGE(0);
|
||||
}
|
||||
</script>
|
||||
@@ -71,34 +118,70 @@ function onBack() {
|
||||
size="large"
|
||||
>
|
||||
<Motion>
|
||||
<el-form-item prop="phone">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: transformI18n($t('login:UsernameReg')),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
prop="username"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm.phone"
|
||||
v-model="ruleForm.username"
|
||||
clearable
|
||||
:placeholder="t('login.purePhone')"
|
||||
:prefix-icon="useRenderIcon(Iphone)"
|
||||
:placeholder="t('login:Username')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(UserName, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="100">
|
||||
<el-form-item prop="verifyCode">
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
v-model="ruleForm.email"
|
||||
clearable
|
||||
:placeholder="t('login:Email')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Mail, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
<Motion :delay="100">
|
||||
<el-form-item prop="code">
|
||||
<div class="w-full flex justify-between">
|
||||
<el-input
|
||||
v-model="ruleForm.verifyCode"
|
||||
v-model="ruleForm.code"
|
||||
clearable
|
||||
:placeholder="t('login.pureSmsVerifyCode')"
|
||||
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
|
||||
:placeholder="t('login:EmailVerifyCode')"
|
||||
:prefix-icon="
|
||||
useRenderIcon('ri:shield-keyhole-line', {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
<el-button
|
||||
:disabled="isDisabled"
|
||||
class="ml-2"
|
||||
@click="useVerifyCode().start(ruleFormRef, 'phone')"
|
||||
@click="start(ruleFormRef, 'email')"
|
||||
>
|
||||
{{
|
||||
text.length > 0
|
||||
? text + t("login.pureInfo")
|
||||
: t("login.pureGetVerifyCode")
|
||||
? text + t("login:Info")
|
||||
: t("login:GetVerifyCode")
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -111,8 +194,14 @@ function onBack() {
|
||||
v-model="ruleForm.password"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.purePassword')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
:placeholder="t('login:Password')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Lock, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
@@ -123,31 +212,42 @@ function onBack() {
|
||||
v-model="ruleForm.repeatPassword"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.pureSure')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
:placeholder="t('login:Sure')"
|
||||
:prefix-icon="
|
||||
useRenderIcon(Lock, {
|
||||
color: '#4380FF',
|
||||
width: 32,
|
||||
height: 32
|
||||
})
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="250">
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="w-full"
|
||||
size="default"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="onUpdate(ruleFormRef)"
|
||||
>
|
||||
{{ t("login.pureDefinite") }}
|
||||
</el-button>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button
|
||||
class="w-[75%]"
|
||||
size="default"
|
||||
type="primary"
|
||||
round
|
||||
:loading="loading"
|
||||
@click="onUpdate(ruleFormRef)"
|
||||
>
|
||||
{{ t("login:Definite") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="300">
|
||||
<el-form-item>
|
||||
<el-button class="w-full" size="default" @click="onBack">
|
||||
{{ t("login.pureBack") }}
|
||||
</el-button>
|
||||
<div class="w-full flex justify-center items-center">
|
||||
<el-button class="w-[75%]" size="default" round @click="onBack">
|
||||
{{ t("login:Back") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
</el-form>
|
||||
|
||||
@@ -31,7 +31,9 @@ import Lock from "@iconify-icons/ri/lock-fill";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import User from "@iconify-icons/ri/user-3-fill";
|
||||
import Info from "@iconify-icons/ri/information-line";
|
||||
import { GetCaptchaAPI } from "@/api/user";
|
||||
import { GetCaptchaAPI } from "@/api/login";
|
||||
import { getUserInfoAPI } from "@/api/login";
|
||||
import { setUserInfo } from "@/utils/auth";
|
||||
|
||||
defineOptions({
|
||||
name: "Login"
|
||||
@@ -79,17 +81,21 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
if (res.code === 200) {
|
||||
useUserStoreHook().SET_ACCESSTOKEN(res.data.accessToken);
|
||||
// 获取后端路由
|
||||
return initRouter().then(() => {
|
||||
return initRouter().then(async () => {
|
||||
disabled.value = true;
|
||||
const res = await getUserInfoAPI();
|
||||
if (res.success) {
|
||||
setUserInfo(res.data);
|
||||
}
|
||||
router
|
||||
.push(getTopMenu(true).path)
|
||||
.then(() => {
|
||||
message(t("login.pureLoginSuccess"), { type: "success" });
|
||||
message(t("login:LoginSuccess"), { type: "success" });
|
||||
})
|
||||
.finally(() => (disabled.value = false));
|
||||
});
|
||||
} else {
|
||||
message(t("login.pureLoginFail"), { type: "error" });
|
||||
message(t("login:LoginFail"), { type: "error" });
|
||||
await getImgCode();
|
||||
}
|
||||
})
|
||||
@@ -107,9 +113,9 @@ const immediateDebounce: any = debounce(
|
||||
/**获取验证码 */
|
||||
const getImgCode = async () => {
|
||||
const res = await GetCaptchaAPI();
|
||||
if (res.code === 200) {
|
||||
imgCode.value = res.data.image;
|
||||
imgId.value = res.data.captchaId;
|
||||
if (res.success) {
|
||||
imgCode.value = res.data.captcha;
|
||||
imgId.value = res.data.uuid;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,9 +128,6 @@ useEventListener(document, "keydown", ({ code }) => {
|
||||
immediateDebounce(ruleFormRef.value);
|
||||
});
|
||||
|
||||
// watch(imgCode, value => {
|
||||
// useUserStoreHook().SET_VERIFYCODE(value);
|
||||
// });
|
||||
watch(checked, bool => {
|
||||
useUserStoreHook().SET_ISREMEMBERED(bool);
|
||||
});
|
||||
@@ -208,7 +211,7 @@ onMounted(async () => {
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: transformI18n($t('login.pureUsernameReg')),
|
||||
message: transformI18n($t('login:UsernameReg')),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
@@ -217,7 +220,7 @@ onMounted(async () => {
|
||||
<el-input
|
||||
v-model="ruleForm.username"
|
||||
clearable
|
||||
:placeholder="t('login.pureUsername')"
|
||||
:placeholder="t('login:Username')"
|
||||
:prefix-icon="useRenderIcon(User)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -229,7 +232,7 @@ onMounted(async () => {
|
||||
v-model="ruleForm.password"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.purePassword')"
|
||||
:placeholder="t('login:Password')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -240,7 +243,7 @@ onMounted(async () => {
|
||||
<el-input
|
||||
v-model="ruleForm.verifyCode"
|
||||
clearable
|
||||
:placeholder="t('login.pureVerifyCode')"
|
||||
:placeholder="t('login:VerifyCode')"
|
||||
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
|
||||
>
|
||||
<template v-slot:append>
|
||||
@@ -273,10 +276,10 @@ onMounted(async () => {
|
||||
<option value="7">7</option>
|
||||
<option value="30">30</option>
|
||||
</select>
|
||||
{{ t("login.pureRemember") }}
|
||||
{{ t("login:Remember") }}
|
||||
<IconifyIconOffline
|
||||
v-tippy="{
|
||||
content: t('login.pureRememberInfo'),
|
||||
content: t('login:RememberInfo'),
|
||||
placement: 'top'
|
||||
}"
|
||||
:icon="Info"
|
||||
@@ -289,7 +292,7 @@ onMounted(async () => {
|
||||
type="primary"
|
||||
@click="useUserStoreHook().SET_CURRENTPAGE(4)"
|
||||
>
|
||||
{{ t("login.pureForget") }}
|
||||
{{ t("login:Forget") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button
|
||||
@@ -300,7 +303,7 @@ onMounted(async () => {
|
||||
:disabled="disabled"
|
||||
@click="onLogin(ruleFormRef)"
|
||||
>
|
||||
{{ t("login.pureLogin") }}
|
||||
{{ t("login:Login") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
@@ -2,31 +2,31 @@ import { $t } from "@/plugins/i18n";
|
||||
|
||||
const operates = [
|
||||
{
|
||||
title: $t("login.purePhoneLogin")
|
||||
title: $t("login:PhoneLogin")
|
||||
},
|
||||
{
|
||||
title: $t("login.pureQRCodeLogin")
|
||||
title: $t("login:QRCodeLogin")
|
||||
},
|
||||
{
|
||||
title: $t("login.pureRegister")
|
||||
title: $t("login:Register")
|
||||
}
|
||||
];
|
||||
|
||||
const thirdParty = [
|
||||
{
|
||||
title: $t("login.pureWeChatLogin"),
|
||||
title: $t("login:WeChatLogin"),
|
||||
icon: "wechat"
|
||||
},
|
||||
{
|
||||
title: $t("login.pureAlipayLogin"),
|
||||
title: $t("login:AlipayLogin"),
|
||||
icon: "alipay"
|
||||
},
|
||||
{
|
||||
title: $t("login.pureQQLogin"),
|
||||
title: $t("login:QQLogin"),
|
||||
icon: "qq"
|
||||
},
|
||||
{
|
||||
title: $t("login.pureWeiBoLogin"),
|
||||
title: $t("login:WeiBoLogin"),
|
||||
icon: "weibo"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -16,9 +16,9 @@ const loginRules = reactive<FormRules>({
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePassWordReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordReg"))));
|
||||
} else if (!REGEXP_PWD.test(value)) {
|
||||
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -30,7 +30,7 @@ const loginRules = reactive<FormRules>({
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
|
||||
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -46,9 +46,9 @@ const phoneRules = reactive<FormRules>({
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePhoneReg"))));
|
||||
callback(new Error(transformI18n($t("login:PhoneReg"))));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error(transformI18n($t("login.purePhoneCorrectReg"))));
|
||||
callback(new Error(transformI18n($t("login:PhoneCorrectReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -60,9 +60,9 @@ const phoneRules = reactive<FormRules>({
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
|
||||
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
|
||||
} else if (!REGEXP_SIX.test(value)) {
|
||||
callback(new Error(transformI18n($t("login.pureVerifyCodeSixReg"))));
|
||||
callback(new Error(transformI18n($t("login:VerifyCodeSixReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -74,27 +74,22 @@ const phoneRules = reactive<FormRules>({
|
||||
|
||||
/** 忘记密码校验 */
|
||||
const updateRules = reactive<FormRules>({
|
||||
phone: [
|
||||
email: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePhoneReg"))));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error(transformI18n($t("login.purePhoneCorrectReg"))));
|
||||
callback(new Error(transformI18n($t("login:EmailReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
}
|
||||
],
|
||||
verifyCode: [
|
||||
code: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
|
||||
} else if (!REGEXP_SIX.test(value)) {
|
||||
callback(new Error(transformI18n($t("login.pureVerifyCodeSixReg"))));
|
||||
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -106,9 +101,9 @@ const updateRules = reactive<FormRules>({
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login.purePassWordReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordReg"))));
|
||||
} else if (!REGEXP_PWD.test(value)) {
|
||||
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
|
||||
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -118,4 +113,81 @@ const updateRules = reactive<FormRules>({
|
||||
]
|
||||
});
|
||||
|
||||
export { loginRules, phoneRules, updateRules };
|
||||
/** 注册账号校验 */
|
||||
const registerRules = reactive<FormRules>({
|
||||
phone: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:PhoneReg"))));
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error(transformI18n($t("login:PhoneCorrectReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:PassWordReg"))));
|
||||
} else if (!REGEXP_PWD.test(value)) {
|
||||
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
username: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:UsernameReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
nickname: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:NicknameReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error(transformI18n($t("login:EmailReg"))));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export { loginRules, phoneRules, updateRules, registerRules };
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { hasAuth, getAuths } from "@/router/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButtonRouter"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">当前拥有的code列表:{{ getAuths() }}</p>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Auth value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasAuth('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-if="hasAuth(['permission:btn:edit'])" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
hasAuth([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
指令方式判断权限(该方式不能动态修改权限)
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-auth="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-auth="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,109 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { hasPerms } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
|
||||
const { permissions } = useUserStoreHook();
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButtonLogin"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">当前拥有的code列表:{{ permissions }}</p>
|
||||
<p v-show="permissions?.[0] === '*:*:*'" class="mb-2">
|
||||
*:*:* 代表拥有全部按钮级别权限
|
||||
</p>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Perms value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasPerms('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasPerms(['permission:btn:edit'])"
|
||||
plain
|
||||
type="primary"
|
||||
>
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
hasPerms([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
指令方式判断权限(该方式不能动态修改权限)
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-perms="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-perms="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,66 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { initRouter } from "@/router/utils";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { type CSSProperties, ref, computed } from "vue";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionPage"
|
||||
});
|
||||
|
||||
const elStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: "85vw",
|
||||
justifyContent: "start"
|
||||
};
|
||||
});
|
||||
|
||||
const username = ref(useUserStoreHook()?.username);
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: "admin",
|
||||
label: "管理员角色"
|
||||
},
|
||||
{
|
||||
value: "common",
|
||||
label: "普通角色"
|
||||
}
|
||||
];
|
||||
|
||||
function onChange() {
|
||||
useUserStoreHook()
|
||||
.loginByUsername({ username: username.value, password: "admin123" })
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
storageLocal().removeItem("async-routes");
|
||||
usePermissionStoreHook().clearAllCachePage();
|
||||
initRouter();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">
|
||||
模拟后台根据不同角色返回对应路由,观察左侧菜单变化(管理员角色可查看系统管理菜单、普通角色不可查看系统管理菜单)
|
||||
</p>
|
||||
<el-card shadow="never" :style="elStyle">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>当前角色:{{ username }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-select v-model="username" class="!w-[160px]" @change="onChange">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
193
src/views/system/department/components/form.vue
Normal file
193
src/views/system/department/components/form.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import { FormRules } from "element-plus";
|
||||
import { isEmail, isPhone } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemDepartmentForm"
|
||||
});
|
||||
/**
|
||||
* 表单
|
||||
*/
|
||||
interface FormItemProps {
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
id: string;
|
||||
parent_id: string;
|
||||
name: string;
|
||||
principal: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
remark: string;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
const formRules = reactive<FormRules>({
|
||||
name: [{ 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: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback();
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error("请输入正确的邮箱格式"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
id: "",
|
||||
higherDeptOptions: [],
|
||||
parent_id: "",
|
||||
name: "",
|
||||
principal: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
sort: 0,
|
||||
status: 1,
|
||||
remark: ""
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col>
|
||||
<el-form-item label="上级部门">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parent_id"
|
||||
class="w-full"
|
||||
:options="newFormInline.higherDeptOptions"
|
||||
:props="{
|
||||
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
|
||||
placeholder="请输入部门名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="部门负责人">
|
||||
<el-input
|
||||
v-model="newFormInline.principal"
|
||||
clearable
|
||||
placeholder="请输入部门负责人"
|
||||
/>
|
||||
</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
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</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
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</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"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
controls-position="right"
|
||||
/>
|
||||
</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
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
:style="switchStyle"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="newFormInline.remark"
|
||||
placeholder="请输入备注信息"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
172
src/views/system/department/index.vue
Normal file
172
src/views/system/department/index.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useDepartment } from "./utils/hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemDepartment"
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete
|
||||
} = useDepartment();
|
||||
onBeforeRouteUpdate((to, from, next) => {
|
||||
onSearch();
|
||||
next();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="部门名称:" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入部门名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
{{ t("buttons:Search") }}
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
{{ t("buttons:Reset") }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="部门管理"
|
||||
:columns="columns"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
{{ t("buttons:Add") }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
adaptive
|
||||
border
|
||||
stripe
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
default-expand-all
|
||||
:default-sort="{ prop: 'sort', order: 'ascending' }"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
{{ t("buttons:Update") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
{{ t("buttons:Add") }}
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除部门名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="danger"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
{{ t("buttons:Delete") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
239
src/views/system/department/utils/hook.tsx
Normal file
239
src/views/system/department/utils/hook.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "../components/form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import { reactive, ref, onMounted, h } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
import type { DepartmentInfo } from "types/system";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import {
|
||||
deleteDepartmentAPI,
|
||||
getDepartmentListAPI,
|
||||
postAddDepartmentAPI,
|
||||
putUpdateDepartmentAPI
|
||||
} from "@/api/system";
|
||||
|
||||
export const useDepartment = () => {
|
||||
const form = reactive({
|
||||
/**部门ID */
|
||||
id: "",
|
||||
/**部门名称 */
|
||||
name: "",
|
||||
/**附属部门ID */
|
||||
parent_id: "",
|
||||
/**部门负责人 */
|
||||
principal: "",
|
||||
/**部门电话 */
|
||||
phone: "",
|
||||
/**部门邮件 */
|
||||
email: "",
|
||||
/**备注 */
|
||||
remark: "",
|
||||
/**排序 */
|
||||
sort: 0,
|
||||
/**状态 */
|
||||
status: ""
|
||||
});
|
||||
/**
|
||||
* 表单Ref
|
||||
*/
|
||||
const formRef = ref();
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
const dataList = ref<DepartmentInfo[]>([]);
|
||||
/**
|
||||
* 加载状态
|
||||
*/
|
||||
const loading = ref(true);
|
||||
/**
|
||||
* 标签样式
|
||||
*/
|
||||
const { tagStyle, formatHigherDeptOptions } = usePublicHooks();
|
||||
/**
|
||||
* 表格列设置
|
||||
*/
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "部门名称",
|
||||
prop: "name",
|
||||
width: 180,
|
||||
align: "left"
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "sort",
|
||||
minWidth: 70
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
minWidth: 100,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} style={tagStyle.value(row.status)}>
|
||||
{row.status === 1 ? "启用" : "停用"}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
minWidth: 200,
|
||||
prop: "create_time",
|
||||
formatter: ({ create_time }) =>
|
||||
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "修改时间",
|
||||
minWidth: 200,
|
||||
prop: "update_time",
|
||||
formatter: ({ update_time }) =>
|
||||
dayjs(update_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
minWidth: 320
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 230,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
/**
|
||||
* 初次查询
|
||||
*/
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getDepartmentListAPI({
|
||||
page: 1,
|
||||
pageSize: 9999,
|
||||
...form
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = handleTree(res.data.result, "id", "parent_id"); // 处理成树结构
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
const openDialog = (title = "新增", row?: FormItemProps) => {
|
||||
addDialog({
|
||||
title: `${title}部门`,
|
||||
props: {
|
||||
formInline: {
|
||||
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)),
|
||||
id: row?.id ?? "",
|
||||
parent_id: row?.parent_id ?? "",
|
||||
name: row?.name ?? "",
|
||||
principal: row?.principal ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sort: row?.sort ?? 0,
|
||||
status: row?.status ?? 1,
|
||||
remark: row?.remark ?? ""
|
||||
}
|
||||
},
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () =>
|
||||
h(editForm, {
|
||||
formInline: {
|
||||
higherDeptOptions: formatHigherDeptOptions(
|
||||
cloneDeep(dataList.value)
|
||||
),
|
||||
id: row?.id ?? "",
|
||||
parent_id: row?.parent_id ?? "",
|
||||
name: row?.name ?? "",
|
||||
principal: row?.principal ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sort: row?.sort ?? 0,
|
||||
status: row?.status ?? 1,
|
||||
remark: row?.remark ?? ""
|
||||
},
|
||||
ref: formRef
|
||||
}),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
function chores() {
|
||||
message(`您${title}了部门名称为${curData.name}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
FormRef.validate(async (valid: any) => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
const res = await postAddDepartmentAPI(curData);
|
||||
if (res.success) {
|
||||
chores();
|
||||
} else {
|
||||
message(`添加失败!`, {
|
||||
type: "error"
|
||||
});
|
||||
done();
|
||||
}
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
const res = await putUpdateDepartmentAPI(curData, row.id);
|
||||
if (res.success) {
|
||||
chores();
|
||||
} else {
|
||||
message(`修改失败!`, {
|
||||
type: "error"
|
||||
});
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 删除部门
|
||||
*/
|
||||
const handleDelete = async (row: DepartmentInfo) => {
|
||||
const res = await deleteDepartmentAPI(row.id);
|
||||
if (res.code === 200) {
|
||||
message(`您删除了部门:${row.name}及其附属部门`, { type: "success" });
|
||||
onSearch();
|
||||
} else {
|
||||
message(`删除失败!`, {
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
dataList,
|
||||
columns,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete
|
||||
};
|
||||
};
|
||||
53
src/views/system/hooks.ts
Normal file
53
src/views/system/hooks.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// 抽离可公用的工具函数等用于系统管理页面逻辑
|
||||
import { computed } from "vue";
|
||||
import { useDark } from "@pureadmin/utils";
|
||||
|
||||
export function usePublicHooks() {
|
||||
const { isDark } = useDark();
|
||||
|
||||
const switchStyle = computed(() => {
|
||||
return {
|
||||
"--el-switch-on-color": "#6abe39",
|
||||
"--el-switch-off-color": "#e84749"
|
||||
};
|
||||
});
|
||||
|
||||
const tagStyle = computed(() => {
|
||||
return (status: number) => {
|
||||
return status === 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"
|
||||
}
|
||||
: {
|
||||
"--el-tag-text-color": isDark.value ? "#e84749" : "#cf1322",
|
||||
"--el-tag-bg-color": isDark.value ? "#2b1316" : "#fff1f0",
|
||||
"--el-tag-border-color": isDark.value ? "#58191c" : "#ffa39e"
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
const formatHigherDeptOptions = treeList => {
|
||||
// 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].disabled = treeList[i].status === 0 ? true : false;
|
||||
formatHigherDeptOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
};
|
||||
|
||||
return {
|
||||
/** 当前网页是否为`dark`模式 */
|
||||
isDark,
|
||||
/** 表现更鲜明的`el-switch`组件 */
|
||||
switchStyle,
|
||||
/** 表现更鲜明的`el-tag`组件 */
|
||||
tagStyle,
|
||||
/** 获取上级部门级联选择器的数据结构*/
|
||||
formatHigherDeptOptions
|
||||
};
|
||||
}
|
||||
93
src/views/system/i18n/components/form.vue
Normal file
93
src/views/system/i18n/components/form.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-form ref="ruleFormRef" :model="newFormInline" label-width="82px">
|
||||
<el-row :gutter="30">
|
||||
<re-col :value="24" :xm="24" :sm="24">
|
||||
<el-form-item label="国际化key" prop="key">
|
||||
<el-input
|
||||
v-model="newFormInline.key"
|
||||
placeholder="请输入国际化关键词~"
|
||||
clearable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="24" :xm="24" :sm="24">
|
||||
<el-form-item label="语言类型" prop="locale_id">
|
||||
<el-select
|
||||
v-model="newFormInline.locale_id"
|
||||
placeholder="请选择语言类型~"
|
||||
clearable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in localeList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="24" :xm="24" :sm="24">
|
||||
<el-form-item label="国际化值" prop="translation">
|
||||
<el-input
|
||||
v-model="newFormInline.translation"
|
||||
placeholder="请输入国际化值~"
|
||||
clearable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { LanguageInfo } from "types/system";
|
||||
import { getLocaleListAPI } from "@/api/i18n";
|
||||
import { message } from "@/utils/message";
|
||||
const ruleFormRef = ref();
|
||||
interface PropsInfo {
|
||||
title: string;
|
||||
key: string;
|
||||
locale_id: string;
|
||||
translation: string;
|
||||
}
|
||||
|
||||
type ProsData = {
|
||||
formInline: PropsInfo;
|
||||
};
|
||||
const props = withDefaults(defineProps<ProsData>(), {
|
||||
formInline: () => ({
|
||||
title: "新增",
|
||||
key: "",
|
||||
locale_id: "",
|
||||
translation: ""
|
||||
})
|
||||
});
|
||||
const newFormInline = ref<PropsInfo>(props.formInline);
|
||||
defineExpose({ newFormInline });
|
||||
/**语言类型 */
|
||||
const localeList = ref<LanguageInfo[]>([]);
|
||||
|
||||
/**
|
||||
* 获取语言类型
|
||||
*/
|
||||
const getLocaleList = async () => {
|
||||
const res = await getLocaleListAPI({
|
||||
page: 1,
|
||||
pageSize: 9999
|
||||
});
|
||||
if (res.success) {
|
||||
localeList.value = res.data.result;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
onMounted(async () => {
|
||||
await getLocaleList();
|
||||
});
|
||||
</script>
|
||||
287
src/views/system/i18n/hook.tsx
Normal file
287
src/views/system/i18n/hook.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "./components/form.vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { type Ref, ref, reactive, onMounted, h } from "vue";
|
||||
import type { LanguageInfo, TranslationInfo } from "types/system";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import {
|
||||
deleteI18nAPI,
|
||||
getI18nListAPI,
|
||||
getLocaleListAPI,
|
||||
postAddI18nAPI,
|
||||
putUpdateI18nAPI
|
||||
} from "@/api/i18n";
|
||||
|
||||
export const useI18n = (tableRef: Ref) => {
|
||||
/**
|
||||
* 查询表单
|
||||
*/
|
||||
const form = reactive({
|
||||
key: "",
|
||||
locale_id: "",
|
||||
translation: ""
|
||||
});
|
||||
/**
|
||||
* 表单Ref
|
||||
*/
|
||||
const formRef = ref(null);
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
const dataList = ref<TranslationInfo[]>([]);
|
||||
/**
|
||||
* 加载状态
|
||||
*/
|
||||
const loading = ref(true);
|
||||
/**
|
||||
* 已选数量
|
||||
*/
|
||||
const selectedNum = ref<number>(0);
|
||||
/**
|
||||
* 分页参数
|
||||
*/
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true
|
||||
});
|
||||
/**
|
||||
* 表格列设置
|
||||
*/
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "勾选列", // 如果需要表格多选,此处label必须设置
|
||||
type: "selection",
|
||||
fixed: "left",
|
||||
reserveSelection: true // 数据刷新后保留选项
|
||||
},
|
||||
{
|
||||
label: "国际化key",
|
||||
prop: "key"
|
||||
},
|
||||
{
|
||||
label: "国际化值",
|
||||
prop: "translation"
|
||||
},
|
||||
{
|
||||
label: "语言编码",
|
||||
prop: "locale_code"
|
||||
},
|
||||
{
|
||||
label: "语言名称",
|
||||
prop: "locale_name"
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "create_time",
|
||||
formatter: ({ create_time }) =>
|
||||
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 220,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 初次查询
|
||||
*/
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getI18nListAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: pagination.pageSize,
|
||||
key: form.key,
|
||||
locale_id: form.locale_id,
|
||||
translation: form.translation
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
loading.value = false;
|
||||
};
|
||||
/**
|
||||
* 重置表单
|
||||
* @param formEl 表单ref
|
||||
* @returns
|
||||
*/
|
||||
const resetForm = (formEl: any) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
};
|
||||
/**
|
||||
* 处理删除
|
||||
* @param row
|
||||
*/
|
||||
const handleDelete = async (row: TranslationInfo) => {
|
||||
const res = await deleteI18nAPI(row.id);
|
||||
if (res.success) {
|
||||
onSearch();
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 处理每页数量变化
|
||||
*/
|
||||
const handleSizeChange = async (val: number) => {
|
||||
const res = await getI18nListAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: val,
|
||||
key: form.key,
|
||||
locale_id: form.locale_id,
|
||||
translation: form.translation
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理页码变化
|
||||
* @param val
|
||||
*/
|
||||
const handleCurrentChange = async (val: number) => {
|
||||
const res = await getI18nListAPI({
|
||||
page: val,
|
||||
pageSize: pagination.pageSize,
|
||||
key: form.key,
|
||||
locale_id: form.locale_id,
|
||||
translation: form.translation
|
||||
});
|
||||
if (res.code === 200) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
const handleSelectionChange = async (val: any) => {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
};
|
||||
|
||||
/** 取消选择 */
|
||||
const onSelectionCancel = async () => {
|
||||
selectedNum.value = 0;
|
||||
// 用于多选表格,清空用户的选择
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
};
|
||||
|
||||
const openDialog = async (title = "新增", row?: TranslationInfo) => {
|
||||
addDialog({
|
||||
title: `${title}国际化项`,
|
||||
props: {
|
||||
formInline: {
|
||||
title: title,
|
||||
key: row?.key ?? "",
|
||||
locale_id: row?.locale_id ?? "",
|
||||
translation: row?.translation ?? ""
|
||||
}
|
||||
},
|
||||
width: "45%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () =>
|
||||
h(editForm, {
|
||||
formInline: {
|
||||
title: title,
|
||||
key: row?.key ?? "",
|
||||
locale_id: row?.locale_id ?? "",
|
||||
translation: row?.translation ?? ""
|
||||
},
|
||||
ref: formRef
|
||||
}),
|
||||
beforeSure: async (done, {}) => {
|
||||
const FormData = formRef.value.newFormInline;
|
||||
let form = {
|
||||
key: FormData.key ?? "",
|
||||
locale_id: FormData.locale_id ?? "",
|
||||
translation: FormData.translation ?? ""
|
||||
};
|
||||
if (title === "新增") {
|
||||
const res = await postAddI18nAPI(form);
|
||||
if (res.success) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
message(res.msg, { type: res.success ? "success" : "error" });
|
||||
} else {
|
||||
const res = await putUpdateI18nAPI(form, row.id);
|
||||
if (res.success) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
message(res.msg, { type: res.success ? "success" : "error" });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**语言类型 */
|
||||
const localeList = ref<LanguageInfo[]>([]);
|
||||
|
||||
/**
|
||||
* 获取语言类型
|
||||
*/
|
||||
const getLocaleList = async () => {
|
||||
const res = await getLocaleListAPI({
|
||||
page: 1,
|
||||
pageSize: 9999
|
||||
});
|
||||
if (res.success) {
|
||||
localeList.value = res.data.result;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 页面加载执行
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await onSearch();
|
||||
await getLocaleList();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
formRef,
|
||||
dataList,
|
||||
loading,
|
||||
pagination,
|
||||
columns,
|
||||
selectedNum,
|
||||
localeList,
|
||||
onSearch,
|
||||
openDialog,
|
||||
resetForm,
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
onSelectionCancel,
|
||||
getLocaleList
|
||||
};
|
||||
};
|
||||
198
src/views/system/i18n/index.vue
Normal file
198
src/views/system/i18n/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="国际化key" prop="key">
|
||||
<el-input
|
||||
v-model="form.key"
|
||||
placeholder="请输入国际化关键词~"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="语言类型" prop="locale_id">
|
||||
<el-select
|
||||
v-model="form.locale_id"
|
||||
placeholder="请选择语言类型~"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in localeList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="国际化值" prop="translation">
|
||||
<el-input
|
||||
v-model="form.translation"
|
||||
placeholder="请输入国际化值~"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</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"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增')"
|
||||
>
|
||||
新增
|
||||
</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="是否确认删除?">
|
||||
<template #reference>
|
||||
<el-button type="danger" text class="mr-1"> 批量删除 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
adaptive
|
||||
border
|
||||
stripe
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除国际化key为 ${row.key} 的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="danger"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "I18nIndex"
|
||||
});
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "./hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
/**
|
||||
* 表格Ref
|
||||
*/
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
formRef,
|
||||
dataList,
|
||||
localeList,
|
||||
loading,
|
||||
pagination,
|
||||
columns,
|
||||
selectedNum,
|
||||
onSearch,
|
||||
openDialog,
|
||||
resetForm,
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
onSelectionCancel
|
||||
} = useI18n(tableRef);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
: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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
50
src/views/system/language/components/form.vue
Normal file
50
src/views/system/language/components/form.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-form ref="ruleFormRef" :model="newFormInline" label-width="82px">
|
||||
<el-row :gutter="30">
|
||||
<re-col :value="24" :xm="24" :sm="24">
|
||||
<el-form-item label="语言编码" prop="code">
|
||||
<el-input
|
||||
v-model="newFormInline.code"
|
||||
placeholder="请输入语言编码~"
|
||||
clearable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :value="24" :xm="24" :sm="24">
|
||||
<el-form-item label="语言名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
placeholder="请输入语言名称~"
|
||||
clearable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import ReCol from "@/components/ReCol";
|
||||
const ruleFormRef = ref();
|
||||
interface PropsInfo {
|
||||
title: string;
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
type ProsData = {
|
||||
formInline: PropsInfo;
|
||||
};
|
||||
const props = withDefaults(defineProps<ProsData>(), {
|
||||
formInline: () => ({
|
||||
title: "新增",
|
||||
code: "",
|
||||
name: ""
|
||||
})
|
||||
});
|
||||
const newFormInline = ref<PropsInfo>(props.formInline);
|
||||
defineExpose({ newFormInline });
|
||||
</script>
|
||||
281
src/views/system/language/hook.tsx
Normal file
281
src/views/system/language/hook.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import dayjs from "dayjs";
|
||||
import editForm from "./components/form.vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { type Ref, ref, reactive, onMounted, h } from "vue";
|
||||
import type { LanguageInfo, TranslationInfo } from "types/system";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { addDialog } from "@/components/ReDialog";
|
||||
import {
|
||||
getLocaleListAPI,
|
||||
deleteLocaleAPI,
|
||||
postAddLocaleAPI,
|
||||
putUpdateLocaleAPI,
|
||||
getI18nHandleListAPI
|
||||
} from "@/api/i18n";
|
||||
|
||||
import jsyaml from "js-yaml";
|
||||
|
||||
export const useLocale = (tableRef: Ref) => {
|
||||
/**
|
||||
* 查询表单
|
||||
*/
|
||||
const form = reactive({
|
||||
name: "",
|
||||
code: ""
|
||||
});
|
||||
/**
|
||||
* 表单Ref
|
||||
*/
|
||||
const formRef = ref(null);
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
const dataList = ref<LanguageInfo[]>([]);
|
||||
/**
|
||||
* 加载状态
|
||||
*/
|
||||
const loading = ref(true);
|
||||
/**
|
||||
* 已选数量
|
||||
*/
|
||||
const selectedNum = ref<number>(0);
|
||||
/**
|
||||
* 分页参数
|
||||
*/
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true
|
||||
});
|
||||
/**
|
||||
* 表格列设置
|
||||
*/
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "勾选列", // 如果需要表格多选,此处label必须设置
|
||||
type: "selection",
|
||||
fixed: "left",
|
||||
reserveSelection: true // 数据刷新后保留选项
|
||||
},
|
||||
{
|
||||
label: "语言编码",
|
||||
prop: "code"
|
||||
},
|
||||
{
|
||||
label: "语言名称",
|
||||
prop: "name"
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "create_time",
|
||||
formatter: ({ create_time }) =>
|
||||
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 200,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 初次查询
|
||||
*/
|
||||
const onSearch = async () => {
|
||||
loading.value = true;
|
||||
const res = await getLocaleListAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: pagination.pageSize,
|
||||
name: form.name,
|
||||
code: form.code
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
loading.value = false;
|
||||
};
|
||||
/**
|
||||
* 重置表单
|
||||
* @param formEl 表单ref
|
||||
* @returns
|
||||
*/
|
||||
const resetForm = (formEl: any) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
};
|
||||
/**
|
||||
* 处理删除
|
||||
* @param row
|
||||
*/
|
||||
const handleDelete = async (row: TranslationInfo) => {
|
||||
const res = await deleteLocaleAPI(row.id);
|
||||
if (res.success) {
|
||||
onSearch();
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 处理每页数量变化
|
||||
*/
|
||||
const handleSizeChange = async (val: number) => {
|
||||
const res = await getLocaleListAPI({
|
||||
page: pagination.currentPage,
|
||||
pageSize: val,
|
||||
name: form.name,
|
||||
code: form.code
|
||||
});
|
||||
if (res.success) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理页码变化
|
||||
* @param val
|
||||
*/
|
||||
const handleCurrentChange = async (val: number) => {
|
||||
const res = await getLocaleListAPI({
|
||||
page: val,
|
||||
pageSize: pagination.pageSize,
|
||||
name: form.name,
|
||||
code: form.code
|
||||
});
|
||||
if (res.code === 200) {
|
||||
dataList.value = res.data.result;
|
||||
pagination.total = res.data.total;
|
||||
pagination.currentPage = res.data.page;
|
||||
}
|
||||
message(res.msg, {
|
||||
type: res.success ? "success" : "error"
|
||||
});
|
||||
};
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
const handleSelectionChange = async (val: any) => {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
};
|
||||
|
||||
/** 取消选择 */
|
||||
const onSelectionCancel = async () => {
|
||||
selectedNum.value = 0;
|
||||
// 用于多选表格,清空用户的选择
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
};
|
||||
|
||||
const openDialog = async (title = "新增", row?: LanguageInfo) => {
|
||||
addDialog({
|
||||
title: `${title}国际化项`,
|
||||
props: {
|
||||
formInline: {
|
||||
title: title,
|
||||
name: row?.name ?? "",
|
||||
code: row?.code ?? ""
|
||||
}
|
||||
},
|
||||
width: "45%",
|
||||
draggable: true,
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () =>
|
||||
h(editForm, {
|
||||
formInline: {
|
||||
title: title,
|
||||
name: row?.name ?? "",
|
||||
code: row?.code ?? ""
|
||||
},
|
||||
ref: formRef
|
||||
}),
|
||||
beforeSure: async (done, {}) => {
|
||||
const FormData = formRef.value.newFormInline;
|
||||
let form = {
|
||||
name: FormData.name ?? "",
|
||||
code: FormData.code ?? ""
|
||||
};
|
||||
if (title === "新增") {
|
||||
const res = await postAddLocaleAPI(form);
|
||||
if (res.success) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
message(res.msg, { type: res.success ? "success" : "error" });
|
||||
} else {
|
||||
const res = await putUpdateLocaleAPI(form, row.id);
|
||||
if (res.success) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
message(res.msg, { type: res.success ? "success" : "error" });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出 YAML 文件
|
||||
*/
|
||||
const export_to_yaml = async (row: LanguageInfo) => {
|
||||
const res = await getI18nHandleListAPI(row.id); // 调用 API 获取数据
|
||||
if (res.success) {
|
||||
// 将 JSON 转换为 YAML
|
||||
const yamlString = jsyaml.dump(res.data.data);
|
||||
|
||||
// 创建 Blob 对象
|
||||
const blob = new Blob([yamlString], { type: "text/yaml" });
|
||||
|
||||
// 生成下载链接
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// 创建 <a> 元素并触发下载
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${row.code}.yaml`; // 设置下载文件名
|
||||
document.body.appendChild(link); // 将 <a> 元素添加到 DOM 中
|
||||
link.click(); // 模拟点击下载
|
||||
|
||||
// 清理 URL 对象
|
||||
URL.revokeObjectURL(url);
|
||||
document.body.removeChild(link); // 移除 <a> 元素
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 页面加载执行
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
formRef,
|
||||
dataList,
|
||||
loading,
|
||||
pagination,
|
||||
columns,
|
||||
selectedNum,
|
||||
onSearch,
|
||||
openDialog,
|
||||
resetForm,
|
||||
export_to_yaml,
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
onSelectionCancel
|
||||
};
|
||||
};
|
||||
200
src/views/system/language/index.vue
Normal file
200
src/views/system/language/index.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
||||
>
|
||||
<el-form-item label="语言编码" prop="code">
|
||||
<el-input
|
||||
v-model="form.code"
|
||||
placeholder="请输入语言编码~"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="语言名称" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入语言名称~"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</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"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog('新增')"
|
||||
>
|
||||
新增
|
||||
</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="是否确认删除?">
|
||||
<template #reference>
|
||||
<el-button type="danger" text class="mr-1"> 批量删除 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
adaptive
|
||||
border
|
||||
stripe
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认导出语言名称为 ${row.name} 的这条数据为yaml文件`"
|
||||
@confirm="export_to_yaml(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Download)"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除语言名称为 ${row.name} 的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="danger"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "LocaleIndex"
|
||||
});
|
||||
import { ref } from "vue";
|
||||
import { useLocale } from "./hook";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import Download from "@iconify-icons/ri/file-download-line";
|
||||
/**
|
||||
* 表格Ref
|
||||
*/
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
formRef,
|
||||
dataList,
|
||||
loading,
|
||||
pagination,
|
||||
columns,
|
||||
selectedNum,
|
||||
onSearch,
|
||||
openDialog,
|
||||
resetForm,
|
||||
export_to_yaml,
|
||||
handleDelete,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
onSelectionCancel
|
||||
} = useLocale(tableRef);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
: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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,5 +5,5 @@ defineOptions({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Pure-Admin-Thin(国际化版本)</h1>
|
||||
<h1>首页</h1>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user