From 34e345512869197f0dfa6dd3e1a92b8a4cf766ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BB=96=E5=BD=A6=E6=A3=8B?= Date: Thu, 14 Mar 2024 10:27:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E6=96=B0=E5=A2=9E=E6=9D=83=E9=99=90=E5=8F=8A=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=BB=84=E5=90=88=E5=BC=8F=E5=87=BD=E6=95=B0=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/src/components/PermissionRender.vue | 10 +++ .../admin/src/composables/useSubmit.ts | 15 ++-- .../admin/src/directives/permission.ts | 32 ++++++++ new-ui/projects/admin/src/http/config.ts | 5 +- new-ui/projects/admin/src/http/login.ts | 7 +- new-ui/projects/admin/src/main.ts | 5 ++ new-ui/projects/admin/src/router/index.ts | 17 ++++ new-ui/projects/admin/src/router/menu.ts | 10 +++ new-ui/projects/admin/src/stores/auth.ts | 20 +++-- new-ui/projects/admin/src/stores/counter.ts | 12 --- .../projects/admin/src/views/NoPermission.vue | 11 +++ .../admin/src/views/Order/OrderContainer.vue | 20 ++++- new-ui/projects/admin/src/views/Order/api.ts | 8 ++ .../SysPermission/SysPermissionContainer.vue | 82 +++++++++++++++++++ .../views/SysPermission/SysPermissionForm.vue | 63 ++++++++++++++ .../admin/src/views/SysPermission/api.ts | 25 ++++++ 16 files changed, 308 insertions(+), 34 deletions(-) create mode 100644 new-ui/projects/admin/src/components/PermissionRender.vue create mode 100644 new-ui/projects/admin/src/directives/permission.ts delete mode 100644 new-ui/projects/admin/src/stores/counter.ts create mode 100644 new-ui/projects/admin/src/views/NoPermission.vue create mode 100644 new-ui/projects/admin/src/views/SysPermission/SysPermissionContainer.vue create mode 100644 new-ui/projects/admin/src/views/SysPermission/SysPermissionForm.vue create mode 100644 new-ui/projects/admin/src/views/SysPermission/api.ts diff --git a/new-ui/projects/admin/src/components/PermissionRender.vue b/new-ui/projects/admin/src/components/PermissionRender.vue new file mode 100644 index 00000000..07493888 --- /dev/null +++ b/new-ui/projects/admin/src/components/PermissionRender.vue @@ -0,0 +1,10 @@ + + diff --git a/new-ui/projects/admin/src/composables/useSubmit.ts b/new-ui/projects/admin/src/composables/useSubmit.ts index 35a34aee..9f44b196 100644 --- a/new-ui/projects/admin/src/composables/useSubmit.ts +++ b/new-ui/projects/admin/src/composables/useSubmit.ts @@ -1,5 +1,4 @@ import { ref, reactive, unref } from "vue"; -import { Message } from "@arco-design/web-vue"; import type { BaseResponse } from "@chatgpt-plus/packages/type"; function useSubmit = Record, R = any>(defaultData?: T) { const formRef = ref(); @@ -10,14 +9,16 @@ function useSubmit = Record, R = any> submitting.value = true; try { const hasError = await formRef.value?.validate(); - if (!hasError) { - const { data, message } = await api({ ...formData ?? {}, ...unref(params) }); - message && Message.success(message); - return Promise.resolve({ formData, data }); + if (hasError) return Promise.reject({ validateErrors: hasError }); + + const { data, code, message } = await api({ ...formData ?? {}, ...unref(params) }); + if (code) { + return Promise.reject({ requestErrors: message }) } - return Promise.reject(false); + + return Promise.resolve({ formData, data }); } catch (err) { - return Promise.reject(err); + return Promise.reject({ errors: err }); } finally { submitting.value = false; } diff --git a/new-ui/projects/admin/src/directives/permission.ts b/new-ui/projects/admin/src/directives/permission.ts new file mode 100644 index 00000000..35c51897 --- /dev/null +++ b/new-ui/projects/admin/src/directives/permission.ts @@ -0,0 +1,32 @@ +import { useAuthStore } from "@/stores/auth"; + +// 判断操作权限 +export function hasPermission(permissionTag: string | string[] | boolean) { + const authStore = useAuthStore(); + const { is_super_admin, permissions = [] } = authStore; + if (is_super_admin) { + return true; + } + if (Array.isArray(permissionTag)) { + return permissionTag.every((tag) => permissions.includes(tag)); + } + if (typeof permissionTag === "string") { + return permissions.includes(permissionTag); + } + return permissionTag; +} + +function checkPermission(el, binding) { + if (!hasPermission(binding.value)) { + el.parentNode && el.parentNode.removeChild(el); + } +} + +export const permission = { + mounted(el, binding) { + checkPermission(el, binding); + }, + updated(el, binding) { + checkPermission(el, binding); + }, +}; diff --git a/new-ui/projects/admin/src/http/config.ts b/new-ui/projects/admin/src/http/config.ts index d7f24509..14ddc588 100644 --- a/new-ui/projects/admin/src/http/config.ts +++ b/new-ui/projects/admin/src/http/config.ts @@ -8,8 +8,9 @@ export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/admin/uploa export const instance = createInstance(import.meta.env.VITE_PROXY_BASE_URL) instance.interceptors.request.use((config) => { - config.headers[__AUTH_KEY] = localStorage.getItem(__AUTH_KEY); - config.headers["Authorization"] = localStorage.getItem(__AUTH_KEY); + const TOKEN = JSON.parse(localStorage.getItem(__AUTH_KEY))?.token + config.headers[__AUTH_KEY] = TOKEN; + config.headers["Authorization"] = TOKEN; return config; }); diff --git a/new-ui/projects/admin/src/http/login.ts b/new-ui/projects/admin/src/http/login.ts index 8e159b65..8b083330 100644 --- a/new-ui/projects/admin/src/http/login.ts +++ b/new-ui/projects/admin/src/http/login.ts @@ -1,9 +1,6 @@ import http from "@/http/config"; -export const userLogin = (data: { - username: string; - password: string; -}) => { +export const userLogin = (data) => { return http({ url: "/api/admin/login", method: "post", @@ -26,7 +23,7 @@ export const getSession = () => { }; -export const loginLog = (params?: Record) => { +export const loginLog = (params) => { return http({ url: "/api/admin/user/loginLog", method: "get", diff --git a/new-ui/projects/admin/src/main.ts b/new-ui/projects/admin/src/main.ts index 559f2f7c..08dba0c6 100644 --- a/new-ui/projects/admin/src/main.ts +++ b/new-ui/projects/admin/src/main.ts @@ -3,6 +3,8 @@ import { createPinia } from "pinia"; import ArcoVue from "@arco-design/web-vue"; import ArcoVueIcon from "@arco-design/web-vue/es/icon"; import "@arco-design/web-vue/dist/arco.css"; +import PermissionRender from "@/components/PermissionRender.vue"; +import { permission } from "@/directives/permission"; import App from "./App.vue"; import router from "./router"; @@ -14,6 +16,9 @@ app.use(router); app.use(ArcoVue); app.use(ArcoVueIcon); +app.component("PermissionRender", PermissionRender); +app.directive("permission", permission); + app.mount("#app"); app.config.warnHandler = (msg, vm, trace) => { if (msg.includes('Invalid prop name: "key" is a reserved property.')) { diff --git a/new-ui/projects/admin/src/router/index.ts b/new-ui/projects/admin/src/router/index.ts index a578cbd3..dabd58d1 100644 --- a/new-ui/projects/admin/src/router/index.ts +++ b/new-ui/projects/admin/src/router/index.ts @@ -1,14 +1,27 @@ import { createRouter, createWebHashHistory } from 'vue-router' import { useAuthStore } from "@/stores/auth"; import CustomLayout from '@/components/CustomLayout.vue' +import { hasPermission } from "@/directives/permission"; import menu from './menu' +declare module 'vue-router' { + interface RouteMeta { + title?: string + permission?: string + } +} + const whiteListRoutes = [ { path: "/login", name: "Login", component: () => import("@/views/LoginView.vue"), }, + { + path: "/403", + name: "403", + component: () => import("@/views/NoPermission.vue"), + }, { path: "/:pathMatch(.*)*", name: "404", @@ -44,9 +57,13 @@ router.beforeEach((to, _, next) => { return; } if (!authStore.token) { + authStore.$reset(); next({ name: "Login" }); return; } + if (to.meta.permission) { + next(!hasPermission(to.meta.permission) ? { name: "403" } : undefined); + } next(); }); diff --git a/new-ui/projects/admin/src/router/menu.ts b/new-ui/projects/admin/src/router/menu.ts index 926dac74..4f037cba 100644 --- a/new-ui/projects/admin/src/router/menu.ts +++ b/new-ui/projects/admin/src/router/menu.ts @@ -12,6 +12,7 @@ import { IconCodepen, IconWechatpay, IconRobot, + IconSafe, } from "@arco-design/web-vue/es/icon"; const menu = [ @@ -133,6 +134,15 @@ const menu = [ }, component: () => import("@/views/SysAdmin/SysAdminContainer.vue"), }, + { + path: "/sysPermission", + name: "SysPermission", + meta: { + title: "权限配置", + icon: IconSafe, + }, + component: () => import("@/views/SysPermission/SysPermissionContainer.vue"), + }, ]; export default menu; diff --git a/new-ui/projects/admin/src/stores/auth.ts b/new-ui/projects/admin/src/stores/auth.ts index 1e9e90b4..9027648a 100644 --- a/new-ui/projects/admin/src/stores/auth.ts +++ b/new-ui/projects/admin/src/stores/auth.ts @@ -3,19 +3,29 @@ import { Message } from '@arco-design/web-vue' import { userLogin, userLogout } from '@/http/login' import router from '@/router' +const defaultState: { + token: string + is_super_admin?: boolean; + permissions?: string[] +} = { + token: null, + is_super_admin: false, + permissions: [] +} + export const useAuthStore = defineStore({ - id: __AUTH_KEY, - state: () => ({ token: null } as { token: string | null }), + id: Symbol(__AUTH_KEY).toString(), + state: () => ({ ...defaultState }), actions: { init() { - this.$state.token = localStorage.getItem(__AUTH_KEY); + this.$state = JSON.parse(localStorage.getItem(__AUTH_KEY)); }, async login(params: any) { try { const { data } = await userLogin(params) if (data) { - this.$state.token = data; - localStorage.setItem(__AUTH_KEY, data) + this.$state = data; + localStorage.setItem(__AUTH_KEY, JSON.stringify(data)) Message.success('登录成功'); router.replace({ name: 'home' }) return Promise.resolve(data) diff --git a/new-ui/projects/admin/src/stores/counter.ts b/new-ui/projects/admin/src/stores/counter.ts deleted file mode 100644 index b6757ba5..00000000 --- a/new-ui/projects/admin/src/stores/counter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ref, computed } from 'vue' -import { defineStore } from 'pinia' - -export const useCounterStore = defineStore('counter', () => { - const count = ref(0) - const doubleCount = computed(() => count.value * 2) - function increment() { - count.value++ - } - - return { count, doubleCount, increment } -}) diff --git a/new-ui/projects/admin/src/views/NoPermission.vue b/new-ui/projects/admin/src/views/NoPermission.vue new file mode 100644 index 00000000..64b7e278 --- /dev/null +++ b/new-ui/projects/admin/src/views/NoPermission.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/new-ui/projects/admin/src/views/Order/OrderContainer.vue b/new-ui/projects/admin/src/views/Order/OrderContainer.vue index 9757d3b5..9358450c 100644 --- a/new-ui/projects/admin/src/views/Order/OrderContainer.vue +++ b/new-ui/projects/admin/src/views/Order/OrderContainer.vue @@ -1,8 +1,9 @@ - diff --git a/new-ui/projects/admin/src/views/Order/api.ts b/new-ui/projects/admin/src/views/Order/api.ts index 6bb73492..45abec0d 100644 --- a/new-ui/projects/admin/src/views/Order/api.ts +++ b/new-ui/projects/admin/src/views/Order/api.ts @@ -6,4 +6,12 @@ export const getList = (data?: Record) => { method: "post", data }) +} + +export const remove = (data) => { + return http({ + url: "/api/admin/order/remove", + method: "post", + data + }) } \ No newline at end of file diff --git a/new-ui/projects/admin/src/views/SysPermission/SysPermissionContainer.vue b/new-ui/projects/admin/src/views/SysPermission/SysPermissionContainer.vue new file mode 100644 index 00000000..4724ebbc --- /dev/null +++ b/new-ui/projects/admin/src/views/SysPermission/SysPermissionContainer.vue @@ -0,0 +1,82 @@ + + diff --git a/new-ui/projects/admin/src/views/SysPermission/SysPermissionForm.vue b/new-ui/projects/admin/src/views/SysPermission/SysPermissionForm.vue new file mode 100644 index 00000000..60fdf480 --- /dev/null +++ b/new-ui/projects/admin/src/views/SysPermission/SysPermissionForm.vue @@ -0,0 +1,63 @@ + + + diff --git a/new-ui/projects/admin/src/views/SysPermission/api.ts b/new-ui/projects/admin/src/views/SysPermission/api.ts new file mode 100644 index 00000000..49ba6b6e --- /dev/null +++ b/new-ui/projects/admin/src/views/SysPermission/api.ts @@ -0,0 +1,25 @@ +import http from "@/http/config"; + +export const getList = (params) => { + return http({ + url: "/api/admin/sysPermission/list", + method: "get", + params + }) +} + +export const save = (data) => { + return http({ + url: "/api/admin/sysPermission/save", + method: "post", + data + }) +} + +export const remove = (data) => { + return http({ + url: "/api/admin/sysPermission/remove", + method: "post", + data + }) +} \ No newline at end of file