create-jnrs-template-vue 1.2.3 → 1.2.4
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/jnrs-template-vue/.env.development +1 -1
- package/jnrs-template-vue/index.html +1 -1
- package/jnrs-template-vue/package.json +3 -3
- package/jnrs-template-vue/public/system/menu.json +11 -2
- package/jnrs-template-vue/src/api/demos/index.ts +17 -10
- package/jnrs-template-vue/src/api/system/index.ts +11 -1
- package/jnrs-template-vue/src/assets/styles/index.scss +0 -24
- package/jnrs-template-vue/src/assets/styles/init.scss +24 -0
- package/jnrs-template-vue/src/assets/styles/root.scss +4 -0
- package/jnrs-template-vue/src/components/common/CardTable.vue +89 -0
- package/jnrs-template-vue/src/components/common/DictTag.vue +8 -4
- package/jnrs-template-vue/src/components/common/ImageView.vue +16 -7
- package/jnrs-template-vue/src/components/common/PdfView.vue +14 -5
- package/jnrs-template-vue/src/components/select/SelectManager.vue +2 -2
- package/jnrs-template-vue/src/layout/SideMenu.vue +0 -1
- package/jnrs-template-vue/src/layout/TopHeader.vue +7 -14
- package/jnrs-template-vue/src/types/webSocket.ts +19 -0
- package/jnrs-template-vue/src/utils/file.ts +36 -1
- package/jnrs-template-vue/src/views/demos/crud/index.vue +24 -36
- package/jnrs-template-vue/src/views/demos/simpleTable/index.vue +41 -0
- package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +2 -2
- package/jnrs-template-vue/src/views/login/index.vue +18 -15
- package/jnrs-template-vue/src/views/system/dict/index.vue +63 -76
- package/jnrs-template-vue/src/views/system/menu/index.vue +42 -54
- package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +26 -59
- package/jnrs-template-vue/src/views/system/role/index.vue +20 -29
- package/jnrs-template-vue/src/views/visual/index.vue +130 -15
- package/package.json +1 -1
- package/jnrs-template-vue/src/composables/base/useAvatar.ts +0 -36
- package/jnrs-template-vue/src/composables/tools/useMouseSelection.ts +0 -150
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jnrs-template-vue",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "JNRS 信息化管理系统模板",
|
|
5
5
|
"author": "Talia-Tan",
|
|
6
6
|
"private": true,
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@element-plus/icons-vue": "^2.3.2",
|
|
22
|
-
"@jnrs/shared": "1.1.
|
|
23
|
-
"@jnrs/vue-core": "1.2.
|
|
22
|
+
"@jnrs/shared": "1.1.9",
|
|
23
|
+
"@jnrs/vue-core": "1.2.4",
|
|
24
24
|
"@vueuse/core": "^14.1.0",
|
|
25
25
|
"element-plus": "^2.11.9",
|
|
26
26
|
"pinia": "^3.0.4",
|
|
@@ -15,16 +15,25 @@
|
|
|
15
15
|
"path": "/unitTest",
|
|
16
16
|
"name": "UnitTest",
|
|
17
17
|
"meta": {
|
|
18
|
-
"title": "
|
|
18
|
+
"title": "基础功能测试",
|
|
19
19
|
"todoCount": 8
|
|
20
20
|
},
|
|
21
21
|
"component": "/demos/unitTest/index"
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
"path": "/simpleTable",
|
|
25
|
+
"name": "SimpleTable",
|
|
26
|
+
"meta": {
|
|
27
|
+
"title": "简单数据表格模板",
|
|
28
|
+
"todoCount": 0
|
|
29
|
+
},
|
|
30
|
+
"component": "/demos/simpleTable/index"
|
|
31
|
+
},
|
|
23
32
|
{
|
|
24
33
|
"path": "/crud",
|
|
25
34
|
"name": "Crud",
|
|
26
35
|
"meta": {
|
|
27
|
-
"title": "
|
|
36
|
+
"title": "完整增删改查模板",
|
|
28
37
|
"todoCount": 0
|
|
29
38
|
},
|
|
30
39
|
"component": "/demos/crud/index"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Pagination, PageTableData, FileContainer } from '@/types'
|
|
2
2
|
import { axiosRequest } from '@jnrs/vue-core/request'
|
|
3
|
+
import { extractFieldId } from '@/utils/file'
|
|
4
|
+
import { objectToFormData } from '@/utils/packages'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* 项目
|
|
@@ -26,6 +28,7 @@ export interface ProjectItem extends FileContainer {
|
|
|
26
28
|
type AddProjectOmitKeys =
|
|
27
29
|
| 'id'
|
|
28
30
|
| 'programCode'
|
|
31
|
+
| 'program'
|
|
29
32
|
| 'code'
|
|
30
33
|
| 'manager'
|
|
31
34
|
| 'plannedFinishDate'
|
|
@@ -36,7 +39,7 @@ type AddProjectOmitKeys =
|
|
|
36
39
|
|
|
37
40
|
export type AddProjectItem = Omit<ProjectItem, AddProjectOmitKeys> & {
|
|
38
41
|
id?: string
|
|
39
|
-
managerId?: Record<string, unknown>
|
|
42
|
+
managerId?: Record<string, unknown> | unknown
|
|
40
43
|
newAttachmentFile: []
|
|
41
44
|
newImageFiles: []
|
|
42
45
|
}
|
|
@@ -64,7 +67,7 @@ export const NotFoundApi = () => {
|
|
|
64
67
|
* 测试 无权限
|
|
65
68
|
* @param showErrorMsg 是否显示错误信息
|
|
66
69
|
*/
|
|
67
|
-
export const
|
|
70
|
+
export const NoAuthApi = (showErrorMsg: boolean) => {
|
|
68
71
|
return axiosRequest({
|
|
69
72
|
url: '/mock/auth/no',
|
|
70
73
|
method: 'post',
|
|
@@ -83,31 +86,35 @@ export const DetailsApi = (): Promise<FileContainer> => {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
|
-
*
|
|
89
|
+
* 表单新增(包含文件上传类需使用 formData 类型)
|
|
87
90
|
*/
|
|
88
|
-
export const
|
|
91
|
+
export const EditApi = (data: AddProjectItem) => {
|
|
92
|
+
const formData = objectToFormData({
|
|
93
|
+
...data,
|
|
94
|
+
managerId: extractFieldId(data.managerId, 'managerId')
|
|
95
|
+
})
|
|
89
96
|
return axiosRequest({
|
|
90
97
|
url: '/mock/demos/save',
|
|
91
98
|
method: 'post',
|
|
92
|
-
data
|
|
99
|
+
data: formData
|
|
93
100
|
})
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
/**
|
|
97
104
|
* 数据详情
|
|
98
105
|
*/
|
|
99
|
-
export const
|
|
106
|
+
export const TableApi = (data?: ProjectQuery): Promise<PageTableData<ProjectItem>> => {
|
|
100
107
|
return axiosRequest({
|
|
101
108
|
url: '/mock/demos/table',
|
|
102
109
|
method: 'get',
|
|
103
|
-
data
|
|
110
|
+
data
|
|
104
111
|
})
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
/**
|
|
108
115
|
* 下载数据导入模板
|
|
109
116
|
*/
|
|
110
|
-
export const
|
|
117
|
+
export const ImportTemplateApi = (): Promise<Blob> => {
|
|
111
118
|
return axiosRequest({
|
|
112
119
|
url: '/mock/project/template',
|
|
113
120
|
method: 'post'
|
|
@@ -117,7 +124,7 @@ export const DemosImportTemplateApi = (): Promise<Blob> => {
|
|
|
117
124
|
/**
|
|
118
125
|
* 项目数据导入
|
|
119
126
|
*/
|
|
120
|
-
export const
|
|
127
|
+
export const ImportDataApi = (data: { file: File }) => {
|
|
121
128
|
return axiosRequest({
|
|
122
129
|
url: '/mock/project/import',
|
|
123
130
|
method: 'post',
|
|
@@ -128,7 +135,7 @@ export const DemosImportDataApi = (data: FormData) => {
|
|
|
128
135
|
/**
|
|
129
136
|
* 项目数据导出
|
|
130
137
|
*/
|
|
131
|
-
export const
|
|
138
|
+
export const ExportApi = (data: Record<string, unknown>): Promise<Blob> => {
|
|
132
139
|
return axiosRequest({
|
|
133
140
|
url: '/mock/project/export',
|
|
134
141
|
method: 'post',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { axiosRequest } from '@jnrs/vue-core/request'
|
|
2
1
|
import type { Dict, DictItem, User, Role } from '@jnrs/shared'
|
|
3
2
|
import type { MenuItem } from '@jnrs/vue-core'
|
|
3
|
+
import { axiosRequest } from '@jnrs/vue-core/request'
|
|
4
|
+
import { objectToFormData } from '@/utils/packages'
|
|
4
5
|
|
|
5
6
|
// 菜单
|
|
6
7
|
export const MenuApi = (): Promise<MenuItem[]> => {
|
|
@@ -47,6 +48,15 @@ export const UserInfoApi = (): Promise<User> => {
|
|
|
47
48
|
})
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// 修改头像
|
|
52
|
+
export const AvatarChangeApi = (data: File) => {
|
|
53
|
+
return axiosRequest({
|
|
54
|
+
url: '/api/user/avatar',
|
|
55
|
+
method: 'post',
|
|
56
|
+
data: objectToFormData({ file: data })
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
// 修改密码
|
|
51
61
|
interface PasswordChange {
|
|
52
62
|
userId: number
|
|
@@ -3,27 +3,3 @@
|
|
|
3
3
|
@use './root.scss';
|
|
4
4
|
@use './common.scss';
|
|
5
5
|
@use './animation.scss';
|
|
6
|
-
|
|
7
|
-
body {
|
|
8
|
-
width: 100vw;
|
|
9
|
-
height: 100vh;
|
|
10
|
-
color: var(--jnrs-font-primary);
|
|
11
|
-
background: var(--jnrs-background-primary);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
#app {
|
|
15
|
-
min-width: 1280px;
|
|
16
|
-
height: 100%;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
* 禁止用户选中页面元素
|
|
21
|
-
*/
|
|
22
|
-
.no-select {
|
|
23
|
-
-khtml-user-drag: none;
|
|
24
|
-
-webkit-user-drag: none;
|
|
25
|
-
-webkit-user-select: none;
|
|
26
|
-
-moz-user-select: none;
|
|
27
|
-
-ms-user-select: none;
|
|
28
|
-
user-select: none;
|
|
29
|
-
}
|
|
@@ -27,3 +27,27 @@ img {
|
|
|
27
27
|
object-fit: cover;
|
|
28
28
|
object-position: center center;
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
width: 100vw;
|
|
33
|
+
height: 100vh;
|
|
34
|
+
color: var(--jnrs-font-primary);
|
|
35
|
+
background: var(--jnrs-background-primary);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#app {
|
|
39
|
+
min-width: 1280px;
|
|
40
|
+
height: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* 禁止用户选中页面元素
|
|
45
|
+
*/
|
|
46
|
+
.no-select {
|
|
47
|
+
-khtml-user-drag: none;
|
|
48
|
+
-webkit-user-drag: none;
|
|
49
|
+
-webkit-user-select: none;
|
|
50
|
+
-moz-user-select: none;
|
|
51
|
+
-ms-user-select: none;
|
|
52
|
+
user-select: none;
|
|
53
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@Author : TanRui
|
|
3
|
+
@WeChat : Tan578853789
|
|
4
|
+
@File : CardTable.vue
|
|
5
|
+
@Date : 2026/01/06
|
|
6
|
+
@Desc. : 卡片数据表格组件
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import type { PageTableData, Pagination } from '@/types'
|
|
11
|
+
import { ref, onActivated } from 'vue'
|
|
12
|
+
import { JnPagination, JnTable } from '@jnrs/vue-core/components'
|
|
13
|
+
import { debounce } from '@jnrs/shared/lodash'
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/**
|
|
17
|
+
* 获取数据表格 api 函数
|
|
18
|
+
*/
|
|
19
|
+
getTableDataApi?: (...args: any[]) => Promise<PageTableData<any>>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { getTableDataApi } = defineProps<Props>()
|
|
23
|
+
|
|
24
|
+
const loading = ref(false)
|
|
25
|
+
const tableData = ref()
|
|
26
|
+
const total = ref(0)
|
|
27
|
+
const pagination = ref<Pagination>({ pageNo: 1, pageSize: 10 })
|
|
28
|
+
|
|
29
|
+
const getTable = debounce(async () => {
|
|
30
|
+
if (!getTableDataApi) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
loading.value = true
|
|
34
|
+
try {
|
|
35
|
+
const res = await getTableDataApi({
|
|
36
|
+
...pagination.value
|
|
37
|
+
})
|
|
38
|
+
tableData.value = res.list.map((item) => ({
|
|
39
|
+
...item,
|
|
40
|
+
newImageFiles: item.imageDocument?.attachments ?? undefined,
|
|
41
|
+
newAttachmentFile: item.attachmentDocument?.attachments ?? undefined
|
|
42
|
+
}))
|
|
43
|
+
console.log(456)
|
|
44
|
+
total.value = res.count
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error)
|
|
47
|
+
} finally {
|
|
48
|
+
loading.value = false
|
|
49
|
+
}
|
|
50
|
+
}, 300)
|
|
51
|
+
|
|
52
|
+
onActivated(() => {
|
|
53
|
+
console.log(123)
|
|
54
|
+
|
|
55
|
+
getTable()
|
|
56
|
+
})
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<el-card class="cardTable" v-loading="loading">
|
|
61
|
+
<template #header>
|
|
62
|
+
<div class="cardTable_header">
|
|
63
|
+
<slot name="header"></slot>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<JnTable
|
|
68
|
+
:data="tableData"
|
|
69
|
+
:pagination="pagination"
|
|
70
|
+
:autoHeight="true"
|
|
71
|
+
:showScrollbar="true"
|
|
72
|
+
:showIndexColumn="true"
|
|
73
|
+
>
|
|
74
|
+
<slot name="table"></slot>
|
|
75
|
+
</JnTable>
|
|
76
|
+
|
|
77
|
+
<JnPagination :total="total" v-model="pagination" @change="getTable" />
|
|
78
|
+
</el-card>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style lang="scss" scoped>
|
|
82
|
+
.cardTable {
|
|
83
|
+
.cardTable_header {
|
|
84
|
+
display: flex;
|
|
85
|
+
justify-content: space-between;
|
|
86
|
+
align-items: center;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -58,13 +58,17 @@ const computedColor = computed(() => {
|
|
|
58
58
|
|
|
59
59
|
<style lang="scss" scoped>
|
|
60
60
|
.dictTag {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
padding: 1px 8px;
|
|
65
|
+
border-radius: 4px;
|
|
63
66
|
white-space: nowrap;
|
|
67
|
+
transform: scale(0.8);
|
|
64
68
|
|
|
65
69
|
.dictTag_label_showColor {
|
|
66
|
-
font-size:
|
|
67
|
-
filter: invert(0.
|
|
70
|
+
font-size: 1.1em;
|
|
71
|
+
filter: invert(0.5) brightness(0.5);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
</style>
|
|
@@ -13,9 +13,14 @@ interface Props {
|
|
|
13
13
|
* 要加载的文件列表 | 文件名唯一标识 uniqueFileName
|
|
14
14
|
*/
|
|
15
15
|
loadKeys: Attachment[] | string | undefined
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 是否清理文件副作用
|
|
19
|
+
*/
|
|
20
|
+
clearSideEffects: boolean
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
const { loadKeys } = defineProps<Props>()
|
|
23
|
+
const { loadKeys, clearSideEffects = false } = defineProps<Props>()
|
|
19
24
|
|
|
20
25
|
// 第一张显示的图片
|
|
21
26
|
const posterUrl = ref('')
|
|
@@ -28,10 +33,12 @@ const revokeFns = ref<(() => void)[]>([])
|
|
|
28
33
|
|
|
29
34
|
// 清理当前所有 Object URL
|
|
30
35
|
const clearUrls = () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
if (clearSideEffects) {
|
|
37
|
+
revokeFns.value.forEach((revoke) => revoke())
|
|
38
|
+
revokeFns.value = []
|
|
39
|
+
fileList.value = []
|
|
40
|
+
posterUrl.value = ''
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
// 加载文件
|
|
@@ -105,7 +112,7 @@ const loadFilesRaw = async (keys: Props['loadKeys']) => {
|
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
const loadFiles = debounce(loadFilesRaw,
|
|
115
|
+
const loadFiles = debounce(loadFilesRaw, 500)
|
|
109
116
|
|
|
110
117
|
watch(
|
|
111
118
|
() => loadKeys,
|
|
@@ -116,7 +123,9 @@ watch(
|
|
|
116
123
|
)
|
|
117
124
|
|
|
118
125
|
onActivated(() => {
|
|
119
|
-
|
|
126
|
+
if (clearSideEffects) {
|
|
127
|
+
loadFiles(loadKeys)
|
|
128
|
+
}
|
|
120
129
|
})
|
|
121
130
|
|
|
122
131
|
// 副作用清理
|
|
@@ -12,9 +12,14 @@ interface Props {
|
|
|
12
12
|
* 要加载的文件列表 | 文件名唯一标识 uniqueFileName
|
|
13
13
|
*/
|
|
14
14
|
loadKeys: Attachment[] | string | undefined
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 是否清理文件副作用
|
|
18
|
+
*/
|
|
19
|
+
clearSideEffects: boolean
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
const { loadKeys } = defineProps<Props>()
|
|
22
|
+
const { loadKeys, clearSideEffects = false } = defineProps<Props>()
|
|
18
23
|
|
|
19
24
|
// 存储每个 URL 对应的 URL 对象
|
|
20
25
|
const fileList = ref<FileItem[]>([])
|
|
@@ -24,9 +29,11 @@ const revokeFns = ref<(() => void)[]>([])
|
|
|
24
29
|
|
|
25
30
|
// 清理当前所有 Object URL
|
|
26
31
|
const clearUrls = () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
if (clearSideEffects) {
|
|
33
|
+
revokeFns.value.forEach((revoke) => revoke())
|
|
34
|
+
revokeFns.value = []
|
|
35
|
+
fileList.value = []
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
const loadFiles = async (keys: Props['loadKeys']) => {
|
|
@@ -87,7 +94,9 @@ watch(
|
|
|
87
94
|
)
|
|
88
95
|
|
|
89
96
|
onActivated(() => {
|
|
90
|
-
|
|
97
|
+
if (clearSideEffects) {
|
|
98
|
+
loadFiles(loadKeys)
|
|
99
|
+
}
|
|
91
100
|
})
|
|
92
101
|
|
|
93
102
|
// 副作用清理
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
import {
|
|
10
|
+
import { TableApi } from '@/api/demos/index'
|
|
11
11
|
import { JnSelectTemplate } from '@jnrs/vue-core/components'
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
|
-
<JnSelectTemplate tableName="项目经理" :keyValue="{ label: 'manager', value: 'managerId' }" :listApi="
|
|
15
|
+
<JnSelectTemplate tableName="项目经理" :keyValue="{ label: 'manager', value: 'managerId' }" :listApi="TableApi">
|
|
16
16
|
<template #table>
|
|
17
17
|
<el-table-column prop="manager" label="项目经理" align="center" sortable />
|
|
18
18
|
</template>
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref
|
|
2
|
+
import { ref } from 'vue'
|
|
3
3
|
import { storeToRefs } from 'pinia'
|
|
4
4
|
import { ElMessageBox } from 'element-plus'
|
|
5
5
|
import { handleRouter } from '@jnrs/vue-core/router'
|
|
6
6
|
import { GlobalSetting } from '@jnrs/vue-core/components'
|
|
7
7
|
import { useSystemStore, useAuthStore, useMenuStore } from '@jnrs/vue-core/pinia'
|
|
8
8
|
import { LogoutApi } from '@/api/system'
|
|
9
|
-
import { getDictLabel, getDictColor } from '@/utils/packages'
|
|
10
|
-
|
|
11
9
|
import ImageView from '@/components/common/ImageView.vue'
|
|
10
|
+
import DictTag from '@/components/common/DictTag.vue'
|
|
12
11
|
|
|
13
12
|
const { userInfo, clearAuth } = useAuthStore()
|
|
14
13
|
const { clearMenu } = useMenuStore()
|
|
15
14
|
|
|
16
|
-
const roleLabel = computed(() => getDictLabel('role', userInfo?.role || ''))
|
|
17
|
-
const roleColor = computed(() => getDictColor('role', userInfo?.role || ''))
|
|
18
|
-
|
|
19
15
|
const systemStore = useSystemStore()
|
|
20
16
|
const { documentFullscreen } = storeToRefs(systemStore)
|
|
21
17
|
const { toggleFullScreen } = systemStore
|
|
@@ -29,8 +25,8 @@ const handleLogout = async () => {
|
|
|
29
25
|
type: 'warning'
|
|
30
26
|
})
|
|
31
27
|
await LogoutApi()
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
clearAuth()
|
|
29
|
+
clearMenu()
|
|
34
30
|
handleRouter({ name: 'Login' }, 'replace')
|
|
35
31
|
} catch {}
|
|
36
32
|
}
|
|
@@ -68,16 +64,12 @@ const showGlobalSetting = () => {
|
|
|
68
64
|
<span class="userMenu_reference">
|
|
69
65
|
<ImageView class="userMenu_avatar" :loadKeys="userInfo?.avatarFileName" />
|
|
70
66
|
<span>{{ userInfo?.name }}</span>
|
|
71
|
-
<
|
|
67
|
+
<DictTag dictName="role" :value="userInfo.role" v-if="userInfo?.role" />
|
|
72
68
|
<el-icon class="userMenu_icon"><arrow-down /></el-icon>
|
|
73
69
|
</span>
|
|
74
70
|
</template>
|
|
75
71
|
<div class="userMenu_dropdown">
|
|
76
72
|
<ImageView class="userMenu_dropdown_avatar" :loadKeys="userInfo?.avatarFileName" />
|
|
77
|
-
<b>
|
|
78
|
-
<span>{{ userInfo?.name }}</span>
|
|
79
|
-
<span class="userMenu_roleName" :style="{ color: roleColor }" v-if="userInfo?.role">[{{ roleLabel }}]</span>
|
|
80
|
-
</b>
|
|
81
73
|
<div class="loginDateTime" v-if="userInfo?.loginDateTime">
|
|
82
74
|
<span>登录时间</span>
|
|
83
75
|
<p>{{ userInfo?.loginDateTime }}</p>
|
|
@@ -132,13 +124,14 @@ $topHoverSize: 35px;
|
|
|
132
124
|
display: flex;
|
|
133
125
|
align-items: center;
|
|
134
126
|
margin-left: 16px;
|
|
135
|
-
transition: all 0.25s ease;
|
|
136
127
|
cursor: pointer;
|
|
137
128
|
&:hover {
|
|
138
129
|
span {
|
|
130
|
+
transition: all 0.25s ease;
|
|
139
131
|
color: var(--jnrs-color-primary) !important;
|
|
140
132
|
}
|
|
141
133
|
.userMenu_icon {
|
|
134
|
+
transition: all 0.25s ease;
|
|
142
135
|
color: var(--jnrs-color-primary);
|
|
143
136
|
}
|
|
144
137
|
}
|
|
@@ -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
|
+
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* @Desc. : 文件处理相关函数
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
// import { blobToUrl } from '@jnrs/shared'
|
|
10
9
|
import { useObjectUrl } from '@vueuse/core'
|
|
11
10
|
import { FileApi } from '@/api/common'
|
|
12
11
|
|
|
@@ -19,3 +18,39 @@ export const getFileUrl = async (uniqueFileName: string) => {
|
|
|
19
18
|
const blob = await FileApi(uniqueFileName)
|
|
20
19
|
return useObjectUrl(blob)
|
|
21
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
|
+
}
|