yanghao 2 недель назад
Родитель
Сommit
3331daf8ca
9 измененных файлов с 300 добавлено и 46 удалено
  1. 1 0
      package.json
  2. 51 0
      pnpm-lock.yaml
  3. 100 16
      src/components/home/header.vue
  4. 40 0
      src/i18n/index.ts
  5. 34 0
      src/i18n/lang/en.json
  6. 35 0
      src/i18n/lang/zh.json
  7. 2 0
      src/main.ts
  8. 32 27
      src/views/index.vue
  9. 5 3
      src/views/login.vue

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "pinia-plugin-persistedstate": "^4.7.1",
     "qs": "^6.14.0",
     "vue": "^3.5.22",
+    "vue-i18n": "^11.4.6",
     "vue-infinite-loading": "3.0.0-alpha.0-0",
     "vue-router": "^4.6.3",
     "vue-types": "^6.0.0",

+ 51 - 0
pnpm-lock.yaml

@@ -50,6 +50,9 @@ importers:
       vue:
         specifier: ^3.5.22
         version: 3.5.22(typescript@5.9.3)
+      vue-i18n:
+        specifier: ^11.4.6
+        version: 11.4.6(vue@3.5.22(typescript@5.9.3))
       vue-infinite-loading:
         specifier: 3.0.0-alpha.0-0
         version: 3.0.0-alpha.0-0(vue@3.5.22(typescript@5.9.3))
@@ -443,6 +446,22 @@ packages:
     peerDependencies:
       vue: '>=3'
 
+  '@intlify/core-base@11.4.6':
+    resolution: {integrity: sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==}
+    engines: {node: '>= 22'}
+
+  '@intlify/devtools-types@11.4.6':
+    resolution: {integrity: sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==}
+    engines: {node: '>= 22'}
+
+  '@intlify/message-compiler@11.4.6':
+    resolution: {integrity: sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==}
+    engines: {node: '>= 22'}
+
+  '@intlify/shared@11.4.6':
+    resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==}
+    engines: {node: '>= 22'}
+
   '@jridgewell/gen-mapping@0.3.13':
     resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
 
@@ -1647,6 +1666,12 @@ packages:
       '@vue/composition-api':
         optional: true
 
+  vue-i18n@11.4.6:
+    resolution: {integrity: sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==}
+    engines: {node: '>= 22'}
+    peerDependencies:
+      vue: ^3.0.0
+
   vue-infinite-loading@3.0.0-alpha.0-0:
     resolution: {integrity: sha512-k51cQtes3hyF3D28Hi1hk1zDmgiJ8EakNnccb9xqHc4MGDKeHizzdrFV+eP70qYFJjRqo2LJ3Ovs+X8/kGNsbA==}
     peerDependencies:
@@ -2010,6 +2035,24 @@ snapshots:
       '@iconify/types': 2.0.0
       vue: 3.5.22(typescript@5.9.3)
 
+  '@intlify/core-base@11.4.6':
+    dependencies:
+      '@intlify/devtools-types': 11.4.6
+      '@intlify/message-compiler': 11.4.6
+      '@intlify/shared': 11.4.6
+
+  '@intlify/devtools-types@11.4.6':
+    dependencies:
+      '@intlify/core-base': 11.4.6
+      '@intlify/shared': 11.4.6
+
+  '@intlify/message-compiler@11.4.6':
+    dependencies:
+      '@intlify/shared': 11.4.6
+      source-map-js: 1.2.1
+
+  '@intlify/shared@11.4.6': {}
+
   '@jridgewell/gen-mapping@0.3.13':
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.5
@@ -3170,6 +3213,14 @@ snapshots:
     dependencies:
       vue: 3.5.22(typescript@5.9.3)
 
+  vue-i18n@11.4.6(vue@3.5.22(typescript@5.9.3)):
+    dependencies:
+      '@intlify/core-base': 11.4.6
+      '@intlify/devtools-types': 11.4.6
+      '@intlify/shared': 11.4.6
+      '@vue/devtools-api': 6.6.4
+      vue: 3.5.22(typescript@5.9.3)
+
   vue-infinite-loading@3.0.0-alpha.0-0(vue@3.5.22(typescript@5.9.3)):
     dependencies:
       tiny-emitter: 2.1.0

+ 100 - 16
src/components/home/header.vue

@@ -10,8 +10,10 @@
       <div class="flex items-center gap-2 cursor-pointer" @click="goHome">
         <img :src="logo" alt="logo" class="w-9 h-9 rounded-md" />
         <img :src="logo2" alt="logo" class="h-10 hidden lg:flex" />
