create-jnrs-vue 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/bin/create.mjs +221 -0
- package/bin/upgrade.mjs +40 -0
- package/jnrs-template-vue/.env.development +13 -0
- package/jnrs-template-vue/.env.production +4 -0
- package/jnrs-template-vue/.prettierrc.json +12 -0
- package/jnrs-template-vue/README.md +48 -0
- package/jnrs-template-vue/auto-imports.d.ts +17 -0
- package/jnrs-template-vue/components.d.ts +51 -0
- package/jnrs-template-vue/eslint.config.ts +40 -0
- package/jnrs-template-vue/index.html +13 -0
- package/jnrs-template-vue/package.json +55 -0
- package/jnrs-template-vue/public/favicon.ico +0 -0
- package/jnrs-template-vue/public/system/menu.json +137 -0
- package/jnrs-template-vue/src/App.vue +45 -0
- package/jnrs-template-vue/src/api/common/index.ts +28 -0
- package/jnrs-template-vue/src/api/demos/index.ts +155 -0
- package/jnrs-template-vue/src/api/request.ts +53 -0
- package/jnrs-template-vue/src/api/system/index.ts +107 -0
- package/jnrs-template-vue/src/api/user/index.ts +12 -0
- package/jnrs-template-vue/src/assets/fonts/.keep +0 -0
- package/jnrs-template-vue/src/assets/fonts/AlibabaPuHuiTi-Regular.woff2 +0 -0
- package/jnrs-template-vue/src/assets/fonts/AlimamaShuHeiTi-Bold.woff2 +0 -0
- package/jnrs-template-vue/src/assets/images/common/403.png +0 -0
- package/jnrs-template-vue/src/assets/images/common/404.png +0 -0
- package/jnrs-template-vue/src/assets/images/common/avatar.png +0 -0
- package/jnrs-template-vue/src/assets/images/common/jnrs-white.svg +1 -0
- package/jnrs-template-vue/src/assets/styles/animation.scss +0 -0
- package/jnrs-template-vue/src/assets/styles/common.scss +39 -0
- package/jnrs-template-vue/src/assets/styles/fonts.scss +27 -0
- package/jnrs-template-vue/src/assets/styles/index.scss +5 -0
- package/jnrs-template-vue/src/assets/styles/init.scss +41 -0
- package/jnrs-template-vue/src/assets/styles/root.scss +13 -0
- package/jnrs-template-vue/src/components/common/CardTable.vue +90 -0
- package/jnrs-template-vue/src/components/common/DictTag.vue +74 -0
- package/jnrs-template-vue/src/components/common/ImageView.vue +144 -0
- package/jnrs-template-vue/src/components/common/PdfView.vue +115 -0
- package/jnrs-template-vue/src/components/select/SelectManager.vue +26 -0
- package/jnrs-template-vue/src/directives/permissions.ts +28 -0
- package/jnrs-template-vue/src/layout/BlankLayout.vue +15 -0
- package/jnrs-template-vue/src/layout/RouterTabs /344/277/256/345/244/215/350/267/257/347/224/261/350/267/263/350/275/254/346/220/272/345/270/246/345/217/202/346/225/260/351/227/256/351/242/230.vue" +150 -0
- package/jnrs-template-vue/src/layout/RouterTabs.vue +142 -0
- package/jnrs-template-vue/src/layout/SideMenu.vue +208 -0
- package/jnrs-template-vue/src/layout/SideMenuItem.vue +38 -0
- package/jnrs-template-vue/src/layout/TopHeader.vue +184 -0
- package/jnrs-template-vue/src/layout/index.vue +71 -0
- package/jnrs-template-vue/src/locales/en.ts +14 -0
- package/jnrs-template-vue/src/locales/index.ts +23 -0
- package/jnrs-template-vue/src/locales/zhCn.ts +14 -0
- package/jnrs-template-vue/src/main.ts +31 -0
- package/jnrs-template-vue/src/router/index.ts +77 -0
- package/jnrs-template-vue/src/router/routes.ts +48 -0
- package/jnrs-template-vue/src/types/env.d.ts +12 -0
- package/jnrs-template-vue/src/types/index.ts +81 -0
- package/jnrs-template-vue/src/types/webSocket.ts +19 -0
- package/jnrs-template-vue/src/utils/file.ts +56 -0
- package/jnrs-template-vue/src/utils/packages.ts +116 -0
- package/jnrs-template-vue/src/utils/permissions.ts +16 -0
- package/jnrs-template-vue/src/views/common/403.vue +52 -0
- package/jnrs-template-vue/src/views/common/404.vue +52 -0
- package/jnrs-template-vue/src/views/demos/crud/index.vue +355 -0
- package/jnrs-template-vue/src/views/demos/simpleTable/index.vue +41 -0
- package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +137 -0
- package/jnrs-template-vue/src/views/demos/unitTest/index.vue +131 -0
- package/jnrs-template-vue/src/views/home/index.vue +9 -0
- package/jnrs-template-vue/src/views/lingshuSmart/editorPage.vue +9 -0
- package/jnrs-template-vue/src/views/login/index.vue +314 -0
- package/jnrs-template-vue/src/views/system/dict/index.vue +161 -0
- package/jnrs-template-vue/src/views/system/menu/index.vue +43 -0
- package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +108 -0
- package/jnrs-template-vue/src/views/system/mine/index.vue +83 -0
- package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +105 -0
- package/jnrs-template-vue/src/views/system/role/editDialog.vue +94 -0
- package/jnrs-template-vue/src/views/system/role/index.vue +41 -0
- package/jnrs-template-vue/src/views/visual/index.vue +143 -0
- package/jnrs-template-vue/tsconfig.json +25 -0
- package/jnrs-template-vue/vite.config.ts +71 -0
- package/jnrs-template-vue/viteMockServe/fail.ts +38 -0
- package/jnrs-template-vue/viteMockServe/file/mock-pdf.pdf +0 -0
- package/jnrs-template-vue/viteMockServe/file/mock-png-0.png +0 -0
- package/jnrs-template-vue/viteMockServe/file/mock-png-1.png +0 -0
- package/jnrs-template-vue/viteMockServe/file.ts +67 -0
- package/jnrs-template-vue/viteMockServe/index.ts +87 -0
- package/jnrs-template-vue/viteMockServe/json/detailsRes.json +56 -0
- package/jnrs-template-vue/viteMockServe/json/dictItemRes.json +27 -0
- package/jnrs-template-vue/viteMockServe/json/dictRes.json +21 -0
- package/jnrs-template-vue/viteMockServe/json/loginRes_admin.json +157 -0
- package/jnrs-template-vue/viteMockServe/json/loginRes_user.json +713 -0
- package/jnrs-template-vue/viteMockServe/json/roleRes.json +37 -0
- package/jnrs-template-vue/viteMockServe/json/tableRes.json +390 -0
- package/jnrs-template-vue/viteMockServe/success.ts +39 -0
- package/package.json +41 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import 'element-plus/dist/index.css'
|
|
2
|
+
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
3
|
+
import '@jnrs/shared/styles/theme.scss'
|
|
4
|
+
import '@/assets/styles/index.scss'
|
|
5
|
+
|
|
6
|
+
import { createApp } from 'vue'
|
|
7
|
+
import { createPinia } from 'pinia'
|
|
8
|
+
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
9
|
+
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
10
|
+
import App from './App.vue'
|
|
11
|
+
import { router } from './router'
|
|
12
|
+
import locales from './locales/index'
|
|
13
|
+
import { permissions } from './directives/permissions'
|
|
14
|
+
|
|
15
|
+
const app = createApp(App)
|
|
16
|
+
|
|
17
|
+
app.directive('permissions', permissions)
|
|
18
|
+
|
|
19
|
+
// element-plus 自动引入所有图标
|
|
20
|
+
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
21
|
+
app.component(key, component)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// pinia 相关
|
|
25
|
+
const pinia = createPinia()
|
|
26
|
+
pinia.use(piniaPluginPersistedstate)
|
|
27
|
+
|
|
28
|
+
app.use(pinia)
|
|
29
|
+
app.use(router)
|
|
30
|
+
app.use(locales)
|
|
31
|
+
app.mount('#app')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FileModules, RouteLocationNormalizedGeneric } from '@jnrs/vue-core/router'
|
|
2
|
+
import type { MenuItem } from '@jnrs/vue-core'
|
|
3
|
+
import { LAYOUT_NAME, GLOBAL_COMPONENT, routes } from './routes'
|
|
4
|
+
import { createVueRouter } from '@jnrs/vue-core/router'
|
|
5
|
+
import { useAuthStore, useMenuStore } from '@jnrs/vue-core/pinia'
|
|
6
|
+
import { MenuApi } from '@/api/system'
|
|
7
|
+
import { hasMenuViewPermission } from '@/utils/permissions'
|
|
8
|
+
|
|
9
|
+
const fileModules = import.meta.glob('/src/views/**/*.vue') as FileModules
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 递归过滤菜单树,仅保留当前用户有权限访问的节点
|
|
13
|
+
* TODO 如果是后端返回的数据就没必要判断了,后端会判断
|
|
14
|
+
*/
|
|
15
|
+
function filterMenuByPermission(menus: MenuItem[]): MenuItem[] {
|
|
16
|
+
return menus
|
|
17
|
+
.map((item) => {
|
|
18
|
+
const children = item.children ? filterMenuByPermission(item.children) : []
|
|
19
|
+
|
|
20
|
+
const hasSelfPermission = hasMenuViewPermission(item.meta?.permissions)
|
|
21
|
+
|
|
22
|
+
if (hasSelfPermission) {
|
|
23
|
+
return { ...item, ...(children.length > 0 ? { children } : {}) }
|
|
24
|
+
}
|
|
25
|
+
if (children.length > 0) {
|
|
26
|
+
return { ...item, children }
|
|
27
|
+
}
|
|
28
|
+
return null
|
|
29
|
+
})
|
|
30
|
+
.filter(Boolean) as MenuItem[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const router = createVueRouter({
|
|
34
|
+
options: {
|
|
35
|
+
routes
|
|
36
|
+
},
|
|
37
|
+
fileModules,
|
|
38
|
+
layoutName: LAYOUT_NAME,
|
|
39
|
+
globalComponent: GLOBAL_COMPONENT,
|
|
40
|
+
handleBeforeEach: async (to: RouteLocationNormalizedGeneric) => {
|
|
41
|
+
// 如果是不需要[身份验证]的页面,直接放行
|
|
42
|
+
if (to.meta.noAuth) return true
|
|
43
|
+
|
|
44
|
+
// 页面刷新处理:未[身份验证]时,重定向到[身份验证]页
|
|
45
|
+
const { hasAuthenticated } = useAuthStore()
|
|
46
|
+
if (!hasAuthenticated) {
|
|
47
|
+
return { name: 'Login', query: { redirect: to.path }, replace: true }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 校验访问权限
|
|
51
|
+
if (to.meta.permissions && !hasMenuViewPermission(to.meta.permissions)) {
|
|
52
|
+
return { name: '403' }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 页面刷新处理:菜单未获取时,先获取菜单,再重定向当前路由,即重新开始进入 handleBeforeEach
|
|
56
|
+
const { hasFetchedAsyncMenus, asyncSetMenus } = useMenuStore()
|
|
57
|
+
if (!hasFetchedAsyncMenus) {
|
|
58
|
+
try {
|
|
59
|
+
const menuRes = await MenuApi()
|
|
60
|
+
/**
|
|
61
|
+
* 校验用户的菜单访问权限,进行菜单筛选
|
|
62
|
+
* TODO 如果是后端返回的数据就没必要校验筛选了,后端会筛选
|
|
63
|
+
*/
|
|
64
|
+
const perRes = filterMenuByPermission(menuRes)
|
|
65
|
+
|
|
66
|
+
await asyncSetMenus(perRes)
|
|
67
|
+
return to.fullPath
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw error
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export { router }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author : TanRui
|
|
3
|
+
* @WeChat : Tan578853789
|
|
4
|
+
* @File : routes.ts
|
|
5
|
+
* @Date : 2025/10/07
|
|
6
|
+
* @Desc. : 基础路由配置
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const LAYOUT_NAME = 'Layout'
|
|
10
|
+
export const GLOBAL_COMPONENT = import('@/layout/BlankLayout.vue')
|
|
11
|
+
|
|
12
|
+
export const routes = [
|
|
13
|
+
{
|
|
14
|
+
name: '403',
|
|
15
|
+
path: '/403',
|
|
16
|
+
component: () => import('@/views/common/403.vue'),
|
|
17
|
+
meta: { title: '403', noAuth: true, global: true }
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: '404',
|
|
21
|
+
path: '/:pathMatch(.*)*',
|
|
22
|
+
component: () => import('@/views/common/404.vue'),
|
|
23
|
+
meta: { title: '404', global: true }
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'Login',
|
|
27
|
+
path: '/login',
|
|
28
|
+
component: () => import('@/views/login/index.vue'),
|
|
29
|
+
meta: { title: '登录', noAuth: true, global: true }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: LAYOUT_NAME,
|
|
33
|
+
path: '/',
|
|
34
|
+
component: () => import('@/layout/index.vue'),
|
|
35
|
+
meta: { title: '框架' },
|
|
36
|
+
children: [
|
|
37
|
+
{
|
|
38
|
+
path: '/',
|
|
39
|
+
name: 'Home',
|
|
40
|
+
meta: {
|
|
41
|
+
title: '工作台',
|
|
42
|
+
icon: 'HomeFilled'
|
|
43
|
+
},
|
|
44
|
+
component: () => import('@/views/home/index.vue')
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件信息
|
|
3
|
+
*/
|
|
4
|
+
export interface Attachment {
|
|
5
|
+
/**
|
|
6
|
+
* 数据 id
|
|
7
|
+
*/
|
|
8
|
+
id: number
|
|
9
|
+
/**
|
|
10
|
+
* 文件 id
|
|
11
|
+
*/
|
|
12
|
+
documentId: number
|
|
13
|
+
/**
|
|
14
|
+
* 文件名
|
|
15
|
+
*/
|
|
16
|
+
fileName: string
|
|
17
|
+
/**
|
|
18
|
+
* 文件名唯一标识(用于文件获取 API)
|
|
19
|
+
*/
|
|
20
|
+
uniqueFileName: string
|
|
21
|
+
/**
|
|
22
|
+
* MIME 类型
|
|
23
|
+
*/
|
|
24
|
+
fileType: string
|
|
25
|
+
/**
|
|
26
|
+
* 文件大小 单位:字节(Bytes)
|
|
27
|
+
*/
|
|
28
|
+
fileSize: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 文件容器(用于图片或附件)
|
|
33
|
+
*/
|
|
34
|
+
export interface Document {
|
|
35
|
+
id: number
|
|
36
|
+
description: string | null
|
|
37
|
+
attachments: Attachment[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 图片 & 附件数据
|
|
42
|
+
*/
|
|
43
|
+
export interface FileContainer {
|
|
44
|
+
/**
|
|
45
|
+
* 图片
|
|
46
|
+
*/
|
|
47
|
+
imageDocument?: Document
|
|
48
|
+
/**
|
|
49
|
+
* 附件
|
|
50
|
+
*/
|
|
51
|
+
attachmentDocument?: Document
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 分页
|
|
56
|
+
*/
|
|
57
|
+
export interface Pagination {
|
|
58
|
+
/**
|
|
59
|
+
* 当前页码
|
|
60
|
+
*/
|
|
61
|
+
pageNo: number
|
|
62
|
+
/**
|
|
63
|
+
* 每页大小
|
|
64
|
+
*/
|
|
65
|
+
pageSize: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 分页列表数据
|
|
70
|
+
*/
|
|
71
|
+
// eslint-disable-next-line
|
|
72
|
+
export interface PageTableData<T = Record<string, any>> extends Pagination {
|
|
73
|
+
/**
|
|
74
|
+
* 数据列表
|
|
75
|
+
*/
|
|
76
|
+
list: T[]
|
|
77
|
+
/**
|
|
78
|
+
* 数据总数
|
|
79
|
+
*/
|
|
80
|
+
count: number
|
|
81
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MsgIdMessage {
|
|
2
|
+
msgId: string
|
|
3
|
+
d: unknown
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface TypeDataMessage {
|
|
7
|
+
type: string | number
|
|
8
|
+
data: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 类型守卫函数 MsgId 结构
|
|
12
|
+
export function isMsgIdMessage(msg: unknown): msg is MsgIdMessage {
|
|
13
|
+
return typeof msg === 'object' && msg !== null && 'msgId' in msg && 'd' in msg
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 类型守卫函数 Type 结构
|
|
17
|
+
export function isTypeDataMessage(msg: unknown): msg is TypeDataMessage {
|
|
18
|
+
return typeof msg === 'object' && msg !== null && 'type' in msg && 'data' in msg
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author : TanRui
|
|
3
|
+
* @WeChat : Tan578853789
|
|
4
|
+
* @File : file.ts
|
|
5
|
+
* @Date : 2025/10/16
|
|
6
|
+
* @Desc. : 文件处理相关函数
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useObjectUrl } from '@vueuse/core'
|
|
10
|
+
import { FileApi } from '@/api/common'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 根据文件下载接口返回的二进制数据生成 URL(用于图片、PDF预览)
|
|
14
|
+
* @param uniqueFileName 文件唯一名称
|
|
15
|
+
* @returns 响应式文件 URL
|
|
16
|
+
*/
|
|
17
|
+
export const getFileUrl = async (uniqueFileName: string) => {
|
|
18
|
+
const blob = await FileApi(uniqueFileName)
|
|
19
|
+
return useObjectUrl(blob)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 从可能为原始值或对象的输入中提取指定字段的值
|
|
24
|
+
* @param value - 输入值(可能是 string/number/对象)
|
|
25
|
+
* @param fieldName - 要提取的对象字段名(默认 'id')
|
|
26
|
+
* @returns 提取出的 string | number 值,或 undefined
|
|
27
|
+
*/
|
|
28
|
+
export function extractFieldId(value: unknown, fieldName: string = 'id'): string | number | undefined {
|
|
29
|
+
// 1. 排除 null 和 undefined
|
|
30
|
+
if (value == null) return undefined
|
|
31
|
+
|
|
32
|
+
// 2. 如果是原始 ID 类型,直接返回
|
|
33
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
34
|
+
return value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 3. 必须是普通对象(非数组、非 Date 等)
|
|
38
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 4. 检查对象是否包含目标字段
|
|
43
|
+
if (!(fieldName in value)) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 5. 安全获取字段值(使用 keyof 断言确保类型安全)
|
|
48
|
+
const fieldValue = (value as Record<string, unknown>)[fieldName]
|
|
49
|
+
|
|
50
|
+
// 6. 只接受 string 或 number 类型
|
|
51
|
+
if (typeof fieldValue === 'string' || typeof fieldValue === 'number') {
|
|
52
|
+
return fieldValue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author : TanRui
|
|
3
|
+
* @WeChat : Tan578853789
|
|
4
|
+
* @File : common.ts
|
|
5
|
+
* @Date : 2025/12/17
|
|
6
|
+
* @Desc. : 对 packages 的一些公用方法进行封装
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ElMessage, ElLoading } from 'element-plus'
|
|
10
|
+
import {
|
|
11
|
+
getDictList as _getDictList,
|
|
12
|
+
getDictLabel as _getDictLabel,
|
|
13
|
+
getDictValue as _getDictValue,
|
|
14
|
+
getDictColor as _getDictColor,
|
|
15
|
+
downloadByBlob as _downloadByBlob
|
|
16
|
+
} from '@jnrs/shared'
|
|
17
|
+
import { useAuthStore } from '@jnrs/vue-core/pinia'
|
|
18
|
+
import { objectToFormData as _objectToFormData } from '@jnrs/shared'
|
|
19
|
+
import { FileApi } from '@/api/common'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 根据字典名称获取字典数据
|
|
23
|
+
* @param name 字典名称
|
|
24
|
+
* @returns 某字典名称所对应的字典列表数据
|
|
25
|
+
*/
|
|
26
|
+
export const getDictList = (name: string) => {
|
|
27
|
+
const { dict } = useAuthStore()
|
|
28
|
+
if (dict) {
|
|
29
|
+
return _getDictList(name, dict)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 根据字典名称和字典值获取字典标签
|
|
35
|
+
* @param name 字典名称
|
|
36
|
+
* @param value 字典值
|
|
37
|
+
* @returns 某字典名称所对应的字典标签
|
|
38
|
+
*/
|
|
39
|
+
export const getDictLabel = (name: string, value: string | number) => {
|
|
40
|
+
const { dict } = useAuthStore()
|
|
41
|
+
if (dict) {
|
|
42
|
+
return _getDictLabel(name, value, dict)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 根据字典名称和字典标签获取字典值
|
|
48
|
+
* @param name 字典名称
|
|
49
|
+
* @param label 字典标签
|
|
50
|
+
* @returns 某字典名称所对应的字典值
|
|
51
|
+
*/
|
|
52
|
+
export const getDictValue = (name: string, label: string) => {
|
|
53
|
+
const { dict } = useAuthStore()
|
|
54
|
+
if (dict) {
|
|
55
|
+
return _getDictValue(name, label, dict)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 根据字典名称和字典值获取字典颜色
|
|
61
|
+
* @param name 字典名称
|
|
62
|
+
* @param value 字典值
|
|
63
|
+
* @returns 某字典名称所对应的字典颜色
|
|
64
|
+
*/
|
|
65
|
+
export const getDictColor = (name: string, value: string | number) => {
|
|
66
|
+
const { dict } = useAuthStore()
|
|
67
|
+
if (dict) {
|
|
68
|
+
return _getDictColor(name, value, dict)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 将对象转为 FormData
|
|
74
|
+
* @param obj 对象
|
|
75
|
+
* @returns FormData
|
|
76
|
+
*/
|
|
77
|
+
export const objectToFormData = (obj: Record<string, unknown>) => {
|
|
78
|
+
// 根据后端返回结果处理的映射关系
|
|
79
|
+
const mapConfig = {
|
|
80
|
+
// 图片
|
|
81
|
+
newImageFiles: { finallyKey: 'orginImageNames', valueKey: 'uniqueFileName' },
|
|
82
|
+
// 通用附件
|
|
83
|
+
newAttachmentFile: { finallyKey: 'orginAttachmentName', valueKey: 'uniqueFileName' },
|
|
84
|
+
// 程序文件
|
|
85
|
+
newProgramFile: { finallyKey: 'orginProgramName', valueKey: 'uniqueFileName' },
|
|
86
|
+
// 数据库
|
|
87
|
+
databaseFile: { finallyKey: 'databaseFile', valueKey: 'uniqueFileName' }
|
|
88
|
+
}
|
|
89
|
+
return _objectToFormData(obj, mapConfig)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 根据文件下载接口返回的二进制数据进行下载
|
|
94
|
+
* @param uniqueFileName 后端存储的唯一文件名
|
|
95
|
+
* @param filename 下载文件显示的文件名
|
|
96
|
+
* @returns loading 下载状态
|
|
97
|
+
*/
|
|
98
|
+
export const downloadFile = async (uniqueFileName: string, filename?: string) => {
|
|
99
|
+
const elLoading = ElLoading.service({
|
|
100
|
+
lock: true,
|
|
101
|
+
text: '文件下载中...',
|
|
102
|
+
background: 'rgba(0, 0, 0, 0.5)'
|
|
103
|
+
})
|
|
104
|
+
let loading = true
|
|
105
|
+
try {
|
|
106
|
+
const blob = await FileApi(uniqueFileName)
|
|
107
|
+
_downloadByBlob(blob, filename || uniqueFileName)
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(error)
|
|
110
|
+
ElMessage.warning('文件下载失败')
|
|
111
|
+
} finally {
|
|
112
|
+
elLoading.close()
|
|
113
|
+
loading = false
|
|
114
|
+
}
|
|
115
|
+
return loading
|
|
116
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hasPermissionWithSuffix } from '@jnrs/shared'
|
|
2
|
+
import { useAuthStore } from '@jnrs/vue-core/pinia'
|
|
3
|
+
|
|
4
|
+
// 菜单查看权限
|
|
5
|
+
export function hasMenuViewPermission(requiredPerms: string[] | undefined) {
|
|
6
|
+
const { userInfo } = useAuthStore()
|
|
7
|
+
const userPerms = userInfo?.permissions || []
|
|
8
|
+
return hasPermissionWithSuffix(requiredPerms, userPerms, ':view', 'some')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 按钮操作权限
|
|
12
|
+
export function hasPermission(requiredPerms: string[] | undefined) {
|
|
13
|
+
const { userInfo } = useAuthStore()
|
|
14
|
+
const userPerms = userInfo?.permissions || []
|
|
15
|
+
return hasPermissionWithSuffix(requiredPerms, userPerms)
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { router } from '@/router'
|
|
3
|
+
import { handleRouter } from '@jnrs/vue-core/router'
|
|
4
|
+
|
|
5
|
+
const goLogin = () => {
|
|
6
|
+
handleRouter({ name: 'Login' }, 'replace')
|
|
7
|
+
}
|
|
8
|
+
const goHome = () => {
|
|
9
|
+
handleRouter({ path: '/' }, 'replace')
|
|
10
|
+
}
|
|
11
|
+
const goBack = () => {
|
|
12
|
+
router.back()
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="main">
|
|
18
|
+
<div class="main_mid">
|
|
19
|
+
<img alt="404" src="@/assets/images/common/403.png" />
|
|
20
|
+
<h3>抱歉,您访问的资源权限受限</h3>
|
|
21
|
+
<div class="main_mid_btn">
|
|
22
|
+
<el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
|
|
23
|
+
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
|
|
24
|
+
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style lang="scss" scoped>
|
|
31
|
+
.main {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 100vh;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
background: var(--jnrs-background-primary);
|
|
39
|
+
.main_mid {
|
|
40
|
+
text-align: center;
|
|
41
|
+
img {
|
|
42
|
+
width: 300px;
|
|
43
|
+
}
|
|
44
|
+
h3 {
|
|
45
|
+
color: var(--jnrs-font-primary);
|
|
46
|
+
}
|
|
47
|
+
.main_mid_btn {
|
|
48
|
+
margin-top: 30px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { router } from '@/router'
|
|
3
|
+
import { handleRouter } from '@jnrs/vue-core/router'
|
|
4
|
+
|
|
5
|
+
const goLogin = () => {
|
|
6
|
+
handleRouter({ name: 'Login' }, 'replace')
|
|
7
|
+
}
|
|
8
|
+
const goHome = () => {
|
|
9
|
+
handleRouter({ path: '/' }, 'replace')
|
|
10
|
+
}
|
|
11
|
+
const goBack = () => {
|
|
12
|
+
router.back()
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="main">
|
|
18
|
+
<div class="main_mid">
|
|
19
|
+
<img alt="404" src="@/assets/images/common/404.png" />
|
|
20
|
+
<h3>抱歉,您访问的页面不存在</h3>
|
|
21
|
+
<div class="main_mid_btn">
|
|
22
|
+
<el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
|
|
23
|
+
<el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
|
|
24
|
+
<el-button type="primary" @click="goBack">返回上一页</el-button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style lang="scss" scoped>
|
|
31
|
+
.main {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 100vh;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
background: var(--jnrs-background-primary);
|
|
39
|
+
.main_mid {
|
|
40
|
+
text-align: center;
|
|
41
|
+
img {
|
|
42
|
+
width: 300px;
|
|
43
|
+
}
|
|
44
|
+
h3 {
|
|
45
|
+
color: var(--jnrs-font-primary);
|
|
46
|
+
}
|
|
47
|
+
.main_mid_btn {
|
|
48
|
+
margin-top: 30px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</style>
|