create-jnrs-template-vue 1.2.6 → 1.2.8

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/README.md CHANGED
@@ -9,15 +9,18 @@ TypeScript、Vue3 生态
9
9
  ## 🧩 安装使用说明
10
10
  新项目默认名称为 jnrs-template-vue
11
11
  ```shell
12
- ✅ 正确用法
12
+ # ✅ 正确用法
13
+ # 创建新项目
13
14
  pnpm create jnrs-template-vue@latest
15
+ # 在当前目录下创建项目
16
+ pnpm create jnrs-template-vue@latest .
14
17
 
15
- cd jnrs-template-vue
18
+ # 安装依赖/启动开发/打包构建
16
19
  pnpm i
17
20
  pnpm dev
18
21
  pnpm build
19
22
 
20
- 错误用法(不要这样做!)
23
+ # 错误用法
21
24
  pnpm add create-jnrs-template-vue
22
25
  ```
23
26
 
package/bin/create.mjs CHANGED
@@ -1,3 +1,11 @@
1
+ /**
2
+ * @Author : TanRui
3
+ * @WeChat : Tan578853789
4
+ * @File : create.mjs
5
+ * @Date : 2026/01/01
6
+ * @Desc. : 脚手架创建脚本
7
+ */
8
+
1
9
  import { fileURLToPath } from 'url'
2
10
  import { dirname, join, relative } from 'path'
3
11
  import { promises as fs, existsSync } from 'fs'
@@ -68,6 +76,9 @@ async function main() {
68
76
  const argv = minimist(process.argv.slice(2), { string: ['_'] })
69
77
  let targetDir = argv._[0]
70
78
 
79
+ // 新增:判断是否使用当前目录
80
+ const isCurrentDir = targetDir === '.'
81
+
71
82
  if (!targetDir) {
72
83
  const { name } = await prompts({
73
84
  type: 'text',
@@ -85,7 +96,8 @@ async function main() {
85
96
  })
86
97
  if (!name) process.exit(1)
87
98
  targetDir = name
88
- } else {
99
+ } else if (!isCurrentDir) {
100
+ // 仅当不是当前目录时,才校验包名和目录是否存在
89
101
  if (!isValidPackageName(targetDir)) {
90
102
  console.error('❌ 无效的项目名称:', targetDir)
91
103
  console.error(' 包名称必须有效(小写、无空格等)')
@@ -97,7 +109,8 @@ async function main() {
97
109
  }
98
110
  }
99
111
 
100
- const root = join(process.cwd(), targetDir.trim())
112
+ // 设置根目录:如果是当前目录,则用 process.cwd()
113
+ const root = isCurrentDir ? process.cwd() : join(process.cwd(), targetDir.trim())
101
114
 
102
115
  // 复制模板
103
116
  const templateDir = join(__dirname, '..', 'jnrs-template-vue')
@@ -106,7 +119,13 @@ async function main() {
106
119
  // 更新 package.json
107
120
  const pkgPath = join(root, 'package.json')
108
121
  const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'))
109
- pkg.name = toValidPackageName(targetDir)
122
+
123
+ // 如果是当前目录,使用当前文件夹名称作为包名;否则使用 targetDir
124
+ const pkgName = isCurrentDir
125
+ ? toValidPackageName(process.cwd().split(/[\\/]/).pop() || 'jnrs-template-vue')
126
+ : toValidPackageName(targetDir)
127
+
128
+ pkg.name = pkgName
110
129
  await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2))
111
130
 
112
131
  // 检测可用包管理器
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jnrs-template-vue",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "JNRS 信息化管理系统",
5
5
  "author": "talia_tan",
6
6
  "private": true,
@@ -19,10 +19,11 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@element-plus/icons-vue": "^2.3.2",
22
- "@jnrs/shared": "1.1.11",
23
- "@jnrs/vue-core": "1.2.8",
22
+ "@jnrs/shared": "1.1.12",
23
+ "@jnrs/vue-core": "1.2.9",
24
+ "@jnrs/lingshu-smart": "2.2.3",
24
25
  "@vueuse/core": "^14.1.0",
25
- "element-plus": "^2.11.9",
26
+ "element-plus": "^2.13.3",
26
27
  "pinia": "^3.0.4",
27
28
  "pinia-plugin-persistedstate": "^4.7.1",
28
29
  "vue": "^3.5.25",
@@ -3,8 +3,6 @@
3
3
  "msg": "操作成功",
4
4
  "data": [
5
5
  {
6
- "path": "/demos",
7
- "name": "Demos",
8
6
  "meta": {
9
7
  "title": "功能演示",
10
8
  "icon": "StarFilled",
@@ -37,6 +35,23 @@
37
35
  "todoCount": 0
38
36
  },
39
37
  "component": "/demos/crud/index"
38
+ },
39
+ {
40
+ "path": "/testRedirect",
41
+ "name": "TestRedirect",
42
+ "meta": {
43
+ "title": "重定向测试"
44
+ },
45
+ "redirect": "/crud"
46
+ },
47
+ {
48
+ "path": "/testAlias",
49
+ "name": "TestAlias",
50
+ "meta": {
51
+ "title": "相同component不同path",
52
+ "testId": 1
53
+ },
54
+ "component": "/demos/crud/index"
40
55
  }
41
56
  ]
42
57
  },
@@ -51,6 +66,17 @@
51
66
  },
