mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	| @@ -3,6 +3,7 @@ import qs from 'query-string'; | ||||
| import { ListParam as DeptParam } from '@/api/system/dept'; | ||||
| import { ListParam as MenuParam } from '@/api/system/menu'; | ||||
| import { ListParam as RoleParam } from '@/api/system/role'; | ||||
| import { ListParam as OptionParam } from '@/api/system/config'; | ||||
| import { TreeNodeData } from '@arco-design/web-vue'; | ||||
| import { LabelValueState } from '@/store/modules/dict/types'; | ||||
|  | ||||
| @@ -39,6 +40,15 @@ export function listDict(code: string) { | ||||
|   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) { | ||||
|   return axios.post(`${BASE_URL}/file`, data); | ||||
| } | ||||
|   | ||||
| @@ -1,28 +1,14 @@ | ||||
| <template> | ||||
|   <a-layout-footer class="footer"> | ||||
|     {{ `Copyright © 2022-${new Date().getFullYear()}` }}  | ||||
|     <a | ||||
|       href="https://blog.charles7c.top/about/me" | ||||
|       target="_blank" | ||||
|       rel="noopener" | ||||
|     > | ||||
|       Charles7c | ||||
|     </a> | ||||
|     <span> ⋅ </span> | ||||
|     <a | ||||
|       href="https://github.com/Charles7c/continew-admin" | ||||
|       target="_blank" | ||||
|       rel="noopener" | ||||
|       >{{ $t('title') }}</a | ||||
|     >  v1.2.0-SNAPSHOT | ||||
|     <span> ⋅ </span> | ||||
|     <a href="https://beian.miit.gov.cn" target="_blank" rel="noopener"> | ||||
|       津ICP备2022005864号-2 | ||||
|     </a> | ||||
|     <div v-html="appStore.getCopyright"></div> | ||||
|   </a-layout-footer> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup></script> | ||||
| <script lang="ts" setup> | ||||
|   import { useAppStore } from '@/store'; | ||||
|  | ||||
|   const appStore = useAppStore(); | ||||
| </script> | ||||
|  | ||||
| <style lang="less" scoped> | ||||
|   .footer { | ||||
| @@ -33,13 +19,4 @@ | ||||
|     color: var(--color-text-2); | ||||
|     text-align: center; | ||||
|   } | ||||
|  | ||||
|   a { | ||||
|     text-decoration: none; | ||||
|     color: var(--color-text-2); | ||||
|   } | ||||
|  | ||||
|   a:hover { | ||||
|     color: rgb(var(--gray-6)); | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -2,12 +2,12 @@ | ||||
|   <div class="navbar"> | ||||
|     <div class="left-side"> | ||||
|       <a-space> | ||||
|         <img alt="logo" src="/logo.svg" /> | ||||
|         <img alt="logo" :src="getFile(appStore.getLogo)" height="33"/> | ||||
|         <a-typography-title | ||||
|           :style="{ margin: 0, fontSize: '18px' }" | ||||
|           :heading="5" | ||||
|         > | ||||
|           {{ $t('title') }} | ||||
|           {{ appStore.getTitle }} | ||||
|         </a-typography-title> | ||||
|         <icon-menu-fold | ||||
|           v-if="!topMenu && appStore.device === 'mobile'" | ||||
| @@ -198,6 +198,7 @@ | ||||
|   import useUser from '@/hooks/user'; | ||||
|   import Menu from '@/components/menu/index.vue'; | ||||
|   import getAvatar from '@/utils/avatar'; | ||||
|   import getFile from '@/utils/file'; | ||||
|   import MessageBox from '../message-box/index.vue'; | ||||
|  | ||||
|   const appStore = useAppStore(); | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| import type { Router, LocationQueryRaw } from 'vue-router'; | ||||
| import NProgress from 'nprogress'; // progress bar | ||||
|  | ||||
| import { useLoginStore } from '@/store'; | ||||
| import { useLoginStore, useAppStore } from '@/store'; | ||||
| import { isLogin } from '@/utils/auth'; | ||||
|  | ||||
| export default function setupUserLoginInfoGuard(router: Router) { | ||||
|   router.beforeEach(async (to, from, next) => { | ||||
|     NProgress.start(); | ||||
|     const loginStore = useLoginStore(); | ||||
|     const appStore = useAppStore(); | ||||
|     appStore.init(); | ||||
|     if (isLogin()) { | ||||
|       if (loginStore.roles[0]) { | ||||
|         next(); | ||||
|   | ||||
| @@ -9,7 +9,9 @@ import type { MessageReturn } from '@arco-design/web-vue/es/message/interface'; | ||||
| import type { RouteRecordNormalized } from 'vue-router'; | ||||
| import defaultSettings from '@/config/settings.json'; | ||||
| 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 = ( | ||||
|   appMenu: RouteRecordNormalized[], | ||||
| @@ -45,6 +47,18 @@ const useAppStore = defineStore('app', { | ||||
|       ); | ||||
|       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: { | ||||
| @@ -97,6 +111,51 @@ const useAppStore = defineStore('app', { | ||||
|     clearServerMenu() { | ||||
|       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' | ||||
|         ); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,11 @@ | ||||
| import type { RouteRecordNormalized } from 'vue-router'; | ||||
|  | ||||
| export interface Config { | ||||
|   site_title?: string; | ||||
|   site_copyright?: string; | ||||
|   site_logo?: string; | ||||
|   site_favicon?: string; | ||||
| } | ||||
| export interface AppState { | ||||
|   theme: string; | ||||
|   colorWeak: boolean; | ||||
| @@ -18,4 +24,5 @@ export interface AppState { | ||||
|   menuFromServer: boolean; | ||||
|   serverMenu: RouteRecordNormalized[]; | ||||
|   [key: string]: unknown; | ||||
|   config?: Config; | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <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> | ||||
|     <a-form | ||||
|       ref="formRef" | ||||
| @@ -71,7 +71,7 @@ | ||||
|   import { useI18n } from 'vue-i18n'; | ||||
|   import { useRouter } from 'vue-router'; | ||||
|   import { useStorage } from '@vueuse/core'; | ||||
|   import { useLoginStore } from '@/store'; | ||||
|   import { useLoginStore, useAppStore } from '@/store'; | ||||
|   import { encryptByRsa } from '@/utils/encrypt'; | ||||
|   // import debug from '@/utils/env'; | ||||
|  | ||||
| @@ -79,6 +79,7 @@ | ||||
|  | ||||
|   const captchaImgBase64 = ref(''); | ||||
|   const loginStore = useLoginStore(); | ||||
|   const appStore = useAppStore(); | ||||
|   const loading = ref(false); | ||||
|   const { t } = useI18n(); | ||||
|   const router = useRouter(); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| <template> | ||||
|   <div class="container"> | ||||
|     <div class="logo"> | ||||
|       <img src="/logo.svg" alt="logo" /> | ||||
|       <div class="logo-text">{{ $t('title') }}</div> | ||||
|       <img :src="getFile(appStore.getLogo)" alt="logo" height="33" /> | ||||
|       <div class="logo-text">{{ appStore.getTitle }}</div> | ||||
|     </div> | ||||
|     <LoginBanner /> | ||||
|     <div class="content"> | ||||
| @@ -18,8 +18,12 @@ | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import Footer from '@/components/footer/index.vue'; | ||||
|   import { useAppStore } from '@/store'; | ||||
|   import getFile from '@/utils/file'; | ||||
|   import LoginBanner from './components/banner.vue'; | ||||
|   import LoginForm from './components/login-form.vue'; | ||||
|  | ||||
|   const appStore = useAppStore(); | ||||
| </script> | ||||
|  | ||||
| <style lang="less" scoped> | ||||
|   | ||||
| @@ -170,6 +170,7 @@ | ||||
|   } from '@/api/system/config'; | ||||
|   import { upload } from '@/api/common'; | ||||
|   import getFile from '@/utils/file'; | ||||
|   import { useAppStore } from '@/store'; | ||||
|  | ||||
|   const { proxy } = getCurrentInstance() as any; | ||||
|   const dataList = ref<DataRecord[]>([]); | ||||
| @@ -180,6 +181,7 @@ | ||||
|   const siteCopyright = ref<DataRecord>(); | ||||
|   const siteLogo = ref<DataRecord>(); | ||||
|   const siteFavicon = ref<DataRecord>(); | ||||
|   const appStore = useAppStore(); | ||||
|  | ||||
|   const data = reactive({ | ||||
|     queryParams: { | ||||
| @@ -251,7 +253,7 @@ | ||||
|           } | ||||
|         ); | ||||
|         save(optionList).then((res) => { | ||||
|           // siteConfigStore().save(data.form); | ||||
|           appStore.save(form.value); | ||||
|           handleCancel(); | ||||
|           proxy.$message.success(res.msg); | ||||
|         }); | ||||
| @@ -360,6 +362,7 @@ | ||||
|     await resetValue(queryParams.value); | ||||
|     proxy.$message.success('恢复成功'); | ||||
|     await getConfig(); | ||||
|     appStore.save(form.value); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -37,6 +37,7 @@ import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
|  | ||||
| import cn.dev33.satoken.annotation.SaIgnore; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.util.ClassUtil; | ||||
| 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.system.model.query.DeptQuery; | ||||
| 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.vo.RoleVO; | ||||
| import top.charles7c.cnadmin.system.service.*; | ||||
| @@ -78,6 +80,7 @@ public class CommonController { | ||||
|     private final DictItemService dictItemService; | ||||
|     private final ProjectProperties projectProperties; | ||||
|     private final LocalStorageProperties localStorageProperties; | ||||
|     private final OptionService optionService; | ||||
|  | ||||
|     @Operation(summary = "上传文件", description = "上传文件") | ||||
|     @PostMapping("/file") | ||||
| @@ -123,6 +126,14 @@ public class CommonController { | ||||
|         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 | ||||
|      *            枚举类型 | ||||
|      * @return 枚举字典 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 GitHub
						GitHub