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.
Files changed (30) hide show
  1. package/jnrs-template-vue/.env.development +1 -1
  2. package/jnrs-template-vue/index.html +1 -1
  3. package/jnrs-template-vue/package.json +3 -3
  4. package/jnrs-template-vue/public/system/menu.json +11 -2
  5. package/jnrs-template-vue/src/api/demos/index.ts +17 -10
  6. package/jnrs-template-vue/src/api/system/index.ts +11 -1
  7. package/jnrs-template-vue/src/assets/styles/index.scss +0 -24
  8. package/jnrs-template-vue/src/assets/styles/init.scss +24 -0
  9. package/jnrs-template-vue/src/assets/styles/root.scss +4 -0
  10. package/jnrs-template-vue/src/components/common/CardTable.vue +89 -0
  11. package/jnrs-template-vue/src/components/common/DictTag.vue +8 -4
  12. package/jnrs-template-vue/src/components/common/ImageView.vue +16 -7
  13. package/jnrs-template-vue/src/components/common/PdfView.vue +14 -5
  14. package/jnrs-template-vue/src/components/select/SelectManager.vue +2 -2
  15. package/jnrs-template-vue/src/layout/SideMenu.vue +0 -1
  16. package/jnrs-template-vue/src/layout/TopHeader.vue +7 -14
  17. package/jnrs-template-vue/src/types/webSocket.ts +19 -0
  18. package/jnrs-template-vue/src/utils/file.ts +36 -1
  19. package/jnrs-template-vue/src/views/demos/crud/index.vue +24 -36
  20. package/jnrs-template-vue/src/views/demos/simpleTable/index.vue +41 -0
  21. package/jnrs-template-vue/src/views/demos/unitTest/RequestPage.vue +2 -2
  22. package/jnrs-template-vue/src/views/login/index.vue +18 -15
  23. package/jnrs-template-vue/src/views/system/dict/index.vue +63 -76
  24. package/jnrs-template-vue/src/views/system/menu/index.vue +42 -54
  25. package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +26 -59
  26. package/jnrs-template-vue/src/views/system/role/index.vue +20 -29
  27. package/jnrs-template-vue/src/views/visual/index.vue +130 -15
  28. package/package.json +1 -1
  29. package/jnrs-template-vue/src/composables/base/useAvatar.ts +0 -36
  30. package/jnrs-template-vue/src/composables/tools/useMouseSelection.ts +0 -150
@@ -1,18 +1,16 @@
1
1
  <script setup lang="ts">
2
+ import type { FormInstance } from 'element-plus'
2
3
  import { ref, onMounted } from 'vue'
3
- import { storeToRefs } from 'pinia'
4
- import { ElMessage } from 'element-plus'
5
- import type { UploadProps } from 'element-plus'
6
4
  import { useAuthStore } from '@jnrs/vue-core/pinia'
7
- import { useAvatar } from '@/composables/base/useAvatar'
8
5
  import { getDictList } from '@/utils/packages'
9
- import type { User } from '@jnrs/shared'
6
+ import { JnFileUpload } from '@jnrs/vue-core/components'
7
+ import { AvatarChangeApi } from '@/api/system'
8
+ import { objectMatchAssign } from '@jnrs/shared'
10
9
 