52
67
  "component": "/visual/index"
53
68
  },
69
+ {
70
+ "path": "/lingshuSmart/editorPage",
71
+ "name": "LingshuSmartEditorPage",
72
+ "meta": {
73
+ "title": "Lingshu Smart",
74
+ "icon": "Promotion",
75
+ "noAuth": true,
76
+ "global": true
77
+ },
78
+ "component": "/lingshuSmart/editorPage"
79
+ },
54
80
  {
55
81
  "meta": {
56
82
  "title": "系统管理",
@@ -76,8 +102,7 @@
76
102
  "todoCount": 0,
77
103
  "permissions": ["user:view", "user:edit"]
78
104
  },
79
- "component": "/system/user/index",
80
- "redirect": "/crud"
105
+ "component": "/system/user/index"
81
106
  },
82
107
  {
83
108
  "path": "/system/role",
@@ -124,7 +124,7 @@ export const ImportTemplateApi = (): Promise<Blob> => {
124
124
  /**
125
125
  * 项目数据导入
126
126
  */
127
- export const ImportDataApi = (data: { file: File }) => {
127
+ export const ImportDataApi = (data: FormData) => {
128
128
  return axiosRequest({
129
129
  url: '/mock/project/import',
130
130
  method: 'post',
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <div class="routerTabs">
3
+ <el-tabs v-model="activeTab" type="card" @tab-remove="removeTab" @tab-click="handleTabClick">
4
+ <el-tab-pane
5
+ v-for="item in tabs"
6
+ :key="item.fullPath"
7
+ :name="item.fullPath"
8
+ :closable="item.path !== HOME_PATH"
9
+ >
10
+ <template #label>
11
+ <span>{{ tabLabel(item) }}</span>
12
+ </template>
13
+ </el-tab-pane>
14
+ </el-tabs>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup>
19
+ import { ref, watch, computed, onMounted } from 'vue'
20
+ import { useRouter, useRoute } from 'vue-router'
21
+
22
+ const HOME_PATH = '/home/index'
23
+ const router = useRouter()
24
+ const route = useRoute()
25
+
26
+ // 使用 fullPath 作为激活 Tab 的值
27
+ const activeTab = ref(route.fullPath)
28
+
29
+ // Tab 列表:每个 tab 包含 fullPath、path、meta
30
+ const tabs = ref([
31
+ {
32
+ fullPath: route.fullPath,
33
+ path: route.path,
34
+ meta: route.meta
35
+ }
36
+ ])
37
+
38
+ // 标签页标题计算
39
+ const tabLabel = computed(() => {
40
+ return function (item) {
41
+ let label = item.meta?.title || '未命名'
42
+ if (item.meta?.fullPathTitle) {
43
+ label = item.meta.fullPathTitle.replace(/,/g, '/')
44
+ }
45
+ return label
46
+ }
47
+ })
48
+
49
+ // 初始化:插入首页(如果当前不是首页)
50
+ onMounted(() => {
51
+ if (route.path !== HOME_PATH) {
52
+ tabs.value.unshift({
53
+ fullPath: HOME_PATH,
54
+ path: HOME_PATH,
55
+ meta: { title: '工作台' }
56
+ })
57
+ }
58
+ })
59
+
60
+ // 添加标签页(基于当前 route)
61
+ const addTab = () => {
62
+ const currentRoute = route
63
+
64
+ // 如果已存在相同 fullPath 的 Tab,直接激活,不重复添加
65
+ const existingTab = tabs.value.find((tab) => tab.fullPath === currentRoute.fullPath)
66
+ if (existingTab) {
67
+ activeTab.value = currentRoute.fullPath
68
+ return
69
+ }
70
+
71
+ // 添加新 Tab
72
+ tabs.value.push({
73
+ fullPath: currentRoute.fullPath,
74
+ path: currentRoute.path,
75
+ meta: currentRoute.meta
76
+ })
77
+ activeTab.value = currentRoute.fullPath
78
+ }
79
+
80
+ // 移除标签页
81
+ const removeTab = (targetFullPath) => {
82
+ if (targetFullPath === HOME_PATH) return
83
+
84
+ const currentIndex = tabs.value.findIndex((tab) => tab.fullPath === targetFullPath)
85
+ if (currentIndex === -1) return
86
+
87
+ // 删除 Tab
88
+ tabs.value.splice(currentIndex, 1)
89
+
90
+ // 如果删除的是当前激活的 Tab
91
+ if (activeTab.value === targetFullPath) {
92
+ const nextTab = tabs.value[currentIndex] || tabs.value[currentIndex - 1]
93
+ if (nextTab) {
94
+ activeTab.value = nextTab.fullPath
95
+ router.push(nextTab.fullPath)
96
+ } else {
97
+ // 回退到首页
98
+ activeTab.value = HOME_PATH
99
+ router.push(HOME_PATH)
100
+ }
101
+ }
102
+ }
103
+
104
+ // 监听路由变化(使用 fullPath)
105
+ watch(
106
+ () => route.fullPath,
107
+ (newFullPath) => {
108
+ // 避免由 Tab 点击触发的导航再次添加 Tab(防止循环)
109
+ if (newFullPath !== activeTab.value) {
110
+ addTab()
111
+ }
112
+ },
113
+ { immediate: true }
114
+ )
115
+
116
+ // 处理标签页点击
117
+ const handleTabClick = (tab) => {
118
+ const fullPath = tab.props.name // el-tab-pane 的 name 即 fullPath
119
+ if (fullPath !== activeTab.value) {
120
+ activeTab.value = fullPath
121
+ router.push(fullPath)
122
+ }
123
+ }
124
+ </script>
125
+
126
+ <style lang="scss" scoped>
127
+ .routerTabs {
128
+ :deep(.el-tabs__header) {
129
+ margin-bottom: 0;
130
+ height: 32px;
131
+ }
132
+ :deep(.el-tabs__item) {
133
+ color: #888;
134
+ font-size: 12px;
135
+ height: 32px;
136
+ }
137
+ :deep(.el-tabs__item.is-active) {
138
+ color: #09a2a5;
139
+ border-bottom-color: #f2f2f2;
140
+ }
141
+ :deep(.el-tabs__nav-prev) {
142
+ color: #09a2a5;
143
+ background: #fff;
144
+ }
145
+ :deep(.el-tabs__nav-next) {
146
+ color: #09a2a5;
147
+ background: #fff;
148
+ }
149
+ }
150
+ </style>
@@ -30,7 +30,7 @@ const route = useRoute()
30
30
  class="leftSide_menu"
31
31
  popper-class="layoutPage_leftSide_menu_popper"
32
32
  :router="true"
33
- :default-active="route.path"
33
+ :default-active="route.name"
34
34
  :collapse="menuCollapse"
35
35
  :unique-opened="true"
36
36
  >
@@ -11,7 +11,7 @@ const { menuCollapse } = useSystemStore()
11
11
 
12
12
  <template>
13
13
  <!-- 一级路由 -->
14
- <el-menu-item :index="listItem.path" v-if="!listItem.children">
14
+ <el-menu-item :index="listItem.name" :route="listItem.path" v-if="!listItem.children">
15
15
  <el-icon v-if="listItem.meta.icon">
16
16
  <component :is="listItem.meta.icon" />
17
17
  </el-icon>
@@ -1,7 +1,7 @@
1
- import { LAYOUT_NAME, GLOBAL_COMPONENT, routes } from './routes'
2
- import { createVueRouter } from '@jnrs/vue-core/router'
3
1
  import type { FileModules, RouteLocationNormalizedGeneric } from '@jnrs/vue-core/router'
4
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
5
  import { useAuthStore, useMenuStore } from '@jnrs/vue-core/pinia'
6
6
  import { MenuApi } from '@/api/system'
7
7
  import { hasMenuViewPermission } from '@/utils/permissions'
@@ -5,6 +5,7 @@ import type { ProjectItem, AddProjectItem } from '@/api/demos/index'
5
5
  import { ref, onActivated } from 'vue'
6
6
  import { ElMessage } from 'element-plus'
7
7
  import { Plus } from '@element-plus/icons-vue'
8
+ import { useRoute } from '@jnrs/vue-core/router'
8
9
  import { objectMatchAssign, dateFormatsToObject } from '@jnrs/shared'
9
10
  import { debounce } from '@jnrs/shared/lodash'
10
11
  import { isNumberGtZero } from '@jnrs/shared/validator'
@@ -26,6 +27,7 @@ const loading = ref(false)
26
27
  const tableData = ref<ProcessedProjectItem[]>([])
27
28
  const total = ref(0)
28
29
  const pagination = ref<Pagination>({ pageNo: 1, pageSize: 20 })
30
+ const route = useRoute()
29
31
 
30
32
  // 编辑
31
33
  const editDialogRef = ref()
@@ -128,6 +130,7 @@ const submitForm = () => {
128
130
  }
129
131
 
130
132
  onActivated(() => {
133
+ console.log(route.meta)
131
134
  getTable()
132
135
  })
133
136
  </script>
@@ -31,6 +31,21 @@ const handleRouteChange = () => {
31
31
  })
32
32
  }
33
33
 
34
+ const handleRouterSystemMine = () => {
35
+ handleRouter({
36
+ name: 'SystemMine',
37
+ query: {
38
+ id: 1
39
+ }
40
+ })
41
+ // handleRouter({
42
+ // path: '/system/mine/index',
43
+ // query: {
44
+ // id: 1
45
+ // }
46
+ // })
47
+ }
48
+
34
49
  const changeI18n = () => {
35
50
  datetime.value = formatDateTime() + ' ' + formatWeekday()
36
51
  isFloatGtZero({}, '0', (msg) => {
@@ -80,7 +95,8 @@ onMounted(() => {
80
95
  </ul>
81
96
  <RequestPage />
82
97
  <p>完整路由测试(先点击按钮,获取完整菜单数据)</p>
83
- <el-button type="success" plain @click="handleMenuApi">获取完整菜单数据</el-button>
98
+ <el-button type="success" plain size="small" @click="handleRouterSystemMine">跳转到个人中心</el-button>
99
+ <el-button type="success" plain size="small" @click="handleMenuApi">获取完整菜单数据</el-button>
84
100
  <el-cascader
85
101
  v-model="currentRoute"
86
102
  :options="routeOptions"
@@ -89,6 +105,7 @@ onMounted(() => {
89
105
  value: 'name',
90
106
  label: 'name'
91
107
  }"
108
+ size="small"
92
109
  :show-all-levels="false"
93
110
  @change="handleRouteChange"
94
111
  >
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { LingshuSmartEditor } from '@jnrs/lingshu-smart/components'
3
+ </script>
4
+
5
+ <template>
6
+ <LingshuSmartEditor />
7
+ </template>
8
+
9
+ <style lang="scss" scoped></style>
@@ -21,13 +21,13 @@ const { menus } = useMenuStore()
21
21
  <el-icon v-if="row.meta.icon"><component :is="row.meta.icon" /></el-icon>
22
22
  </template>
23
23
  </el-table-column>
24
- <el-table-column prop="path" label="路由地址" min-width="120" header-align="center" />
25
- <el-table-column prop="component" label="component path" min-width="120" header-align="center" />
24
+ <el-table-column prop="path" label="path" min-width="120" header-align="center" />
25
+ <el-table-column prop="name" label="name" min-width="120" header-align="center" />
26
+ <el-table-column prop="component" label="component" min-width="120" header-align="center" />
26
27
  <el-table-column prop="redirect" label="redirect" min-width="120" header-align="center" />
27
- <el-table-column prop="editRole" label="权限标识" min-width="120" header-align="center">
28
+ <el-table-column prop="meta.permissions" label="permissions" min-width="120" header-align="center">
28
29
  <template #default="{ row }">
29
- <span v-if="!row.meta.permissions" style="color: #999">auto</span>
30
- <span v-else>{{ row.meta.permissions }}</span>
30
+ <span>{{ row.meta.permissions }}</span>
31
31
  </template>
32
32
  </el-table-column>
33
33
  <el-table-column prop="type" label="类型" width="80" align="center">
@@ -36,18 +36,8 @@ const { menus } = useMenuStore()
36
36
  <el-tag type="success" v-else>目录</el-tag>
37
37
  </template>
38
38
  </el-table-column>
39
- <el-table-column prop="type" label="NoAuthApi" width="80" align="center">
40
- <template #default="{ row }">
41
- <el-tag type="danger" v-if="row.meta.noAuth">否</el-tag>
42
- <el-tag type="success" v-else>是</el-tag>
43
- </template>
44
- </el-table-column>
45
- <el-table-column prop="type" label="Global" width="80" align="center">
46
- <template #default="{ row }">
47
- <el-tag type="success" v-if="row.meta.global">是</el-tag>
48
- <el-tag type="primary" v-else>否</el-tag>
49
- </template>
50
- </el-table-column>
39
+ <el-table-column prop="meta.noAuth" label="meta.noAuth" width="80" align="center" />
40
+ <el-table-column prop="meta.global" label="meta.global" width="80" align="center" />
51
41
  </JnTable>
52
42
  </el-card>
53
43
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-jnrs-template-vue",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "巨能前端工程化开发,Vue 项目模板脚手架",
5
5
  "keywords": [
6
6
  "vue",
@@ -1,9 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useForm.ts
5
- * @Date : 2025/11/30
6
- * @Desc. : 表单通用逻辑
7
- */
8
-
9
- export function useForm() {}
@@ -1,9 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useModal.ts
5
- * @Date : 2025/11/30
6
- * @Desc. : 弹窗控制逻辑
7
- */
8
-
9
- export function useModal() {}
@@ -1,35 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useTable.ts
5
- * @Date : 2025/11/30
6
- * @Desc. : 表格、分页、筛选等通用逻辑
7
- */
8
-
9
- import { ref, computed } from 'vue'
10
-
11
- export function useTable<T>(initialData: T[] = []) {
12
- const data = ref<T[]>(initialData)
13
- const pageSize = ref(10)
14
- const pageNo = ref(1)
15
-
16
- // 分页数据
17
- const paginatedData = computed(() => {
18
- const start = (pageNo.value - 1) * pageSize.value
19
- return data.value?.slice(start, start + pageSize.value)
20
- })
21
-
22
- // 设置新的数据
23
- const setNewData = (newData: T[]) => {
24
- data.value = newData
25
- pageNo.value = 1
26
- }
27
-
28
- return {
29
- data,
30
- pageSize,
31
- pageNo,
32
- paginatedData,
33
- setNewData
34
- }
35
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useTable.ts
5
- * @Date : 2025/11/30
6
- * @Desc. : 表格
7
- */
8
-
9
- import { ref, computed } from 'vue'
10
-
11
- export function useTable<T>(initialData: T[] = []) {
12
- const data = ref<T[]>(initialData)
13
- const pageSize = ref(10)
14
- const pageNo = ref(1)
15
-
16
- // 分页数据
17
- const paginatedData = computed(() => {
18
- const start = (pageNo.value - 1) * pageSize.value
19
- return data.value?.slice(start, start + pageSize.value)
20
- })
21
-
22
- // 设置新的数据
23
- const setNewData = (newData: T[]) => {
24
- data.value = newData
25
- pageNo.value = 1
26
- }
27
-
28
- return {
29
- data,
30
- pageSize,
31
- pageNo,
32
- paginatedData,
33
- setNewData
34
- }
35
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * @Author : TanRui
3
- * @WeChat : Tan578853789
4
- * @File : useUser.ts
5
- * @Date : 2025/11/30
6
- * @Desc. : 用户相关逻辑
7
- */
8
-
9
- export function useUser() {}