create-jnrs-template-vue 1.1.5 → 1.1.7

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 (45) hide show
  1. package/jnrs-template-vue/.env.development +4 -5
  2. package/jnrs-template-vue/.env.production +1 -1
  3. package/jnrs-template-vue/.prettierrc.json +1 -1
  4. package/jnrs-template-vue/auto-imports.d.ts +1 -0
  5. package/jnrs-template-vue/components.d.ts +14 -0
  6. package/jnrs-template-vue/package.json +1 -1
  7. package/jnrs-template-vue/public/system/menu.json +27 -8
  8. package/jnrs-template-vue/src/App.vue +2 -2
  9. package/jnrs-template-vue/src/api/request.ts +4 -1
  10. package/jnrs-template-vue/src/api/{base → system}/index.ts +48 -20
  11. package/jnrs-template-vue/src/directives/permissions.ts +28 -0
  12. package/jnrs-template-vue/src/layout/RouterTabs.vue +1 -1
  13. package/jnrs-template-vue/src/layout/SideMenu.vue +2 -4
  14. package/jnrs-template-vue/src/layout/SideMenuItem.vue +1 -1
  15. package/jnrs-template-vue/src/layout/TopHeader.vue +20 -28
  16. package/jnrs-template-vue/src/layout/index.vue +35 -5
  17. package/jnrs-template-vue/src/locales/index.ts +10 -3
  18. package/jnrs-template-vue/src/main.ts +3 -2
  19. package/jnrs-template-vue/src/router/index.ts +41 -6
  20. package/jnrs-template-vue/src/router/routes.ts +28 -28
  21. package/jnrs-template-vue/src/stores/mock.ts +4 -2
  22. package/jnrs-template-vue/src/utils/permissions.ts +16 -0
  23. package/jnrs-template-vue/src/views/common/403.vue +5 -9
  24. package/jnrs-template-vue/src/views/common/404.vue +5 -9
  25. package/jnrs-template-vue/src/views/home/index.vue +54 -20
  26. package/jnrs-template-vue/src/views/login/index.vue +36 -20
  27. package/jnrs-template-vue/src/views/system/dict/index.vue +174 -0
  28. package/jnrs-template-vue/src/views/system/menu/index.vue +65 -0
  29. package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +38 -30
  30. package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +12 -20
  31. package/jnrs-template-vue/src/views/system/role/editDialog.vue +94 -0
  32. package/jnrs-template-vue/src/views/system/role/index.vue +45 -4
  33. package/jnrs-template-vue/vite.config.ts +2 -1
  34. package/jnrs-template-vue/viteMockServe/dictItemRes.json +27 -0
  35. package/jnrs-template-vue/viteMockServe/dictRes.json +141 -0
  36. package/jnrs-template-vue/viteMockServe/fail.ts +26 -0
  37. package/jnrs-template-vue/viteMockServe/index.ts +36 -27
  38. package/jnrs-template-vue/viteMockServe/{login.json → loginRes_admin.json} +4 -3
  39. package/jnrs-template-vue/viteMockServe/loginRes_user.json +713 -0
  40. package/jnrs-template-vue/viteMockServe/roleRes.json +37 -0
  41. package/jnrs-template-vue/viteMockServe/success.ts +31 -0
  42. package/package.json +1 -1
  43. package/jnrs-template-vue/.env.example +0 -14
  44. package/jnrs-template-vue/src/types/index.ts +0 -2
  45. package/jnrs-template-vue/src/views/system/user/index.vue +0 -9
@@ -10,25 +10,37 @@ export const LAYOUT_NAME = 'Layout'
10
10
  export const GLOBAL_COMPONENT = import('@/layout/BlankLayout.vue')
11
11
 
