Procházet zdrojové kódy

feat: 新增系统页/多语言持久化

weibo.xia před 1 týdnem
rodič
revize
f85604d8f7

+ 17 - 2
apps/web-velofex/src/layouts/select-lang.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
 
 import { loadLocaleMessages } from '@vben/locales';
+import { preferences, updatePreferences } from '@vben/preferences';
 
 import { Dropdown } from 'antdv-next';
 
@@ -18,11 +19,25 @@ const items = [
 
 const show = ref(false);
 
+const currentLabel = computed(() => {
+  const selected = items.find((item) => item.key === preferences.app.locale);
+  return selected?.label ?? 'Language';
+});
+
 const onOpenChange = (open: boolean) => {
   show.value = open;
 };
 
 const onMenuClick = (info: any) => {
+  if (preferences.app.locale === info.key) {
+    return;
+  }
+  updatePreferences({
+    app: {
+      locale: info.key,
+    },
+  });
+
   loadLocaleMessages(info.key);
 };
 </script>
@@ -41,7 +56,7 @@ const onMenuClick = (info: any) => {
         src="@/assets/image/earth_18301626@2x.png"
         width="20px"
       />
-      <span>Language</span>
+      <span>{{ currentLabel }}</span>
       <img
         :class="show ? 'rotate-180' : ''"
         alt="polygon"

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 46 - 19
apps/web-velofex/src/locales/langs/en-US/page.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 47 - 20
apps/web-velofex/src/locales/langs/zh-CN/page.json


+ 10 - 0
apps/web-velofex/src/router/routes/external/router-a.ts

@@ -4,6 +4,16 @@ import { LayoutA } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
+  {
+    name: 'JumpToEnterprise',
+    path: '/jumpToEnterprise',
+    component: () => import('#/views/external/jump-to-enterprise.vue'),
+    meta: {
+      hideInMenu: true,
+      hideInTab: true,
+      title: $t('dashboard.jumpToEnterprise'),
+    },
+  },
   {
     component: LayoutA,
     meta: {

+ 398 - 0
apps/web-velofex/src/views/external/jump-to-enterprise.vue

@@ -0,0 +1,398 @@
+<script setup lang="ts">
+import type { MenuProps } from 'antdv-next';
+
+import { ref } from 'vue';
+
+import useAvatar from '@/assets/image/user.png';
+import { Menu } from 'antdv-next';
+
+import Avatar from '#/layouts/header/avatar.vue';
+import SelectLang from '#/layouts/select-lang.vue';
+import { $t } from '#/locales';
+
+const designConfigItems: MenuProps['items'] = [
+  { key: 'tablesViews', label: 'Tables & Views' },
+  { key: 'pageDesign', label: 'Page Design' },
+  { key: 'pageCode', label: 'Page Code' },
+  { key: 'codeRelease', label: 'Code Release' },
+  { key: 'frameworkCode', label: 'Framework Code' },
+  { key: 'basicData', label: 'Basic Data' },
+  { key: 'sqlTools', label: 'SQL Tools' },
+];
+
+const systemTableChildren: MenuProps['items'] = [
+  {
+    key: 'basic-tables',
+    label: 'Basic Tables',
+  },
+  {
+    key: 'workflow-tables',
+    label: 'Workflow Tables',
+  },
+];
+
+const dataConfigChildren: MenuProps['items'] = [
+  { key: 'systemEnums', label: 'System Enums' },
+];
+
+const leftMenuItems: MenuProps['items'] = [
+  {
+    key: 'design-configuration',
+    label: 'Design Configuration',
+    children: designConfigItems,
+  },
+  {
+    key: 'system-tables',
+    label: 'System Tables',
+    children: systemTableChildren,
+  },
+  {
+    key: 'data-configuration',
+    label: 'Data Configuration',
+    children: dataConfigChildren,
+  },
+];
+
+const selectedKeys = ['tablesViews'];
+const openMenuKeys = ref<string[]>([
+  'design-configuration',
+  'system-tables',
+  'basic-tables',
+]);
+const isMobileSidebarOpen = ref(false);
+
+function handleMenuOpenChange(keys: string[]) {
+  openMenuKeys.value = keys;
+}
+
+function openMobileSidebar() {
+  isMobileSidebarOpen.value = true;
+}
+
+function closeMobileSidebar() {
+  isMobileSidebarOpen.value = false;
+}
+</script>
+
+<template>
+  <div class="enterprise-page">
+    <div
+      :class="[{ show: isMobileSidebarOpen }]"
+      class="mobile-mask"
+      @click="closeMobileSidebar"
+    ></div>
+    <div class="enterprise-shell">
+      <aside
+        :class="[{ 'mobile-open': isMobileSidebarOpen }]"
+        class="left-panel"
+      >
+        <div class="brand-card">
+          <div class="logo-dot">CC</div>
+          <div class="brand-text">Costa Coffee CMS</div>
+          <button
+            class="mobile-close-btn"
+            type="button"
+            @click="closeMobileSidebar"
+          >
+            x
+          </button>
+        </div>
+
+        <Menu
+          :items="leftMenuItems"
+          :open-keys="openMenuKeys"
+          :selected-keys="selectedKeys"
+          class="enterprise-menu"
+          mode="inline"
+          @open-change="handleMenuOpenChange"
+        />
+      </aside>
+
+      <section class="right-panel">
+        <header class="top-bar">
+          <div class="title-wrap">
+            <h1>{{ $t('enterprisePage.header.title') }}</h1>
+            <div class="crumb">
+              {{ $t('enterprisePage.header.breadcrumb') }}
+            </div>
+          </div>
+
+          <div class="top-actions">
+            <button
+              class="mobile-toggle-btn"
+              type="button"
+              @click="openMobileSidebar"
+            >
+              Menu
+            </button>
+            <SelectLang />
+            <Avatar :avatar="useAvatar" class="cursor-pointer" />
+          </div>
+        </header>
+
+        <div class="content-placeholder"></div>
+      </section>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.enterprise-page {
+  min-height: 100vh;
+  background: radial-gradient(
+      circle at 78% 88%,
+      rgb(193 233 255 / 35%),
+      transparent 36%
+    ),
+    radial-gradient(circle at 70% 92%, rgb(255 216 199 / 30%), transparent 30%),
+    #f2f0f3;
+}
+
+.enterprise-shell {
+  display: flex;
+  min-height: 100vh;
+  overflow: hidden;
+  background: #f8f6f8;
+  box-shadow: 0 10px 30px rgb(60 34 51 / 8%);
+}
+
+.mobile-mask {
+  position: fixed;
+  inset: 0;
+  z-index: 20;
+  pointer-events: none;
+  background: rgb(26 15 23 / 42%);
+  opacity: 0;
+  transition: opacity 0.2s ease;
+}
+
+.mobile-mask.show {
+  pointer-events: auto;
+  opacity: 1;
+}
+
+.left-panel {
+  position: relative;
+  z-index: 30;
+  width: 256px;
+  padding: 24px 16px;
+  background: #f8f6f8;
+  border-right: none;
+}
+
+.brand-card {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  padding-bottom: 20px;
+  margin-bottom: 18px;
+  border-bottom: 1px solid #ece6ed;
+}
+
+.mobile-close-btn {
+  display: none;
+  width: 28px;
+  height: 28px;
+  margin-left: auto;
+  font-size: 12px;
+  line-height: 1;
+  color: #7a4860;
+  background: #fff;
+  border: 1px solid #d8cbd5;
+  border-radius: 6px;
+}
+
+.logo-dot {
+  display: grid;
+  place-items: center;
+  width: 46px;
+  height: 46px;
+  font-size: 12px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 0.6px;
+  background: linear-gradient(145deg, #9c1f47, #6f0c2d);
+  border-radius: 50%;
+}
+
+.brand-text {
+  font-size: 14px;
+  font-weight: 600;
+  color: #4f4250;
+}
+
+:deep(.enterprise-menu) {
+  background: transparent;
+  border-inline-end: none;
+}
+
+:deep(.enterprise-menu .ant-menu-item) {
+  height: auto;
+  padding: 9px 10px;
+  margin: 0 0 6px;
+  font-size: 13px;
+  line-height: 1.2;
+  color: #6f6771;
+  border-radius: 8px;
+}
+
+:deep(.enterprise-menu .ant-menu-item-selected) {
+  font-weight: 600;
+  color: #fff;
+  background: #8b1648;
+}
+
+:deep(.enterprise-menu .ant-menu-item-selected::after) {
+  display: none;
+}
+
+:deep(.enterprise-menu .ant-menu-sub .ant-menu-item) {
+  font-size: 12px;
+}
+
+:deep(.ant-menu-light.ant-menu-root.ant-menu-inline) {
+  border-inline-end: none;
+}
+
+:deep(.ant-menu-light.ant-menu-inline .ant-menu-sub.ant-menu-inline) {
+  background: none;
+}
+
+:deep(.enterprise-menu .ant-menu-submenu-title) {
+  height: auto;
+  padding: 8px 10px;
+  margin: 0 0 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #5a4f5a;
+  border-radius: 8px;
+}
+
+:deep(.enterprise-menu .ant-menu-submenu-title:hover) {
+  color: #5a4f5a;
+}
+
+.right-panel {
+  position: relative;
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  padding: 28px 32px;
+  overflow: hidden;
+  border-radius: 50px 0 0 50px;
+  box-shadow: 0 8px 20px rgb(95 67 84 / 10%);
+}
+
+.right-panel::before {
+  position: absolute;
+  inset: 0;
+  z-index: 0;
+  content: '';
+  background: url('@/assets/image/bg.png') no-repeat center center;
+  background-color: #fff;
+  background-size: cover;
+}
+
+.right-panel > * {
+  position: relative;
+  z-index: 1;
+}
+
+.top-bar {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #f0ebf1;
+}
+
+.title-wrap h1 {
+  margin: 0;
+  font-size: 34px;
+  font-weight: 700;
+  color: #3e2b33;
+  letter-spacing: 0.2px;
+}
+
+.crumb {
+  margin-top: 8px;
+  font-size: 14px;
+  color: #8b808a;
+}
+
+.top-actions {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.mobile-toggle-btn {
+  display: none;
+  height: 32px;
+  padding: 0 12px;
+  font-size: 12px;
+  color: #6f4e60;
+  background: #fff;
+  border: 1px solid #dcced7;
+  border-radius: 16px;
+}
+
+.content-placeholder {
+  flex: 1;
+}
+
+@media (width <= 960px) {
+  .enterprise-page {
+    padding: 10px;
+  }
+
+  .enterprise-shell {
+    flex-direction: column;
+    min-height: calc(100vh - 20px);
+  }
+
+  .left-panel {
+    position: fixed;
+    inset: 0 auto 0 0;
+    width: 280px;
+    border-right: 1px solid #efebf1;
+    transition: transform 0.24s ease;
+    transform: translateX(-100%);
+  }
+
+  .left-panel.mobile-open {
+    transform: translateX(0);
+  }
+
+  .mobile-close-btn {
+    display: block;
+  }
+
+  .right-panel {
+    padding: 20px 16px;
+    margin: 0;
+    border-radius: 0;
+    box-shadow: none;
+  }
+
+  .right-panel::before {
+    border-radius: 0;
+  }
+
+  .top-bar {
+    flex-direction: column;
+    gap: 12px;
+    align-items: flex-start;
+  }
+
+  .mobile-toggle-btn {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .title-wrap h1 {
+    font-size: 26px;
+  }
+}
+</style>