vue3-element-plus-app-chu 0.0.15 → 0.0.16

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
@@ -16,6 +16,8 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
16
16
 
17
17
  应用外壳位于 `src/layout/index.vue`,由 `App.vue` 通过 `RouterView` 挂载,统一承载侧边导航、折叠状态和业务页面内容。菜单配置在 `src/router/menus.ts`,路由表在 `src/router/index.ts`,新增业务页面时两处各追加一项即可。业务地址、浏览器前进后退和刷新都由 vue-router 驱动;头寸预报列表页由 `KeepAlive` 缓存,返回时保留筛选和分页状态。
18
18
 
19
+ “注资/预注资管理”为一级分组菜单,下设“数字人民币注资明细查询”和“数字人民币账户行余额查询申请”。页面目录统一放在 `src/view/dcps/injection-management/` 下,路由分别为 `/injection-management/injection` 和 `/injection-management/balance`;旧地址 `/injection`、`/balance` 继续可用。
20
+
19
21
  ## 数字人民币头寸预报
20
22
 
21
23
  默认入口为数字人民币头寸预报(`/forecast`),侧边菜单可切换至其他数字人民币页面。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-element-plus-app-chu",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, ref } from 'vue'
2
+ import { computed, onMounted, ref, watch } from 'vue'
3
+ import type { MenuInstance } from 'element-plus'
3
4
  import { RouterView, useRoute, useRouter } from 'vue-router'
4
5
  import { layoutMenus as menus } from '@/router/menus'
5
6
 
@@ -7,12 +8,19 @@ const route = useRoute()
7
8
  const router = useRouter()
8
9
  const firstMenu = menus[0]!
9
10
  const collapsed = ref(false)
10
- // 子页面通过 meta.menu 指向所属一级菜单,进入新增、详情时侧边栏仍保持高亮。
11
+ const menuRef = ref<MenuInstance>()
12
+ // 子页面通过 meta.menu 指向所属页面菜单,进入新增、详情时侧边栏仍保持高亮。
11
13
  const activeMenu = computed(() => route.meta.menu ?? String(route.name ?? firstMenu.name))
12
14
  const selectMenu = (name: string) => {
13
15
  if (name !== route.name) void router.push({ name })
14
16
  }
15
17
 
18
+ // 直接访问子页面或通过浏览器前进、后退切换时,自动展开所属一级菜单。
19
+ watch([activeMenu, menuRef], ([name, menu]) => {
20
+ const parentMenu = menus.find((item) => item.children?.some((child) => child.name === name))
21
+ if (parentMenu) menu?.open(parentMenu.name)
22
+ }, { flush: 'post' })
23
+
16
24
  onMounted(() => {
17
25
  collapsed.value = window.innerWidth <= 760
18
26
  })