12
12
  export const routes = [
13
- {
14
- path: '/login',
15
- component: () => import('@/layout/BlankLayout.vue'),
16
- meta: { title: '登录' },
17
- children: [
18
- {
19
- path: '',
20
- name: 'Login',
21
- meta: { title: '登录', noAuth: true },
22
- component: () => import('@/views/login/index.vue')
23
- }
24
- ]
25
- },
26
13
  // {
27
- // name: 'Login',
28
14
  // path: '/login',
29
- // component: () => import('@/views/login/index.vue'),
30
- // meta: { title: '登录', noAuth: true, global: true }
15
+ // component: () => import('@/layout/BlankLayout.vue'),
16
+ // meta: { title: '登录' },
17
+ // children: [
18
+ // {
19
+ // path: '',
20
+ // name: 'Login',
21
+ // meta: { title: '登录', noAuth: true },
22
+ // component: () => import('@/views/login/index.vue')
23
+ // }
24
+ // ]
31
25
  // },
26
+ {
27
+ name: '403',
28
+ path: '/403',
29
+ component: () => import('@/views/common/403.vue'),
30
+ meta: { title: '403', noAuth: true, global: true }
31
+ },
32
+ {
33
+ name: '404',
34
+ path: '/:pathMatch(.*)*',
35
+ component: () => import('@/views/common/404.vue'),
36
+ meta: { title: '404', global: true }
37
+ },
38
+ {
39
+ name: 'Login',
40
+ path: '/login',
41
+ component: () => import('@/views/login/index.vue'),
42
+ meta: { title: '登录', noAuth: true, global: true }
43
+ },
32
44
  {
33
45
  name: LAYOUT_NAME,
34
46
  path: '/',
@@ -45,17 +57,5 @@ export const routes = [
45
57
  component: () => import('@/views/home/index.vue')
46
58
  }
47
59
  ]
48
- },
49
- {
50
- name: '403',
51
- path: '/403',
52
- component: () => import('@/views/common/403.vue'),
53
- meta: { title: '403', noAuth: true, global: true }
54
- },
55
- {
56
- name: '404',
57
- path: '/:pathMatch(.*)*',
58
- component: () => import('@/views/common/404.vue'),
59
- meta: { title: '404', global: true }
60
60
  }
61
61
  ]
@@ -1,13 +1,15 @@
1
1
  import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
2
3
 
3
4
  /**
4
5
  * Mock仓库
5
6
  * @returns isMock 是否使用mock数据
6
7
  */
