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 * @returns object
*/ */
export function transferHelpDoc(data: Record<string, any>) { export function transferStaticFile(data: Record<string, any>) {
return request({ return request({
url: `/helpDoc`, url: `/transferStaticFile`,
method: 'post', method: 'post',
data, data,
}); });

View File

@@ -23,7 +23,8 @@ import { getServerTime } from '@/api';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n(); import { parseUrlPath } from '@/plugins/file-static-url';
const { t, currentLocale } = useI18n();
const routerStore = useRouterStore(); const routerStore = useRouterStore();
const tabsStore = useTabsStore(); const tabsStore = useTabsStore();
const appStore = useAppStore(); const appStore = useAppStore();
@@ -121,6 +122,35 @@ function fnComponentSetName(component: any, to: any) {
// 清空导航栏标签 // 清空导航栏标签
tabsStore.clear(); 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) { function fnClickHelpDoc(language?: string) {
const routeData = router.resolve({ name: 'HelpDoc' }); const routeData = router.resolve({ name: 'HelpDoc' });
@@ -220,7 +250,7 @@ document.addEventListener('visibilitychange', function () {
> >
<img <img
class="logo-icon" class="logo-icon"
:src="appStore.getLOGOIcon" :src="logoUrl"
:alt="appStore.appName" :alt="appStore.appName"
:title="appStore.appName" :title="appStore.appName"
/> />
@@ -231,7 +261,7 @@ document.addEventListener('visibilitychange', function () {
<template v-if="appStore.logoType === 'brand'"> <template v-if="appStore.logoType === 'brand'">
<img <img
class="logo-brand" class="logo-brand"
:src="appStore.getLOGOBrand" :src="logoUrl"
:alt="appStore.appName" :alt="appStore.appName"
:title="appStore.appName" :title="appStore.appName"
/> />
@@ -300,7 +330,7 @@ document.addEventListener('visibilitychange', function () {
type="link" type="link"
size="small" size="small"
@click="fnClickHelpDoc()" @click="fnClickHelpDoc()"
v-if="appStore.getHelpDoc !== '#'" v-if="helpDocUrl !== '#'"
> >
{{ t('loayouts.basic.helpDoc') }} {{ t('loayouts.basic.helpDoc') }}
</a-button> </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 { getSysConf } from '@/api';
import defaultLOGOIcon from '@/assets/logo_icon.png'; import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
import defaultLOGOBrand from '@/assets/logo_brand.png';
import { RESULT_CODE_SUCCESS } from '@/constants/result-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 { defineStore } from 'pinia';
import { sessionGet } from '@/utils/cache-session-utils';
/**应用参数类型 */ /**应用参数类型 */
type AppStore = { type AppStore = {
@@ -14,6 +13,7 @@ type AppStore = {
appCode: string; appCode: string;
/**应用版本 */ /**应用版本 */
appVersion: string; appVersion: string;
/**应用版权声明 */ /**应用版权声明 */
copyright: string; copyright: string;
/**LOGO显示类型 */ /**LOGO显示类型 */
@@ -29,6 +29,10 @@ type AppStore = {
helpDoc: string; helpDoc: string;
/**官方网址 */ /**官方网址 */
officialUrl: string; officialUrl: string;
/**国际化切换 */
i18nOpen: boolean;
/**国际化默认语言 */
i18nDefault: string;
}; };
const useAppStore = defineStore('app', { const useAppStore = defineStore('app', {
@@ -36,6 +40,7 @@ const useAppStore = defineStore('app', {
appName: import.meta.env.VITE_APP_NAME, appName: import.meta.env.VITE_APP_NAME,
appCode: import.meta.env.VITE_APP_CODE, appCode: import.meta.env.VITE_APP_CODE,
appVersion: import.meta.env.VITE_APP_VERSION, appVersion: import.meta.env.VITE_APP_VERSION,
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`, copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
logoType: 'icon', logoType: 'icon',
filePathIcon: '', filePathIcon: '',
@@ -44,85 +49,10 @@ const useAppStore = defineStore('app', {
loginBackground: '', loginBackground: '',
helpDoc: '', helpDoc: '',
officialUrl: '', officialUrl: '',
i18nOpen: true,
i18nDefault: 'en_US',
}), }),
getters: { 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}`;
},
},
actions: { actions: {
/**设置网页标题 */ /**设置网页标题 */
setTitle(title?: string) { setTitle(title?: string) {
@@ -136,16 +66,6 @@ const useAppStore = defineStore('app', {
setCopyright(text: string) { setCopyright(text: string) {
this.copyright = text; 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() { async fnSysConf() {
const res = await getSysConf(); const res = await getSysConf();
@@ -156,16 +76,25 @@ const useAppStore = defineStore('app', {
this.filePathIcon = res.data.filePathIcon; this.filePathIcon = res.data.filePathIcon;
this.filePathBrand = res.data.filePathBrand; this.filePathBrand = res.data.filePathBrand;
// 修改html内容-小图当作favicon.ico // 修改html内容-小图当作favicon.ico
if (this.logoType) { if (this.logoType) {
const iconDom = document.querySelector("link[rel~='icon']"); const iconDom = document.querySelector("link[rel~='icon']");
if (iconDom) { 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.registerUser = res.data.registerUser === 'true';
this.loginBackground = res.data.loginBackground; this.loginBackground = res.data.loginBackground;
this.helpDoc = res.data.helpDoc; this.helpDoc = res.data.helpDoc;
this.officialUrl = res.data.officialUrl; this.officialUrl = res.data.officialUrl;
this.i18nOpen = res.data.i18nOpen === 'true';
this.i18nDefault = res.data.i18nDefault;
} }
return res; return res;
}, },

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message } from 'ant-design-vue/lib'; 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 useUserStore from '@/store/modules/user';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { getCaptchaImage } from '@/api/login'; import { getCaptchaImage } from '@/api/login';
@@ -9,7 +9,8 @@ import useI18n from '@/hooks/useI18n';
import { toRaw } from 'vue'; import { toRaw } from 'vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { sessionGet } from '@/utils/cache-session-utils'; 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 appStore = useAppStore();
const router = useRouter(); const router = useRouter();
const route = useRoute(); 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 calcBG = computed(() => {
const bgURL = appStore.getLoginBackground; const bgURL = parseUrlPath(appStore.loginBackground);
if (bgURL && bgURL !== '#') { if (bgURL && bgURL !== '#') {
return { return {
backgroundImage: `url(${bgURL})`, backgroundImage: `url(${bgURL})`,
@@ -137,19 +154,11 @@ function fnChangeLocale(e: any) {
<header class="header"> <header class="header">
<template v-if="appStore.logoType === 'icon'"> <template v-if="appStore.logoType === 'icon'">
<img <img :src="logoUrl" class="logo-icon" :alt="appStore.appName" />
:src="appStore.getLOGOIcon"
class="logo-icon"
:alt="appStore.appName"
/>
<span class="title">{{ appStore.appName }}</span> <span class="title">{{ appStore.appName }}</span>
</template> </template>
<template v-if="appStore.logoType === 'brand'"> <template v-if="appStore.logoType === 'brand'">
<img <img :src="logoUrl" class="logo-brand" :alt="appStore.appName" />
:src="appStore.getLOGOBrand"
class="logo-brand"
:alt="appStore.appName"
/>
</template> </template>
</header> </header>

View File

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