@@ -31,14 +39,27 @@ onMounted(() => {
31
39
 
32
40
  <div v-if="!collapsed" class="rail-caption">OPERATIONS</div>
33
41
  <el-menu
42
+ ref="menuRef"
34
43
  class="dcps-menu"
35
44
  :default-active="activeMenu"
36
45
  @select="selectMenu"
37
46
  >
38
- <el-menu-item v-for="menu in menus" :key="menu.name" :index="menu.name">
39
- <span v-if="collapsed" class="menu-label menu-label--compact">{{ menu.compactTitle }}</span>
40
- <span v-else class="menu-label">{{ menu.title }}</span>
41
- </el-menu-item>
47
+ <template v-for="menu in menus" :key="menu.name">
48
+ <el-sub-menu v-if="menu.children?.length" :index="menu.name">
49
+ <template #title>
50
+ <span v-if="collapsed" class="menu-label menu-label--compact">{{ menu.compactTitle }}</span>
51
+ <span v-else class="menu-label">{{ menu.title }}</span>
52
+ </template>
53
+ <el-menu-item v-for="child in menu.children" :key="child.name" :index="child.name">
54
+ <span v-if="collapsed" class="menu-label menu-label--compact">{{ child.compactTitle }}</span>
55
+ <span v-else class="menu-label menu-label--child">{{ child.title }}</span>
56
+ </el-menu-item>
57
+ </el-sub-menu>
58
+ <el-menu-item v-else :index="menu.name">
59
+ <span v-if="collapsed" class="menu-label menu-label--compact">{{ menu.compactTitle }}</span>
60
+ <span v-else class="menu-label">{{ menu.title }}</span>
61
+ </el-menu-item>
62
+ </template>
42
63
  </el-menu>
43
64
 
44
65
  <div class="rail-bottom">
@@ -99,7 +120,9 @@ onMounted(() => {
99
120
 
100
121
  .is-collapsed .side-rail { width: 76px; }
101
122
 
102
- .is-collapsed .dcps-menu :deep(.el-menu-item) { padding: 0 6px !important; }
123
+ .is-collapsed .dcps-menu :deep(.el-menu-item),
124
+ .is-collapsed .dcps-menu :deep(.el-sub-menu__title) { padding: 0 6px !important; }
125
+ .is-collapsed .dcps-menu :deep(.el-sub-menu__icon-arrow) { display: none; }
103
126
 
104
127
  .rail-brand {
105
128
  display: flex;
@@ -141,7 +164,8 @@ onMounted(() => {
141
164
  border-right: 0;
142
165
  }
143
166
 
144
- .dcps-menu :deep(.el-menu-item) {
167
+ .dcps-menu :deep(.el-menu-item),
168
+ .dcps-menu :deep(.el-sub-menu__title) {
145
169
  height: 46px;
146
170
  margin: 3px 0;
147
171
  padding: 0 12px !important;
@@ -150,10 +174,14 @@ onMounted(() => {
150
174
  font-size: 12px;
151
175
  }
152
176
 
177
+ .dcps-menu :deep(.el-sub-menu .el-menu-item) { height: auto; min-height: 46px; padding: 9px 12px 9px 24px !important; }
178
+ .dcps-menu :deep(.el-sub-menu__title) { padding-right: 32px !important; }
179
+ .dcps-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title) { color: var(--el-color-primary); }
153
180
  .dcps-menu :deep(.el-menu-item:hover) { color: var(--el-color-primary); }
154
181
  .dcps-menu :deep(.el-menu-item.is-active) { border-color: var(--el-color-primary-light-8); color: var(--el-color-primary); background: var(--el-color-primary-light-9); }
155
182
  .dcps-menu :deep(.el-menu-item.is-active::before) { position: absolute; left: 0; width: 3px; height: 19px; border-radius: 0 4px 4px 0; background: var(--el-color-primary); content: ''; }
156
183
  .menu-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.4; }
184
+ .menu-label--child { white-space: normal; }
157
185
 
158
186
  .menu-label--compact {
159
187
  width: 26px;
@@ -212,6 +240,7 @@ onMounted(() => {
212
240
  .rail-bottom { justify-content: center; margin-right: 0; margin-left: 0; padding-right: 0; padding-left: 0; }
213
241
  .collapse-button { justify-content: center; padding: 0; }
214
242
  .is-collapsed .side-rail { width: 64px; }
215
- .dcps-menu :deep(.el-menu-item) { justify-content: center; padding: 0 !important; }
243
+ .dcps-menu :deep(.el-menu-item),
244
+ .dcps-menu :deep(.el-sub-menu__title) { justify-content: center; padding: 0 !important; }
216
245
  }
217
246
  </style>
@@ -1,15 +1,15 @@
1
1
  import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
2
2
  import AppLayout from '@/layout/index.vue'
3
3
  import DigitalRmbAuthorization from '@/view/dcps/auth/index.vue'
4
- import DigitalRmbAccountBalance from '@/view/dcps/balance/index.vue'
4
+ import DigitalRmbAccountBalance from '@/view/dcps/injection-management/balance/index.vue'
5
5
  import DigitalRmbForecast from '@/view/dcps/forecast/index.vue'
6
- import DigitalRmbInjection from '@/view/dcps/injection/index.vue'
6
+ import DigitalRmbInjection from '@/view/dcps/injection-management/injection/index.vue'
7
7
  import DigitalRmbWriteOff from '@/view/dcps/write-off/index.vue'
8
8
  import DigitalRmbPositionEstimation from '@/view/dcps/estimation/index.vue'
9
9
 
10
10
  declare module 'vue-router' {
11
11
  interface RouteMeta {
12
- /** 一级菜单路由名;子页面用它保持侧边菜单高亮。 */
12
+ /** 所属页面菜单的路由名;新增、详情等子页面用它保持侧边菜单高亮。 */
13
13
  menu?: string
14
14
  /** 浏览器标题使用的页面名称。 */
15
15
  title?: string
@@ -65,16 +65,26 @@ const routes: RouteRecordRaw[] = [
65
65
  meta: { menu: 'estimation', title: '数字人民币头寸匡算' },
66
66
  },
67
67
  {
68
- path: 'injection',
69
- name: 'injection',
70
- component: DigitalRmbInjection,
71
- meta: { menu: 'injection', title: '数字人民币注资明细查询' },
72
- },
73
- {
74
- path: 'balance',
75
- name: 'balance',
76
- component: DigitalRmbAccountBalance,
77
- meta: { menu: 'balance', title: '数字人民币账户行余额查询申请' },
68
+ path: 'injection-management',
69
+ name: 'injection-management',
70
+ redirect: { name: 'injection' },
71
+ meta: { title: '注资/预注资管理' },
72
+ children: [
73
+ {
74
+ path: 'injection',
75
+ alias: '/injection',
76
+ name: 'injection',
77
+ component: DigitalRmbInjection,
78
+ meta: { menu: 'injection', title: '数字人民币注资明细查询' },
79
+ },
80
+ {
81
+ path: 'balance',
82
+ alias: '/balance',
83
+ name: 'balance',
84
+ component: DigitalRmbAccountBalance,
85
+ meta: { menu: 'balance', title: '数字人民币账户行余额查询申请' },
86
+ },
87
+ ],
78
88
  },
79
89
  {
80
90
  path: 'auth',
@@ -1,16 +1,28 @@
1
- /** 侧边菜单项;name 与对应的一级路由名保持一致。 */
2
- export interface LayoutMenu {
1
+ /** 侧边菜单项;name 与对应的路由名保持一致。 */
2
+ interface LayoutMenuItem {
3
3
  name: string
4
4
  title: string
5
5
  compactTitle: string
6
6
  }
7
7
 
8
- // 侧边菜单配置:新增业务页面时在这里追加一项,并在 router 中添加对应路由。
8
+ /** 一级菜单可包含二级页面菜单。 */
9
+ export interface LayoutMenu extends LayoutMenuItem {
10
+ children?: LayoutMenuItem[]
11
+ }
12
+
13
+ // 侧边菜单配置:业务页面可直接添加,或放入所属一级菜单的 children 中。
9
14
  export const layoutMenus: LayoutMenu[] = [
10
15
  { name: 'forecast', title: '数字人民币头寸预报', compactTitle: '头寸预报' },
11
16
  { name: 'estimation', title: '数字人民币头寸匡算', compactTitle: '匡算' },
12
- { name: 'injection', title: '数字人民币注资明细查询', compactTitle: '注资明细' },
13
- { name: 'balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询' },
17
+ {
18
+ name: 'injection-management',
19
+ title: '注资/预注资管理',
20
+ compactTitle: '注资管理',
21
+ children: [
22
+ { name: 'injection', title: '数字人民币注资明细查询', compactTitle: '注资明细' },
23
+ { name: 'balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询' },
24
+ ],
25
+ },
14
26
  { name: 'auth', title: '数字人民币授权', compactTitle: '授权' },
15
27
  { name: 'write-off', title: '数字人民币核销', compactTitle: '核销' },
16
28
  ]
@@ -2,6 +2,8 @@
2
2
 
3
3
  `src/view/dcps` 下每个业务页面统一保持三个文件,不再拆分子组件、工具文件或运行时配置:
4
4
 
5
+ 一级菜单为分组时,页面放在该分组目录下。例如“注资/预注资管理”的两个页面分别位于 `injection-management/injection/` 和 `injection-management/balance/`,各自保留下面三个文件。
6
+
5
7
  | 文件 | 用途 |
6
8
  | --- | --- |
7
9
  | `api.ts` | 当前页面用到的接口定义。 |
@@ -28,8 +30,9 @@
28
30
  | `/forecast/:predRefNo/edit` | `forecast-edit` | `forecast/index.vue` | 修改(`mode: 'edit'`) | 修改数字人民币头寸预报 |
29
31
  | `/forecast/:predRefNo` | `forecast-detail` | `forecast/index.vue` | 详情(`mode: 'detail'`) | 数字人民币头寸预报详情 |
30
32
  | `/estimation` | `estimation` | `estimation/index.vue` | 页面 | 数字人民币头寸匡算 |
31
- | `/injection` | `injection` | `injection/index.vue` | 页面 | 数字人民币注资明细查询 |
32
- | `/balance` | `balance` | `balance/index.vue` | 页面 | 数字人民币账户行余额查询申请 |
33
+ | `/injection-management` | `injection-management` | — | 重定向到 `injection` | 注资/预注资管理 |
34
+ | `/injection-management/injection` | `injection` | `injection-management/injection/index.vue` | 页面 | 数字人民币注资明细查询 |
35
+ | `/injection-management/balance` | `balance` | `injection-management/balance/index.vue` | 页面 | 数字人民币账户行余额查询申请 |
33
36
  | `/auth` | `auth` | `auth/index.vue` | 页面 | 数字人民币授权 |
34
37
  | `/write-off` | `write-off` | `write-off/index.vue` | 页面 | 数字人民币核销 |
35
38
  | `/` | — | 重定向到 `forecast` | — | — |
@@ -39,14 +42,15 @@
39
42
 
40
43
  - 头寸预报的列表、新增、修改、详情共用 `forecast/index.vue`,通过路由 `props.mode` 切换页面形态;修改和详情通过 `props.predRefNo` 接收路由参数。
41
44
  - `forecast/index.vue` 顶部使用一个普通 `<script>` 块保存列表记录缓存,跳转修改或详情前暂存列表行,用于补齐详情接口未返回的字段。
42
- - 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应的一级路由 `name` 保持一致。
45
+ - 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应路由的 `name` 保持一致;分组菜单通过 `children` 配置二级页面,页面的 `meta.menu` 指向自己的菜单项。
46
+ - “注资/预注资管理”对应 `injection-management` 分组,包含注资明细查询和账户行余额查询申请两个页面;访问子页面时自动展开父菜单。旧地址 `/injection`、`/balance` 保留为对应页面的别名。
43
47
  - `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报页面;新增、修改、详情按路由 `key` 分别缓存,返回列表时自动重新查询。
44
48
 
45
49
  ## 新增页面检查清单
46
50
 
47
- 1. 在 `src/view/dcps/<模块>/` 下新建 `index.vue`,并添加 `defineOptions({ name: 'Dcps<模块>Page' })`。
51
+ 1. 在 `src/view/dcps/<模块>/` 下新建 `index.vue`;如归属某个分组,放在 `src/view/dcps/<分组>/<模块>/` 下,并添加 `defineOptions({ name: 'Dcps<模块>Page' })`。
48
52
  2. 同目录补充 `api.ts`、`typs.ts`;不要再创建 `components/`、`utils.ts`、`runtime.ts` 等额外文件。
49
53
  3. 弹窗、子表格、表单直接写在 `index.vue` 中;工具函数写在 `index.vue` 的 `<script setup>` 内。
50
- 4. 在 `src/router/index.ts` 的 `children` 中新增路由,配置 `path`、`name`、`component`、`meta.menu`、`meta.title`。
51
- 5. 如需出现在侧边菜单,在 `src/router/menus.ts` 中追加菜单项,`name` 与一级路由名一致。
54
+ 4. 在 `src/router/index.ts` 中新增路由;分组页面添加到对应分组路由的 `children` 下,配置 `path`、`name`、`component`、`meta.menu`、`meta.title`。
55
+ 5. 如需出现在侧边菜单,在 `src/router/menus.ts` 中追加菜单项或添加到分组的 `children` 中,`name` 与对应路由名一致。
52
56
  6. 更新本文档的对应关系表。