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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/bin/create.mjs +221 -0
  4. package/bin/upgrade.mjs +40 -0
  5. package/jnrs-template-vue/.env.development +13 -0
  6. package/jnrs-template-vue/.env.production +4 -0
  7. package/jnrs-template-vue/.prettierrc.json +12 -0
  8. package/jnrs-template-vue/README.md +48 -0
  9. package/jnrs-template-vue/auto-imports.d.ts +17 -0
  10. package/jnrs-template-vue/components.d.ts +51 -0
  11. package/jnrs-template-vue/eslint.config.ts +40 -0
  12. package/jnrs-template-vue/index.html +13 -0
  13. package/jnrs-template-vue/package.json +55 -0
  14. package/jnrs-template-vue/public/favicon.ico +0 -0
  15. package/jnrs-template-vue/public/system/menu.json +137 -0
  16. package/jnrs-template-vue/src/App.vue +45 -0
  17. package/jnrs-template-vue/src/api/common/index.ts +28 -0
  18. package/jnrs-template-vue/src/api/demos/index.ts +155 -0
  19. package/jnrs-template-vue/src/api/request.ts +53 -0
  20. package/jnrs-template-vue/src/api/system/index.ts +107 -0
  21. package/jnrs-template-vue/src/api/user/index.ts +12 -0
  22. package/jnrs-template-vue/src/assets/fonts/.keep +0 -0
  23. package/jnrs-template-vue/src/assets/fonts/AlibabaPuHuiTi-Regular.woff2 +0 -0
  24. package/jnrs-template-vue/src/assets/fonts/AlimamaShuHeiTi-Bold.woff2 +0 -0
  25. package/jnrs-template-vue/src/assets/images/common/403.png +0 -0
  26. package/jnrs-template-vue/src/assets/images/common/404.png +0 -0
  27. package/jnrs-template-vue/src/assets/images/common/avatar.png +0 -0
  28. package/jnrs-template-vue/src/assets/images/common/jnrs-white.svg +1 -0
  29. package/jnrs-template-vue/src/assets/styles/animation.scss +0 -0
  30. package/jnrs-template-vue/src/assets/styles/common.scss +39 -0
  31. package/jnrs-template-vue/src/assets/styles/fonts.scss +27 -0
  32. package/jnrs-template-vue/src/assets/styles/index.scss +5 -0
  33. package/jnrs-template-vue/src/assets/styles/init.scss +41 -0
  34. package/jnrs-template-vue/src/assets/styles/root.scss +13 -0
  35. package/jnrs-template-vue/src/components/common/CardTable.vue +90 -0
  36. package/jnrs-template-vue/src/components/common/DictTag.vue +74 -0
  37. package/jnrs-template-vue/src/components/common/ImageView.vue +144 -0
  38. package/jnrs-template-vue/src/components/common/PdfView.vue +115 -0
  39. package/jnrs-template-vue/src/components/select/SelectManager.vue +26 -0
  40. package/jnrs-template-vue/src/directives/permissions.ts +28 -0
  41. package/jnrs-template-vue/src/layout/BlankLayout.vue +15 -0
  42. 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
  43. package/jnrs-template-vue/src/layout/RouterTabs.vue +142 -0
  44. package/jnrs-template-vue/src/layout/SideMenu.vue +208 -0
  45. package/jnrs-template-vue/src/layout/SideMenuItem.vue +38 -0
  46. package/jnrs-template-vue/src/layout/TopHeader.vue +184 -0
  47. package/jnrs-template-vue/src/layout/index.vue +71 -0
  48. package/jnrs-template-vue/src/locales/en.ts +14 -0
  49. package/jnrs-template-vue/src/locales/index.ts +23 -0
  50. package/jnrs-template-vue/src/locales/zhCn.ts +14 -0
  51. package/jnrs-template-vue/src/main.ts +31 -0
  52. package/jnrs-template-vue/src/router/index.ts +77 -0
  53. package/jnrs-template-vue/src/router/routes.ts +48 -0
  54. package/jnrs-template-vue/src/types/env.d.ts +12 -0
  55. package/jnrs-template-vue/src/types/index.ts +81 -0
  56. package/jnrs-template-vue/src/types/webSocket.ts +19 -0
  57. package/jnrs-template-vue/src/utils/file.ts +56 -0
  58. package/jnrs-template-vue/src/utils/packages.ts +116 -0
  59. package/jnrs-template-vue/src/utils/permissions.ts +16 -0
  60. package/jnrs-template-vue/src/views/common/403.vue +52 -0
  61. package/jnrs-template-vue/src/views/common/404.vue +52 -0
  62. package/jnrs-template-vue/src/views/demos/crud/index.vue +355 -0
  63. package/jnrs-template-vue/src/views/demos/simpleTable/index.vue +41 -0
  64. package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +137 -0
  65. package/jnrs-template-vue/src/views/demos/unitTest/index.vue +131 -0
  66. package/jnrs-template-vue/src/views/home/index.vue +9 -0
  67. package/jnrs-template-vue/src/views/lingshuSmart/editorPage.vue +9 -0
  68. package/jnrs-template-vue/src/views/login/index.vue +314 -0
  69. package/jnrs-template-vue/src/views/system/dict/index.vue +161 -0
  70. package/jnrs-template-vue/src/views/system/menu/index.vue +43 -0
  71. package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +108 -0
  72. package/jnrs-template-vue/src/views/system/mine/index.vue +83 -0
  73. package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +105 -0
  74. package/jnrs-template-vue/src/views/system/role/editDialog.vue +94 -0
  75. package/jnrs-template-vue/src/views/system/role/index.vue +41 -0
  76. package/jnrs-template-vue/src/views/visual/index.vue +143 -0
  77. package/jnrs-template-vue/tsconfig.json +25 -0
  78. package/jnrs-template-vue/vite.config.ts +71 -0
  79. package/jnrs-template-vue/viteMockServe/fail.ts +38 -0
  80. package/jnrs-template-vue/viteMockServe/file/mock-pdf.pdf +0 -0
  81. package/jnrs-template-vue/viteMockServe/file/mock-png-0.png +0 -0
  82. package/jnrs-template-vue/viteMockServe/file/mock-png-1.png +0 -0
  83. package/jnrs-template-vue/viteMockServe/file.ts +67 -0
  84. package/jnrs-template-vue/viteMockServe/index.ts +87 -0
  85. package/jnrs-template-vue/viteMockServe/json/detailsRes.json +56 -0
  86. package/jnrs-template-vue/viteMockServe/json/dictItemRes.json +27 -0
  87. package/jnrs-template-vue/viteMockServe/json/dictRes.json +21 -0
  88. package/jnrs-template-vue/viteMockServe/json/loginRes_admin.json +157 -0
  89. package/jnrs-template-vue/viteMockServe/json/loginRes_user.json +713 -0
  90. package/jnrs-template-vue/viteMockServe/json/roleRes.json +37 -0
  91. package/jnrs-template-vue/viteMockServe/json/tableRes.json +390 -0
  92. package/jnrs-template-vue/viteMockServe/success.ts +39 -0
  93. 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,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ // 扩展 ImportMeta 接口
4
+ interface ImportMeta {
5
+ readonly env: ImportMetaEnv
6
+ }
7
+
8
+ interface ImportMetaEnv {
9
+ readonly VITE_USE_MOCK: string
10
+ readonly VITE_BASE_URL: string
11
+ readonly VITE_APP_PORT: string
12
+ }
@@ -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>