修复类型字典问题

This commit is contained in:
微微一笑
2025-08-20 13:47:12 +08:00
parent d96b28a0b9
commit 1cfb5581c8
10 changed files with 104 additions and 48 deletions

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 星汉研创科技 VITE_APP_TITLE = 云平台管理系统
# 开发环境配置 # 开发环境配置
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'

View File

@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VITE_APP_TITLE = 星汉研创科技 VITE_APP_TITLE = 云平台管理系统
# 生产环境配置 晶全1 # 生产环境配置 晶全1
VITE_APP_ENV = 'https://fuyuanshen.com/backend' VITE_APP_ENV = 'https://fuyuanshen.com/backend'

View File

@ -8,8 +8,10 @@
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import { handleThemeStyle } from '@/utils/theme'; import { handleThemeStyle } from '@/utils/theme';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useUserStore } from '@/store/modules/user';
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore();
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
@ -17,4 +19,19 @@ onMounted(() => {
handleThemeStyle(useSettingsStore().theme); handleThemeStyle(useSettingsStore().theme);
}); });
}); });
// 监听租户名称变化,动态更新页面标题
watch(
() => userStore.tenantName,
(value) => {
if (value) {
document.title = userStore.tenantName;
} else {
document.title = import.meta.env.VITE_APP_TITLE;
}
},
{
immediate: true,
}
);
</script> </script>

View File

@ -16,6 +16,7 @@ export type RegisterForm = {
*/ */
export interface LoginData { export interface LoginData {
tenantId?: string; tenantId?: string;
tenantName?: string;
username?: string; username?: string;
password?: string; password?: string;
rememberMe?: boolean; rememberMe?: boolean;

View File

@ -130,7 +130,7 @@ $--color-warning: #e6a23c;
$--color-danger: #f56c6c; $--color-danger: #f56c6c;
$--color-info: #909399; $--color-info: #909399;
$base-sidebar-width: 200px; $base-sidebar-width: 280px;
// the :export directive is the magic sauce for webpack // the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass

View File

@ -5,14 +5,7 @@
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
> >
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in"> <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }}
</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }"> <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
{{ title }} {{ title }}
</h1> </h1>
@ -23,8 +16,8 @@
<script setup lang="ts"> <script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss'; import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import { useUserStore } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
defineProps({ defineProps({
@ -34,8 +27,14 @@ defineProps({
} }
}); });
const title = ref('星汉研创科技'); const userStore = useUserStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
// 使用计算属性动态获取标题
const title = computed(() => {
return userStore.tenantName || import.meta.env.VITE_APP_TITLE || '云平台管理系统';
});
const sideTheme = computed(() => settingsStore.sideTheme); const sideTheme = computed(() => settingsStore.sideTheme);
</script> </script>
@ -62,33 +61,25 @@ const sideTheme = computed(() => settingsStore.sideTheme);
height: 100%; height: 100%;
width: 100%; width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title { & .sidebar-title {
display: inline-block; display: block;
margin: 0; margin: 0;
padding: 0 15px; // 增加左右内边距
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
line-height: 50px; line-height: 50px;
font-size: 16px; // 匹配设计UI的字体大小 font-size: 16px; // 恢复正常字体大小
font-family: font-family:
Avenir, Avenir,
Helvetica Neue, Helvetica Neue,
Arial, Arial,
Helvetica, Helvetica,
sans-serif; sans-serif;
vertical-align: middle; width: 100%; // 使用全宽
} white-space: nowrap;
} overflow: hidden;
text-overflow: ellipsis;
&.collapse { text-align: center; // 居中显示
.sidebar-logo {
margin-right: 0px;
} }
} }
} }

View File