11
- const { userInfo, token } = storeToRefs(useAuthStore())
12
- const { avatar } = useAvatar()
13
- const loading = ref(false)
14
- // const ruleFormRef = ref()
15
- const ruleForm = ref<User>({
10
+ const { userInfo } = useAuthStore()
11
+ // const loading = ref(false)
12
+ const ruleFormRef = ref<FormInstance>()
13
+ const ruleForm = ref({
16
14
  id: 0,
17
15
  account: '',
18
16
  name: '',
@@ -20,7 +18,8 @@ const ruleForm = ref<User>({
20
18
  jobTitle: 0,
21
19
  workgroup: '',
22
20
  role: 0,
23
- avatarFileName: ''
21
+ avatarFileName: '',
22
+ file: []
24
23
  })
25
24
  const rules = ref({
26
25
  // account: { required: true, message: '请输入', trigger: 'change' },
@@ -29,44 +28,14 @@ const rules = ref({
29
28
  // workNo: { required: true, message: '请输入', trigger: 'change' },
30
29
  })
31
30
 
32
- interface ApiResponse {
33
- code: number
34
- message: string
35
- data: string
36
- }
37
-
38
31
  onMounted(() => {
39
- if (userInfo.value) {
40
- ruleForm.value = userInfo.value
41
- }
32
+ const matched = objectMatchAssign(ruleForm.value, userInfo)
33
+ ruleForm.value = matched
42
34
  })
43
-
44
- const handleAvatarSuccess: UploadProps['onSuccess'] = (response: ApiResponse) => {
45
- loading.value = false
46
- if (userInfo.value && response.code === 0) {
47
- userInfo.value.avatarFileName = response.data
48
- ElMessage.success({ message: '头像上传成功' })
49
- }
50
- }
51
-
52
- const handleProgress = () => {
53
- loading.value = true
54
- }
55
-
56
- const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
57
- if (rawFile.type.indexOf('image/') < 0) {
58
- ElMessage.error('上传头像只能是图片格式!')
59
- return false
60
- } else if (rawFile.size / 1024 / 1024 > 10) {
61
- ElMessage.error('上传头像图片大小不能超过10MB!')
62
- return false
63
- }
64
- return true
65
- }
66
35
  </script>
67
36
 
68
37
  <template>
69
- <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto" disabled>
38
+ <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto" :disabled="true">
70
39
  <el-form-item label="登录账号" prop="account">
71
40
  <el-input v-model.trim="ruleForm.account" style="width: 200px" />
72
41
  </el-form-item>
@@ -94,23 +63,21 @@ const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
94
63
  <el-option :label="item.label" :value="item.value" v-for="(item, index) in getDictList('role')" :key="index" />
95
64
  </el-select>
96
65
  </el-form-item>
97
- <el-form-item label="头像" prop="avatarFileName" class="uploader_item">
98
- <el-upload
99
- action="/api/user/avatar"
100
- :headers="{
101
- Authorization: 'Bearer ' + token
102
- }"
103
- :show-file-list="false"
104
- :before-upload="beforeAvatarUpload"
105
- :on-progress="handleProgress"
106
- :on-success="handleAvatarSuccess"
107
- >
108
- <img v-if="avatar" :src="avatar" class="uploader_avatar" />
109
- <el-icon v-else class="uploader_icon"><Plus /></el-icon>
110
- </el-upload>
66
+ <el-form-item label="头像" prop="file" class="uploader_item">
67
+ <JnFileUpload
68
+ v-model="ruleForm.file"
69
+ :formRef="ruleFormRef"
70
+ validateFieldName="newImageFiles"
71
+ accept=".png,.jpg,.bmp,.gif"
72
+ :fileSizeMb="50"
73
+ :limit="1"
74
+ :showFileList="false"
75
+ :autoUploadApi="AvatarChangeApi"
76
+ style="width: 500px"
77
+ />
111
78
  </el-form-item>
112
79
  <!-- <el-form-item>
113
- <el-button class="form_submit" :loading="loading" type="primary" @click="submitForm(ruleFormRef)">
80
+ <el-button class="form_submit" :loading="loading" type="primary" @click="submitForm">
114
81
  保存修改
115
82
  </el-button>
116
83
  </el-form-item> -->
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, onMounted } from 'vue'
3
3
  import { RoleApi } from '@/api/system'
4
+ import { JnTable } from '@jnrs/vue-core/components'
4
5
 
5
6
  const loading = ref(false)
6
7
  const tableData = ref()
@@ -18,33 +19,23 @@ onMounted(() => {
18
19
  </script>
19
20
 
20
21
  <template>
21
- <div class="main">
22
- <el-card shadow="never">
23
- <template #header>
24
- <div class="card_header">
25
- <h3>角色管理</h3>
26
- </div>
27
- </template>
28
- <el-table
29
- class="el_table_role"
30
- header-cell-class-name="tableData_header_cell"
31
- :data="tableData"
32
- scrollbar-always-on
33
- border
34
- v-loading="loading"
35
- >
36
- <el-table-column prop="label" label="角色名称" width="200" align="center" />
37
- <el-table-column prop="value" label="值" width="200" align="center">
38
- <template #default="{ row }">
39
- {{ row.value + ' [' + typeof row.value + ']' }}
40
- </template>
41
- </el-table-column>
42
- <el-table-column prop="permissions" label="可用权限" header-align="center">
43
- <template #default="{ row }">
44
- {{ row.permissions }}
45
- </template>
46
- </el-table-column>
47
- </el-table>
48
- </el-card>
49
- </div>
22
+ <el-card v-loading="loading">
23
+ <template #header>
24
+ <span>角色管理</span>
25
+ </template>
26
+
27
+ <JnTable :data="tableData">
28
+ <el-table-column prop="label" label="角色名称" width="200" align="center" />
29
+ <el-table-column prop="value" label="值" width="200" align="center">
30
+ <template #default="{ row }">
31
+ {{ row.value + ' [' + typeof row.value + ']' }}
32
+ </template>
33
+ </el-table-column>
34
+ <el-table-column prop="permissions" label="可用权限" header-align="center">
35
+ <template #default="{ row }">
36
+ {{ row.permissions }}
37
+ </template>
38
+ </el-table-column>
39
+ </JnTable>
40
+ </el-card>
50
41
  </template>
@@ -1,22 +1,59 @@
1
1
  <script setup lang="ts">
2
- const goLogin = () => {
3
- // removeAll()
4
- window.location.href = '/login'
5
- }
6
- const goHome = () => {
7
- window.location.href = '/'
8
- }
9
- const goBack = () => {
10
- window.history.go(-1)
11
- }
2
+ import type { MsgIdMessage, TypeDataMessage } from '@/types/webSocket'
3
+ import { isMsgIdMessage, isTypeDataMessage } from '@/types/webSocket'
4
+ import { storeToRefs } from 'pinia'
5
+ import { useWebSocket } from '@jnrs/vue-core/composables'
6
+ import { useRouter } from '@jnrs/vue-core/router'
7
+ import { useSystemStore } from '@jnrs/vue-core/pinia'
8
+
9
+ const router = useRouter()
10
+ const systemStore = useSystemStore()
11
+ const { documentFullscreen } = storeToRefs(systemStore)
12
+ const { toggleFullScreen } = systemStore
13
+
14
+ const { isLoading, wsStateInfo } = useWebSocket({
15
+ url: '/ws/monitor',
16
+ connectionTimeoutDuration: 3_000, // 修改仅为测试
17
+ onMessage: (rawMessage: unknown) => {
18
+ if (isMsgIdMessage(rawMessage)) {
19
+ const msg = rawMessage as MsgIdMessage
20
+ if (msg.msgId && msg.d && typeof msg.d === 'object') {
21
+ // console.log('接收 msgId 数据:', msg)
22
+ }
23
+ } else if (isTypeDataMessage(rawMessage)) {
24
+ const msg = rawMessage as TypeDataMessage
25
+ if (msg.type && msg.data && typeof msg.data === 'object') {
26
+ // console.log('接收 type 数据:', msg)
27
+ }
28
+ }
29
+ }
30
+ })
12
31
  </script>
13
32
 
14
33
  <template>
15
- <div>visual</div>
16
- <div class="main_mid_btn">
17
- <el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
18
- <el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
19
- <el-button type="primary" @click="goBack">返回上一页</el-button>
34
+ <div
35
+ class="visual"
36
+ v-loading="isLoading"
37
+ element-loading-background="rgba(0,0,0,.5)"
38
+ element-loading-text="网络加载中..."
39
+ >
40
+ <div class="visual_head">
41
+ <div class="visual_head_left">
42
+ <div class="visual_title">
43
+ <b>数字孪生看板</b>
44
+ <span>{{ wsStateInfo }}</span>
45
+ </div>
46
+ </div>
47
+ <div class="visual_head_right">
48
+ <span class="btn" @click="toggleFullScreen">
49
+ {{ !documentFullscreen ? '启用全屏' : '退出全屏' }}
50
+ </span>
51
+ <span class="btn" @click="router.push('/')">返回首页</span>
52
+ </div>
53
+ </div>
54
+ <div class="visual_canvas">
55
+ <i>canvas</i>
56
+ </div>
20
57
  </div>
21
58
  </template>
22
59
 
@@ -25,4 +62,82 @@ const goBack = () => {
25
62
  @function px2vw($px) {
26
63
  @return math.div($px, 1920) * 100vw;
27
64
  }
65
+
66
+ .visual {
67
+ position: relative;
68
+ width: 100%;
69
+ height: 100%;
70
+ font-size: px2vw(18);
71
+ overflow: hidden;
72
+ user-select: none;
73
+
74
+ .visual_head {
75
+ position: absolute;
76
+ z-index: 10;
77
+ display: flex;
78
+ justify-content: space-between;
79
+ align-items: top;
80
+ width: 100%;
81
+ padding: px2vw(10);
82
+ color: #fff;
83
+
84
+ .visual_title {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ font-size: px2vw(24);
88
+ height: px2vw(30);
89
+ background: #000;
90
+ border: px2vw(2) solid #f2f2f2;
91
+ margin: 0 0 px2vw(5) 0;
92
+ overflow: hidden;
93
+
94
+ b {
95
+ display: inline-block;
96
+ padding: 0 px2vw(5);
97
+ font-size: px2vw(24);
98
+ background: #f2f2f2;
99
+ color: #000;
100
+ }
101
+
102
+ span {
103
+ padding: 0 px2vw(8);
104
+ color: #f2f2f2;
105
+ box-sizing: border-box;
106
+ }
107
+ }
108
+
109
+ .visual_head_right {
110
+ .btn {
111
+ align-items: center;
112
+ padding: 0 px2vw(8);
113
+ color: #f2f2f2;
114
+ background: var(--jnrs-color-primary);
115
+ border-radius: px2vw(2);
116
+ margin: 0 px2vw(2);
117
+ transition: all 0.25s ease;
118
+ cursor: pointer;
119
+
120
+ &:hover {
121
+ filter: brightness(1.3);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ .visual_canvas {
128
+ position: relative;
129
+ width: 100%;
130
+ height: 100%;
131
+
132
+ i {
133
+ position: absolute;
134
+ top: 50%;
135
+ left: 50%;
136
+ transform: translate(-50%, -50%);
137
+ font-size: px2vw(100);
138
+ color: #fff;
139
+ opacity: 0.5;
140
+ }
141
+ }
142
+ }
28
143
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-jnrs-template-vue",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "巨能前端工程化开发,Vue 项目模板脚手架",
5
5
  "keywords": [
6
6
  "vue",
@@ -1,36 +0,0 @@
1
- import { ref, toRefs, watch } from 'vue'
2
- // import { getImgSrc } from '@/utils/common'
3
- // import { useCommonStore } from '@jnrs/vue-core/pinia'
4
- // import imgFailed from '@/assets/img/common/404.png'
5
- import defaultAvatar from '@/assets/images/common/avatar.png'
6
-
7
- export const useAvatar = () => {
8
- // const { userInfo } = toRefs(useCommonStore())
9
- const avatar = ref(defaultAvatar)
10
-
11
- // watch(
12
- // () => userInfo.value.avatar,
13
- // nv => {
14
- // if (nv) {
15
- // getImgSrc(nv).then(res => {
16
- // avatar.value = URL.createObjectURL(res.data)
17
- // })
18
- // }
19
- // }
20
- // )
21
-
22
- // const at = userInfo.value.avatar
23
- // if (at) {
24
- // getImgSrc(at)
25
- // .then(res => {
26
- // avatar.value = URL.createObjectURL(res.data)
27
- // })
28
- // .catch(() => {
29
- // avatar.value = imgFailed
30
- // })
31
- // }
32
-
33
- return {
34
- avatar
35
- }
36
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useMouseSelection.ts
5
- * @Date : 2025/04/14
6
- * @Desc. : 鼠标框选表格进行多选、回车执行回调
7
- */
8
-
9
- import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
10
-
11
- /**
12
- * @param {*} containerClass 表格或容器类名,默认值 el-table,如果页面存在多个表格,则必须分别传入自定义类名
13
- * @param {*} rowClass 表格或容器类名,默认值 el-table__row
14
- * @param {*} callback 框选后的回调,默认处理的是 input[type='checkbox'] 元素
15
- * @returns handleMouseDown
16
- * @example <el-table @mousedown="handleMouseDown" @selection-change="handleSelectionChange">
17
- */
18
- export function useMouseSelection({
19
- containerClass = 'jn_table',
20
- rowClass = 'el-table__row',
21
- callback = handleSelection
22
- } = {}) {
23
- let isDragging = false
24
- let startPoint = { x: 0, y: 0 }
25
- let endPoint = { x: 0, y: 0 }
26
-
27
- onMounted(() => {
28
- window.addEventListener('mouseup', handleMouseUp)
29
- })
30
-
31
- onUnmounted(() => {
32
- window.removeEventListener('mouseup', handleMouseUp)
33
- })
34
-
35
- onActivated(() => {
36
- window.addEventListener('mouseup', handleMouseUp)
37
- })
38
-
39
- onDeactivated(() => {
40
- window.removeEventListener('mouseup', handleMouseUp)
41
- })
42
-
43
- // 鼠标按下事件
44
- const handleMouseDown = (event: MouseEvent) => {
45
- window.addEventListener('mousemove', handleMouseMove)
46
- isDragging = false
47
- startPoint = { x: event.clientX, y: event.clientY }
48
- endPoint = { ...startPoint }
49
- }
50
-
51
- // 鼠标移动事件
52
- const handleMouseMove = (event: MouseEvent) => {
53
- endPoint = { x: event.clientX, y: event.clientY }
54
- // 判断是否为拖动
55
- if (Math.abs(endPoint.x - startPoint.x) > 5 && Math.abs(endPoint.y - startPoint.y) > 25) {
56
- isDragging = true
57
- }
58
- // 如果没有拖动,则认为是单击
59
- if (isDragging) {
60
- selectRows()
61
- }
62
- setFixedRectBoxStyle()
63
- }
64
-
65
- // 鼠标抬起事件
66
- const handleMouseUp = () => {
67
- window.removeEventListener('mousemove', handleMouseMove)
68
- // 如果没有拖动,则认为是单击
69
- if (isDragging) {
70
- isDragging = false
71
- }
72
- setFixedRectBoxStyle()
73
- }
74
-
75
- // 判断选中的行
76
- const selectRows = () => {
77
- const container = document.querySelector(`.${containerClass}`) as HTMLElement
78
- const rows = container?.querySelectorAll(`.${rowClass}`) as NodeListOf<HTMLElement>
79
- if (!rows) {
80
- return false
81
- }
82
- rows.forEach((row) => {
83
- const rect = row.getBoundingClientRect()
84
- const isIntersecting =
85
- rect.right > Math.min(startPoint.x, endPoint.x) &&
86
- rect.left < Math.max(startPoint.x, endPoint.x) &&
87
- rect.bottom > Math.min(startPoint.y, endPoint.y) &&
88
- rect.top < Math.max(startPoint.y, endPoint.y)
89
- if (isIntersecting && callback) {
90
- callback(row)
91
- }
92
- })
93
- }
94
-
95
- // 设置框选框的位置
96
- const setFixedRectBoxStyle = () => {
97
- const selectionBox = document.querySelector('.useMouseSelection_fixedRectBox') as HTMLElement
98
- if (selectionBox) {
99
- const left = Math.min(startPoint.x, endPoint.x) + 'px'
100
- const top = Math.min(startPoint.y, endPoint.y) + 'px'
101
- const width = Math.abs(endPoint.x - startPoint.x) + 'px'
102
- const height = Math.abs(endPoint.y - startPoint.y) + 'px'
103
- Object.assign(selectionBox.style, {
104
- display: isDragging ? 'block' : 'none',
105
- left: left,
106
- top: top,
107
- width: width,
108
- height: height
109
- })
110
-
111
- const container = document.querySelector(`.${containerClass}`) as HTMLElement
112
- Object.assign(container.style, {
113
- 'user-select': isDragging ? 'none' : 'auto'
114
- })
115
- }
116
- }
117
-
118
- checkAndAddElement()
119
-
120
- return {
121
- handleMouseDown
122
- }
123
- }
124
-
125
- // 回调选中逻辑
126
- const handleSelection = (row: HTMLElement) => {
127
- const checkbox = row.querySelector("input[type='checkbox']") as HTMLInputElement
128
- if (checkbox && !checkbox.checked) {
129
- checkbox.click()
130
- }
131
- }
132
-
133
- // 往页面上添加框选框元素
134
- const checkAndAddElement = () => {
135
- const fixedRectBox = document.querySelector('.useMouseSelection_fixedRectBox')
136
- if (!fixedRectBox) {
137
- const newDiv = document.createElement('div')
138
- newDiv.className = 'useMouseSelection_fixedRectBox'
139
- Object.assign(newDiv.style, {
140
- display: 'none',
141
- position: 'fixed',
142
- 'z-index': 100,
143
- border: '2px dashed #409eff',
144
- 'background-color': 'rgba(64, 158, 255, 0.2)',
145
- 'pointer-events': 'none',
146
- 'will-change': 'transform'
147
- })
148
- document.body.appendChild(newDiv)
149
- }
150
- }