7
8
  export const useMockStore = defineStore(
8
- 'mock',
9
+ 'jnrs-app-mock',
9
10
  () => {
10
- const isMock = import.meta.env.VITE_USE_MOCK === 'true' // 是否使用mock数据
11
+ // 是否使用mock数据
12
+ const isMock = ref(import.meta.env.VITE_USE_MOCK === 'true')
11
13
 
12
14
  return { isMock }
13
15
  },
@@ -0,0 +1,16 @@
1
+ import { hasPermissionWithSuffix } from '@jnrs/shared'
2
+ import { useAuthStore } from '@/stores'
3
+
4
+ // 菜单查看权限
5
+ export function hasMenuViewPermission(requiredPerms: string[] | undefined) {
6
+ const { userInfo } = useAuthStore()
7
+ const userPerms = userInfo?.permissions || []
8
+ return hasPermissionWithSuffix(requiredPerms, userPerms, ':view', 'some')
9
+ }
10
+
11
+ // 按钮操作权限
12
+ export function hasPermission(requiredPerms: string[] | undefined) {
13
+ const { userInfo } = useAuthStore()
14
+ const userPerms = userInfo?.permissions || []
15
+ return hasPermissionWithSuffix(requiredPerms, userPerms)
16
+ }
@@ -1,11 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { router } from '@/router'
3
- import { useRoute, handleRouter } from '@jnrs/vue-core/router'
4
-
5
- const route = useRoute()
3
+ import { handleRouter } from '@jnrs/vue-core/router'
6
4
 
7
5
  const goLogin = () => {
8
- handleRouter({ name: 'Login', query: { redirect: route.fullPath } }, 'replace')
6
+ handleRouter({ name: 'Login' }, 'replace')
9
7
  }
10
8
  const goHome = () => {
11
9
  handleRouter({ path: '/' }, 'replace')
@@ -22,9 +20,7 @@ const goBack = () => {
22
20
  <h3>抱歉,您访问的资源权限受限</h3>
23
21
  <div class="main_mid_btn">
24
22
  <el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
25
- <el-button type="primary" plain @click="goHome" style="margin-right: 20px">
26
- 返回首页
27
- </el-button>
23
+ <el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
28
24
  <el-button type="primary" @click="goBack">返回上一页</el-button>
29
25
  </div>
30
26
  </div>
@@ -38,15 +34,15 @@ const goBack = () => {
38
34
  justify-content: center;
39
35
  width: 100%;
40
36
  height: 100vh;
41
- background: #f9fafe;
42
37
  overflow: hidden;
38
+ background: var(--jnrs-background-primary);
43
39
  .main_mid {
44
40
  text-align: center;
45
41
  img {
46
42
  width: 300px;
47
43
  }
48
44
  h3 {
49
- color: #2a63a4;
45
+ color: var(--jnrs-font-primary);
50
46
  }
51
47
  .main_mid_btn {
52
48
  margin-top: 30px;
@@ -1,11 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { router } from '@/router'
3
- import { useRoute, handleRouter } from '@jnrs/vue-core/router'
4
-
5
- const route = useRoute()
3
+ import { handleRouter } from '@jnrs/vue-core/router'
6
4
 
7
5
  const goLogin = () => {
8
- handleRouter({ name: 'Login', query: { redirect: route.fullPath } }, 'replace')
6
+ handleRouter({ name: 'Login' }, 'replace')
9
7
  }
10
8
  const goHome = () => {
11
9
  handleRouter({ path: '/' }, 'replace')
@@ -22,9 +20,7 @@ const goBack = () => {
22
20
  <h3>抱歉,您访问的页面不存在</h3>
23
21
  <div class="main_mid_btn">
24
22
  <el-button @click="goLogin" style="margin-right: 20px">重新登录</el-button>
25
- <el-button type="primary" plain @click="goHome" style="margin-right: 20px">
26
- 返回首页
27
- </el-button>
23
+ <el-button type="primary" plain @click="goHome" style="margin-right: 20px">返回首页</el-button>
28
24
  <el-button type="primary" @click="goBack">返回上一页</el-button>
29
25
  </div>
30
26
  </div>
@@ -38,15 +34,15 @@ const goBack = () => {
38
34
  justify-content: center;
39
35
  width: 100%;
40
36
  height: 100vh;
41
- background: #f9fafe;
42
37
  overflow: hidden;
38
+ background: var(--jnrs-background-primary);
43
39
  .main_mid {
44
40
  text-align: center;
45
41
  img {
46
42
  width: 300px;
47
43
  }
48
44
  h3 {
49
- color: #2a63a4;
45
+ color: var(--jnrs-font-primary);
50
46
  }
51
47
  .main_mid_btn {
52
48
  margin-top: 30px;
@@ -1,13 +1,18 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, onMounted } from 'vue'
3
+ import { storeToRefs } from 'pinia'
3
4
  import { ElMessage } from 'element-plus'
4
- import { MenuApi, LoginApi, UserInfoApi } from '@/api/base'
5
+ import { MenuApi, LoginApi, UserInfoApi } from '@/api/system'
5
6
  import { NotFoundApi, NoAuth } from '@/api/mock'
6
7
  import { handleRouter } from '@jnrs/vue-core/router'
7
- import type { MenuItem } from '@/types'
8
+ import type { MenuItem } from '@jnrs/vue-core'
9
+ import { hasPermission } from '@/utils/permissions'
10
+ import { useMockStore } from '@/stores'
11
+
12
+ const { isMock } = storeToRefs(useMockStore())
8
13
 
9
14
  const loginParams = ref({
10
- account: 'admin',
15
+ account: 'user',
11
16
  password: '123456'
12
17
  })
13
18
 
@@ -15,9 +20,7 @@ const routeOptions = ref<MenuItem[]>([])
15
20
  const currentRoute = ref('')
16
21
  const datePicker = ref(new Date())
17
22
 
18
- onMounted(() => {
19
- handleInfoApi()
20
- })
23
+ onMounted(() => {})
21
24
 
22
25
  const handleInfoApi = async () => {
23
26
  try {
@@ -32,7 +35,6 @@ const handleMenuApi = async () => {
32
35
  try {
33
36
  const res = await MenuApi()
34
37
  routeOptions.value = res
35
- console.log(res)
36
38
  } catch (error) {
37
39
  console.log(error)
38
40
  }
@@ -71,29 +73,52 @@ const handleRouteChange = () => {
71
73
  name: currentRoute.value
72
74
  })
73
75
  }
74
-
75
- // import { MyButton, MyModal } from '../src' // 直接导入本地源码
76
- // import { useUserStore } from '../src'
77
76
  </script>
78
77
 
79
78
  <template>
80
79
  <div>
81
- <h1>Playground - 测试组件</h1>
80
+ <h3>Playground - 功能测试</h3>
81
+ <p>权限测试(admin 账号拥有全部权限,请用非 admin 账号测试该功能)</p>
82
+ <ul>
83
+ <li>
84
+ <span>有权限:(期望:正常显示按钮)</span>
85
+ <el-button type="primary" size="small" v-permissions="['mine:view']">添加</el-button>
86
+ </li>
87
+ <li>
88
+ <span>无权限:(期望:按钮禁用 -> UI 组件:使用 UI 框架提供的禁用属性)</span>
89
+ <el-button type="primary" size="small" :disabled="!hasPermission(['test:del'])">删除</el-button>
90
+ </li>
91
+ <li>
92
+ <span>无权限:(期望:按钮禁用 -> 原生元素:添加 disabled 属性和类名)</span>
93
+ <button size="small" v-permissions.disabled="['test:del']">删除</button>
94
+ </li>
95
+ <li>
96
+ <span>无权限:(期望:按钮不可见 -> 添加样式 display:none)</span>
97
+ <button size="small" v-permissions.display="['test:del']">删除</button>
98
+ </li>
99
+ <li>
100
+ <span>无权限:(期望:按钮不可见 -> remove HTMLElement)</span>
101
+ <button size="small" v-permissions="['test:del']">删除</button>
102
+ </li>
103
+ </ul>
82
104
  <p>网络请求测试</p>
105
+ <div>
106
+ <span>服务器类型:</span>
107
+ <el-switch v-model="isMock" active-text="Mock 服务器" inactive-text="后端服务器" />
108
+ </div>
83
109
  <el-button-group>
84
- <el-button type="primary" size="small" @click="handleMenuApi">GET</el-button>
110
+ <el-button type="primary" size="small" @click="handleMenuApi">获取完整菜单数据</el-button>
111
+ <el-button type="primary" size="small" @click="handleInfoApi">获取用户数据</el-button>
85
112
  <el-button type="primary" size="small" @click="handleNotFoundApi">404</el-button>
86
113
  <el-button type="primary" size="small" @click="handleNoAuth()">暂无权限</el-button>
87
- <el-button type="primary" size="small" @click="handleNoAuth(false)">
88
- 暂无权限(不显示Msg)
89
- </el-button>
114
+ <el-button type="primary" size="small" @click="handleNoAuth(false)">暂无权限(不显示 Message)</el-button>
90
115
  </el-button-group>
91
- <el-input v-model="loginParams.account" size="small" style="width: 150px">
116
+ <el-input v-model="loginParams.account" size="small" style="width: 200px">
92
117
  <template #append>
93
- <el-button size="small" @click="handleLogin">登录</el-button>
118
+ <el-button size="small" @click="handleLogin">验证登录接口</el-button>
94
119
  </template>
95
120
  </el-input>
96
- <p>路由测试</p>
121
+ <p>完整路由测试(先点击按钮,获取完整菜单数据)</p>
97
122
  <el-cascader
98
123
  v-model="currentRoute"
99
124
  :options="routeOptions"
@@ -111,8 +136,17 @@ const handleRouteChange = () => {
111
136
  </el-cascader>
112
137
  <p>Element 组件测试</p>
113
138
  <el-date-picker-panel v-model="datePicker" />
114
- <!-- <MyButton type="primary">测试按钮</MyButton>
115
- <MyModal v-model="show">内容</MyModal> -->
116
139
  </div>
117
140
  <router-view></router-view>
118
141
  </template>
142
+
143
+ <style scoped lang="scss">
144
+ h3 {
145
+ color: var(--jnrs-color-primary);
146
+ }
147
+ p {
148
+ margin: 24px 0 8px;
149
+ color: var(--jnrs-background-primary);
150
+ background: var(--jnrs-font-primary);
151
+ }
152
+ </style>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { FormInstance, FormRules } from 'element-plus'
3
3
  import { ref } from 'vue'
4
- import { LoginApi } from '@/api/base/index'
4
+ import { RoleApi, LoginApi } from '@/api/system'
5
5
  import { useAuthStore } from '@/stores'
6
6
  import { handleRouter, useRoute } from '@jnrs/vue-core/router'
7
7
  import { isWeakPwd } from '@jnrs/shared/validator'
@@ -9,6 +9,12 @@ import { formatDateTime, formatWeekday } from '@jnrs/shared'
9
9
 
10
10
  const route = useRoute()
11
11
  const loading = ref(false)
12
+ const {
13
+ setUserInfo,
14
+ setToken,
15
+ setDict
16
+ // setRole
17
+ } = useAuthStore()
12
18
 
13
19
  // 表单 ref
14
20
  const ruleFormRef = ref<FormInstance>()
@@ -27,23 +33,40 @@ const rules = ref<FormRules>({
27
33
  })
28
34
 
29
35
  // 提交函数
30
- const submitForm = () => {
36
+ const submitForm = async () => {
31
37
  if (!ruleFormRef.value) return
32
38
  ruleFormRef.value.validate(async (valid) => {
33
39
  if (!valid) return
34
40
  loading.value = true
35
41
  try {
36
- const res = await LoginApi(ruleForm.value)
37
- const { token, dict, ...userInfo } = res
38
- userInfo.loginDateTime = formatDateTime() + ' ' + formatWeekday()
39
- const { asyncSetAuth } = useAuthStore()
40
- await asyncSetAuth({
41
- token: token,
42
- dict: dict,
43
- userInfo: userInfo
42
+ const loginRes = await LoginApi(ruleForm.value)
43
+ const { token, dict, ...restParameter } = loginRes
44
+ const loginDateTime = formatDateTime() + ' ' + formatWeekday()
45
+
46
+ // 获取角色列表,将登录人角色对应的权限叠加给当前登录人(一次性获取叠加)
47
+ const roleRes = await RoleApi()
48
+ let permissionsMerger = restParameter.permissions
49
+ if (Array.isArray(restParameter.permissions)) {
50
+ permissionsMerger = [
51
+ ...new Set([
52
+ ...restParameter.permissions,
53
+ ...(roleRes.find((r) => r.value === restParameter.role)?.permissions ?? []).flat()
54
+ ])
55
+ ]
56
+ }
57
+ // setRole(roleRes) // 由于是一次性获取叠加,此处不需要保存角色列表到全局状态
58
+
59
+ setUserInfo({
60
+ ...restParameter,
61
+ permissions: permissionsMerger,
62
+ account: ruleForm.value.account,
63
+ loginDateTime
44
64
  })
65
+ setToken(token)
66
+ setDict(dict)
45
67
  handleRouter({ path: route.query.redirect?.toString() || '/' }, 'replace')
46
- } catch {
68
+ } catch (e) {
69
+ console.log(e)
47
70
  } finally {
48
71
  loading.value = false
49
72
  }
@@ -72,12 +95,7 @@ const submitForm = () => {
72
95
  @keyup.enter="submitForm()"
73
96
  >
74
97
  <el-form-item prop="account">
75
- <el-input
76
- v-model="ruleForm.account"
77
- placeholder="请输入账号"
78
- prefix-icon="User"
79
- clearable
80
- />
98
+ <el-input v-model="ruleForm.account" placeholder="请输入账号" prefix-icon="User" clearable />
81
99
  </el-form-item>
82
100
  <el-form-item prop="password">
83
101
  <el-input
@@ -90,9 +108,7 @@ const submitForm = () => {
90
108
  ></el-input>
91
109
  </el-form-item>
92
110
  <el-form-item>
93
- <el-button class="btn" type="primary" :loading="loading" @click="submitForm()">
94
- 登 录
95
- </el-button>
111
+ <el-button class="btn" type="primary" :loading="loading" @click="submitForm()">登 录</el-button>
96
112
  </el-form-item>
97
113
  </el-form>
98
114
  <div class="greeting">欢迎使用</div>
@@ -0,0 +1,174 @@
1
+ <script setup lang="ts">
2
+ import { ElMessage } from 'element-plus'
3
+ import { ref, onActivated } from 'vue'
4
+ import { DictApi, DictDetailApi, DictChangeApi } from '@/api/system'
5
+ import { useAsyncTableHeight } from '@jnrs/vue-core/composables'
6
+ import type { DictItem } from '@jnrs/shared'
7
+ import { hasPermission } from '@/utils/permissions'
8
+
9
+ const { maxHeight } = useAsyncTableHeight('el_table_dict', 0)
10
+ const PERMISSION_EDIT = ['dict:edit']
11
+ const loading = ref(false)
12
+ const tableData = ref<DictItem[]>()
13
+ const predefineColors = ref([
14
+ '#000000',
15
+ '#999999',
16
+ '#f2f2f2',
17
+ '#ffffff',
18
+ '#65DC79',
19
+ '#5887F7',
20
+ '#E43634',
21
+ '#FEA822',
22
+ '#C9512C'
23
+ ])
24
+
25
+ onActivated(() => {
26
+ getList()
27
+ })
28
+
29
+ const getList = () => {
30
+ loading.value = true
31
+ DictApi()
32
+ .then((res) => {
33
+ tableData.value = res
34
+ })
35
+ .finally(() => {
36
+ loading.value = false
37
+ })
38
+ }
39
+
40
+ const handleExpandChange = (row: DictItem) => {
41
+ loading.value = true
42
+ DictDetailApi(String(row.id))
43
+ .then((res) => {
44
+ row.options = res
45
+ })
46
+ .finally(() => {
47
+ loading.value = false
48
+ })
49
+ }
50
+
51
+ // 保存键值对
52
+ const editKeyValue = (row: DictItem) => {
53
+ if (!row.label) {
54
+ ElMessage({
55
+ type: 'warning',
56
+ message: 'label 不能为空'
57
+ })
58
+ return false
59
+ }
60
+ loading.value = true
61
+ DictChangeApi(row)
62
+ .then(() => {
63
+ ElMessage({
64
+ type: 'success',
65
+ message: '修改成功',
66
+ grouping: true
67
+ })
68
+ })
69
+ .finally(() => {
70
+ loading.value = false
71
+ })
72
+ }
73
+ </script>
74
+
75
+ <template>
76
+ <div class="main">
77
+ <el-card shadow="never">
78
+ <template #header>
79
+ <div class="card_header">
80
+ <h3>字典管理</h3>
81
+ </div>
82
+ </template>
83
+ <el-table
84
+ class="el_table_dict"
85
+ :data="tableData"
86
+ :height="maxHeight"
87
+ scrollbar-always-on
88
+ border
89
+ v-loading="loading"
90
+ row-key="id"
91
+ @expand-change="handleExpandChange"
92
+ >
93
+ <el-table-column prop="dictName" label="字典名" />
94
+ <el-table-column prop="describe" label="说明" />
95
+ <el-table-column type="expand" fixed="right" label="字典值" width="100">
96
+ <template #default="scope">
97
+ <div class="table_expand animation15" style="animation-duration: 0.5s; animation-delay: 0s">
98
+ <div class="table_expand_top">
99
+ <h5>字典值列表</h5>
100
+ </div>
101
+ <el-table :data="scope.row.options" :border="true" size="small" row-key="value">
102
+ <el-table-column label="value" prop="value" width="100" align="center"></el-table-column>
103
+ <el-table-column label="label" prop="label">
104
+ <template #default="optionScope">
105
+ <el-input
106
+ v-model="optionScope.row.label"
107
+ maxlength="20"
108
+ :disabled="!hasPermission(PERMISSION_EDIT)"
109
+ />
110
+ </template>
111
+ </el-table-column>
112
+ <el-table-column label="颜色" prop="displayColor">
113
+ <template #default="optionScope">
114
+ <el-color-picker
115
+ v-model="optionScope.row.displayColor"
116
+ :predefine="predefineColors"
117
+ :disabled="!hasPermission(PERMISSION_EDIT)"
118
+ />
119
+ <span style="margin-left: 10px">
120
+ {{ optionScope.row.displayColor || '无' }}
121
+ </span>
122
+ </template>
123
+ </el-table-column>
124
+ <el-table-column prop="sequence" label="排序" width="180" align="center">
125
+ <template #default="optionScope">
126
+ <el-input-number
127
+ v-model="optionScope.row.sequence"
128
+ :min="1"
129
+ :max="999"
130
+ :step="1"
131
+ controls-position="right"
132
+ step-strictly
133
+ :disabled="!hasPermission(PERMISSION_EDIT)"
134
+ style="width: 140px"
135
+ />
136
+ </template>
137
+ </el-table-column>
138
+ <el-table-column label="操作" width="100" align="center" fixed="right" v-permissions="PERMISSION_EDIT">
139
+ <template #default="optionScope">
140
+ <el-button type="success" size="small" @click="editKeyValue(optionScope.row)">确认修改</el-button>
141
+ </template>
142
+ </el-table-column>
143
+ </el-table>
144
+ </div>
145
+ </template>
146
+ </el-table-column>
147
+ </el-table>
148
+ </el-card>
149
+ </div>
150
+ </template>
151
+
152
+ <style scoped lang="scss">
153
+ .table_expand {
154
+ padding: 10px;
155
+ color: var(--jnrs-font-primary);
156
+ background: var(--jnrs-background-primary);
157
+
158
+ .table_expand_top {
159
+ position: relative;
160
+
161
+ h5 {
162
+ font-size: 14px;
163
+ margin-bottom: 10px;
164
+ }
165
+
166
+ .table_expand_top_btn {
167
+ position: absolute;
168
+ right: 0;
169
+ top: 0;
170
+ z-index: 1;
171
+ }
172
+ }
173
+ }
174
+ </style>
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { useMenuStore } from '@/stores'
4
+ import { useAsyncTableHeight } from '@jnrs/vue-core/composables'
5
+
6
+ const { maxHeight } = useAsyncTableHeight('el_table_menu', 0)
7
+ const loading = ref(false)
8
+
9
+ const { menus } = useMenuStore()
10
+ </script>
11
+
12
+ <template>
13
+ <div>
14
+ <el-card shadow="never">
15
+ <template #header>
16
+ <div class="card_header">
17
+ <h3>菜单管理</h3>
18
+ </div>
19
+ </template>
20
+ <el-table
21
+ class="el_table_menu"
22
+ :data="menus"
23
+ :height="maxHeight"
24
+ scrollbar-always-on
25
+ row-key="meta.uuid"
26
+ border
27
+ v-loading="loading"
28
+ >
29
+ <el-table-column prop="meta.title" label="标题" min-width="120" header-align="center" />
30
+ <el-table-column prop="icon" label="图标" width="60" align="center">
31
+ <template #default="{ row }">
32
+ <el-icon v-if="row.meta.icon"><component :is="row.meta.icon" /></el-icon>
33
+ </template>
34
+ </el-table-column>
35
+ <el-table-column prop="path" label="路由地址" min-width="120" header-align="center" />
36
+ <el-table-column prop="component" label="component path" min-width="120" header-align="center" />
37
+ <el-table-column prop="redirect" label="redirect" min-width="120" header-align="center" />
38
+ <el-table-column prop="editRole" label="权限标识" min-width="120" header-align="center">
39
+ <template #default="{ row }">
40
+ <span v-if="!row.meta.permissions" style="color: #999">auto</span>
41
+ <span v-else>{{ row.meta.permissions }}</span>
42
+ </template>
43
+ </el-table-column>
44
+ <el-table-column prop="type" label="类型" width="80" align="center">
45
+ <template #default="{ row }">
46
+ <el-tag type="primary" v-if="row.path">菜单</el-tag>
47
+ <el-tag type="success" v-else>目录</el-tag>
48
+ </template>
49
+ </el-table-column>
50
+ <el-table-column prop="type" label="NoAuth" width="80" align="center">
51
+ <template #default="{ row }">
52
+ <el-tag type="danger" v-if="row.meta.noAuth">否</el-tag>
53
+ <el-tag type="success" v-else>是</el-tag>
54
+ </template>
55
+ </el-table-column>
56
+ <el-table-column prop="type" label="Global" width="80" align="center">
57
+ <template #default="{ row }">
58
+ <el-tag type="success" v-if="row.meta.global">是</el-tag>
59
+ <el-tag type="primary" v-else>否</el-tag>
60
+ </template>
61
+ </el-table-column>
62
+ </el-table>
63
+ </el-card>
64
+ </div>
65
+ </template>