-        <span class="text-[#828182] text-[14px]" style="text-wrap-mode: nowrap"
-          >智慧经营平台</span
+        <span
+          class="text-[#828182] text-[14px]"
+          style="text-wrap-mode: nowrap"
+          >{{ $t("nav.title") }}</span
         >
       </div>
 
@@ -29,7 +31,7 @@
               style="text-wrap-mode: nowrap"
               @click="router.push('/')"
             >
-              首页
+              {{ $t("nav.home") }}
             </a>
           </li>
 
@@ -45,7 +47,7 @@
               "
               @click="goFlow"
             >
-              流程门户
+              {{ $t("nav.flow") }}
             </a>
           </li>
 
@@ -61,7 +63,7 @@
               @click="goDrive"
               style="text-wrap-mode: nowrap"
             >
-              驾驶舱门户
+              {{ $t("nav.drive") }}
             </a>
           </li>
 
@@ -76,7 +78,7 @@
               "
               style="text-wrap-mode: nowrap"
             >
-              报表门户
+              {{ $t("nav.report") }}
             </a>
           </li>
         </ul>
@@ -86,6 +88,29 @@
         style="border-left: 1px solid #a5bbdb"
         class="hidden lg:flex items-center gap-3 h-[40%] pl-4"
       >
+        <el-dropdown trigger="click" @command="handleLanguageCommand">
+          <button
+            class="flex items-center gap-1 px-2 py-1 rounded-md hover:bg-gray-500/20 transition-colors cursor-pointer"
+          >
+            <Icon
+              icon="mdi:translate"
+              class="text-[18px]! mt-1"
+              style="color: var(--text-secondary)"
+            />
+          </button>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item
+                v-for="item in languageOptions"
+                :key="item.value"
+                :command="item.value"
+                :disabled="currentLocale === item.value"
+              >
+                {{ item.label }}
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
         <button
           @click="themeStore.toggleTheme()"
           class="p-2 rounded-full mt-1 hover:bg-gray-500/60 transition-colors cursor-pointer"
@@ -332,10 +357,10 @@
             <template #dropdown>
               <el-dropdown-menu>
                 <el-dropdown-item command="profile">
-                  <span>个人中心</span>
+                  <span>{{ $t("common.person") }}</span>
                 </el-dropdown-item>
                 <el-dropdown-item command="logout">
-                  <span>退出登录</span>
+                  <span>{{ $t("common.logout") }}</span>
                 </el-dropdown-item>
               </el-dropdown-menu>
             </template>
@@ -347,12 +372,35 @@
             class="text-md flex items-center justify-center cursor-pointer h-full py-4"
             @click="login"
           >
-            登 录
+            {{ $t("common.login") }}
           </div>
         </template>
       </div>
 
       <div class="lg:hidden">
+        <el-dropdown trigger="click" @command="handleLanguageCommand">
+          <button
+            class="rounded-md relative top-1 right-5 px-2 py-1 hover:bg-gray-500/20 transition-colors cursor-pointer"
+          >
+            <Icon
+              icon="mdi:translate"
+              class="text-[16px]"
+              style="color: var(--text-secondary)"
+            />
+          </button>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item
+                v-for="item in languageOptions"
+                :key="item.value"
+                :command="item.value"
+                :disabled="currentLocale === item.value"
+              >
+                {{ item.label }}
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
         <button
           @click="themeStore.toggleTheme()"
           class="rounded-full relative top-1 right-4 hover:bg-gray-500/60 transition-colors cursor-pointer"
@@ -605,7 +653,7 @@
                 drawer = false;
               "
             >
-              首页
+              {{ $t("nav.home") }}
             </a>
           </li>
           <li>
@@ -618,7 +666,7 @@
               "
               @click="goFlow"
             >
-              流程门户
+              {{ $t("nav.flow") }}
             </a>
           </li>
           <li v-hasPermi="['portal:dashboard:view']">
@@ -631,7 +679,7 @@
               "
               @click="goDrive"
             >
-              驾驶舱门户
+              {{ $t("nav.drive") }}
             </a>
           </li>
           <li>
@@ -643,7 +691,7 @@
                   : 'nav-item-default'
               "
             >
-              报表门户
+              {{ $t("nav.report") }}
             </a>
           </li>
         </ul>
@@ -663,10 +711,10 @@
               <template #dropdown>
                 <el-dropdown-menu>
                   <el-dropdown-item command="profile">
