create-jnrs-vue 1.2.19 → 1.2.21

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 (35) hide show
  1. package/README.md +1 -0
  2. package/bin/create.mjs +0 -6
  3. package/jnrs-vue/.gitignore +2 -0
  4. package/jnrs-vue/README.md +1 -0
  5. package/jnrs-vue/components.d.ts +3 -1
  6. package/jnrs-vue/package.json +3 -3
  7. package/jnrs-vue/src/App.vue +1 -1
  8. package/jnrs-vue/src/api/demos/index.ts +12 -3
  9. package/jnrs-vue/src/api/system/index.ts +3 -0
  10. package/jnrs-vue/src/assets/styles/animation.scss +15 -0
  11. package/jnrs-vue/src/components/common/CardTable.vue +1 -1
  12. package/jnrs-vue/src/components/common/DictTag.vue +8 -6
  13. package/jnrs-vue/src/components/common/ImageView.vue +1 -1
  14. package/jnrs-vue/src/components/common/PdfView.vue +1 -1
  15. package/jnrs-vue/src/components/select/SelectManager.vue +2 -2
  16. package/jnrs-vue/src/composables/useCrud.ts +131 -0
  17. package/jnrs-vue/src/layout/RouterTabs.vue +151 -3
  18. package/jnrs-vue/src/layout/SideMenu.vue +212 -139
  19. package/jnrs-vue/src/layout/TopHeader.vue +44 -22
  20. package/jnrs-vue/src/locales/en.ts +40 -1
  21. package/jnrs-vue/src/locales/index.ts +2 -2
  22. package/jnrs-vue/src/locales/zhCn.ts +40 -1
  23. package/jnrs-vue/src/main.ts +2 -2
  24. package/jnrs-vue/src/router/routes.ts +1 -1
  25. package/jnrs-vue/src/views/demos/crud/index.vue +47 -9
  26. package/jnrs-vue/src/views/demos/simpleTable/index.vue +2 -2
  27. package/jnrs-vue/src/views/home/index.vue +312 -3
  28. package/jnrs-vue/src/views/login/index.vue +2 -2
  29. package/jnrs-vue/vite.config.ts +2 -1
  30. package/jnrs-vue/viteMockServe/fail.ts +3 -3
  31. package/jnrs-vue/viteMockServe/file.ts +4 -4
  32. package/jnrs-vue/viteMockServe/success.ts +9 -1
  33. package/package.json +1 -1
  34. package/jnrs-vue/dot_gitignore +0 -160
  35. package/jnrs-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" +0 -150
@@ -2,16 +2,16 @@
2
2
  import type { FormInstance, FormRules } from 'element-plus'
3
3
  import type { Attachment, Pagination } from '@/types'
4
4
  import type { ProjectItem, AddProjectItem } from '@/api/demos/index'
5
- import { ref, onActivated } from 'vue'
6
- import { ElMessage } from 'element-plus'
5
+ import { ref, onActivated, nextTick } from 'vue'
6
+ import { ElMessage, ElMessageBox } from 'element-plus'
7
7
  import { Plus } from '@element-plus/icons-vue'
8
8
  import { useRoute } from '@jnrs/vue-core/router'
9
9
  import { objectMatchAssign, dateFormatsToObject } from '@jnrs/shared'
10
10
  import { debounce } from '@jnrs/shared/lodash'
11
11
  import { isNumberGtZero } from '@jnrs/shared/validator'
12
12
  import { getDictList, downloadFile } from '@/utils/packages'
13
- import { TableApi, EditApi, ImportTemplateApi, ImportDataApi, ExportApi } from '@/api/demos/index'
14
-
13
+ import { useI18n } from '@/locales'
14
+ import { ListApi, EditApi, DelApi, ImportTemplateApi, ImportDataApi, ExportApi } from '@/api/demos/index'
15
15
  import { JnDialog, JnDatetime, JnPagination, JnFileUpload, JnTable, JnImportAndExport } from '@jnrs/vue-core/components'
