diff --git a/src/App.vue b/src/App.vue index 6bbca27..cb34b25 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,6 +2,7 @@ import { getToken } from '@/utils/auth' import { initializeLocale, translateLiteral } from '@/locales' import useUserStore from '@/store/modules/user' +import { initializeTerminalType } from '@/utils/terminal' let wrapped = false @@ -48,6 +49,7 @@ function wrapUniTextApi() { export default { onLaunch: function () { initializeLocale() + initializeTerminalType() wrapUniTextApi() if (getToken()) { useUserStore().getInfo().catch(() => {}) diff --git a/src/api/login.js b/src/api/login.js index 8302fcd..d44bc01 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -1,4 +1,5 @@ import request from '@/utils/request' +import { getTerminalType } from '@/utils/terminal' const permissionInfoCache = new Map() const permissionInfoPending = new Map() @@ -12,16 +13,35 @@ function normalizePermissionParams(params = {}) { function createPermissionCacheKey(params = {}) { const normalizedParams = normalizePermissionParams(params) + const terminalType = getTerminalType() return JSON.stringify( - Object.keys(normalizedParams) + Object.keys({ ...normalizedParams, terminalType }) .sort() .reduce((result, key) => { - result[key] = normalizedParams[key] + result[key] = key === 'terminalType' ? terminalType : normalizedParams[key] return result }, {}) ) } +function filterPermissionMenus(menus, terminalType) { + if (!Array.isArray(menus)) { + return [] + } + + return menus.filter((menu) => { + if (!menu || typeof menu !== 'object') { + return false + } + + if (menu.terminalType === null || typeof menu.terminalType === 'undefined' || menu.terminalType === '') { + return true + } + + return Number(menu.terminalType) === Number(terminalType) + }) +} + export function clearPermissionInfoCache() { permissionInfoCache.clear() permissionInfoPending.clear() @@ -62,6 +82,7 @@ export function register(data) { function getPermissionInfo(params = {}) { const normalizedParams = normalizePermissionParams(params) + const terminalType = getTerminalType() const cacheKey = createPermissionCacheKey(normalizedParams) if (permissionInfoCache.has(cacheKey)) { @@ -80,8 +101,15 @@ function getPermissionInfo(params = {}) { const wrappedPromise = requestPromise .then((res) => { - permissionInfoCache.set(cacheKey, res) - return res + const nextRes = { + ...res, + data: { + ...res?.data, + menus: filterPermissionMenus(res?.data?.menus, terminalType) + } + } + permissionInfoCache.set(cacheKey, nextRes) + return nextRes }) .finally(() => { permissionInfoPending.delete(cacheKey) diff --git a/src/components/common/TabBar.vue b/src/components/common/TabBar.vue index 293ee41..24cb2bd 100644 --- a/src/components/common/TabBar.vue +++ b/src/components/common/TabBar.vue @@ -4,10 +4,12 @@ :fixed="true" :placeholder="true" :border="false" @change="handleChange" zIndex="1000"> @@ -61,6 +63,14 @@ function normalizeRoute(path = '') { return String(path || '').trim().replace(/^\/+/, '') } +function isUniIcon(icon) { + return String(icon || '').startsWith('uni-icons:') +} + +function getUniIconName(icon) { + return String(icon || '').replace(/^uni-icons:/, '').trim() +} + function resolveTabIcons(path, index) { const route = normalizeRoute(path) if (route === 'pages/index') { @@ -84,6 +94,23 @@ function resolveTabIcons(path, index) { return dynamicTabIconList[index % dynamicTabIconList.length] } +function resolveTabIconMeta(menu, index) { + if (isUniIcon(menu?.icon)) { + return { + iconType: 'uni-icons', + iconName: getUniIconName(menu.icon) + } + } + + const icons = resolveTabIcons(menu?.path, index) + return { + iconType: 'image', + iconName: '', + icon: icons.icon, + selectedIcon: icons.selectedIcon + } +} + function getCurrentActiveIndex() { const pages = getCurrentPages() if (pages && pages.length > 0) { @@ -97,11 +124,13 @@ function getCurrentActiveIndex() { const tabList = computed(() => { const dynamicTabList = getTabBarMenus(menus.value) .map((menu, index) => { - const icons = resolveTabIcons(menu.path, index) + const iconMeta = resolveTabIconMeta(menu, index) return { text: String(menu.name || menu.enName || '').trim() || `菜单${index + 1}`, - icon: icons.icon, - selectedIcon: icons.selectedIcon, + icon: iconMeta.icon, + selectedIcon: iconMeta.selectedIcon, + iconType: iconMeta.iconType, + iconName: iconMeta.iconName, path: menu.path } }) @@ -112,6 +141,8 @@ const tabList = computed(() => { text: t('nav.mine'), icon: mineIcon, selectedIcon: mineSelectedIcon, + iconType: 'image', + iconName: '', path: '/pages/mine' } ] diff --git a/src/locales/en-US.js b/src/locales/en-US.js index e65bc5d..0a4354c 100644 --- a/src/locales/en-US.js +++ b/src/locales/en-US.js @@ -514,10 +514,16 @@ export default { setting: { language: 'System Language', currentLanguage: 'Current: {language}', + terminalMode: 'Terminal Mode', + currentTerminal: 'Current Terminal: {terminal}', + switchingTerminal: 'Switching terminal...', + terminalSwitched: 'Switched to {terminal}', switchLanguage: 'Switch Language', checkUpdate: 'Check Updates', cleanCache: 'Clear Cache', logout: 'Log Out', + mobile: 'Mobile', + scanner: 'Scanner', zhCN: 'Chinese', enUS: 'English' }, diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js index f72d755..c0e5170 100644 --- a/src/locales/zh-CN.js +++ b/src/locales/zh-CN.js @@ -514,10 +514,16 @@ export default { setting: { language: '系统语言', currentLanguage: '当前语言:{language}', + terminalMode: '终端模式', + currentTerminal: '当前终端:{terminal}', + switchingTerminal: '正在切换终端...', + terminalSwitched: '已切换到{terminal}', switchLanguage: '切换语言', checkUpdate: '检查更新', cleanCache: '清理缓存', logout: '退出登录', + mobile: '手机', + scanner: '扫码器', zhCN: '中文', enUS: '英文' }, diff --git a/src/pages_mine/pages/setting/index.vue b/src/pages_mine/pages/setting/index.vue index 79a69d7..2ac52b2 100644 --- a/src/pages_mine/pages/setting/index.vue +++ b/src/pages_mine/pages/setting/index.vue @@ -15,6 +15,13 @@ {{ t('mine.changePassword') }} + + + + {{ t('setting.terminalMode') }} + + {{ t('setting.currentTerminal', { terminal: currentTerminalLabel }) }} + @@ -51,6 +58,7 @@ import { useI18n } from 'vue-i18n' import useUserStore from '@/store/modules/user' import { getCurrentLocale, setLocale } from '@/locales' import NavBar from '@/components/common/NavBar.vue' +import { getTerminalType, setTerminalType, TERMINAL_TYPE_MOBILE, TERMINAL_TYPE_SCANNER } from '@/utils/terminal' const userStore = useUserStore() const { t } = useI18n() @@ -59,7 +67,9 @@ const pageTitle = computed(() => t('nav.setting')) const windowHeight = ref(uni.getSystemInfoSync().windowHeight); const popup = ref(null); const currentLocale = ref(getCurrentLocale()) +const currentTerminalType = ref(getTerminalType()) const currentLanguageLabel = computed(() => currentLocale.value === 'en-US' ? t('setting.enUS') : t('setting.zhCN')) +const currentTerminalLabel = computed(() => currentTerminalType.value === TERMINAL_TYPE_SCANNER ? t('setting.scanner') : t('setting.mobile')) function handleToPwd() { uni.navigateTo({ @@ -99,6 +109,46 @@ function handleLanguageChange() { } }) } + +function handleTerminalChange() { + uni.showActionSheet({ + itemList: [t('setting.mobile'), t('setting.scanner')], + success: async ({ tapIndex }) => { + const nextTerminalType = tapIndex === 1 ? TERMINAL_TYPE_SCANNER : TERMINAL_TYPE_MOBILE + if (nextTerminalType === currentTerminalType.value) { + return + } + + const previousTerminalType = currentTerminalType.value + setTerminalType(nextTerminalType) + currentTerminalType.value = nextTerminalType + uni.showLoading({ + title: t('setting.switchingTerminal') + }) + + try { + await userStore.refreshPermissionInfo() + uni.showToast({ + title: t('setting.terminalSwitched', { terminal: currentTerminalLabel.value }), + icon: 'none', + duration: 1000 + }) + } catch (error) { + setTerminalType(previousTerminalType) + currentTerminalType.value = previousTerminalType + await userStore.refreshPermissionInfo().catch(() => {}) + uni.showToast({ + title: t('common.saveFailed'), + icon: 'none', + duration: 1000 + }) + } finally { + uni.hideLoading() + } + } + }) +} + function handleLogout() { popup.value.open(); }; diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 1e2430b..cde5ead 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,5 +1,6 @@ import { login, logout, getInfo, clearPermissionInfoCache } from "@/api/login"; import { getToken, setToken, removeToken } from "@/utils/auth"; +import { removeTerminalType } from "@/utils/terminal"; import defAva from "@/static/images/profile.jpg"; import { defineStore } from "pinia"; import { initAllDict } from "@/utils/dict"; @@ -76,12 +77,17 @@ const useUserStore = defineStore("user", { }); }); }, + refreshPermissionInfo() { + clearPermissionInfoCache(); + return this.getInfo(); + }, // 退出系统 logOut() { return new Promise((resolve, reject) => { logout() .then(() => { clearPermissionInfoCache(); + removeTerminalType(); this.token = ""; this.roles = []; this.permissions = []; diff --git a/src/utils/permissionMenu.js b/src/utils/permissionMenu.js index f1e3483..3ed337a 100644 --- a/src/utils/permissionMenu.js +++ b/src/utils/permissionMenu.js @@ -216,6 +216,10 @@ export function buildPageModules(tabMenu) { } export function resolveMenuUrl(menu) { + if (String(menu?.path || '').trim() === '/' || String(menu?.component || '').trim() === '/') { + return '/pages/index' + } + const directRoute = getDirectRoute(menu?.path) || getDirectRoute(menu?.component) if (directRoute) { return directRoute diff --git a/src/utils/terminal.ts b/src/utils/terminal.ts new file mode 100644 index 0000000..0ddac03 --- /dev/null +++ b/src/utils/terminal.ts @@ -0,0 +1,46 @@ +const TerminalTypeKey = 'App-Terminal-Type' + +export const TERMINAL_TYPE_MOBILE = 1 +export const TERMINAL_TYPE_SCANNER = 2 + +function normalizeTerminalType(value: unknown) { + return Number(value) === TERMINAL_TYPE_SCANNER ? TERMINAL_TYPE_SCANNER : TERMINAL_TYPE_MOBILE +} + +export function detectTerminalType() { + const systemInfo = uni.getSystemInfoSync() + const width = Number(systemInfo.windowWidth || systemInfo.screenWidth || 0) + const height = Number(systemInfo.windowHeight || systemInfo.screenHeight || 0) + + if (width === 480 && height === 800) { + return TERMINAL_TYPE_SCANNER + } + + return TERMINAL_TYPE_MOBILE +} + +export function getTerminalType() { + const storedValue = uni.getStorageSync(TerminalTypeKey) + if (storedValue === '' || storedValue === null || typeof storedValue === 'undefined') { + return TERMINAL_TYPE_MOBILE + } + return normalizeTerminalType(storedValue) +} + +export function setTerminalType(value: unknown) { + const terminalType = normalizeTerminalType(value) + uni.setStorageSync(TerminalTypeKey, terminalType) + return terminalType +} + +export function removeTerminalType() { + uni.removeStorageSync(TerminalTypeKey) +} + +export function initializeTerminalType() { + const storedValue = uni.getStorageSync(TerminalTypeKey) + if (storedValue === '' || storedValue === null || typeof storedValue === 'undefined') { + return setTerminalType(detectTerminalType()) + } + return normalizeTerminalType(storedValue) +} \ No newline at end of file