create-jnrs-template-vue 1.1.4 → 1.1.6
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/jnrs-template-vue/.prettierrc.json +1 -1
- package/jnrs-template-vue/auto-imports.d.ts +1 -0
- package/jnrs-template-vue/components.d.ts +11 -2
- package/jnrs-template-vue/package.json +18 -17
- package/jnrs-template-vue/public/system/menu.json +29 -9
- package/jnrs-template-vue/src/App.vue +36 -5
- package/jnrs-template-vue/src/api/{base → system}/index.ts +30 -6
- package/jnrs-template-vue/src/assets/styles/init.scss +8 -3
- package/jnrs-template-vue/src/assets/styles/root.scss +7 -24
- package/jnrs-template-vue/src/layout/RouterTabs.vue +5 -4
- package/jnrs-template-vue/src/layout/SideMenu.vue +29 -20
- package/jnrs-template-vue/src/layout/SideMenuItem.vue +1 -1
- package/jnrs-template-vue/src/layout/TopHeader.vue +30 -19
- package/jnrs-template-vue/src/layout/index.vue +36 -6
- package/jnrs-template-vue/src/locales/en.ts +9 -0
- package/jnrs-template-vue/src/locales/index.ts +16 -0
- package/jnrs-template-vue/src/locales/zhCn.ts +9 -0
- package/jnrs-template-vue/src/main.ts +5 -1
- package/jnrs-template-vue/src/router/index.ts +10 -4
- package/jnrs-template-vue/src/types/index.d.ts +6 -0
- package/jnrs-template-vue/src/utils/common.ts +58 -0
- package/jnrs-template-vue/src/views/common/403.vue +2 -2
- package/jnrs-template-vue/src/views/common/404.vue +2 -2
- package/jnrs-template-vue/src/views/home/index.vue +6 -3
- package/jnrs-template-vue/src/views/login/index.vue +15 -26
- package/jnrs-template-vue/src/views/system/dict/index.vue +176 -0
- package/jnrs-template-vue/src/views/system/menu/index.vue +67 -0
- package/jnrs-template-vue/src/views/system/mine/baseInfo.vue +38 -30
- package/jnrs-template-vue/src/views/system/mine/securitySettings.vue +22 -31
- package/jnrs-template-vue/src/views/system/role/editDialog.vue +94 -0
- package/jnrs-template-vue/src/views/system/role/index.vue +30 -3
- package/jnrs-template-vue/src/views/visual/index.vue +6 -1
- package/jnrs-template-vue/vite.config.ts +6 -4
- package/jnrs-template-vue/viteMockServe/dictItemRes.json +27 -0
- package/jnrs-template-vue/viteMockServe/dictRes.json +141 -0
- package/jnrs-template-vue/viteMockServe/fail.ts +26 -0
- package/jnrs-template-vue/viteMockServe/index.ts +18 -25
- package/jnrs-template-vue/viteMockServe/{login.json → loginRes.json} +1 -0
- package/jnrs-template-vue/viteMockServe/success.ts +31 -0
- package/package.json +1 -1
- package/jnrs-template-vue/src/types/index.ts +0 -1
- package/jnrs-template-vue/src/views/system/user/index.vue +0 -9
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, toRefs } from 'vue'
|
|
2
3
|
import SideMenu from './SideMenu.vue'
|
|
3
4
|
import TopHeader from './TopHeader.vue'
|
|
4
5
|
import RouterTabs from './RouterTabs.vue'
|
|
6
|
+
import { useAuthStore, useSystemStore } from '@/stores'
|
|
7
|
+
|
|
8
|
+
const watermarkFont = ref({
|
|
9
|
+
color: 'rgba(0, 0, 0, 0)',
|
|
10
|
+
fontSize: 12
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const { userInfo } = useAuthStore()
|
|
14
|
+
|
|
15
|
+
const { computedThemeMode } = toRefs(useSystemStore())
|
|
16
|
+
watch(
|
|
17
|
+
computedThemeMode,
|
|
18
|
+
(newVal) => {
|
|
19
|
+
if (newVal === 'dark') {
|
|
20
|
+
watermarkFont.value.color = 'rgba(255, 255, 255, .06)'
|
|
21
|
+
} else {
|
|
22
|
+
watermarkFont.value.color = 'rgba(0, 0, 0, .06)'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
immediate: true
|
|
27
|
+
}
|
|
28
|
+
)
|
|
5
29
|
</script>
|
|
6
30
|
|
|
7
31
|
<template>
|
|
@@ -13,11 +37,17 @@ import RouterTabs from './RouterTabs.vue'
|
|
|
13
37
|
</el-header>
|
|
14
38
|
<RouterTabs />
|
|
15
39
|
<el-main>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
<el-watermark
|
|
41
|
+
:font="watermarkFont"
|
|
42
|
+
:content="[userInfo?.name, userInfo?.workNo]"
|
|
43
|
+
style="width: 100%; height: 100%"
|
|
44
|
+
>
|
|
45
|
+
<router-view v-slot="{ Component }">
|
|
46
|
+
<keep-alive>
|
|
47
|
+
<component :is="Component" />
|
|
48
|
+
</keep-alive>
|
|
49
|
+
</router-view>
|
|
50
|
+
</el-watermark>
|
|
21
51
|
</el-main>
|
|
22
52
|
</el-container>
|
|
23
53
|
</el-container>
|
|
@@ -35,7 +65,7 @@ import RouterTabs from './RouterTabs.vue'
|
|
|
35
65
|
height: 100%;
|
|
36
66
|
|
|
37
67
|
.el-main {
|
|
38
|
-
padding:
|
|
68
|
+
padding: 16px;
|
|
39
69
|
}
|
|
40
70
|
}
|
|
41
71
|
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createI18n } from 'vue-i18n'
|
|
2
|
+
import zhCN from './zhCn'
|
|
3
|
+
import en from './en'
|
|
4
|
+
|
|
5
|
+
const i18n = createI18n({
|
|
6
|
+
legacy: false, // 启用 Composition API 模式
|
|
7
|
+
globalInjection: true, // 允许在模板中使用 $t
|
|
8
|
+
locale: 'zhCn',
|
|
9
|
+
fallbackLocale: 'en',
|
|
10
|
+
messages: {
|
|
11
|
+
zhCn: zhCN,
|
|
12
|
+
en: en
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export default i18n
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'element-plus/dist/index.css'
|
|
2
|
-
import '
|
|
2
|
+
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
3
|
+
import '@jnrs/shared/styles/theme.scss'
|
|
4
|
+
import '@/assets/styles/main.scss'
|
|
3
5
|
|
|
4
6
|
import { createApp } from 'vue'
|
|
5
7
|
import { createPinia } from 'pinia'
|
|
@@ -7,6 +9,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
|
7
9
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
8
10
|
import App from './App.vue'
|
|
9
11
|
import { router } from './router'
|
|
12
|
+
import locales from './locales/index'
|
|
10
13
|
|
|
11
14
|
const app = createApp(App)
|
|
12
15
|
|
|
@@ -21,4 +24,5 @@ pinia.use(piniaPluginPersistedstate)
|
|
|
21
24
|
|
|
22
25
|
app.use(pinia)
|
|
23
26
|
app.use(router)
|
|
27
|
+
app.use(locales)
|
|
24
28
|
app.mount('#app')
|
|
@@ -2,7 +2,8 @@ import { LAYOUT_NAME, GLOBAL_COMPONENT, routes } from './routes'
|
|
|
2
2
|
import { createVueRouter } from '@jnrs/vue-core/router'
|
|
3
3
|
import type { FileModules, RouteLocationNormalizedGeneric } from '@jnrs/vue-core/router'
|
|
4
4
|
import { useAuthStore, useMenuStore } from '@/stores'
|
|
5
|
-
import { MenuApi } from '@/api/
|
|
5
|
+
import { MenuApi } from '@/api/system'
|
|
6
|
+
import { hasPermission } from '@jnrs/shared'
|
|
6
7
|
|
|
7
8
|
const fileModules = import.meta.glob('/src/views/**/*.vue') as FileModules
|
|
8
9
|
|
|
@@ -14,15 +15,20 @@ const router = createVueRouter({
|
|
|
14
15
|
layoutName: LAYOUT_NAME,
|
|
15
16
|
globalComponent: GLOBAL_COMPONENT,
|
|
16
17
|
handleBeforeEach: async (to: RouteLocationNormalizedGeneric) => {
|
|
17
|
-
//
|
|
18
|
+
// 如果是不需要[身份验证]的页面,直接放行
|
|
18
19
|
if (to.meta.noAuth) return true
|
|
19
20
|
|
|
20
|
-
//
|
|
21
|
-
const { hasAuthenticated } = useAuthStore()
|
|
21
|
+
// 页面刷新处理:未[身份验证]时,重定向到[身份验证]页
|
|
22
|
+
const { hasAuthenticated, userInfo } = useAuthStore()
|
|
22
23
|
if (!hasAuthenticated) {
|
|
23
24
|
return { name: 'Login', query: { redirect: to.path }, replace: true }
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
// 校验访问权限
|
|
28
|
+
if (to.meta.permissions && !hasPermission(to.meta.permissions, userInfo?.permissions || [])) {
|
|
29
|
+
return { name: '403' }
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
// 页面刷新处理:菜单未获取时,先获取菜单,再重定向当前路由,即重新开始进入handleBeforeEach
|
|
27
33
|
const { hasFetchedAsyncMenus, asyncSetMenus } = useMenuStore()
|
|
28
34
|
if (!hasFetchedAsyncMenus) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOneDictList as _getOneDictList,
|
|
3
|
+
getDictLabel as _getDictLabel,
|
|
4
|
+
getDictValue as _getDictValue,
|
|
5
|
+
getDictColor as _getDictColor
|
|
6
|
+
} from '@jnrs/shared'
|
|
7
|
+
import { useAuthStore } from '@jnrs/vue-core/pinia'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 根据字典名称获取字典数据
|
|
11
|
+
* @param name 字典名称
|
|
12
|
+
* @returns 某字典名称所对应的字典列表数据
|
|
13
|
+
*/
|
|
14
|
+
export const getOneDictList = (name: string) => {
|
|
15
|
+
const { dict } = useAuthStore()
|
|
16
|
+
if (dict) {
|
|
17
|
+
return _getOneDictList(name, dict)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 根据字典名称和字典值获取字典标签
|
|
23
|
+
* @param name 字典名称
|
|
24
|
+
* @param value 字典值
|
|
25
|
+
* @returns 某字典名称所对应的字典标签
|
|
26
|
+
*/
|
|
27
|
+
export const getDictLabel = (name: string, value: string | number) => {
|
|
28
|
+
const { dict } = useAuthStore()
|
|
29
|
+
if (dict) {
|
|
30
|
+
return _getDictLabel(name, value, dict)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 根据字典名称和字典标签获取字典值
|
|
36
|
+
* @param name 字典名称
|
|
37
|
+
* @param label 字典标签
|
|
38
|
+
* @returns 某字典名称所对应的字典值
|
|
39
|
+
*/
|
|
40
|
+
export const getDictValue = (name: string, label: string) => {
|
|
41
|
+
const { dict } = useAuthStore()
|
|
42
|
+
if (dict) {
|
|
43
|
+
return _getDictValue(name, label, dict)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 根据字典名称和字典值获取字典颜色
|
|
49
|
+
* @param name 字典名称
|
|
50
|
+
* @param value 字典值
|
|
51
|
+
* @returns 某字典名称所对应的字典颜色
|
|
52
|
+
*/
|
|
53
|
+
export const getDictColor = (name: string, value: string | number) => {
|
|
54
|
+
const { dict } = useAuthStore()
|
|
55
|
+
if (dict) {
|
|
56
|
+
return _getDictColor(name, value, dict)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -38,15 +38,15 @@ const goBack = () => {
|
|
|
38
38
|
justify-content: center;
|
|
39
39
|
width: 100%;
|
|
40
40
|
height: 100vh;
|
|
41
|
-
background: #f9fafe;
|
|
42
41
|
overflow: hidden;
|
|
42
|
+
background: var(--jnrs-background-primary);
|
|
43
43
|
.main_mid {
|
|
44
44
|
text-align: center;
|
|
45
45
|
img {
|
|
46
46
|
width: 300px;
|
|
47
47
|
}
|
|
48
48
|
h3 {
|
|
49
|
-
color:
|
|
49
|
+
color: var(--jnrs-font-primary);
|
|
50
50
|
}
|
|
51
51
|
.main_mid_btn {
|
|
52
52
|
margin-top: 30px;
|
|
@@ -38,15 +38,15 @@ const goBack = () => {
|
|
|
38
38
|
justify-content: center;
|
|
39
39
|
width: 100%;
|
|
40
40
|
height: 100vh;
|
|
41
|
-
background: #f9fafe;
|
|
42
41
|
overflow: hidden;
|
|
42
|
+
background: var(--jnrs-background-primary);
|
|
43
43
|
.main_mid {
|
|
44
44
|
text-align: center;
|
|
45
45
|
img {
|
|
46
46
|
width: 300px;
|
|
47
47
|
}
|
|
48
48
|
h3 {
|
|
49
|
-
color:
|
|
49
|
+
color: var(--jnrs-font-primary);
|
|
50
50
|
}
|
|
51
51
|
.main_mid_btn {
|
|
52
52
|
margin-top: 30px;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from 'vue'
|
|
3
3
|
import { ElMessage } from 'element-plus'
|
|
4
|
-
import { MenuApi, LoginApi, UserInfoApi } from '@/api/
|
|
4
|
+
import { MenuApi, LoginApi, UserInfoApi } from '@/api/system'
|
|
5
5
|
import { NotFoundApi, NoAuth } from '@/api/mock'
|
|
6
6
|
import { handleRouter } from '@jnrs/vue-core/router'
|
|
7
|
-
import type { MenuItem } from '
|
|
7
|
+
import type { MenuItem } from '@jnrs/vue-core'
|
|
8
8
|
|
|
9
9
|
const loginParams = ref({
|
|
10
10
|
account: 'admin',
|
|
@@ -13,6 +13,7 @@ const loginParams = ref({
|
|
|
13
13
|
|
|
14
14
|
const routeOptions = ref<MenuItem[]>([])
|
|
15
15
|
const currentRoute = ref('')
|
|
16
|
+
const datePicker = ref(new Date())
|
|
16
17
|
|
|
17
18
|
onMounted(() => {
|
|
18
19
|
handleInfoApi()
|
|
@@ -76,7 +77,7 @@ const handleRouteChange = () => {
|
|
|
76
77
|
</script>
|
|
77
78
|
|
|
78
79
|
<template>
|
|
79
|
-
<div
|
|
80
|
+
<div>
|
|
80
81
|
<h1>Playground - 测试组件</h1>
|
|
81
82
|
<p>网络请求测试</p>
|
|
82
83
|
<el-button-group>
|
|
@@ -108,6 +109,8 @@ const handleRouteChange = () => {
|
|
|
108
109
|
<span>{{ data.meta.title }}</span>
|
|
109
110
|
</template>
|
|
110
111
|
</el-cascader>
|
|
112
|
+
<p>Element 组件测试</p>
|
|
113
|
+
<el-date-picker-panel v-model="datePicker" />
|
|
111
114
|
<!-- <MyButton type="primary">测试按钮</MyButton>
|
|
112
115
|
<MyModal v-model="show">内容</MyModal> -->
|
|
113
116
|
</div>
|
|
@@ -1,13 +1,15 @@
|
|
|
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/
|
|
4
|
+
import { 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'
|
|
8
|
+
import { formatDateTime, formatWeekday } from '@jnrs/shared'
|
|
8
9
|
|
|
9
10
|
const route = useRoute()
|
|
10
11
|
const loading = ref(false)
|
|
12
|
+
const { setUserInfo, setToken, setDict } = useAuthStore()
|
|
11
13
|
|
|
12
14
|
// 表单 ref
|
|
13
15
|
const ruleFormRef = ref<FormInstance>()
|
|
@@ -33,13 +35,15 @@ const submitForm = () => {
|
|
|
33
35
|
loading.value = true
|
|
34
36
|
try {
|
|
35
37
|
const res = await LoginApi(ruleForm.value)
|
|
36
|
-
const { token, dict, ...
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const { token, dict, ...restParameter } = res
|
|
39
|
+
const loginDateTime = formatDateTime() + ' ' + formatWeekday()
|
|
40
|
+
setUserInfo({
|
|
41
|
+
...restParameter,
|
|
42
|
+
account: ruleForm.value.account,
|
|
43
|
+
loginDateTime
|
|
42
44
|
})
|
|
45
|
+
setToken(token)
|
|
46
|
+
setDict(dict)
|
|
43
47
|
handleRouter({ path: route.query.redirect?.toString() || '/' }, 'replace')
|
|
44
48
|
} catch {
|
|
45
49
|
} finally {
|
|
@@ -70,12 +74,7 @@ const submitForm = () => {
|
|
|
70
74
|
@keyup.enter="submitForm()"
|
|
71
75
|
>
|
|
72
76
|
<el-form-item prop="account">
|
|
73
|
-
<el-input
|
|
74
|
-
v-model="ruleForm.account"
|
|
75
|
-
placeholder="请输入账号"
|
|
76
|
-
prefix-icon="User"
|
|
77
|
-
clearable
|
|
78
|
-
/>
|
|
77
|
+
<el-input v-model="ruleForm.account" placeholder="请输入账号" prefix-icon="User" clearable />
|
|
79
78
|
</el-form-item>
|
|
80
79
|
<el-form-item prop="password">
|
|
81
80
|
<el-input
|
|
@@ -88,9 +87,7 @@ const submitForm = () => {
|
|
|
88
87
|
></el-input>
|
|
89
88
|
</el-form-item>
|
|
90
89
|
<el-form-item>
|
|
91
|
-
<el-button class="btn" type="primary" :loading="loading" @click="submitForm()">
|
|
92
|
-
登 录
|
|
93
|
-
</el-button>
|
|
90
|
+
<el-button class="btn" type="primary" :loading="loading" @click="submitForm()">登 录</el-button>
|
|
94
91
|
</el-form-item>
|
|
95
92
|
</el-form>
|
|
96
93
|
<div class="greeting">欢迎使用</div>
|
|
@@ -102,14 +99,6 @@ const submitForm = () => {
|
|
|
102
99
|
</template>
|
|
103
100
|
|
|
104
101
|
<style scoped lang="scss">
|
|
105
|
-
@use 'sass:math';
|
|
106
|
-
|
|
107
|
-
$design-width: 1920; // 设计稿宽度
|
|
108
|
-
|
|
109
|
-
@function px2vw($px) {
|
|
110
|
-
@return math.div($px, $design-width) * 100vw;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
102
|
.main {
|
|
114
103
|
position: relative;
|
|
115
104
|
width: 100%;
|
|
@@ -147,8 +136,8 @@ $design-width: 1920; // 设计稿宽度
|
|
|
147
136
|
width: 50%;
|
|
148
137
|
height: 100%;
|
|
149
138
|
box-shadow: 5px 0 10px rgba(0, 0, 0, 0.5);
|
|
150
|
-
background: url('@/assets/img/common/card_bg.png') no-repeat;
|
|
151
|
-
background-size: 100% 100%;
|
|
139
|
+
// background: url('@/assets/img/common/card_bg.png') no-repeat;
|
|
140
|
+
// background-size: 100% 100%;
|
|
152
141
|
text-align: center;
|
|
153
142
|
font-size: 22px;
|
|
154
143
|
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
|
|
8
|
+
const { maxHeight } = useAsyncTableHeight('el_table_dict', 0)
|
|
9
|
+
const loading = ref(false)
|
|
10
|
+
const tableData = ref<DictItem[]>()
|
|
11
|
+
const predefineColors = ref([
|
|
12
|
+
'#000000',
|
|
13
|
+
'#999999',
|
|
14
|
+
'#f2f2f2',
|
|
15
|
+
'#ffffff',
|
|
16
|
+
'#65DC79',
|
|
17
|
+
'#5887F7',
|
|
18
|
+
'#E43634',
|
|
19
|
+
'#FEA822',
|
|
20
|
+
'#C9512C'
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
onActivated(() => {
|
|
24
|
+
getList()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const getList = () => {
|
|
28
|
+
loading.value = true
|
|
29
|
+
DictApi()
|
|
30
|
+
.then((res) => {
|
|
31
|
+
tableData.value = res
|
|
32
|
+
})
|
|
33
|
+
.finally(() => {
|
|
34
|
+
loading.value = false
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleExpandChange = (row: DictItem) => {
|
|
39
|
+
loading.value = true
|
|
40
|
+
DictDetailApi(String(row.id))
|
|
41
|
+
.then((res) => {
|
|
42
|
+
row.options = res
|
|
43
|
+
})
|
|
44
|
+
.finally(() => {
|
|
45
|
+
loading.value = false
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 保存键值对
|
|
50
|
+
const editKeyValue = (row: DictItem) => {
|
|
51
|
+
if (!row.label) {
|
|
52
|
+
ElMessage({
|
|
53
|
+
type: 'warning',
|
|
54
|
+
message: 'label 不能为空'
|
|
55
|
+
})
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
loading.value = true
|
|
59
|
+
DictChangeApi(row)
|
|
60
|
+
.then(() => {
|
|
61
|
+
ElMessage({
|
|
62
|
+
type: 'success',
|
|
63
|
+
message: '修改成功',
|
|
64
|
+
grouping: true
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
.finally(() => {
|
|
68
|
+
loading.value = false
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div class="main">
|
|
75
|
+
<el-card shadow="never">
|
|
76
|
+
<template #header>
|
|
77
|
+
<div class="card_header">
|
|
78
|
+
<h3>字典管理</h3>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
<el-table
|
|
82
|
+
class="el_table_dict"
|
|
83
|
+
:data="tableData"
|
|
84
|
+
:height="maxHeight"
|
|
85
|
+
scrollbar-always-on
|
|
86
|
+
border
|
|
87
|
+
v-loading="loading"
|
|
88
|
+
row-key="id"
|
|
89
|
+
@expand-change="handleExpandChange"
|
|
90
|
+
>
|
|
91
|
+
<el-table-column prop="dictName" label="字典名" />
|
|
92
|
+
<el-table-column prop="describe" label="说明" />
|
|
93
|
+
<el-table-column type="expand" fixed="right" label="字典值" width="100">
|
|
94
|
+
<template #default="scope">
|
|
95
|
+
<div
|
|
96
|
+
class="table_expand animation15"
|
|
97
|
+
style="animation-duration: 0.5s; animation-delay: 0s"
|
|
98
|
+
>
|
|
99
|
+
<div class="table_expand_top">
|
|
100
|
+
<h5>字典值列表</h5>
|
|
101
|
+
</div>
|
|
102
|
+
<el-table :data="scope.row.options" :border="true" size="small" row-key="value">
|
|
103
|
+
<el-table-column
|
|
104
|
+
label="value"
|
|
105
|
+
prop="value"
|
|
106
|
+
width="100"
|
|
107
|
+
align="center"
|
|
108
|
+
></el-table-column>
|
|
109
|
+
<el-table-column label="label" prop="label">
|
|
110
|
+
<template #default="optionScope">
|
|
111
|
+
<el-input v-model="optionScope.row.label" maxlength="20" />
|
|
112
|
+
</template>
|
|
113
|
+
</el-table-column>
|
|
114
|
+
<el-table-column label="颜色" prop="displayColor">
|
|
115
|
+
<template #default="optionScope">
|
|
116
|
+
<el-color-picker
|
|
117
|
+
v-model="optionScope.row.displayColor"
|
|
118
|
+
:predefine="predefineColors"
|
|
119
|
+
/>
|
|
120
|
+
<span style="margin-left: 10px">
|
|
121
|
+
{{ optionScope.row.displayColor || '无' }}
|
|
122
|
+
</span>
|
|
123
|
+
</template>
|
|
124
|
+
</el-table-column>
|
|
125
|
+
<el-table-column prop="sequence" label="排序" width="180" align="center">
|
|
126
|
+
<template #default="optionScope">
|
|
127
|
+
<el-input-number
|
|
128
|
+
v-model="optionScope.row.sequence"
|
|
129
|
+
:min="1"
|
|
130
|
+
:max="999"
|
|
131
|
+
:step="1"
|
|
132
|
+
controls-position="right"
|
|
133
|
+
step-strictly
|
|
134
|
+
style="width: 140px"
|
|
135
|
+
/>
|
|
136
|
+
</template>
|
|
137
|
+
</el-table-column>
|
|
138
|
+
<el-table-column label="操作" width="100" align="center" fixed="right">
|
|
139
|
+
<template #default="optionScope">
|
|
140
|
+
<el-button type="success" size="small" @click="editKeyValue(optionScope.row)">
|
|
141
|
+
确认修改
|
|
142
|
+
</el-button>
|
|
143
|
+
</template>
|
|
144
|
+
</el-table-column>
|
|
145
|
+
</el-table>
|
|
146
|
+
</div>
|
|
147
|
+
</template>
|
|
148
|
+
</el-table-column>
|
|
149
|
+
</el-table>
|
|
150
|
+
</el-card>
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<style scoped lang="scss">
|
|
155
|
+
.table_expand {
|
|
156
|
+
padding: 10px;
|
|
157
|
+
color: var(--jnrs-font-primary);
|
|
158
|
+
background: var(--jnrs-background-primary);
|
|
159
|
+
|
|
160
|
+
.table_expand_top {
|
|
161
|
+
position: relative;
|
|
162
|
+
|
|
163
|
+
h5 {
|
|
164
|
+
font-size: 14px;
|
|
165
|
+
margin-bottom: 10px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.table_expand_top_btn {
|
|
169
|
+
position: absolute;
|
|
170
|
+
right: 0;
|
|
171
|
+
top: 0;
|
|
172
|
+
z-index: 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { useMenuStore } from '@/stores'
|
|
4
|
+
import { getDictLabel } from '@/utils/common'
|
|
5
|
+
import { useAsyncTableHeight } from '@jnrs/vue-core/composables'
|
|
6
|
+
|
|
7
|
+
const { maxHeight } = useAsyncTableHeight('el_table_menu', 0)
|
|
8
|
+
const loading = ref(false)
|
|
9
|
+
|
|
10
|
+
const { menus } = useMenuStore()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div>
|
|
15
|
+
<el-card shadow="never">
|
|
16
|
+
<template #header>
|
|
17
|
+
<div class="card_header">
|
|
18
|
+
<h3>菜单管理</h3>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
<el-table
|
|
22
|
+
class="el_table_menu"
|
|
23
|
+
:data="menus"
|
|
24
|
+
:height="maxHeight"
|
|
25
|
+
scrollbar-always-on
|
|
26
|
+
row-key="meta.uuid"
|
|
27
|
+
border
|
|
28
|
+
v-loading="loading"
|
|
29
|
+
>
|
|
30
|
+
<el-table-column prop="meta.title" label="名称" min-width="120" />
|
|
31
|
+
<el-table-column prop="path" label="路由地址" min-width="120" />
|
|
32
|
+
<el-table-column prop="role" label="权限" min-width="120">
|
|
33
|
+
<template #default="{ row }">
|
|
34
|
+
<span v-if="!row.meta.role" style="color: #999">auto</span>
|
|
35
|
+
<template v-else>
|
|
36
|
+
{{ row.meta.role.map((d: string | number) => getDictLabel('role', d)) }}
|
|
37
|
+
</template>
|
|
38
|
+
</template>
|
|
39
|
+
</el-table-column>
|
|
40
|
+
<el-table-column prop="editRole" label="操作权限" min-width="120">
|
|
41
|
+
<template #default="scope">
|
|
42
|
+
<template v-if="!scope.row.editRole">
|
|
43
|
+
<span style="color: #999" v-if="!scope.row.role">auto</span>
|
|
44
|
+
<!-- <template v-else>
|
|
45
|
+
{{ scope.row.role.map((d) => getDictLabel('role', d.split('Role')[1])) }}
|
|
46
|
+
</template> -->
|
|
47
|
+
</template>
|
|
48
|
+
<!-- <template v-else>
|
|
49
|
+
{{ scope.row.editRole.map((d) => getDictLabel('role', d.split('Role')[1])) }}
|
|
50
|
+
</template> -->
|
|
51
|
+
</template>
|
|
52
|
+
</el-table-column>
|
|
53
|
+
<el-table-column prop="type" label="类型" width="80" align="center">
|
|
54
|
+
<template #default="scope">
|
|
55
|
+
<el-tag type="primary" v-if="scope.row.path">菜单</el-tag>
|
|
56
|
+
<el-tag type="success" v-else>目录</el-tag>
|
|
57
|
+
</template>
|
|
58
|
+
</el-table-column>
|
|
59
|
+
<el-table-column prop="activate" label="图标" width="80" align="center">
|
|
60
|
+
<template #default="scope">
|
|
61
|
+
<el-icon v-if="scope.row.icon"><component :is="scope.row.icon" /></el-icon>
|
|
62
|
+
</template>
|
|
63
|
+
</el-table-column>
|
|
64
|
+
</el-table>
|
|
65
|
+
</el-card>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|