16
16
  import ImageView from '@/components/common/ImageView.vue'
17
17
  import PdfView from '@/components/common/PdfView.vue'
@@ -23,6 +23,7 @@ interface ProcessedProjectItem extends ProjectItem {
23
23
  newAttachmentFile?: Attachment[]
24
24
  }
25
25
 
26
+ const { t: $t } = useI18n()
26
27
  const loading = ref(false)
27
28
  const tableData = ref<ProcessedProjectItem[]>([])
28
29
  const total = ref(0)
@@ -73,7 +74,7 @@ const handleSelectionChange = (row: ProjectItem[]) => {
73
74
  const getTable = debounce(async () => {
74
75
  loading.value = true
75
76
  try {
76
- const res = await TableApi({
77
+ const res = await ListApi({
77
78
  ...pagination.value,
78
79
  ...queryForm.value
79
80
  })
@@ -93,7 +94,7 @@ const getTable = debounce(async () => {
93
94
  // 新增和修改
94
95
  const handleEdit = (row?: ProjectItem) => {
95
96
  editDialogRef.value.open()
96
- queueMicrotask(() => {
97
+ nextTick(() => {
97
98
  ruleFormRef.value?.resetFields()
98
99
  const matched = objectMatchAssign(ruleForm.value, row)
99
100
  ruleForm.value = matched
@@ -107,6 +108,34 @@ const handleEdit = (row?: ProjectItem) => {
107
108
  })
108
109
  }
109
110
 
111
+ const handleDelete = (row: ProjectItem) => {
112
+ ElMessageBox.confirm('<p style="color: #f30">请注意,您尚未保存的数据将会丢失。</p>', '确定要删除吗?', {
113
+ dangerouslyUseHTMLString: true,
114
+ confirmButtonType: 'danger',
115
+ confirmButtonText: $t('global.action.confirm'),
116
+ cancelButtonText: $t('global.action.cancel')
117
+ })
118
+ .then(async () => {
119
+ loading.value = true
120
+ try {
121
+ await DelApi(row.id)
122
+ ElMessage({
123
+ message: '',
124
+ grouping: true,
125
+ showClose: true,
126
+ type: 'success'
127
+ })
128
+ editDialogRef.value.close()
129
+ getTable()
130
+ } catch (err) {
131
+ console.error(err)
132
+ } finally {
133
+ loading.value = false
134
+ }
135
+ })
136
+ .catch(() => {})
137
+ }
138
+
110
139
  // 提交表单
111
140
  const submitForm = () => {
112
141
  ruleFormRef.value?.validate(async (valid) => {
@@ -120,6 +149,8 @@ const submitForm = () => {
120
149
  showClose: true,
121
150
  type: 'success'
122
151
  })
152
+ editDialogRef.value.close()
153
+ getTable()
123
154
  } catch (err) {
124
155
  console.error(err)
125
156
  } finally {
@@ -130,14 +161,20 @@ const submitForm = () => {
130
161
  }
131
162
 
132
163
  onActivated(() => {
133
- console.log(route.meta)
164
+ console.log(route.meta) // 获取路由元信息
134
165
  getTable()
135
166
  })
136
167
  </script>
137
168
 
138
169
  <template>
139
170
  <!-- 编辑弹窗 -->
140
- <JnDialog ref="editDialogRef" title="项目管理" width="600px">
171
+ <JnDialog
172
+ ref="editDialogRef"
173
+ title="项目管理"
174
+ width="600px"
175
+ :close-on-click-modal="false"
176
+ :close-on-press-escape="true"
177
+ >
141
178
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto" v-loading="loading">
142
179
  <el-form-item prop="id"></el-form-item>
143
180
  <el-form-item label="项目名称" prop="name">
@@ -342,9 +379,10 @@ onActivated(() => {
342
379
  <PdfView :loadKeys="row.newAttachmentFile" isPdf />
343
380
  </template>
344
381
  </el-table-column>
345
- <el-table-column label="操作" width="100" align="center" fixed="right">
382
+ <el-table-column label="操作" width="120" align="center" fixed="right">
346
383
  <template #default="{ row }">
347
384
  <el-button link type="primary" @click.stop="handleEdit(row)">编辑</el-button>
385
+ <el-button link type="danger" @click.stop="handleDelete(row)">删除</el-button>
348
386
  </template>
349
387
  </el-table-column>
350
388
  </JnTable>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { TableApi } from '@/api/demos/index'
2
+ import { ListApi } from '@/api/demos/index'
3
3
  import { JnDatetime } from '@jnrs/vue-core/components'
4
4
  import CardTable from '@/components/common/CardTable.vue'
5
5
  import ImageView from '@/components/common/ImageView.vue'
@@ -8,7 +8,7 @@ import DictTag from '@/components/common/DictTag.vue'
8
8
  </script>
9
9
 
10
10
  <template>
11
- <CardTable :getTableDataApi="TableApi">
11
+ <CardTable :getTableDataApi="ListApi">
12
12
  <template #header>项目列表</template>
13
13
  <template #table>
14
14
  <el-table-column prop="name" label="项目名称" min-width="200" sortable show-overflow-tooltip />
@@ -1,9 +1,318 @@
1
1
  <script setup lang="ts">
2
- import { ref } from 'vue'
2
+ import { ref, computed, onActivated, onDeactivated } from 'vue'
3
+ import { formatDateTime, formatWeekday, formatGreeting } from '@jnrs/shared'
4
+ import { useAuthStore } from '@jnrs/vue-core/pinia'
5
+ import { handleRouter } from '@jnrs/vue-core/router'
6
+ import ImageView from '@/components/common/ImageView.vue'
7
+
8
+ const { userInfo } = useAuthStore()
9
+
10
+ // 当前时间
11
+ const currentTime = ref('0000-00-00 00:00:00')
12
+ let timer: ReturnType<typeof setInterval>
13
+
14
+ // 格式化时间
15
+ const formattedTime = computed(() => {
16
+ const d = currentTime.value
17
+ return {
18
+ date: d.split(' ')[0],
19
+ time: d.split(' ')[1],
20
+ weekday: formatWeekday(),
21
+ greeting: formatGreeting()
22
+ }
23
+ })
24
+
25
+ // 统计卡片配置
26
+ const statCards = [
27
+ { titleKey: 'home.quickEntry.unitTest', path: '/unitTest', icon: 'IceCreamSquare', value: 999 },
28
+ { titleKey: 'home.quickEntry.simpleTable', path: '/simpleTable', icon: 'Box', value: 1 },
29
+ { titleKey: 'home.quickEntry.crud', path: '/crud', icon: 'Notification', value: 22 },
30
+ { titleKey: 'home.quickEntry.visual', path: '/visual', icon: 'Cpu', value: 0 }
31
+ ]
32
+
33
+ // 快捷入口配置
34
+ const quickEntries = [
35
+ { titleKey: 'home.quickEntry.unitTest', path: '/unitTest', icon: 'IceCreamSquare', color: '#409eff' },
36
+ { titleKey: 'home.quickEntry.simpleTable', path: '/simpleTable', icon: 'Box', color: '#67c23a' },
37
+ { titleKey: 'home.quickEntry.crud', path: '/crud', icon: 'Notification', color: '#e6a23c' },
38
+ { titleKey: 'home.quickEntry.visual', path: '/visual', icon: 'Cpu', color: '#9c27b0' }
39
+ ]
40
+
41
+ // 跳转页面
42
+ const navigateTo = (path: string) => {
43
+ handleRouter({ path })
44
+ }
45
+
46
+ onActivated(() => {
47
+ timer = setInterval(() => {
48
+ currentTime.value = formatDateTime()
49
+ }, 1000)
50
+ })
51
+
52
+ onDeactivated(() => {
53
+ clearInterval(timer)
54
+ })
3
55
  </script>
4
56
 
5
57
  <template>
6
- <div>欢迎使用</div>
58
+ <div class="home-page">
59
+ <!-- 顶部欢迎区域 -->
60
+ <el-card shadow="never" class="welcome-card">
61
+ <div class="welcome-content">
62
+ <div class="welcome-left">
63
+ <div class="avatar-wrapper">
64
+ <ImageView v-if="userInfo?.avatarFileName" class="avatar" :loadKeys="userInfo.avatarFileName" />
65
+ <img v-else class="avatar" src="@/assets/images/common/avatar.png" alt="avatar" />
66
+ </div>
67
+ <div class="welcome-info">
68
+ <div class="welcome-text">
69
+ <span class="name">{{ userInfo?.name || $t('home.welcome.defaultName') }}</span>
70
+ <span class="greeting">,{{ formattedTime?.greeting }}</span>
71
+ </div>
72
+ <div class="welcome-meta">
73
+ <span class="account">{{ $t('home.welcome.accountLabel') }}{{ userInfo?.account }}</span>
74
+ <span class="divider">|</span>
75
+ <span class="login-time">{{ $t('home.welcome.lastLoginLabel') }}{{ userInfo?.loginDateTime }}</span>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <div class="welcome-right">
80
+ <div class="time-display">
81
+ <div class="time">{{ formattedTime.time }}</div>
82
+ <div class="date-info">
83
+ <span class="date">{{ formattedTime.date }}</span>
84
+ <span class="weekday">{{ formattedTime.weekday }}</span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </el-card>
90
+
91
+ <!-- 统计卡片 -->
92
+ <el-row :gutter="16" class="stats-row">
93
+ <el-col :span="6" v-for="item in statCards" :key="item.path">
94
+ <el-card shadow="never" class="stat-card" @click="navigateTo(item.path)">
95
+ <div class="stat-content">
96
+ <div class="stat-icon" :style="{ background: `var(--jnrs-color-primary-005)` }">
97
+ <el-icon :size="28"><component :is="item.icon" /></el-icon>
98
+ </div>
99
+ <div class="stat-info">
100
+ <div class="stat-value">{{ item.value }}</div>
101
+ <div class="stat-label">{{ $t(item.titleKey) }}</div>
102
+ </div>
103
+ </div>
104
+ </el-card>
105
+ </el-col>
106
+ </el-row>
107
+
108
+ <!-- 快捷入口 -->
109
+ <el-card shadow="never" class="section-card">
110
+ <template #header>
111
+ <div class="section-header">
112
+ <span class="section-title">{{ $t('home.section.quickEntryTitle') }}</span>
113
+ </div>
114
+ </template>
115
+ <div class="quick-entries">
116
+ <div class="entry-item" v-for="entry in quickEntries" :key="entry.path" @click="navigateTo(entry.path)">
117
+ <div class="entry-icon" :style="{ background: entry.color + '15', color: entry.color }">
118
+ <el-icon :size="24"><component :is="entry.icon" /></el-icon>
119
+ </div>
120
+ <div class="entry-title">{{ $t(entry.titleKey) }}</div>
121
+ </div>
122
+ </div>
123
+ </el-card>
124
+ </div>
7
125
  </template>
8
126
 
9
- <style lang="scss" scoped></style>
127
+ <style lang="scss" scoped>
128
+ .home-page {
129
+ padding-bottom: 20px;
130
+ }
131
+
132
+ // 欢迎卡片
133
+ .welcome-card {
134
+ margin-bottom: 16px;
135
+
136
+ :deep(.el-card__body) {
137
+ padding: 24px;
138
+ }
139
+
140
+ .welcome-content {
141
+ display: flex;
142
+ justify-content: space-between;
143
+ align-items: center;
144
+ }
145
+
146
+ .welcome-left {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 20px;
150
+ }
151
+
152
+ .avatar-wrapper {
153
+ .avatar {
154
+ width: 64px;
155
+ height: 64px;
156
+ border-radius: 50%;
157
+ object-fit: cover;
158
+ border: 4px solid var(--jnrs-color-primary-01);
159
+ }
160
+ }
161
+
162
+ .welcome-info {
163
+ .welcome-text {
164
+ font-size: 20px;
165
+ margin-bottom: 8px;
166
+
167
+ .name {
168
+ font-weight: 600;
169
+ color: var(--jnrs-color-primary);
170
+ }
171
+
172
+ .greeting {
173
+ color: var(--jnrs-font-primary-08);
174
+ }
175
+ }
176
+
177
+ .welcome-meta {
178
+ font-size: 13px;
179
+ color: var(--jnrs-font-primary-06);
180
+
181
+ .divider {
182
+ margin: 0 12px;
183
+ color: var(--jnrs-font-primary-02);
184
+ }
185
+ }
186
+ }
187
+
188
+ .welcome-right {
189
+ .time-display {
190
+ text-align: right;
191
+
192
+ .time {
193
+ font-size: 32px;
194
+ font-weight: 600;
195
+ font-family: monospace;
196
+ color: var(--jnrs-color-primary);
197
+ letter-spacing: 2px;
198
+ }
199
+
200
+ .date-info {
201
+ margin-top: 4px;
202
+ font-size: 14px;
203
+ color: var(--jnrs-font-primary-06);
204
+
205
+ .date {
206
+ margin-right: 12px;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // 统计卡片
214
+ .stats-row {
215
+ margin-bottom: 16px;
216
+ }
217
+
218
+ .stat-card {
219
+ cursor: pointer;
220
+ transition: all 0.3s ease;
221
+
222
+ &:hover {
223
+ transform: translateY(-2px);
224
+ }
225
+
226
+ :deep(.el-card__body) {
227
+ padding: 20px;
228
+ }
229
+
230
+ .stat-content {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 16px;
234
+ }
235
+
236
+ .stat-icon {
237
+ width: 56px;
238
+ height: 56px;
239
+ border-radius: 12px;
240
+ display: flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ color: var(--jnrs-color-primary);
244
+ }
245
+
246
+ .stat-info {
247
+ .stat-value {
248
+ font-size: 28px;
249
+ font-weight: 600;
250
+ color: var(--jnrs-font-primary);
251
+ line-height: 1.2;
252
+ }
253
+
254
+ .stat-label {
255
+ font-size: 14px;
256
+ color: var(--jnrs-font-primary-06);
257
+ margin-top: 4px;
258
+ }
259
+ }
260
+ }
261
+
262
+ // 区块卡片
263
+ .section-card {
264
+ margin-bottom: 16px;
265
+
266
+ .section-header {
267
+ .section-title {
268
+ font-size: 16px;
269
+ font-weight: 600;
270
+ color: var(--jnrs-font-primary);
271
+ }
272
+ }
273
+ }
274
+
275
+ // 快捷入口
276
+ .quick-entries {
277
+ display: grid;
278
+ grid-template-columns: repeat(6, 1fr);
279
+ gap: 16px;
280
+
281
+ .entry-item {
282
+ display: flex;
283
+ flex-direction: column;
284
+ align-items: center;
285
+ padding: 20px 16px;
286
+ border-radius: 8px;
287
+ cursor: pointer;
288
+ transition: all 0.3s ease;
289
+ background: var(--jnrs-background-primary);
290
+
291
+ &:hover {
292
+ background: var(--jnrs-color-primary-005);
293
+ transform: translateY(-2px);
294
+
295
+ .entry-icon {
296
+ transform: scale(1.1);
297
+ }
298
+ }
299
+
300
+ .entry-icon {
301
+ width: 48px;
302
+ height: 48px;
303
+ border-radius: 12px;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: center;
307
+ margin-bottom: 12px;
308
+ transition: transform 0.3s ease;
309
+ }
310
+
311
+ .entry-title {
312
+ font-size: 14px;
313
+ color: var(--jnrs-font-primary-08);
314
+ text-align: center;
315
+ }
316
+ }
317
+ }
318
+ </style>
@@ -261,7 +261,7 @@ const submitForm = async () => {
261
261
 
262
262
  .greeting {
263
263
  position: relative;
264
- color: var(--jnrs-color-primary-06);
264
+ color: var(--jnrs-color-primary-05);
265
265
  font-size: 14px;
266
266
  text-align: center;
267
267
  text-transform: uppercase;
@@ -274,7 +274,7 @@ const submitForm = async () => {
274
274
  top: 50%;
275
275
  width: 35%;
276
276
  height: 1px;
277
- background: var(--jnrs-color-primary-06);
277
+ background: var(--jnrs-color-primary-05);
278
278
  transform: translateY(-50%);
279
279
  filter: opacity(0.25);
280
280
  }
@@ -66,6 +66,7 @@ export default defineConfig({
66
66
  '@jnrs/shared': ['@jnrs/shared']
67
67
  }
68
68
  }
69
- }
69
+ },
70
+ chunkSizeWarningLimit: 5000 // chunk 阈值警告,默认 5000 kb
70
71
  }
71
72
  })
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'http'
2
2
 
3
3
  const res_fail = {
4
4
  code: 1,
5
- msg: '操作失败'
5
+ msg: 'mock msg:操作失败'
6
6
  }
7
7
 
8
8
  export default [
@@ -12,7 +12,7 @@ export default [
12
12
  method: 'get',
13
13
  rawResponse: async (req: IncomingMessage, res: ServerResponse) => {
14
14
  res.statusCode = 404
15
- res.end('File not found')
15
+ res.end('mock msg:文件不存在')
16
16
  return
17
17
  }
18
18
  },
@@ -23,7 +23,7 @@ export default [
23
23
  response: () => {
24
24
  return {
25
25
  code: 3,
26
- msg: '暂无权限'
26
+ msg: 'mock msg:暂无权限'
27
27
  }
28
28
  }
29
29
  },
@@ -23,7 +23,7 @@ export default [
23
23
  rawResponse: async (req: IncomingMessage, res: ServerResponse) => {
24
24
  if (!req.url) {
25
25
  res.statusCode = 400
26
- res.end('Bad Request')
26
+ res.end('mock msg:Bad Request')
27
27
  return
28
28
  }
29
29
 
@@ -38,13 +38,13 @@ export default [
38
38
 
39
39
  if (!filePath.startsWith(fileDir + path.sep) && filePath !== fileDir) {
40
40
  res.statusCode = 403
41
- res.end('Forbidden')
41
+ res.end('mock msg:Forbidden')
42
42
  return
43
43
  }
44
44
 
45
45
  if (!fs.existsSync(filePath)) {
46
46
  res.statusCode = 404
47
- res.end('File not found')
47
+ res.end('mock msg:File not found')
48
48
  return
49
49
  }
50
50
 
@@ -60,7 +60,7 @@ export default [
60
60
  } catch (error) {
61
61
  console.error('Mock file serve error:', error)
62
62
  res.statusCode = 500
63
- res.end('Internal Server Error')
63
+ res.end('mock msg:Internal Server Error')
64
64
  }
65
65
  }
66
66
  }
@@ -1,6 +1,6 @@
1
1
  const res_success = {
2
2
  code: 0,
3
- msg: '操作成功'
3
+ msg: 'mock msg:操作成功'
4
4
  }
5
5
 
6
6
  export default [
@@ -35,5 +35,13 @@ export default [
35
35
  response: () => {
36
36
  return res_success
37
37
  }
38
+ },
39
+ // 数据删除
40
+ {
41
+ url: /^\/mock\/demos\/delete\/[a-zA-Z0-9-]+$/,
42
+ method: 'delete',
43
+ response: () => {
44
+ return res_success
45
+ }
38
46
  }
39
47
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-jnrs-vue",
3
- "version": "1.2.19",
3
+ "version": "1.2.21",
4
4
  "description": "巨能前端工程化开发,Vue 项目模板脚手架",
5
5
  "keywords": [
6
6
  "vue",