Merge pull request #19 from Bull-BCLS/dev

refactor: 适配系统配置
This commit is contained in:
Charles7c 2023-09-23 19:27:04 +08:00 committed by GitHub
commit 6013211a09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 114 additions and 39 deletions

View File

@ -3,6 +3,7 @@ import qs from 'query-string';
import { ListParam as DeptParam } from '@/api/system/dept'; import { ListParam as DeptParam } from '@/api/system/dept';
import { ListParam as MenuParam } from '@/api/system/menu'; import { ListParam as MenuParam } from '@/api/system/menu';
import { ListParam as RoleParam } from '@/api/system/role'; import { ListParam as RoleParam } from '@/api/system/role';
import { ListParam as OptionParam } from '@/api/system/config';
import { TreeNodeData } from '@arco-design/web-vue'; import { TreeNodeData } from '@arco-design/web-vue';
import { LabelValueState } from '@/store/modules/dict/types'; import { LabelValueState } from '@/store/modules/dict/types';
@ -39,6 +40,15 @@ export function listDict(code: string) {
return axios.get<LabelValueState[]>(`${BASE_URL}/dict/${code}`); return axios.get<LabelValueState[]>(`${BASE_URL}/dict/${code}`);
} }
export function listOption(params: OptionParam) {
return axios.get<LabelValueState[]>(`${BASE_URL}/option`, {
params,
paramsSerializer: (obj) => {
return qs.stringify(obj);
},
});
}
export function upload(data: FormData) { export function upload(data: FormData) {
return axios.post(`${BASE_URL}/file`, data); return axios.post(`${BASE_URL}/file`, data);
} }

View File

@ -1,28 +1,14 @@
<template> <template>
<a-layout-footer class="footer"> <a-layout-footer class="footer">
{{ `Copyright © 2022-${new Date().getFullYear()}` }}&nbsp; <div v-html="appStore.getCopyright"></div>
<a
href="https://blog.charles7c.top/about/me"
target="_blank"
rel="noopener"
>
Charles7c
</a>
<span>&nbsp;&nbsp;</span>
<a
href="https://github.com/Charles7c/continew-admin"
target="_blank"
rel="noopener"
>{{ $t('title') }}</a
>&nbsp; v1.2.0-SNAPSHOT
<span>&nbsp;&nbsp;</span>
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">
津ICP备2022005864号-2
</a>
</a-layout-footer> </a-layout-footer>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import { useAppStore } from '@/store';
const appStore = useAppStore();
</script>
<style lang="less" scoped> <style lang="less" scoped>
.footer { .footer {
@ -33,13 +19,4 @@
color: var(--color-text-2); color: var(--color-text-2);
text-align: center; text-align: center;
} }
a {
text-decoration: none;
color: var(--color-text-2);
}
a:hover {
color: rgb(var(--gray-6));
}
</style> </style>

View File

@ -2,12 +2,12 @@
<div class="navbar"> <div class="navbar">
<div class="left-side"> <div class="left-side">
<a-space> <a-space>
<img alt="logo" src="/logo.svg" /> <img alt="logo" :src="getFile(appStore.getLogo)" height="33"/>
<a-typography-title <a-typography-title
:style="{ margin: 0, fontSize: '18px' }" :style="{ margin: 0, fontSize: '18px' }"
:heading="5" :heading="5"
> >
{{ $t('title') }} {{ appStore.getTitle }}
</a-typography-title> </a-typography-title>
<icon-menu-fold <icon-menu-fold
v-if="!topMenu && appStore.device === 'mobile'" v-if="!topMenu && appStore.device === 'mobile'"
@ -198,6 +198,7 @@
import useUser from '@/hooks/user'; import useUser from '@/hooks/user';
import Menu from '@/components/menu/index.vue'; import Menu from '@/components/menu/index.vue';
import getAvatar from '@/utils/avatar'; import getAvatar from '@/utils/avatar';
import getFile from '@/utils/file';
import MessageBox from '../message-box/index.vue'; import MessageBox from '../message-box/index.vue';
const appStore = useAppStore(); const appStore = useAppStore();

View File