-                    <span class="pl-2">个人中心</span>
+                    <span class="pl-2">{{ $t("common.person") }}</span>
                   </el-dropdown-item>
                   <el-dropdown-item command="logout">
-                    <span class="pl-2">退出登录</span>
+                    <span class="pl-2">{{ $t("common.logout") }}</span>
                   </el-dropdown-item>
                 </el-dropdown-menu>
               </template>
@@ -677,7 +725,7 @@
             type="primary"
             class="flex-1 bg-[#0050b3]!"
             @click="login"
-            >登录</el-button
+            >{{ $t("common.login") }}</el-button
           >
         </div>
       </div>
@@ -694,6 +742,12 @@ import logo2 from "@/assets/images/logo2.png";
 import person from "@/assets/images/person.png";
 import { useUserStoreWithOut } from "@/stores/useUserStore";
 import { useThemeStore } from "@/stores/useThemeStore";
+import {
+  LOCALE_STORAGE_KEY,
+  SUPPORTED_LOCALES,
+  type AppLocale,
+  useI18n,
+} from "@/i18n";
 const userStore = useUserStoreWithOut();
 const themeStore = useThemeStore();
 import {
@@ -729,8 +783,23 @@ const messages = ref([]);
 const isLoggedIn = computed(
   () => !!userStore.isSetUser || !!userStore.user?.id,
 );
+const { locale } = useI18n();
 const userAvatar = computed(() => userStore.user?.avatar || "");
 const userName = computed(() => userStore.user?.nickname || "");
+const languageOptions: Array<{ label: string; value: AppLocale }> = [
+  { label: "中文", value: "zh" },
+  { label: "English", value: "en" },
+];
+const currentLocale = computed(() =>
+  SUPPORTED_LOCALES.includes(locale.value as AppLocale)
+    ? (locale.value as AppLocale)
+    : "zh",
+);
+const currentLanguageLabel = computed(
+  () =>
+    languageOptions.find((item) => item.value === currentLocale.value)?.label ??
+    "中文",
+);
 
 // 是否有未读消息
 const hasUnreadMessages = computed(() => {
@@ -938,6 +1007,21 @@ function timestampToDateTime(timestamp) {
 const router = useRouter();
 const drawer = ref(false);
 
+const setLanguage = (lang: AppLocale) => {
+  locale.value = lang;
+  localStorage.setItem(LOCALE_STORAGE_KEY, lang);
+  document.documentElement.lang = lang;
+};
+
+const handleLanguageCommand = (command: string | number | object) => {
+  if (
+    typeof command === "string" &&
+    SUPPORTED_LOCALES.includes(command as AppLocale)
+  ) {
+    setLanguage(command as AppLocale);
+  }
+};
+
 // CRM标为已读
 const markAllAsRead = async () => {
   await markMessageAsRead(userStore.getUser.username);

+ 40 - 0
src/i18n/index.ts

@@ -0,0 +1,40 @@
+import { createI18n, useI18n } from "vue-i18n";
+import zh from "./lang/zh.json";
+import en from "./lang/en.json";
+
+export const LOCALE_STORAGE_KEY = "yf-portal-locale";
+export const SUPPORTED_LOCALES = ["zh", "en"] as const;
+export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
+
+const messages = {
+  zh,
+  en,
+};
+
+const getDefaultLocale = (): AppLocale => {
+  if (typeof window === "undefined") {
+    return "zh";
+  }
+
+  const savedLocale = localStorage.getItem(LOCALE_STORAGE_KEY);
+  if (savedLocale && SUPPORTED_LOCALES.includes(savedLocale as AppLocale)) {
+    return savedLocale as AppLocale;
+  }
+
+  return navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
+};
+
+const locale = getDefaultLocale();
+
+if (typeof document !== "undefined") {
+  document.documentElement.lang = locale;
+}
+
+const i18n = createI18n({
+  legacy: false,
+  locale,
+  fallbackLocale: "en",
+  messages,
+});
+
+export { i18n, useI18n };

+ 34 - 0
src/i18n/lang/en.json

@@ -0,0 +1,34 @@
+{
+  "common": {
+    "login": "Login",
+    "logout": "Logout",
+    "person": "Personal Center",
+    "more": "More",
+    "all": "All Tasks"
+  },
+  "nav": {
+    "title": "Smart Operation Platform",
+    "home": "Home",
+    "flow":"Flow Portal",
+    "drive":"Dashboard Portal",
+    "report": "Report Portal"
+  },
+  "banner": {
+    "banner1": "Digital Transformation, Driving Future Growth",
+    "banner2": "Maintain Passion, Rush Towards Goals!",
+    "banner3": "Smart Platform, Empowering Efficient Collaboration"
+  },
+   "flow": {
+    "palceholder": "Please enter the task name to search",
+    "todo":"My Pending",
+    "done":"My Completed"
+  },
+   "home": {
+    "red":"Red-headed Docs",
+    "notice":"Notifications",
+    "news":"News Center",
+    "todoCenter":"Pending Tasks",
+    "consult":"Consult",
+    "consultTip":"Encountering system operation issues, need assistance"
+  }
+}

+ 35 - 0
src/i18n/lang/zh.json

@@ -0,0 +1,35 @@
+{
+  "common": {
+    "login": "登 录",
+    "logout": "退出登录",
+    "person": "个人中心",
+    "more": "更多",
+    "all": "全部任务"
+
+  },
+  "nav": {
+    "title": "智慧经营平台",
+    "home": "首页",
+    "flow":"流程门户",
+    "drive":"驾驶舱门户",
+    "report": "报表门户"
+  },
+  "banner": {
+    "banner1": "数字化转型,驱动未来增长",
+    "banner2": "保持热爱,奔赴目标!",
+    "banner3": "智慧平台,赋能高效协同"
+  },
+  "flow": {
+    "palceholder": "搜索应用、流程",
+    "todo":"我的待办",
+    "done":"我的已办"
+  },
+  "home": {
+    "red":"红头文件",
+    "notice":"通知公告",
+    "news":"新闻中心",
+    "todoCenter":"待办中心",
+    "consult":"立即咨询",
+    "consultTip":"遇到系统操作问题,需要帮助"
+  }
+}

+ 2 - 0
src/main.ts

@@ -1,4 +1,5 @@
 import { createApp } from "vue";
+import { i18n } from "./i18n";
 
 import { setupStore } from "@/stores";
 
@@ -22,6 +23,7 @@ setupMountedFocus(app);
 app.use(ElementPlus, {
   locale: zhCn,
 });
+app.use(i18n);
 
 app.use(router);
 

+ 32 - 27
src/views/index.vue

@@ -68,7 +68,7 @@
           @click="router.push('/flow')"
         >
           <img src="../assets//images/flow.png" alt="" class="w-10 h-10" />
-          流程门户
+          {{ $t("portal.flow") }}
         </button>
         <button
           v-hasPermi="['portal:dashboard:view']"
@@ -77,14 +77,14 @@
           @click="router.push('/drive')"
         >
           <img src="../assets//images/driveicon.png" alt="" class="w-10 h-10" />
-          驾驶舱门户
+          {{ $t("portal.drive") }}
         </button>
         <button
           type="button"
           class="portal-mobile-shortcut flex flex-col items-center justify-center cursor-pointer"
         >
           <img src="../assets//images/report.png" alt="" class="w-10 h-10" />
-          报表门户
+          {{ $t("portal.report") }}
         </button>
       </section>
 
@@ -100,7 +100,7 @@
               <input
                 v-model="searchKeyword"
                 type="text"
-                placeholder="搜索应用、流程、数据..."
+                :placeholder="$t('flow.palceholder')"
                 class="search-input"
                 @keyup.enter="handleSearch"
               />
@@ -116,7 +116,9 @@
               >
                 <button type="button" class="quick-btn quick-btn--dropdown">
                   <img src="../assets//images//td.png" class="w-5 h-5" alt="" />
-                  <span class="btn-label quick-btn__meta">我的待办</span>
+                  <span class="btn-label quick-btn__meta">{{
+                    $t("flow.todo")
+                  }}</span>
                   <span class="quick-btn__count quick-btn__count--todo">{{
                     todo
                   }}</span>
@@ -159,7 +161,9 @@
               >
                 <button type="button" class="quick-btn quick-btn--dropdown">
                   <Icon icon="mdi:check" class="btn-icon" />
-                  <span class="btn-label quick-btn__meta">我的已办</span>
+                  <span class="btn-label quick-btn__meta">{{
+                    $t("flow.done")
+                  }}</span>
                   <span
                     class="p-2 inline-block quick-btn__count quick-btn__count--done"
                     >{{ done }}</span
@@ -285,7 +289,7 @@
                 class="side-card__more"
                 @click="handleNoticeMoreClick"
               >
-                更多
+                {{ $t("common.more") }}
                 <Icon icon="mingcute:right-line" class="text-[18px]" />
               </button>
             </div>
@@ -330,7 +334,7 @@
                 class="side-card__more"
                 @click="router.push('/todo-list')"
               >
-                全部任务
+                {{ $t("common.all") }}
                 <Icon icon="mingcute:right-line" class="text-[18px]" />
               </button>
             </div>
@@ -489,7 +493,7 @@
             <div class="side-card__header w-[90%] ml-[15px]">
               <div class="news notice-badge pb-2">{{ newsPanelTitle }}</div>
               <button type="button" class="side-card__more" @click="goNews">
-                更多
+                {{ $t("common.more") }}
                 <Icon icon="mingcute:right-line" class="text-[18px]" />
               </button>
             </div>
@@ -534,12 +538,12 @@
           >
             <div class="placeholder-panel flex flex-col text-left">
               <!-- <div class="text-sm">需要帮助?</div> -->
-              <div class="text-[10px]">遇到系统操作问题,需要帮助</div>
+              <div class="text-[10px]">{{ t("home.consultTip") }}</div>
               <div
                 class="bg-[#79ebfa] text-sm text-center text-[#004098] py-1 px-2 rounded-full mt-2 w-[30%] cursor-pointer"
                 @click="openConsult"
               >
-                立即咨询
+                {{ t("home.consult") }}
               </div>
             </div>
           </section>
@@ -584,7 +588,8 @@ import Footer from "@components/home/Footer.vue";
 import { useRouter } from "vue-router";
 import axios from "axios";
 import { Icon } from "@iconify/vue";
-import { ElLoading } from "element-plus";
+import { useI18n } from "@/i18n";
+const { t } = useI18n();
 import {
   getMCSsoToken,
   ssoLogin,
@@ -676,12 +681,12 @@ const showDingTalkLoginMask = ref(
   isDingTalkClient() && !userStore.getUser.username && !getAccessToken(),
 );
 
-const todoPanelTitle = "待办中心";
-const newsPanelTitle = "新闻中心";
-const noticeTabs = [
-  { key: "redHead", label: "红头文件" },
-  { key: "notice", label: "通知公告" },
-] as const;
+const todoPanelTitle = t("home.todoCenter");
+const newsPanelTitle = t("home.news");
+const noticeTabs = ref([
+  { key: "redHead", label: t("home.red") },
+  { key: "notice", label: t("home.notice") },
+]);
 
 type NoticeTabKey = (typeof noticeTabs)[number]["key"];
 
@@ -871,32 +876,32 @@ watch(
       slides.value = [
         {
           image: banner2_white,
-          text: "数字化转型,驱动未来增长",
+          text: t("banner.banner1"),
         },
         {
           image: banner1_white,
-          text: "保持热爱,奔赴目标!",
+          text: t("banner.banner2"),
         },
 
         {
           image: banner1_white,
-          text: "智慧平台,赋能高效协同",
+          text: t("banner.banner3"),
         },
       ];
     } else {
       slides.value = [
         {
           image: banner2,
-          text: "数字化转型,驱动未来增长",
+          text: t("banner.banner1"),
         },
         {
           image: banner1,
-          text: "保持热爱,奔赴目标!",
+          text: t("banner.banner2"),
         },
 
         {
           image: banner1,
-          text: "智慧平台,赋能高效协同",
+          text: t("banner.banner3"),
         },
       ];
     }
@@ -907,16 +912,16 @@ watch(
 const slides = ref([
   {
     image: themeStore.theme === "light" ? banner2_white : banner2,
-    text: "数字化转型,驱动未来增长",
+    text: t("banner.banner1"),
   },
   {
     image: themeStore.theme === "light" ? banner1_white : banner1,
-    text: "保持热爱,奔赴目标!",
+    text: t("banner.banner2"),
   },
 
   {
     image: themeStore.theme === "light" ? banner1_white : banner1,
-    text: "智慧平台,赋能高效协同",
+    text: t("banner.banner3"),
   },
 ]);
 

+ 5 - 3
src/views/login.vue

@@ -18,10 +18,12 @@
 
     <div class="relative z-10 w-full max-w-[420px] px-6">
       <div class="bg-white/95 backdrop-blur-sm rounded-lg p-8 shadow-2xl">
-        <h1 class="text-2xl font-bold text-center text-black/90">登录</h1>
+        <h1 class="text-2xl font-bold text-center text-black/90">
+          {{ $t("common.login") }}
+        </h1>
 
         <!-- 用户名密码登陆 -->
-        <!-- <div>
+        <div>
           <el-form
             :model="form"
             :rules="rules"
@@ -62,7 +64,7 @@
               >
             </div>
           </div>
-        </div> -->
+        </div>
 
         <!-- 钉钉登陆 -->
         <div class="text-center">