feat: 图标支持根据语言上传对应图标

This commit is contained in:
TsMask
2023-12-04 16:54:49 +08:00
parent c16a1675f0
commit 96d6cfcfa2
7 changed files with 188 additions and 147 deletions

View File

@@ -79,12 +79,12 @@ export function getSysConf() {
}
/**
* 转存帮助文档
* 转存上传文件到静态资源
* @returns object
*/
export function transferHelpDoc(data: Record<string, any>) {
export function transferStaticFile(data: Record<string, any>) {
return request({
url: `/helpDoc`,
url: `/transferStaticFile`,
method: 'post',
data,
});

View File

@@ -23,7 +23,8 @@ import { getServerTime } from '@/api';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { onMounted } from 'vue';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
import { parseUrlPath } from '@/plugins/file-static-url';
const { t, currentLocale } = useI18n();
const routerStore = useRouterStore();
const tabsStore = useTabsStore();
const appStore = useAppStore();
@@ -121,6 +122,35 @@ function fnComponentSetName(component: any, to: any) {
// 清空导航栏标签
tabsStore.clear();
// LOGO地址
const logoUrl = computed(() => {
let url =
appStore.logoType === 'brand'
? parseUrlPath(appStore.filePathBrand)
: parseUrlPath(appStore.filePathIcon);
if (url.indexOf('{language}') === -1) {
return url;
}
// 语言参数替换
const local = currentLocale.value;
const lang = local.split('_')[0];
return url.replace('{language}', lang);
});
// 系统使用手册地址
const helpDocUrl = computed(() => {
let url = parseUrlPath(appStore.helpDoc);
if (url.indexOf('{language}') === -1) {
return url;
}
// 语言参数替换
const local = currentLocale.value;
const lang = local.split('_')[0];
return url.replace('{language}', lang);
});
/**系统使用手册跳转 */
function fnClickHelpDoc(language?: string) {
const routeData = router.resolve({ name: 'HelpDoc' });
@@ -220,7 +250,7 @@ document.addEventListener('visibilitychange', function () {
>
<img
class="logo-icon"
:src="appStore.getLOGOIcon"
:src="logoUrl"
:alt="appStore.appName"
:title="appStore.appName"
/>
@@ -231,7 +261,7 @@ document.addEventListener('visibilitychange', function () {
<template v-if="appStore.logoType === 'brand'">
<img
class="logo-brand"
:src="appStore.getLOGOBrand"
:src="logoUrl"
:alt="appStore.appName"
:title="appStore.appName"
/>
@@ -300,7 +330,7 @@ document.addEventListener('visibilitychange', function () {
type="link"
size="small"
@click="fnClickHelpDoc()"
v-if="appStore.getHelpDoc !== '#'"
v-if="helpDocUrl !== '#'"
>
{{ t('loayouts.basic.helpDoc') }}
</a-button>

View File

@@ -0,0 +1,21 @@
import { sessionGet } from '@/utils/cache-session-utils';
import { validHttp } from '@/utils/regular-utils';
/**
* 解析资源文件绝对路径 http
* @param path 服务端资源文件
* @returns
*/
export function parseUrlPath(path: string) {
if (!path || path === '#') {
return '#';
}
if (validHttp(path)) {
return path;
}
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}${path}`;
}

View File

@@ -1,10 +1,9 @@
import { getSysConf } from '@/api';
import defaultLOGOIcon from '@/assets/logo_icon.png';
import defaultLOGOBrand from '@/assets/logo_brand.png';
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { validHttp } from '@/utils/regular-utils';
import { parseUrlPath } from '@/plugins/file-static-url';
import { localGet } from '@/utils/cache-local-utils';
import { defineStore } from 'pinia';
import { sessionGet } from '@/utils/cache-session-utils';
/**应用参数类型 */
type AppStore = {
@@ -14,6 +13,7 @@ type AppStore = {
appCode: string;
/**应用版本 */
appVersion: string;
/**应用版权声明 */
copyright: string;
/**LOGO显示类型 */
@@ -29,6 +29,10 @@ type AppStore = {
helpDoc: string;
/**官方网址 */
officialUrl: string;
/**国际化切换 */
i18nOpen: boolean;
/**国际化默认语言 */
i18nDefault: string;
};
const useAppStore = defineStore('app', {
@@ -36,6 +40,7 @@ const useAppStore = defineStore('app', {
appName: import.meta.env.VITE_APP_NAME,
appCode: import.meta.env.VITE_APP_CODE,
appVersion: import.meta.env.VITE_APP_VERSION,
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
logoType: 'icon',
filePathIcon: '',
@@ -44,85 +49,10 @@ const useAppStore = defineStore('app', {
loginBackground: '',
helpDoc: '',
officialUrl: '',
i18nOpen: true,
i18nDefault: 'en_US',
}),
getters: {
/**
* 获取正确LOGO地址-icon
* @param state 内部属性不用传入
* @returns LOGO地址url
*/
getLOGOIcon(state) {
const path = state.filePathIcon;
if (!path || path === '#') {
return defaultLOGOIcon;
}
if (validHttp(path)) {
return path;
}
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}${path}`;
},
/**
* 获取正确LOGO地址-brand
* @param state 内部属性不用传入
* @returns LOGO地址url
*/
getLOGOBrand(state) {
const path = state.filePathBrand;
if (!path || path === '#') {
return defaultLOGOBrand;
}
if (validHttp(path)) {
return path;
}
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}${path}`;
},
/**
* 获取正确登录背景地址
* @param state 内部属性不用传入
* @returns 背景地址url
*/
getLoginBackground(state) {
const path = state.loginBackground;
if (!path || path === '#') {
return '#';
}
if (validHttp(path)) {
return path;
}
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}${path}`;
},
/**
* 获取正确使用手册地址
* @param state 内部属性不用传入
* @returns 手册地址url
*/
getHelpDoc(state) {
const path = state.helpDoc;
if (!path || path === '#') {
return '#';
}
if (validHttp(path)) {
return path;
}
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}${path}`;
},
},
getters: {},
actions: {
/**设置网页标题 */
setTitle(title?: string) {
@@ -136,16 +66,6 @@ const useAppStore = defineStore('app', {
setCopyright(text: string) {
this.copyright = text;
},
/**设置LOGO */
setLOGO(type: 'brand' | 'icon', filePath: string) {
this.logoType = type;
if (type === 'brand') {
this.filePathBrand = filePath;
}
if (type === 'icon') {
this.filePathIcon = filePath;
}
},
// 获取系统配置信息
async fnSysConf() {
const res = await getSysConf();
@@ -156,16 +76,25 @@ const useAppStore = defineStore('app', {
this.filePathIcon = res.data.filePathIcon;
this.filePathBrand = res.data.filePathBrand;
// 修改html内容-小图当作favicon.ico
if (this.logoType) {
if (this.logoType) {
const iconDom = document.querySelector("link[rel~='icon']");
if (iconDom) {
iconDom.setAttribute('href', this.getLOGOIcon);
let url = parseUrlPath(this.filePathIcon);
// 语言参数替换
if (url.indexOf('{language}') !== -1) {
const local = localGet(CACHE_LOCAL_I18N) || 'en_US';
const lang = local.split('_')[0];
url = url.replace('{language}', lang);
}
iconDom.setAttribute('href', url);
}
}
this.registerUser = res.data.registerUser === 'true';
this.loginBackground = res.data.loginBackground;
this.helpDoc = res.data.helpDoc;
this.officialUrl = res.data.officialUrl;
this.i18nOpen = res.data.i18nOpen === 'true';
this.i18nDefault = res.data.i18nDefault;
}
return res;
},

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue/lib';
import { reactive, onMounted, computed } from 'vue';
import { reactive, onMounted, computed, ref } from 'vue';
import useUserStore from '@/store/modules/user';
import useAppStore from '@/store/modules/app';
import { getCaptchaImage } from '@/api/login';
@@ -9,7 +9,8 @@ import useI18n from '@/hooks/useI18n';
import { toRaw } from 'vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { sessionGet } from '@/utils/cache-session-utils';
const { t, changeLocale, optionsLocale } = useI18n();
import { parseUrlPath } from '@/plugins/file-static-url';
const { t, changeLocale, optionsLocale, currentLocale } = useI18n();
const appStore = useAppStore();
const router = useRouter();
const route = useRoute();
@@ -87,9 +88,25 @@ function fnGetCaptcha() {
});
}
// LOGO地址
const logoUrl = computed(() => {
let url =
appStore.logoType === 'brand'
? parseUrlPath(appStore.filePathBrand)
: parseUrlPath(appStore.filePathIcon);
if (url.indexOf('{language}') === -1) {
return url;
}
// 语言参数替换
const local = currentLocale.value;
const lang = local.split('_')[0];
return url.replace('{language}', lang);
});
// 判断是否有背景地址
const calcBG = computed(() => {
const bgURL = appStore.getLoginBackground;
const bgURL = parseUrlPath(appStore.loginBackground);
if (bgURL && bgURL !== '#') {
return {
backgroundImage: `url(${bgURL})`,
@@ -137,19 +154,11 @@ function fnChangeLocale(e: any) {
<header class="header">
<template v-if="appStore.logoType === 'icon'">
<img
:src="appStore.getLOGOIcon"
class="logo-icon"
:alt="appStore.appName"
/>
<img :src="logoUrl" class="logo-icon" :alt="appStore.appName" />
<span class="title">{{ appStore.appName }}</span>
</template>
<template v-if="appStore.logoType === 'brand'">
<img
:src="appStore.getLOGOBrand"
class="logo-brand"
:alt="appStore.appName"
/>
<img :src="logoUrl" class="logo-brand" :alt="appStore.appName" />
</template>
</header>

View File

@@ -1,13 +1,15 @@
<script lang="ts" setup>
import { Modal, message } from 'ant-design-vue/lib';
import { onMounted, reactive } from 'vue';
import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { transferHelpDoc } from '@/api/index';
import { transferStaticFile } from '@/api/index';
import { uploadFileChunk } from '@/api/tool/file';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { useRouter } from 'vue-router';
const appStore = useAppStore();
const router = useRouter();
const { t, currentLocale, optionsLocale } = useI18n();
@@ -79,9 +81,10 @@ function fnSave() {
// 发送请求
const hide = message.loading(t('common.loading'), 0);
state.loading = true;
transferHelpDoc({
transferStaticFile({
language: state.language,
uploadPath: state.filePath,
staticPath: appStore.helpDoc,
}).then(res => {
state.loading = false;
hide();

View File

@@ -3,20 +3,22 @@ import { Modal, message } from 'ant-design-vue/lib';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import IconFont from '@/components/IconFont/index.vue';
import { onMounted, reactive } from 'vue';
import { onMounted, reactive, watch, computed } from 'vue';
import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { uploadFile } from '@/api/tool/file';
import { changeValue } from '@/api/system/config';
import { computed } from 'vue';
import { sessionGet } from '@/utils/cache-session-utils';
import { transferStaticFile } from '@/api';
import { parseUrlPath } from '@/plugins/file-static-url';
const appStore = useAppStore();
const { t } = useI18n();
const { t, currentLocale, optionsLocale } = useI18n();
type StateType = {
edite: boolean;
loading: boolean;
language: string;
filePath: string; // 是否上传文件
flag: string; // 是否变更标记
type: 'brand' | 'icon';
@@ -27,6 +29,7 @@ type StateType = {
let state: StateType = reactive({
edite: false,
loading: false,
language: '',
filePath: '',
flag: '',
type: 'icon',
@@ -39,11 +42,14 @@ function fnBeforeUpload(file: FileType) {
if (state.loading) return false;
const isJpgOrPng = ['image/jpeg', 'image/png'].includes(file.type);
if (!isJpgOrPng) {
message.error('只支持上传图片格式jpg、png', 3);
message.error(
t('views.system.setting.uploadFormat', { format: 'jpg、png' }),
3
);
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片文件大小必须小于 2MB', 3);
message.error(t('views.system.setting.uploadSize', { size: 2 }), 3);
}
return isJpgOrPng && isLt2M;
}
@@ -92,8 +98,8 @@ function fnEdit(v: boolean) {
filePath: '',
flag: `${appStore.logoType}/`,
type: appStore.logoType,
icon: appStore.getLOGOIcon,
brand: appStore.getLOGOBrand,
icon: getLogoURL('icon'),
brand: getLogoURL('brand'),
});
}
}
@@ -107,12 +113,16 @@ function fnSave() {
const reqArr = [];
// 改变LOGO地址
if (state.filePath) {
let changeFilePath = 'sys.logo.filePathIcon';
let changeFilePath = appStore.filePathIcon;
if (state.type === 'brand') {
changeFilePath = 'sys.logo.filePathBrand';
changeFilePath = appStore.filePathBrand;
}
reqArr.push(
changeValue({ key: changeFilePath, value: state.filePath })
transferStaticFile({
language: state.language,
uploadPath: state.filePath,
staticPath: changeFilePath,
})
);
}
// 判断类型是否改变
@@ -128,9 +138,6 @@ function fnSave() {
hide();
if (resArr[0].code === RESULT_CODE_SUCCESS) {
message.success(t('views.system.setting.saveSuccess'), 3);
if (state.filePath) {
appStore.setLOGO(state.type, state.filePath);
}
if (state.type !== appStore.logoType) {
appStore.logoType = state.type;
}
@@ -152,13 +159,38 @@ const changeStatus = computed(() => {
return true;
});
// LOGO地址
function getLogoURL(type: 'brand' | 'icon') {
let url =
type === 'brand'
? parseUrlPath(appStore.filePathBrand)
: parseUrlPath(appStore.filePathIcon);
if (url.indexOf('{language}') === -1) {
return url;
}
// 语言参数替换
const local = state.language;
const lang = local.split('_')[0];
return url.replace('{language}', lang || 'en');
}
// 监听语言切换
watch(
() => state.language,
() => {
state.icon = getLogoURL('icon');
state.brand = getLogoURL('brand');
}
);
onMounted(() => {
Object.assign(state, {
language: currentLocale.value,
filePath: '',
flag: `${appStore.logoType}/`,
type: appStore.logoType,
icon: appStore.getLOGOIcon,
brand: appStore.getLOGOBrand,
icon: getLogoURL('icon'),
brand: getLogoURL('brand'),
});
});
</script>
@@ -166,21 +198,39 @@ onMounted(() => {
<template>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24" style="margin-bottom: 30px">
<div class="header">
<div class="header-brand" v-show="state.type === 'brand'">
<img :width="174" :height="48" :src="state.brand" />
</div>
<div class="header-icon" v-show="state.type === 'icon'">
<img :src="state.icon" />
<h1 :title="appStore.appName">
{{ appStore.appName }}
</h1>
</div>
<div class="header-menu">
<IconFont type="icon-pcduan" style="margin-right: 10px"></IconFont>
{{ t('router.index') }}
</div>
</div>
<a-form layout="vertical">
<a-form-item style="margin-bottom: 12px">
<div class="header">
<div class="header-brand" v-show="state.type === 'brand'">
<img :width="174" :height="48" :src="state.brand" />
</div>
<div class="header-icon" v-show="state.type === 'icon'">
<img :src="state.icon" />
<h1 :title="appStore.appName">
{{ appStore.appName }}
</h1>
</div>
<div class="header-menu">
<IconFont
type="icon-pcduan"
style="margin-right: 10px"
></IconFont>
{{ t('router.index') }}
</div>
</div>
</a-form-item>
<a-form-item>
<a-select v-model:value="state.language" style="width: 100px">
<a-select-option
v-for="opt in optionsLocale"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
<a-form layout="vertical" v-if="state.edite">
<a-form-item>
@@ -257,7 +307,6 @@ onMounted(() => {
height: 48px;
padding-left: 16px;
background-color: #001529;
margin-bottom: 24px;
&-brand {
width: 174px;