@ -1,13 +1,15 @@
import type { Router, LocationQueryRaw } from 'vue-router'; import type { Router, LocationQueryRaw } from 'vue-router';
import NProgress from 'nprogress'; // progress bar import NProgress from 'nprogress'; // progress bar
import { useLoginStore } from '@/store'; import { useLoginStore, useAppStore } from '@/store';
import { isLogin } from '@/utils/auth'; import { isLogin } from '@/utils/auth';
export default function setupUserLoginInfoGuard(router: Router) { export default function setupUserLoginInfoGuard(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
const loginStore = useLoginStore(); const loginStore = useLoginStore();
const appStore = useAppStore();
appStore.init();
if (isLogin()) { if (isLogin()) {
if (loginStore.roles[0]) { if (loginStore.roles[0]) {
next(); next();

View File

@ -9,7 +9,9 @@ import type { MessageReturn } from '@arco-design/web-vue/es/message/interface';
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
import defaultSettings from '@/config/settings.json'; import defaultSettings from '@/config/settings.json';
import { listRoute } from '@/api/auth/login'; import { listRoute } from '@/api/auth/login';
import { AppState } from './types'; import { listOption } from '@/api/common';
import getFile from '@/utils/file';
import { AppState, Config } from './types';
const recursionMenu = ( const recursionMenu = (
appMenu: RouteRecordNormalized[], appMenu: RouteRecordNormalized[],
@ -45,6 +47,18 @@ const useAppStore = defineStore('app', {
); );
return menuList; return menuList;
}, },
getLogo(state: AppState): string | undefined {
return state.config?.site_logo;
},
getFavicon(state: AppState): string | undefined {
return state.config?.site_favicon;
},
getTitle(state: AppState): string | undefined {
return state.config?.site_title;
},
getCopyright(state: AppState): string | undefined {
return state.config?.site_copyright;
},
}, },
actions: { actions: {
@ -97,6 +111,51 @@ const useAppStore = defineStore('app', {
clearServerMenu() { clearServerMenu() {
this.serverMenu = []; this.serverMenu = [];
}, },
/**
*
*/
init() {
listOption({
code: ['site_title', 'site_copyright', 'site_favicon', 'site_logo'],
}).then((res) => {
const resMap = new Map();
res.data.forEach((item) => {
resMap.set(item.label, item.value);
});
this.config = {
site_title: resMap.get('site_title'),
site_copyright: resMap.get('site_copyright'),
site_logo: resMap.get('site_logo'),
site_favicon: resMap.get('site_logo'),
};
document.title = resMap.get('site_title');
document
.querySelector('link[rel="shortcut icon"]')
?.setAttribute(
'href',
getFile(resMap.get('site_favicon')) ||
'https://cnadmin.charles7c.top/favicon.ico'
);
});
},
/**
*
*
* @param config
*/
save(config: Config) {
this.$state.config = config;
document.title = config.site_title || '';
document
.querySelector('link[rel="shortcut icon"]')
?.setAttribute(
'href',
getFile(config.site_favicon) ||
'https://cnadmin.charles7c.top/favicon.ico'
);
},
}, },
}); });

View File

@ -1,5 +1,11 @@
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
export interface Config {
site_title?: string;
site_copyright?: string;
site_logo?: string;
site_favicon?: string;
}
export interface AppState { export interface AppState {
theme: string; theme: string;
colorWeak: boolean; colorWeak: boolean;
@ -18,4 +24,5 @@ export interface AppState {
menuFromServer: boolean; menuFromServer: boolean;
serverMenu: RouteRecordNormalized[]; serverMenu: RouteRecordNormalized[];
[key: string]: unknown; [key: string]: unknown;
config?: Config;
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="login-form-wrapper"> <div class="login-form-wrapper">
<div class="login-form-title">{{ $t('login.form.title') }}</div> <div class="login-form-title">登录 {{ appStore.getTitle }}</div>
<div class="login-form-sub-title">{{ $t('login.form.subTitle') }}</div> <div class="login-form-sub-title">{{ $t('login.form.subTitle') }}</div>
<a-form <a-form
ref="formRef" ref="formRef"
@ -71,7 +71,7 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { useLoginStore } from '@/store'; import { useLoginStore, useAppStore } from '@/store';
import { encryptByRsa } from '@/utils/encrypt'; import { encryptByRsa } from '@/utils/encrypt';
// import debug from '@/utils/env'; // import debug from '@/utils/env';
@ -79,6 +79,7 @@
const captchaImgBase64 = ref(''); const captchaImgBase64 = ref('');
const loginStore = useLoginStore(); const loginStore = useLoginStore();
const appStore = useAppStore();
const loading = ref(false); const loading = ref(false);
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<img src="/logo.svg" alt="logo" /> <img :src="getFile(appStore.getLogo)" alt="logo" height="33" />
<div class="logo-text">{{ $t('title') }}</div> <div class="logo-text">{{ appStore.getTitle }}</div>
</div> </div>
<LoginBanner /> <LoginBanner />
<div class="content"> <div class="content">
@ -18,8 +18,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import Footer from '@/components/footer/index.vue'; import Footer from '@/components/footer/index.vue';
import { useAppStore } from '@/store';
import getFile from '@/utils/file';
import LoginBanner from './components/banner.vue'; import LoginBanner from './components/banner.vue';
import LoginForm from './components/login-form.vue'; import LoginForm from './components/login-form.vue';
const appStore = useAppStore();
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -170,6 +170,7 @@
} from '@/api/system/config'; } from '@/api/system/config';
import { upload } from '@/api/common'; import { upload } from '@/api/common';
import getFile from '@/utils/file'; import getFile from '@/utils/file';
import { useAppStore } from '@/store';
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
const dataList = ref<DataRecord[]>([]); const dataList = ref<DataRecord[]>([]);
@ -180,6 +181,7 @@
const siteCopyright = ref<DataRecord>(); const siteCopyright = ref<DataRecord>();
const siteLogo = ref<DataRecord>(); const siteLogo = ref<DataRecord>();
const siteFavicon = ref<DataRecord>(); const siteFavicon = ref<DataRecord>();
const appStore = useAppStore();
const data = reactive({ const data = reactive({
queryParams: { queryParams: {
@ -251,7 +253,7 @@
} }
); );
save(optionList).then((res) => { save(optionList).then((res) => {
// siteConfigStore().save(data.form); appStore.save(form.value);
handleCancel(); handleCancel();
proxy.$message.success(res.msg); proxy.$message.success(res.msg);
}); });
@ -360,6 +362,7 @@
await resetValue(queryParams.value); await resetValue(queryParams.value);
proxy.$message.success('恢复成功'); proxy.$message.success('恢复成功');
await getConfig(); await getConfig();
appStore.save(form.value);
}; };
/** /**

View File

@ -37,6 +37,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@ -54,6 +55,7 @@ import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
import top.charles7c.cnadmin.monitor.annotation.Log; import top.charles7c.cnadmin.monitor.annotation.Log;
import top.charles7c.cnadmin.system.model.query.DeptQuery; import top.charles7c.cnadmin.system.model.query.DeptQuery;
import top.charles7c.cnadmin.system.model.query.MenuQuery; import top.charles7c.cnadmin.system.model.query.MenuQuery;
import top.charles7c.cnadmin.system.model.query.OptionQuery;
import top.charles7c.cnadmin.system.model.query.RoleQuery; import top.charles7c.cnadmin.system.model.query.RoleQuery;
import top.charles7c.cnadmin.system.model.vo.RoleVO; import top.charles7c.cnadmin.system.model.vo.RoleVO;
import top.charles7c.cnadmin.system.service.*; import top.charles7c.cnadmin.system.service.*;
@ -78,6 +80,7 @@ public class CommonController {
private final DictItemService dictItemService; private final DictItemService dictItemService;
private final ProjectProperties projectProperties; private final ProjectProperties projectProperties;
private final LocalStorageProperties localStorageProperties; private final LocalStorageProperties localStorageProperties;
private final OptionService optionService;
@Operation(summary = "上传文件", description = "上传文件") @Operation(summary = "上传文件", description = "上传文件")
@PostMapping("/file") @PostMapping("/file")
@ -123,6 +126,14 @@ public class CommonController {
return enumClass.map(this::listEnumDict).orElseGet(() -> R.ok(dictItemService.listByDictCode(code))); return enumClass.map(this::listEnumDict).orElseGet(() -> R.ok(dictItemService.listByDictCode(code)));
} }
@SaIgnore
@Operation(summary = "查询参数", description = "查询参数")
@GetMapping("/option")
public R<List<LabelValueVO>> listOption(@Validated OptionQuery query) {
return R.ok(optionService.list(query).stream().map(option -> new LabelValueVO(option.getCode(),
StrUtil.nullToDefault(option.getValue(), option.getDefaultValue()))).collect(Collectors.toList()));
}
/** /**
* 根据枚举类名查询 * 根据枚举类名查询
* *
@ -140,7 +151,7 @@ public class CommonController {
/** /**
* 查询枚举字典 * 查询枚举字典
* *
* @param enumClass * @param enumClass
* 枚举类型 * 枚举类型
* @return 枚举字典 * @return 枚举字典