feat: 外观设置
This commit is contained in:
parent
4aafda3446
commit
d5ca5eeaf4
@ -1,7 +1,6 @@
|
|||||||
import { Result } from '@/request/Result'
|
import { Result } from '@/request/Result'
|
||||||
import { get, post, del, put } from '@/request/index'
|
import { get, post, del, put } from '@/request/index'
|
||||||
import type { TeamMember } from '@/api/type/team'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
const prefix = '/display'
|
const prefix = '/display'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,12 +22,14 @@ const getThemeInfo: () => Promise<Result<any>> = () => {
|
|||||||
* slogan
|
* slogan
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
const postThemeInfo: (data: any) => Promise<Result<boolean>> = (data) => {
|
const postThemeInfo: (data: any, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
|
||||||
return post(`${prefix}/update`, data)
|
data,
|
||||||
|
loading
|
||||||
|
) => {
|
||||||
|
return post(`${prefix}/update`, data, undefined, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getThemeInfo,
|
getThemeInfo,
|
||||||
postThemeInfo
|
postThemeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1 +1 @@
|
|||||||
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232.4409 232.4409"><title>MaxKB</title><path class="cls-1" d="M128.4532,177H98.7785L87.78,187.9985a4.6069,4.6069,0,0,0,3.2576,7.8644h45.1569a4.6069,4.6069,0,0,0,3.2575-7.8644Z"/><path class="cls-1" d="M210.0008,90.7042h-5.85v41.1511h5.85a4.4537,4.4537,0,0,0,4.4537-4.4537V95.1579A4.4537,4.4537,0,0,0,210.0008,90.7042Z"/><path class="cls-1" d="M28.29,90.7042H22.44a4.4538,4.4538,0,0,0-4.4538,4.4537v32.2437a4.4538,4.4538,0,0,0,4.4538,4.4537h5.85Z"/><path class="cls-1" d="M138.8087,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,138.8087,96.1512Z"/><path class="cls-1" d="M95.3622,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,95.3622,96.1512Z"/><path class="cls-1" d="M166.8344,48.8968H65.6064A33.7544,33.7544,0,0,0,31.89,82.6131v57.07A33.7548,33.7548,0,0,0,65.6064,173.4h101.228a33.7549,33.7549,0,0,0,33.7168-33.7168v-57.07A33.7545,33.7545,0,0,0,166.8344,48.8968Zm2.831,90.4457a6.0733,6.0733,0,0,1-6.0732,6.0733H114.2168a43.5922,43.5922,0,0,0-21.3313,5.5757l-16.5647,9.2946v-14.87h-7.472a6.0733,6.0733,0,0,1-6.0733-6.0733v-60.5a6.0733,6.0733,0,0,1,6.0733-6.0733h94.7434a6.0733,6.0733,0,0,1,6.0732,6.0733Z"/></svg>
|
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232.4409 232.4409"><defs><style>.cls-1{fill:#fff;}</style></defs><title>MaxKB</title><path class="cls-1" d="M128.4532,177H98.7785L87.78,187.9985a4.6069,4.6069,0,0,0,3.2576,7.8644h45.1569a4.6069,4.6069,0,0,0,3.2575-7.8644Z"/><path class="cls-1" d="M210.0008,90.7042h-5.85v41.1511h5.85a4.4537,4.4537,0,0,0,4.4537-4.4537V95.1579A4.4537,4.4537,0,0,0,210.0008,90.7042Z"/><path class="cls-1" d="M28.29,90.7042H22.44a4.4538,4.4538,0,0,0-4.4538,4.4537v32.2437a4.4538,4.4538,0,0,0,4.4538,4.4537h5.85Z"/><path class="cls-1" d="M138.8087,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,138.8087,96.1512Z"/><path class="cls-1" d="M95.3622,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,95.3622,96.1512Z"/><path class="cls-1" d="M166.8344,48.8968H65.6064A33.7544,33.7544,0,0,0,31.89,82.6131v57.07A33.7548,33.7548,0,0,0,65.6064,173.4h101.228a33.7549,33.7549,0,0,0,33.7168-33.7168v-57.07A33.7545,33.7545,0,0,0,166.8344,48.8968Zm2.831,90.4457a6.0733,6.0733,0,0,1-6.0732,6.0733H114.2168a43.5922,43.5922,0,0,0-21.3313,5.5757l-16.5647,9.2946v-14.87h-7.472a6.0733,6.0733,0,0,1-6.0733-6.0733v-60.5a6.0733,6.0733,0,0,1,6.0733-6.0733h94.7434a6.0733,6.0733,0,0,1,6.0732,6.0733Z"/></svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -955,7 +955,7 @@ export const iconMap: any = {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'app-minify': {
|
'app-magnify': {
|
||||||
iconReader: () => {
|
iconReader: () => {
|
||||||
return h('i', [
|
return h('i', [
|
||||||
h(
|
h(
|
||||||
@ -976,7 +976,7 @@ export const iconMap: any = {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'app-magnify': {
|
'app-minify': {
|
||||||
iconReader: () => {
|
iconReader: () => {
|
||||||
return h('i', [
|
return h('i', [
|
||||||
h(
|
h(
|
||||||
@ -1071,5 +1071,5 @@ export const iconMap: any = {
|
|||||||
)
|
)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,7 @@
|
|||||||
<div class="login-container w-full h-full">
|
<div class="login-container w-full h-full">
|
||||||
<el-row class="container w-full h-full">
|
<el-row class="container w-full h-full">
|
||||||
<el-col :xs="0" :sm="0" :md="10" :lg="10" :xl="10" class="left-container">
|
<el-col :xs="0" :sm="0" :md="10" :lg="10" :xl="10" class="left-container">
|
||||||
<div class="login-image">
|
<div class="login-image" :style="loginImageStyle"></div>
|
||||||
<img :src="`../src/assets/theme/${themeImg}.jpg`" class="login-image" />
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14" class="right-container flex-center">
|
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14" class="right-container flex-center">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -15,12 +13,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { getThemeImg } from '@/utils/theme'
|
||||||
|
import useStore from '@/stores'
|
||||||
defineOptions({ name: 'LoginLayout' })
|
defineOptions({ name: 'LoginLayout' })
|
||||||
const props = defineProps({
|
const { user } = useStore()
|
||||||
themeImg: {
|
|
||||||
type: String,
|
const fileURL = computed(() => {
|
||||||
default: 'default'
|
if (user.themeInfo.loginImage) {
|
||||||
|
if (typeof user.themeInfo.loginImage === 'string') {
|
||||||
|
return user.themeInfo.loginImage
|
||||||
|
} else {
|
||||||
|
return URL.createObjectURL(user.themeInfo.loginImage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginImageStyle = computed(() => {
|
||||||
|
if (user.themeInfo.loginImage) {
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(${fileURL.value})`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(../src/assets/theme/${getThemeImg(user.themeInfo?.theme)}.jpg)`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -29,7 +48,9 @@ const props = defineProps({
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
||||||
.login-image {
|
.login-image {
|
||||||
object-fit: cover;
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<img v-if="user.themeInfo.loginLogo" :src="fileURL" alt="" height="45px" class="mr-8" />
|
||||||
|
<template v-else>
|
||||||
<svg
|
<svg
|
||||||
v-if="!isDefaultTheme"
|
v-if="!isDefaultTheme"
|
||||||
viewBox="0 0 122 36"
|
viewBox="0 0 122 36"
|
||||||
@ -55,6 +57,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<img v-else src="@/assets/logo/MaxKB-logo.svg" :height="height" />
|
<img v-else src="@/assets/logo/MaxKB-logo.svg" :height="height" />
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
@ -66,9 +69,21 @@ defineProps({
|
|||||||
default: '36px'
|
default: '36px'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { common } = useStore()
|
const { user } = useStore()
|
||||||
const isDefaultTheme = computed(() => {
|
const isDefaultTheme = computed(() => {
|
||||||
return common.isDefaultTheme()
|
return user.isDefaultTheme()
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileURL = computed(() => {
|
||||||
|
if (user.themeInfo.loginLogo) {
|
||||||
|
if (typeof user.themeInfo.loginLogo === 'string') {
|
||||||
|
return user.themeInfo.loginLogo
|
||||||
|
} else {
|
||||||
|
return URL.createObjectURL(user.themeInfo.loginLogo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -45,9 +45,9 @@ defineProps({
|
|||||||
default: '36px'
|
default: '36px'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { common } = useStore()
|
const { user } = useStore()
|
||||||
const isDefaultTheme = computed(() => {
|
const isDefaultTheme = computed(() => {
|
||||||
return common.isDefaultTheme()
|
return user.isDefaultTheme()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -8,9 +8,9 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import TopBar from '../top-bar/index.vue'
|
import TopBar from '../top-bar/index.vue'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { common } = useStore()
|
const { user } = useStore()
|
||||||
const isDefaultTheme = computed(() => {
|
const isDefaultTheme = computed(() => {
|
||||||
return common.isDefaultTheme()
|
return user.isDefaultTheme()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -35,9 +35,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { common, user } = useStore()
|
const { user } = useStore()
|
||||||
const isDefaultTheme = computed(() => {
|
const isDefaultTheme = computed(() => {
|
||||||
return common.isDefaultTheme()
|
return user.isDefaultTheme()
|
||||||
})
|
})
|
||||||
|
|
||||||
const aboutDialogVisible = ref(false)
|
const aboutDialogVisible = ref(false)
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import * as ElementPlusIcons from '@element-plus/icons-vue'
|
|||||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { store } from '@/stores'
|
import { store } from '@/stores'
|
||||||
import theme from '@/theme'
|
|
||||||
import directives from '@/directives'
|
import directives from '@/directives'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
@ -56,8 +55,6 @@ app.use(ElementPlus, {
|
|||||||
locale: zhCn
|
locale: zhCn
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use(theme)
|
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
app.use(Components)
|
app.use(Components)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Role, ComplexPermission } from '@/utils/permission/type'
|
|||||||
const settingRouter = {
|
const settingRouter = {
|
||||||
path: '/setting',
|
path: '/setting',
|
||||||
name: 'setting',
|
name: 'setting',
|
||||||
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
|
meta: { icon: 'Setting', title: '系统管理', permission: 'SETTING:READ' },
|
||||||
redirect: () => {
|
redirect: () => {
|
||||||
if (hasPermission(new Role('ADMIN'), 'AND')) {
|
if (hasPermission(new Role('ADMIN'), 'AND')) {
|
||||||
return '/user'
|
return '/user'
|
||||||
@ -59,7 +59,7 @@ const settingRouter = {
|
|||||||
meta: {
|
meta: {
|
||||||
icon: 'app-setting',
|
icon: 'app-setting',
|
||||||
iconActive: 'app-setting-active',
|
iconActive: 'app-setting-active',
|
||||||
title: '系统设置',
|
title: '系统管理',
|
||||||
activeMenu: '/setting',
|
activeMenu: '/setting',
|
||||||
parentPath: '/setting',
|
parentPath: '/setting',
|
||||||
parentName: 'setting',
|
parentName: 'setting',
|
||||||
|
|||||||
@ -8,7 +8,6 @@ export interface commonTypes {
|
|||||||
paginationConfig: any | null
|
paginationConfig: any | null
|
||||||
search: any
|
search: any
|
||||||
device: string
|
device: string
|
||||||
theme: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCommonStore = defineStore({
|
const useCommonStore = defineStore({
|
||||||
@ -18,16 +17,9 @@ const useCommonStore = defineStore({
|
|||||||
// 搜索和分页缓存
|
// 搜索和分页缓存
|
||||||
paginationConfig: {},
|
paginationConfig: {},
|
||||||
search: {},
|
search: {},
|
||||||
device: DeviceType.Desktop,
|
device: DeviceType.Desktop
|
||||||
theme: ''
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
isDefaultTheme() {
|
|
||||||
return !this.theme || this.theme === '#3370FF'
|
|
||||||
},
|
|
||||||
setTheme(val: string) {
|
|
||||||
this.theme = val
|
|
||||||
},
|
|
||||||
saveBreadcrumb(data: any) {
|
saveBreadcrumb(data: any) {
|
||||||
this.breadcrumb = data
|
this.breadcrumb = data
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { defineStore } from 'pinia'
|
|||||||
import type { User } from '@/api/type/user'
|
import type { User } from '@/api/type/user'
|
||||||
import UserApi from '@/api/user'
|
import UserApi from '@/api/user'
|
||||||
import ThemeApi from '@/api/theme'
|
import ThemeApi from '@/api/theme'
|
||||||
|
import { useElementPlusTheme } from 'use-element-plus-theme'
|
||||||
|
const { changeTheme } = useElementPlusTheme()
|
||||||
|
|
||||||
export interface userStateTypes {
|
export interface userStateTypes {
|
||||||
userType: number // 1 系统操作者 2 对话用户
|
userType: number // 1 系统操作者 2 对话用户
|
||||||
@ -26,6 +28,13 @@ const useUserStore = defineStore({
|
|||||||
themeInfo: null
|
themeInfo: null
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
isDefaultTheme() {
|
||||||
|
return !this.themeInfo?.theme || this.themeInfo?.theme === '#3370FF'
|
||||||
|
},
|
||||||
|
setTheme(data: any) {
|
||||||
|
changeTheme(data?.['theme'])
|
||||||
|
this.themeInfo = data
|
||||||
|
},
|
||||||
isExpire() {
|
isExpire() {
|
||||||
return this.isXPack && !this.XPACK_LICENSE_IS_VALID
|
return this.isXPack && !this.XPACK_LICENSE_IS_VALID
|
||||||
},
|
},
|
||||||
@ -82,8 +91,14 @@ const useUserStore = defineStore({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async theme() {
|
async theme() {
|
||||||
return ThemeApi.getThemeInfo().then((ok) => {
|
return await ThemeApi.getThemeInfo().then((ok) => {
|
||||||
this.themeInfo = ok.data
|
this.themeInfo = ok.data
|
||||||
|
changeTheme(this.themeInfo['theme'])
|
||||||
|
window.document.title = this.themeInfo['title'] || 'MaxKB'
|
||||||
|
const link = document.querySelector('link[rel="icon"]') as any
|
||||||
|
if (link) {
|
||||||
|
link['href'] = this.themeInfo['icon'] || '/favicon.ico'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -369,8 +369,8 @@ h5 {
|
|||||||
|
|
||||||
/* tag */
|
/* tag */
|
||||||
.default-tag {
|
.default-tag {
|
||||||
background: var(--tag-default-bg);
|
background: var(--el-color-primary-light-7);
|
||||||
color: var(--tag-default-color);
|
color: var(--el-color-primary);
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.success-tag {
|
.success-tag {
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import type { InferData } from "./type";
|
|
||||||
const inferData: Array<InferData> = [
|
|
||||||
{
|
|
||||||
key: "primary",
|
|
||||||
value: "#3370FF",
|
|
||||||
},
|
|
||||||
{ key: "success", value: "#67c23a" },
|
|
||||||
{ key: "warning", value: "#e6a23c" },
|
|
||||||
{ key: "danger", value: "#f56c6c" },
|
|
||||||
{ key: "error", value: "#F54A45" },
|
|
||||||
{ key: "info", value: "#909399" },
|
|
||||||
];
|
|
||||||
export default inferData;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import type { KeyValueData } from './type'
|
|
||||||
const keyValueData: KeyValueData = {
|
|
||||||
'--el-header-padding': '0px'
|
|
||||||
}
|
|
||||||
export default keyValueData
|
|
||||||
@ -1,281 +0,0 @@
|
|||||||
import type {
|
|
||||||
ThemeSetting,
|
|
||||||
InferData,
|
|
||||||
KeyValueData,
|
|
||||||
UpdateInferData,
|
|
||||||
UpdateKeyValueData
|
|
||||||
} from './type'
|
|
||||||
import { TinyColor } from '@ctrl/tinycolor'
|
|
||||||
// 引入默认推断数据
|
|
||||||
import inferData from './defaultInferData'
|
|
||||||
// 引入默认keyValue数据
|
|
||||||
import keyValueData from './defaultKeyValueData'
|
|
||||||
// 引入设置对象
|
|
||||||
import setting from './setting'
|
|
||||||
import type { App } from 'vue'
|
|
||||||
declare global {
|
|
||||||
interface ChildNode {
|
|
||||||
innerText: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class Theme {
|
|
||||||
/**
|
|
||||||
* 主题设置
|
|
||||||
*/
|
|
||||||
themeSetting: ThemeSetting
|
|
||||||
/**
|
|
||||||
* 键值数据
|
|
||||||
*/
|
|
||||||
keyValue: KeyValueData
|
|
||||||
/**
|
|
||||||
* 外推数据
|
|
||||||
*/
|
|
||||||
inferData: Array<InferData>
|
|
||||||
/**
|
|
||||||
*是否是第一次初始化
|
|
||||||
*/
|
|
||||||
isFirstWriteStyle: boolean
|
|
||||||
/**
|
|
||||||
* 混色白
|
|
||||||
*/
|
|
||||||
colorWhite: string
|
|
||||||
/**
|
|
||||||
* 混色黑
|
|
||||||
*/
|
|
||||||
colorBlack: string
|
|
||||||
|
|
||||||
constructor(themeSetting: ThemeSetting, keyValue: KeyValueData, inferData: Array<InferData>) {
|
|
||||||
this.themeSetting = themeSetting
|
|
||||||
this.keyValue = keyValue
|
|
||||||
this.inferData = inferData
|
|
||||||
this.isFirstWriteStyle = true
|
|
||||||
this.colorWhite = '#ffffff'
|
|
||||||
this.colorBlack = '#000000'
|
|
||||||
this.initDefaultTheme()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拼接
|
|
||||||
* @param setting 主题设置
|
|
||||||
* @param names 需要拼接的所有值
|
|
||||||
* @returns 拼接后的数据
|
|
||||||
*/
|
|
||||||
getVarName = (setting: ThemeSetting, ...names: Array<string>) => {
|
|
||||||
return (
|
|
||||||
setting.startDivision + setting.namespace + setting.division + names.join(setting.division)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换外推数据
|
|
||||||
* @param setting 主题设置对象
|
|
||||||
* @param inferData 外推数据
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
mapInferMainStyle = (setting: ThemeSetting, inferData: InferData) => {
|
|
||||||
const key: string = this.getVarName(
|
|
||||||
setting,
|
|
||||||
inferData.setting ? inferData.setting.type : setting.colorInferSetting.type,
|
|
||||||
inferData.key
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
[key]: inferData.value,
|
|
||||||
...this.mapInferDataStyle(setting, inferData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 转换外推数据
|
|
||||||
* @param setting 设置
|
|
||||||
* @param inferData 外推数据
|
|
||||||
*/
|
|
||||||
mapInferData = (setting: ThemeSetting, inferData: Array<InferData>) => {
|
|
||||||
return inferData
|
|
||||||
.map((itemData) => {
|
|
||||||
return this.mapInferMainStyle(setting, itemData)
|
|
||||||
})
|
|
||||||
.reduce((pre, next) => {
|
|
||||||
return { ...pre, ...next }
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 转换外推数据
|
|
||||||
* @param setting 主题设置对象
|
|
||||||
* @param inferData 外推数据
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
mapInferDataStyle = (setting: ThemeSetting, inferData: InferData) => {
|
|
||||||
const inferSetting = inferData.setting ? inferData.setting : setting.colorInferSetting
|
|
||||||
if (inferSetting.type === 'color') {
|
|
||||||
return Object.keys(inferSetting)
|
|
||||||
.map((key: string) => {
|
|
||||||
if (key === 'light' || key === 'dark') {
|
|
||||||
return inferSetting[key]
|
|
||||||
.map((l: any) => {
|
|
||||||
const varName = this.getVarName(
|
|
||||||
setting,
|
|
||||||
inferSetting.type,
|
|
||||||
inferData.key,
|
|
||||||
key,
|
|
||||||
l.toString()
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
[varName]: new TinyColor(inferData.value)
|
|
||||||
.mix(key === 'light' ? this.colorWhite : this.colorBlack, l * 10)
|
|
||||||
.toHexString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.reduce((pre: any, next: any) => {
|
|
||||||
return { ...pre, ...next }
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
})
|
|
||||||
.reduce((pre, next) => {
|
|
||||||
return { ...pre, ...next }
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param themeSetting 主题设置
|
|
||||||
* @param keyValueData 键值数据
|
|
||||||
* @returns 映射后的键值数据
|
|
||||||
*/
|
|
||||||
mapKeyValue = (themeSetting: ThemeSetting, keyValueData: KeyValueData) => {
|
|
||||||
return Object.keys(keyValueData)
|
|
||||||
.map((key: string) => {
|
|
||||||
return {
|
|
||||||
[this.updateKeyBySetting(key, themeSetting)]: keyValueData[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.reduce((pre, next) => {
|
|
||||||
return { ...pre, ...next }
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 根据配置文件修改Key
|
|
||||||
* @param key key
|
|
||||||
* @param themeSetting 主题设置
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
updateKeyBySetting = (key: string, themeSetting: ThemeSetting) => {
|
|
||||||
return key.startsWith(themeSetting.startDivision)
|
|
||||||
? key
|
|
||||||
: key.startsWith(themeSetting.namespace)
|
|
||||||
? themeSetting.startDivision + key
|
|
||||||
: key.startsWith(themeSetting.division)
|
|
||||||
? themeSetting.startDivision + themeSetting.namespace
|
|
||||||
: themeSetting.startDivision + themeSetting.namespace + themeSetting.division + key
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param setting 主题设置
|
|
||||||
* @param keyValue 主题键值对数据
|
|
||||||
* @param inferData 外推数据
|
|
||||||
* @returns 合并后的键值对数据
|
|
||||||
*/
|
|
||||||
tokeyValueStyle = () => {
|
|
||||||
return {
|
|
||||||
...this.mapInferData(this.themeSetting, this.inferData),
|
|
||||||
...this.mapKeyValue(this.themeSetting, this.keyValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将keyValue对象转换为S
|
|
||||||
* @param keyValue
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
toString = (keyValue: KeyValueData) => {
|
|
||||||
const inner = Object.keys(keyValue)
|
|
||||||
.map((key: string) => {
|
|
||||||
return key + ':' + keyValue[key] + ';'
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
return `@charset "UTF-8";:root{${inner}}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param elNewStyle 新的变量样式
|
|
||||||
*/
|
|
||||||
writeNewStyle = (elNewStyle: string) => {
|
|
||||||
if (this.isFirstWriteStyle) {
|
|
||||||
const style = document.createElement('style')
|
|
||||||
style.innerText = elNewStyle
|
|
||||||
document.head.appendChild(style)
|
|
||||||
this.isFirstWriteStyle = false
|
|
||||||
} else {
|
|
||||||
if (document.head.lastChild) {
|
|
||||||
document.head.lastChild.innerText = elNewStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改数据并且写入dom
|
|
||||||
* @param updateInferData 平滑数据修改
|
|
||||||
* @param updateKeyvalueData keyValue数据修改
|
|
||||||
*/
|
|
||||||
updateWrite = (updateInferData?: UpdateInferData, updateKeyvalueData?: UpdateKeyValueData) => {
|
|
||||||
this.update(updateInferData, updateKeyvalueData)
|
|
||||||
const newStyle = this.tokeyValueStyle()
|
|
||||||
const newStyleString = this.toString(newStyle)
|
|
||||||
this.writeNewStyle(newStyleString)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改数据
|
|
||||||
* @param inferData
|
|
||||||
* @param keyvalueData
|
|
||||||
*/
|
|
||||||
update = (updateInferData?: UpdateInferData, updateKeyvalueData?: UpdateKeyValueData) => {
|
|
||||||
if (updateInferData) {
|
|
||||||
this.updateInferData(updateInferData)
|
|
||||||
}
|
|
||||||
if (updateKeyvalueData) {
|
|
||||||
this.updateOrCreateKeyValueData(updateKeyvalueData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改外推数据 外推数据只能修改,不能新增
|
|
||||||
* @param inferData
|
|
||||||
*/
|
|
||||||
updateInferData = (updateInferData: UpdateInferData) => {
|
|
||||||
Object.keys(updateInferData).forEach((key) => {
|
|
||||||
const findInfer = this.inferData.find((itemInfer) => {
|
|
||||||
return itemInfer.key === key
|
|
||||||
})
|
|
||||||
if (findInfer) {
|
|
||||||
findInfer.value = updateInferData[key]
|
|
||||||
} else {
|
|
||||||
this.inferData.push({ key, value: updateInferData[key] })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化默认主题
|
|
||||||
*/
|
|
||||||
initDefaultTheme = () => {
|
|
||||||
this.updateWrite()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 修改KeyValue数据
|
|
||||||
* @param keyvalueData keyValue数据
|
|
||||||
*/
|
|
||||||
updateOrCreateKeyValueData = (updateKeyvalueData: UpdateKeyValueData) => {
|
|
||||||
Object.keys(updateKeyvalueData).forEach((key) => {
|
|
||||||
const newKey = this.updateKeyBySetting(key, this.themeSetting)
|
|
||||||
this.keyValue[newKey] = updateKeyvalueData[newKey]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const install = (app: App) => {
|
|
||||||
app.config.globalProperties.theme = new Theme(setting, keyValueData, inferData)
|
|
||||||
}
|
|
||||||
export default { install }
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import type { ThemeSetting } from "./type";
|
|
||||||
const setting: ThemeSetting = {
|
|
||||||
namespace: "el",
|
|
||||||
division: "-",
|
|
||||||
startDivision: "--",
|
|
||||||
colorInferSetting: {
|
|
||||||
light: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
||||||
dark: [2],
|
|
||||||
type: "color",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default setting;
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
interface ThemeSetting {
|
|
||||||
/**
|
|
||||||
*element-ui Namespace
|
|
||||||
*/
|
|
||||||
namespace: string;
|
|
||||||
/**
|
|
||||||
* 数据分隔符
|
|
||||||
*/
|
|
||||||
division: string;
|
|
||||||
/**
|
|
||||||
* 前缀
|
|
||||||
*/
|
|
||||||
startDivision: string;
|
|
||||||
/**
|
|
||||||
* 颜色外推设置
|
|
||||||
*/
|
|
||||||
colorInferSetting: ColorInferSetting;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 颜色混和设置
|
|
||||||
*/
|
|
||||||
interface ColorInferSetting {
|
|
||||||
/**
|
|
||||||
* 与白色混
|
|
||||||
*/
|
|
||||||
light: Array<number>;
|
|
||||||
/**
|
|
||||||
* 与黑色混
|
|
||||||
*/
|
|
||||||
dark: Array<number>;
|
|
||||||
/**
|
|
||||||
* 类型
|
|
||||||
*/
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 平滑数据
|
|
||||||
*/
|
|
||||||
interface KeyValueData {
|
|
||||||
[propName: string]: string;
|
|
||||||
}
|
|
||||||
type UpdateInferData = KeyValueData;
|
|
||||||
|
|
||||||
type UpdateKeyValueData = KeyValueData;
|
|
||||||
/**
|
|
||||||
*平滑数据
|
|
||||||
*/
|
|
||||||
interface InferData {
|
|
||||||
/**
|
|
||||||
* 设置
|
|
||||||
*/
|
|
||||||
setting?: ColorInferSetting | any;
|
|
||||||
/**
|
|
||||||
* 健
|
|
||||||
*/
|
|
||||||
key: string;
|
|
||||||
/**
|
|
||||||
* 值
|
|
||||||
*/
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type {
|
|
||||||
KeyValueData,
|
|
||||||
InferData,
|
|
||||||
ThemeSetting,
|
|
||||||
UpdateInferData,
|
|
||||||
UpdateKeyValueData,
|
|
||||||
};
|
|
||||||
44
ui/src/utils/theme.ts
Normal file
44
ui/src/utils/theme.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
export const themeList = [
|
||||||
|
{
|
||||||
|
label: '默认',
|
||||||
|
value: '#3370FF',
|
||||||
|
loginBackground: 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '活力橙',
|
||||||
|
value: '#FF8800',
|
||||||
|
loginBackground: 'orange'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '松石绿',
|
||||||
|
value: '#00B69D',
|
||||||
|
loginBackground: 'green'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '商务蓝',
|
||||||
|
value: '#4954E6',
|
||||||
|
loginBackground: 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '神秘紫',
|
||||||
|
value: '#7F3BF5',
|
||||||
|
loginBackground: 'purple'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '胭脂红',
|
||||||
|
value: '#F01D94',
|
||||||
|
loginBackground: 'red'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export function getThemeImg(val: string) {
|
||||||
|
return themeList.filter((v) => v.value === val)?.[0]?.loginBackground || 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defautSetting = {
|
||||||
|
icon: '',
|
||||||
|
loginLogo: '',
|
||||||
|
loginImage: '',
|
||||||
|
title: 'MaxKB',
|
||||||
|
slogan: '欢迎使用 MaxKB 智能知识库'
|
||||||
|
}
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<div class="mr-16">
|
<div class="mr-16">
|
||||||
<el-button link @click="enlarge = !enlarge">
|
<el-button link @click="enlarge = !enlarge">
|
||||||
<AppIcon
|
<AppIcon
|
||||||
:iconName="enlarge ? 'app-magnify' : 'app-minify'"
|
:iconName="enlarge ? 'app-minify' : 'app-magnify'"
|
||||||
class="color-secondary"
|
class="color-secondary"
|
||||||
style="font-size: 20px"
|
style="font-size: 20px"
|
||||||
></AppIcon>
|
></AppIcon>
|
||||||
|
|||||||
@ -29,13 +29,6 @@ const tabList = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// 动态引入组件
|
|
||||||
const loadComponent = async (componentName: string) => {
|
|
||||||
await import(`./component/${componentName}.vue`).then((res) => res.default)
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentComponent = computed(() => loadComponent(activeName.value))
|
|
||||||
|
|
||||||
function handleClick() {}
|
function handleClick() {}
|
||||||
|
|
||||||
onMounted(() => {})
|
onMounted(() => {})
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<login-layout v-loading="loading">
|
<login-layout v-loading="loading">
|
||||||
<LoginContainer subTitle="欢迎使用 MaxKB 智能知识库">
|
<LoginContainer :subTitle="user.themeInfo?.slogan || '欢迎使用 MaxKB 智能知识库'">
|
||||||
<h2 class="mb-24">{{ loginMode || '普通登录' }}</h2>
|
<h2 class="mb-24">{{ loginMode || '普通登录' }}</h2>
|
||||||
<el-form
|
<el-form
|
||||||
class="login-form"
|
class="login-form"
|
||||||
@ -109,8 +109,7 @@ const rules = ref<FormRules<LoginRequest>>({
|
|||||||
})
|
})
|
||||||
const loginFormRef = ref<FormInstance>()
|
const loginFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const modeList = ref<string[]>([''])
|
||||||
const modeList = ref<string[]>(['']);
|
|
||||||
const loginMode = ref('')
|
const loginMode = ref('')
|
||||||
|
|
||||||
function changeMode(val: string) {
|
function changeMode(val: string) {
|
||||||
@ -135,16 +134,19 @@ const login = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
user.theme()
|
||||||
user.asyncGetProfile().then((res) => {
|
user.asyncGetProfile().then((res) => {
|
||||||
if (user.isXPack) {
|
if (user.isXPack) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
user.getAuthType().then((res) => {
|
user
|
||||||
modeList.value = [...modeList.value, ...res];
|
.getAuthType()
|
||||||
}).finally(() => (loading.value = false))
|
.then((res) => {
|
||||||
|
modeList.value = [...modeList.value, ...res]
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scope>
|
<style lang="scss" scope>
|
||||||
.login-gradient-divider {
|
.login-gradient-divider {
|
||||||
|
|||||||
@ -3,14 +3,15 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="tag flex-between">
|
<div class="tag flex-between">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<LogoIcon height="24px" class="mr-8" />
|
<img v-if="props.data.icon" :src="fileURL" alt="" height="20px" class="mr-8" />
|
||||||
<span class="ellipsis">{{ title }}</span>
|
<img v-else src="@/assets/logo/logo.svg" height="24px" class="mr-8" />
|
||||||
|
<span class="ellipsis">{{ data.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-icon><Close /></el-icon>
|
<el-icon><Close /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<login-layout style="height: 530px" :themeImg="themeImg">
|
<login-layout style="height: 530px">
|
||||||
<LoginContainer :subTitle="slogan" class="login-container">
|
<LoginContainer :subTitle="data.slogan" class="login-container">
|
||||||
<div class="mask"></div>
|
<div class="mask"></div>
|
||||||
<h2 class="mb-24">{{ '普通登录' }}</h2>
|
<h2 class="mb-24">{{ '普通登录' }}</h2>
|
||||||
<el-form class="login-form">
|
<el-form class="login-form">
|
||||||
@ -42,18 +43,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
themeImg: {
|
data: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: 'default'
|
default: null
|
||||||
},
|
}
|
||||||
slogan: {
|
})
|
||||||
type: String,
|
|
||||||
default: '欢迎使用 MaxKB 智能知识库'
|
const fileURL = computed(() => {
|
||||||
},
|
if (props.data.icon) {
|
||||||
title: {
|
if (typeof props.data.icon === 'string') {
|
||||||
type: String,
|
return props.data.icon
|
||||||
default: 'MaxKB'
|
} else {
|
||||||
|
return URL.createObjectURL(props.data.icon)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="theme-setting">
|
<div class="theme-setting" v-loading="loading">
|
||||||
<h4 class="p-16-24">外观设置</h4>
|
<h4 class="p-16-24">外观设置</h4>
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<div class="p-24 pt-0">
|
<div class="p-24 pt-0">
|
||||||
@ -8,7 +8,7 @@
|
|||||||
<el-radio-group
|
<el-radio-group
|
||||||
v-model="themeForm.theme"
|
v-model="themeForm.theme"
|
||||||
class="app-radio-button-group"
|
class="app-radio-button-group"
|
||||||
@change="changeTheme"
|
@change="changeThemeHandle"
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in themeList" :key="index">
|
<template v-for="(item, index) in themeList" :key="index">
|
||||||
<el-radio-button :label="item.label" :value="item.value" />
|
<el-radio-button :label="item.label" :value="item.value" />
|
||||||
@ -20,19 +20,30 @@
|
|||||||
<el-card shadow="never" class="layout-bg">
|
<el-card shadow="never" class="layout-bg">
|
||||||
<div class="flex-between">
|
<div class="flex-between">
|
||||||
<h5 class="mb-16">页面预览</h5>
|
<h5 class="mb-16">页面预览</h5>
|
||||||
<el-button type="primary" link> 恢复默认 </el-button>
|
<el-button type="primary" link @click="resetForm"> 恢复默认 </el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
<el-row :gutter="8">
|
<el-row :gutter="8">
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
<LoginPreview :themeImg="themeImg" :slogan="themeForm.slogan" :title="themeForm.title" />
|
<LoginPreview :data="themeForm" />
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<div class="theme-form">
|
<div class="theme-form">
|
||||||
<el-card shadow="never" class="mb-8">
|
<el-card shadow="never" class="mb-8">
|
||||||
<div class="flex-between mb-8">
|
<div class="flex-between mb-8">
|
||||||
<span class="lighter">网站 Logo</span>
|
<span class="lighter">网站 Logo</span>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/*"
|
||||||
|
:on-change="
|
||||||
|
(file: any, fileList: any) => onChange(file, fileList, 'icon')
|
||||||
|
"
|
||||||
|
>
|
||||||
<el-button size="small"> 替换图片 </el-button>
|
<el-button size="small"> 替换图片 </el-button>
|
||||||
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
<el-text type="info" size="small"
|
<el-text type="info" size="small"
|
||||||
>顶部网站显示的 Logo,建议尺寸 48 x 48,支持 JPG、PNG、SVG,大小不超过
|
>顶部网站显示的 Logo,建议尺寸 48 x 48,支持 JPG、PNG、SVG,大小不超过
|
||||||
@ -42,7 +53,18 @@
|
|||||||
<el-card shadow="never" class="mb-8">
|
<el-card shadow="never" class="mb-8">
|
||||||
<div class="flex-between mb-8">
|
<div class="flex-between mb-8">
|
||||||
<span class="lighter">登录 Logo</span>
|
<span class="lighter">登录 Logo</span>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/*"
|
||||||
|
:on-change="
|
||||||
|
(file: any, fileList: any) => onChange(file, fileList, 'loginLogo')
|
||||||
|
"
|
||||||
|
>
|
||||||
<el-button size="small"> 替换图片 </el-button>
|
<el-button size="small"> 替换图片 </el-button>
|
||||||
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
<el-text type="info" size="small"
|
<el-text type="info" size="small"
|
||||||
>登录页面右侧 Logo,建议尺寸 204*52,支持 JPG、PNG、SVG,大小不超过
|
>登录页面右侧 Logo,建议尺寸 204*52,支持 JPG、PNG、SVG,大小不超过
|
||||||
@ -52,7 +74,18 @@
|
|||||||
<el-card shadow="never" class="mb-8">
|
<el-card shadow="never" class="mb-8">
|
||||||
<div class="flex-between mb-8">
|
<div class="flex-between mb-8">
|
||||||
<span class="lighter">登录背景图</span>
|
<span class="lighter">登录背景图</span>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/*"
|
||||||
|
:on-change="
|
||||||
|
(file: any, fileList: any) => onChange(file, fileList, 'loginImage')
|
||||||
|
"
|
||||||
|
>
|
||||||
<el-button size="small"> 替换图片 </el-button>
|
<el-button size="small"> 替换图片 </el-button>
|
||||||
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
<el-text type="info" size="small">
|
<el-text type="info" size="small">
|
||||||
左侧背景图,矢量图建议尺寸 576*900,位图建议尺寸1152*1800;支持
|
左侧背景图,矢量图建议尺寸 576*900,位图建议尺寸1152*1800;支持
|
||||||
@ -92,53 +125,34 @@
|
|||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div class="theme-setting__operate w-full p-16-24">
|
<div class="theme-setting__operate w-full p-16-24">
|
||||||
<el-button @click="resetTheme">放弃更新</el-button>
|
<el-button @click="resetTheme">放弃更新</el-button>
|
||||||
<el-button type="primary"> 保存并应用 </el-button>
|
<el-button type="primary" @click="updataTheme(themeFormRef)"> 保存并应用 </el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, watch } from 'vue'
|
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import { onBeforeRouteLeave } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules, UploadFiles } from 'element-plus'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
import LoginPreview from './LoginPreview.vue'
|
import LoginPreview from './LoginPreview.vue'
|
||||||
import { useElementPlusTheme } from 'use-element-plus-theme'
|
import { themeList, defautSetting } from '@/utils/theme'
|
||||||
|
import ThemeApi from '@/api/theme'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { common } = useStore()
|
|
||||||
|
|
||||||
const themeList = [
|
const { user } = useStore()
|
||||||
{
|
|
||||||
label: '默认',
|
onBeforeRouteLeave((to, from) => {
|
||||||
value: '#3370FF',
|
user.setTheme(cloneTheme.value)
|
||||||
loginBackground: 'default'
|
})
|
||||||
},
|
|
||||||
{
|
const themeInfo = computed(() => user.themeInfo)
|
||||||
label: '活力橙',
|
|
||||||
value: '#FF8800',
|
|
||||||
loginBackground: 'orange'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '松石绿',
|
|
||||||
value: '#00B69D',
|
|
||||||
loginBackground: 'green'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '商务蓝',
|
|
||||||
value: '#4954E6',
|
|
||||||
loginBackground: 'default'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '神秘紫',
|
|
||||||
value: '#7F3BF5',
|
|
||||||
loginBackground: 'purple'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '胭脂红',
|
|
||||||
value: '#F01D94',
|
|
||||||
loginBackground: 'red'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const themeFormRef = ref<FormInstance>()
|
const themeFormRef = ref<FormInstance>()
|
||||||
const themeForm = ref({
|
const loading = ref(false)
|
||||||
|
const cloneTheme = ref(null)
|
||||||
|
const themeForm = ref<any>({
|
||||||
theme: '#3370FF',
|
theme: '#3370FF',
|
||||||
icon: '',
|
icon: '',
|
||||||
loginLogo: '',
|
loginLogo: '',
|
||||||
@ -152,24 +166,67 @@ const rules = reactive<FormRules>({
|
|||||||
slogan: [{ required: true, message: '请输入欢迎语', trigger: 'blur' }]
|
slogan: [{ required: true, message: '请输入欢迎语', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
const themeImg = ref('default')
|
const onChange = (file: any, fileList: UploadFiles, attr: string) => {
|
||||||
|
if (attr === 'loginImage') {
|
||||||
|
const isLimit = file?.size / 1024 / 1024 < 5
|
||||||
|
if (!isLimit) {
|
||||||
|
// @ts-ignore
|
||||||
|
MsgError(`文件大小超过 5M`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const isLimit = file?.size / 1024 < 200
|
||||||
|
if (!isLimit) {
|
||||||
|
// @ts-ignore
|
||||||
|
MsgError(`文件大小超过 200KB`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { changeTheme } = useElementPlusTheme(themeForm.value.theme)
|
themeForm.value[attr] = file.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeThemeHandle(val: string) {
|
||||||
|
themeForm.value.theme = val
|
||||||
|
user.setTheme(themeForm.value)
|
||||||
|
}
|
||||||
|
|
||||||
function resetTheme() {
|
function resetTheme() {
|
||||||
themeForm.value.theme = '#3370FF'
|
user.setTheme(cloneTheme.value)
|
||||||
changeTheme(themeForm.value.theme)
|
themeForm.value = cloneDeep(themeInfo.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
function resetForm() {
|
||||||
() => themeForm.value.theme,
|
themeForm.value = {
|
||||||
(val) => {
|
theme: themeForm.value.theme,
|
||||||
if (val) {
|
...defautSetting
|
||||||
common.setTheme(val)
|
|
||||||
themeImg.value = themeList.filter((v) => v.value === val)[0].loginBackground
|
|
||||||
}
|
}
|
||||||
|
user.setTheme(themeForm.value)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
const updataTheme = async (formEl: FormInstance | undefined, test?: string) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate((valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
let fd = new FormData()
|
||||||
|
Object.keys(themeForm.value).map((item) => {
|
||||||
|
fd.append(item, themeForm.value[item])
|
||||||
|
})
|
||||||
|
ThemeApi.postThemeInfo(fd, loading).then((res) => {
|
||||||
|
user.theme()
|
||||||
|
cloneTheme.value = cloneDeep(themeForm.value)
|
||||||
|
MsgSuccess('外观设置成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (themeInfo.value) {
|
||||||
|
themeForm.value = themeInfo.value
|
||||||
|
cloneTheme.value = cloneDeep(themeInfo.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<AppAvatar shape="square avatar-blue">
|
<AppAvatar shape="square" class="avatar-blue">
|
||||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||||
</AppAvatar>
|
</AppAvatar>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user