diff --git a/commitlint.config.js b/commitlint.config.js index eea755d..a6ff741 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,35 +1,15 @@ // @ts-check -/** @type {import("@commitlint/types").UserConfig} */ -export default { - ignores: [commit => commit.includes("init")], - extends: ["@commitlint/config-conventional"], - rules: { - "body-leading-blank": [2, "always"], - "footer-leading-blank": [1, "always"], - "header-max-length": [2, "always", 108], - "subject-empty": [2, "never"], - "type-empty": [2, "never"], - "type-enum": [ - 2, - "always", - [ - "feat", - "fix", - "perf", - "style", - "docs", - "test", - "refactor", - "build", - "ci", - "chore", - "revert", - "wip", - "workflow", - "types", - "release" - ] - ] - } -}; +// /** @type {import("@commitlint/types").UserConfig} */ +// export default { +// ignores: [commit => commit.includes("init")], +// extends: ["@commitlint/config-conventional"], +// rules: { +// "body-leading-blank": [2, "always"], +// "footer-leading-blank": [1, "always"], +// "header-max-length": [2, "always", 108], +// "subject-empty": [2, "never"], +// "type-empty": [2, "never"], +// "type-enum": [2, "always", []] +// } +// }; diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts index 202bf36..dc4b31e 100644 --- a/mock/asyncRoutes.ts +++ b/mock/asyncRoutes.ts @@ -2,8 +2,8 @@ import { defineFakeRoute } from "vite-plugin-fake-server/client"; /** - * roles:页面级别权限,这里模拟二种 "admin"、"common" - * admin:管理员角色 + * roles:页面级别权限,这里模拟二种 "user"、"common" + * user:管理员角色 * common:普通角色 */ const permissionRouter = { @@ -19,7 +19,7 @@ const permissionRouter = { name: "PermissionPage", meta: { title: "页面权限", - roles: ["admin", "common"] + roles: ["user", "common"] } }, { @@ -27,7 +27,7 @@ const permissionRouter = { name: "PermissionButton", meta: { title: "按钮权限", - roles: ["admin", "common"], + roles: ["user", "common"], auths: [ "permission:btn:add", "permission:btn:edit", diff --git a/mock/login.ts b/mock/login.ts index a9c71b1..67a22a7 100644 --- a/mock/login.ts +++ b/mock/login.ts @@ -6,16 +6,16 @@ export default defineFakeRoute([ url: "/login", method: "post", response: ({ body }) => { - if (body.username === "admin") { + if (body.username === "user") { return { success: true, data: { avatar: "https://avatars.githubusercontent.com/u/44761321", - username: "admin", + username: "user", nickname: "小铭", // 一个用户可能有多个角色 - roles: ["admin"], - accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", + roles: ["user"], + accessToken: "eyJhbGciOiJIUzUxMiJ9.user", refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", expires: "2030/10/30 00:00:00" } diff --git a/public/platform-config.json b/public/platform-config.json index a81a393..9982ec6 100644 --- a/public/platform-config.json +++ b/public/platform-config.json @@ -1,6 +1,6 @@ { "Version": "5.5.0", - "Title": "PureAdmin", + "Title": "Wireguard-Dashboard", "FixedHeader": true, "HiddenSideBar": false, "MultiTagsCache": false, diff --git a/src/api/login.ts b/src/api/login.ts new file mode 100644 index 0000000..ab4ef03 --- /dev/null +++ b/src/api/login.ts @@ -0,0 +1,17 @@ +import { http } from "@/utils/http"; +import { baseUri } from "@/api/utils"; + +// 获取验证码 +export const getCaptcha = () => { + return http.request("get", baseUri("/captcha")); +}; + +// 登陆 +export const login = (data?: object) => { + return http.request("post", baseUri("/login"), { data }); +}; + +// 退出登陆 +export const logout = () => { + return http.request("delete", baseUri("/user/logout")); +}; diff --git a/src/api/user.ts b/src/api/user.ts index ab100b5..cb3fafa 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,43 +1,27 @@ import { http } from "@/utils/http"; +import { baseUri } from "@/api/utils"; -export type UserResult = { - success: boolean; - data: { - /** 头像 */ - avatar: string; - /** 用户名 */ - username: string; - /** 昵称 */ - nickname: string; - /** 当前登录用户的角色 */ - roles: Array; - /** `token` */ - accessToken: string; - /** 用于调用刷新`accessToken`的接口时所需的`token` */ - refreshToken: string; - /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ - expires: Date; - }; +// 获取当前登陆用户信息 +export const getUser = () => { + return http.request("get", baseUri("/user")); }; -export type RefreshTokenResult = { - success: boolean; - data: { - /** `token` */ - accessToken: string; - /** 用于调用刷新`accessToken`的接口时所需的`token` */ - refreshToken: string; - /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ - expires: Date; - }; +// 获取用户列表 +export const userList = (params?: object) => { + return http.request("get", baseUri("/user/list"), { params }); }; -/** 登录 */ -export const getLogin = (data?: object) => { - return http.request("post", "/login", { data }); +// 切换用户状态 +export const changeUserStatus = (data?: object) => { + return http.request("put", baseUri("/user/change-status"), { data }); }; -/** 刷新`token` */ -export const refreshTokenApi = (data?: object) => { - return http.request("post", "/refresh-token", { data }); +// 新增/编辑用户信息 +export const editUser = (data?: object) => { + return http.request("post", baseUri("/user/save"), { data }); +}; + +// 删除管理员 +export const deleteUser = (userId: string) => { + return http.request("delete", baseUri("/user/delete/" + userId)); }; diff --git a/src/api/utils.ts b/src/api/utils.ts new file mode 100644 index 0000000..490f229 --- /dev/null +++ b/src/api/utils.ts @@ -0,0 +1 @@ +export const baseUri = (uri: string) => `/api${uri}`; diff --git a/src/components/ReDialog/index.ts b/src/components/ReDialog/index.ts index b471764..e7acd03 100644 --- a/src/components/ReDialog/index.ts +++ b/src/components/ReDialog/index.ts @@ -13,7 +13,12 @@ import type { const dialogStore = ref>([]); /** 打开弹框 */ -const addDialog = (options: DialogOptions) => { +const addDialog = (options: { + contentRenderer: () => any; + title: any; + with: string; + props: { formInline: { name: any } }; +}) => { const open = () => dialogStore.value.push(Object.assign(options, { visible: true })); if (options?.openDelay) { diff --git a/src/components/ReDialog/index.vue b/src/components/ReDialog/index.vue index 1db105f..412af94 100644 --- a/src/components/ReDialog/index.vue +++ b/src/components/ReDialog/index.vue @@ -95,7 +95,9 @@ function handleClose( v-bind="options" v-model="options.visible" class="pure-dialog" - :fullscreen="fullscreen ? true : options?.fullscreen ? true : false" + :align-center="true" + :fullscreen="fullscreen ? true : !!options?.fullscreen" + center @closed="handleClose(options, index)" @opened="eventsCallBack('open', options, index)" @openAutoFocus="eventsCallBack('openAutoFocus', options, index)" diff --git a/src/components/ReDialog/type.ts b/src/components/ReDialog/type.ts index 4cc7476..a9e6f9d 100644 --- a/src/components/ReDialog/type.ts +++ b/src/components/ReDialog/type.ts @@ -158,6 +158,8 @@ type ButtonProps = { interface DialogOptions extends DialogProps { /** 内容区组件的 `props`,可通过 `defineProps` 接收 */ props?: any; + /** dialog宽度 */ + with: string; /** 是否隐藏 `Dialog` 按钮操作区的内容 */ hideFooter?: boolean; /** 确认按钮的 `Popconfirm` 气泡确认框相关配置 */ diff --git a/src/components/ReIcon/src/offlineIcon.ts b/src/components/ReIcon/src/offlineIcon.ts index fc5f912..2f63896 100644 --- a/src/components/ReIcon/src/offlineIcon.ts +++ b/src/components/ReIcon/src/offlineIcon.ts @@ -1,4 +1,4 @@ -// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 +// 这里存放本地图标,在 src/layout/list.vue 文件中加载,避免在首启动加载 import { addIcon } from "@iconify/vue/dist/offline"; // 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标 diff --git a/src/components/ReImageVerify/index.ts b/src/components/ReImageVerify/index.ts new file mode 100644 index 0000000..4215c52 --- /dev/null +++ b/src/components/ReImageVerify/index.ts @@ -0,0 +1,7 @@ +import reImageVerify from "./src/index.vue"; +import { withInstall } from "@pureadmin/utils"; + +/** 图形验证码组件 */ +export const ReImageVerify = withInstall(reImageVerify); + +export default ReImageVerify; diff --git a/src/components/ReImageVerify/src/hooks.ts b/src/components/ReImageVerify/src/hooks.ts new file mode 100644 index 0000000..1eca68d --- /dev/null +++ b/src/components/ReImageVerify/src/hooks.ts @@ -0,0 +1,37 @@ +import { ref, onMounted } from "vue"; +import { getCaptcha } from "@/api/login"; + +// 绘制图形验证码 +export const useImageVerify = () => { + const imgCode = ref(""); + const imgCodeId = ref(""); + + function setImgCode(code: string) { + imgCode.value = code; + } + + function setImgCodeId(codeId: string) { + imgCodeId.value = codeId; + } + + function getImgCode() { + getCaptcha().then(res => { + if (res.code === 200) { + imgCode.value = res.data.captcha; + imgCodeId.value = res.data.id; + } + }); + } + + onMounted(() => { + getImgCode(); + }); + + return { + imgCode, + imgCodeId, + setImgCode, + setImgCodeId, + getImgCode + }; +}; diff --git a/src/components/ReImageVerify/src/index.vue b/src/components/ReImageVerify/src/index.vue new file mode 100644 index 0000000..f35d11f --- /dev/null +++ b/src/components/ReImageVerify/src/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/hooks/useGetGlobalProperties.ts b/src/hooks/useGetGlobalProperties.ts new file mode 100644 index 0000000..7af698c --- /dev/null +++ b/src/hooks/useGetGlobalProperties.ts @@ -0,0 +1,7 @@ +import { getCurrentInstance } from "vue"; + +export default function useGetGlobalProperties() { + const instance = getCurrentInstance(); + const globalProperties = instance?.appContext.config.globalProperties; + return { ...globalProperties }; +} diff --git a/src/layout/components/lay-navbar/component/user.vue b/src/layout/components/lay-navbar/component/user.vue new file mode 100644 index 0000000..7920342 --- /dev/null +++ b/src/layout/components/lay-navbar/component/user.vue @@ -0,0 +1,102 @@ + + + diff --git a/src/layout/components/lay-navbar/index.vue b/src/layout/components/lay-navbar/index.vue index 4e0cfcd..e9bfc7f 100644 --- a/src/layout/components/lay-navbar/index.vue +++ b/src/layout/components/lay-navbar/index.vue @@ -9,6 +9,14 @@ import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse. import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line"; import Setting from "@iconify-icons/ri/settings-3-line"; +import { h, ref } from "vue"; +import { addDialog } from "@/components/ReDialog/index"; +import { editUser as editUserApi, getUser, userList } from "@/api/user"; +import { storageLocal } from "@pureadmin/utils"; +import { setUser, userKey } from "@/utils/auth"; +import forms, { type FormProps } from "./component/user.vue"; +import { useRouter } from "vue-router"; +import useGetGlobalProperties from "@/hooks/useGetGlobalProperties"; const { layout, @@ -21,6 +29,62 @@ const { avatarsStyle, toggleSideBar } = useNav(); + +const { $bus } = useGetGlobalProperties(); + +const router = useRouter(); +const userEditFormRef = ref(); +// eslint-disable-next-line vue/valid-define-emits +const emit = defineEmits(); + +// 打开用户信息编辑窗口 +const openUserInfoDialog = () => { + const loginUser = storageLocal().getItem(userKey); + addDialog({ + width: "20%", + title: loginUser.name, + contentRenderer: () => h(forms, { ref: userEditFormRef }), + props: { + formInline: { + id: loginUser.id, + avatar: loginUser.avatar, + name: loginUser.name, + account: loginUser.account, + email: loginUser.email, + isAdmin: loginUser.isAdmin, + status: loginUser.status + } + }, + beforeSure: (done, { options }) => { + const FormRef = userEditFormRef.value.getUserEditFormRef(); + FormRef.validate(valid => { + if (!valid) return; + editUserApi(options.props.formInline).then(res => { + if (res.code === 200) { + // 重新拉一下当前登陆用户信息 + getUser().then(res => { + if (res.code === 200) { + setUser(res.data); + // 指定路由,刷新页面数据 + if (router.options.history.location === "/user/index") { + userList({ + current: 1, + size: 10 + }).then(res => { + if (res.code === 200) { + $bus.emit("userListData", res); + } + }); + } + } + }); + done(); + } + }); + }); + } + }); +};