@ -12,6 +12,8 @@ export const useUserStore = defineStore('user', () => {
const nickname = ref(''); const nickname = ref('');
const userId = ref<string | number>(''); const userId = ref<string | number>('');
const tenantId = ref<string>(''); const tenantId = ref<string>('');
// 从 localStorage 恢复租户名称
const tenantName = ref<string>(localStorage.getItem('currentTenantName') || '');
const avatar = ref(''); const avatar = ref('');
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限 const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
const permissions = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限 const permissions = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
@ -27,6 +29,10 @@ export const useUserStore = defineStore('user', () => {
const data = res.data; const data = res.data;
setToken(data.access_token); setToken(data.access_token);
token.value = data.access_token; token.value = data.access_token;
// 设置租户名称
if (userInfo.tenantName) {
tenantName.value = userInfo.tenantName;
}
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject(err); return Promise.reject(err);
@ -61,9 +67,12 @@ export const useUserStore = defineStore('user', () => {
const logout = async (): Promise<void> => { const logout = async (): Promise<void> => {
await logoutApi(); await logoutApi();
token.value = ''; token.value = '';
tenantName.value = '';
roles.value = []; roles.value = [];
permissions.value = []; permissions.value = [];
removeToken(); removeToken();
// 清除保存的租户名称
localStorage.removeItem('currentTenantName');
}; };
const setAvatar = (value: string) => { const setAvatar = (value: string) => {
@ -73,6 +82,7 @@ export const useUserStore = defineStore('user', () => {
return { return {
userId, userId,
tenantId, tenantId,
tenantName,
token, token,
nickname, nickname,
avatar, avatar,

View File

@ -1,12 +1,18 @@
import defaultSettings from '@/settings'; import defaultSettings from '@/settings';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import { useUserStore } from '@/store/modules/user';
/** /**
* 动态修改标题 * 动态修改标题
*/ */
export const useDynamicTitle = () => { export const useDynamicTitle = () => {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
if (settingsStore.dynamicTitle) { const userStore = useUserStore();
// 优先使用租户名称
if (userStore.tenantName) {
document.title = userStore.tenantName;
} else if (settingsStore.dynamicTitle) {
document.title = settingsStore.title + ' - ' + import.meta.env.VITE_APP_TITLE; document.title = settingsStore.title + ' - ' + import.meta.env.VITE_APP_TITLE;
} else { } else {
document.title = defaultSettings.title as string; document.title = defaultSettings.title as string;

View File

@ -42,7 +42,7 @@
<el-table-column label="型号名称" align="center" prop="typeName" /> <el-table-column label="型号名称" align="center" prop="typeName" />
<el-table-column label="类型code" align="center" prop="modelDictionary"> <el-table-column label="类型code" align="center" prop="modelDictionary">
<template #default="scope"> <template #default="scope">
{{ modelDictionaryOptions.find(item => item.dictValue == scope.row.modelDictionary)?.dictLabel }} {{ modelDictionaryOptions.find(item => item.dictValue === String(scope.row.modelDictionary))?.dictLabel }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="是否支持蓝牙" align="center" prop="isSupportBle"> <el-table-column label="是否支持蓝牙" align="center" prop="isSupportBle">
@ -119,11 +119,11 @@
<el-col :span="24"> <el-col :span="24">
<el-form-item label="定位方式" prop="locateMode"> <el-form-item label="定位方式" prop="locateMode">
<el-select v-model="form.locateMode" placeholder="请选择"> <el-select v-model="form.locateMode" placeholder="请选择">
<el-option label="无" value="0" /> <el-option label="无" :value="0" />
<el-option label="GPS" value="1" /> <el-option label="GPS" :value="1" />
<el-option label="基站" value="2" /> <el-option label="基站" :value="2" />
<el-option label="wifi" value="3" /> <el-option label="wifi" :value="3" />
<el-option label="北斗" value="4" /> <el-option label="北斗" :value="4" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -132,8 +132,8 @@
<el-col :span="24"> <el-col :span="24">
<el-form-item label="通讯方式" prop="communicationMode"> <el-form-item label="通讯方式" prop="communicationMode">
<el-select v-model="form.communicationMode" placeholder="请选择"> <el-select v-model="form.communicationMode" placeholder="请选择">
<el-option label="4G" value="0" /> <el-option label="4G" :value="0" />
<el-option label="蓝牙" value="1" /> <el-option label="蓝牙" :value="1" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -160,9 +160,9 @@ interface deviceTypeVO {
id: string | number; id: string | number;
typeName: string; typeName: string;
isSupportBle: boolean; isSupportBle: boolean;
locateMode: string; locateMode: number;
modelDictionary: string; modelDictionary: string;
communicationMode: string; communicationMode: number;
createTime: string; createTime: string;
createByName: string; createByName: string;
} }
@ -188,9 +188,9 @@ const dialog = reactive<DialogOption>({
const initFormData: DeviceTypeForm = { const initFormData: DeviceTypeForm = {
typeName: '', typeName: '',
isSupportBle: false, isSupportBle: false,
locateMode: '', locateMode: 0,
modelDictionary: '', modelDictionary: '',
communicationMode: '', communicationMode: 0,
id: '', id: '',
createTime: '', createTime: '',
createByName: '', createByName: '',
@ -300,9 +300,10 @@ const handleUpdate = async (row?: DeviceTypeForm) => {
try { try {
const deviceData = row || ids.value[0]; const deviceData = row || ids.value[0];
Object.assign(form.value, deviceData); Object.assign(form.value, deviceData);
if (form.value.modelDictionary) { // 确保数据类型正确
form.value.modelDictionary = String(form.value.modelDictionary); form.value.modelDictionary = String(form.value.modelDictionary || '');
} form.value.locateMode = Number(form.value.locateMode) || 0;
form.value.communicationMode = Number(form.value.communicationMode) || 0;
} catch (error) { } catch (error) {
dialog.visible = false; dialog.visible = false;
} }
@ -316,7 +317,9 @@ const submitForm = () => {
try { try {
const payload = { const payload = {
...form.value, ...form.value,
modelDictionary: Number(form.value.modelDictionary) modelDictionary: form.value.modelDictionary,
locateMode: Number(form.value.locateMode),
communicationMode: Number(form.value.communicationMode)
}; };
form.value.id ? await api.updateDeviceType(payload) : await api.addDeviceType(payload); form.value.id ? await api.updateDeviceType(payload) : await api.addDeviceType(payload);
proxy?.$modal.msgSuccess('操作成功'); proxy?.$modal.msgSuccess('操作成功');

View File

@ -6,7 +6,7 @@
</div> </div>
<el-form-item v-if="tenantEnabled" prop="tenantId"> <el-form-item v-if="tenantEnabled" prop="tenantId">
<el-select v-model="loginForm.tenantId" filterable :placeholder="proxy.$t('login.selectPlaceholder')" <el-select v-model="loginForm.tenantId" filterable :placeholder="proxy.$t('login.selectPlaceholder')"
style="width: 100%"> style="width: 100%" @change="onTenantChange">
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName"
:value="item.tenantId"></el-option> :value="item.tenantId"></el-option>
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
@ -56,7 +56,7 @@ import { HttpStatus } from '@/enums/RespEnum';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE; const title = ref(import.meta.env.VITE_APP_TITLE);
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter(); const router = useRouter();
@ -102,19 +102,30 @@ const handleLogin = () => {
loginRef.value?.validate(async (valid: boolean, fields: any) => { loginRef.value?.validate(async (valid: boolean, fields: any) => {
if (valid) { if (valid) {
loading.value = true; loading.value = true;
// 获取选中租户的名称并设置到登录表单中
const tenantName = tenantList.value.find(item => item.tenantId === loginForm.value.tenantId)?.companyName;
loginForm.value.tenantName = tenantName;
// 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码 // 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
if (loginForm.value.rememberMe) { if (loginForm.value.rememberMe) {
localStorage.setItem('tenantId', String(loginForm.value.tenantId)); localStorage.setItem('tenantId', String(loginForm.value.tenantId));
localStorage.setItem('tenantName', String(tenantName || ''));
localStorage.setItem('username', String(loginForm.value.username)); localStorage.setItem('username', String(loginForm.value.username));
localStorage.setItem('password', String(loginForm.value.password)); localStorage.setItem('password', String(loginForm.value.password));
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe)); localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
} else { } else {
// 否则移除 // 否则移除
localStorage.removeItem('tenantId'); localStorage.removeItem('tenantId');
localStorage.removeItem('tenantName');
localStorage.removeItem('username'); localStorage.removeItem('username');
localStorage.removeItem('password'); localStorage.removeItem('password');
localStorage.removeItem('rememberMe'); localStorage.removeItem('rememberMe');
} }
// 无论是否记住密码,都保存租户名称用于页面刷新时恢复
if (tenantName) {
localStorage.setItem('currentTenantName', tenantName);
}
// 调用action的登录方法 // 调用action的登录方法
const [err] = await to(userStore.login(loginForm.value)); const [err] = await to(userStore.login(loginForm.value));
if (!err) { if (!err) {
@ -170,6 +181,8 @@ const initTenantList = async () => {
tenantList.value = data.voList; tenantList.value = data.voList;
if (tenantList.value != null && tenantList.value.length !== 0) { if (tenantList.value != null && tenantList.value.length !== 0) {
loginForm.value.tenantId = tenantList.value[0].tenantId; loginForm.value.tenantId = tenantList.value[0].tenantId;
// 初始化标题为第一个租户的名称
onTenantChange(tenantList.value[0].tenantId);
} }
} }
}; };
@ -189,6 +202,21 @@ const doSocialLogin = (type: string) => {
}); });
}; };
/**
* 租户变化时更新标题
*/
const onTenantChange = (tenantId: string) => {
const selectedTenant = tenantList.value.find(item => item.tenantId === tenantId);
if (selectedTenant) {
title.value = selectedTenant.companyName;
// 同时更新页面标题
document.title = selectedTenant.companyName;
} else {
title.value = import.meta.env.VITE_APP_TITLE;
document.title = import.meta.env.VITE_APP_TITLE;
}
};
onMounted(() => { onMounted(() => {
getCode(); getCode();
initTenantList(); initTenantList();