فهرست منبع

feat: 国际化

叶静 1 هفته پیش
والد
کامیت
d26f62e211
37فایلهای تغییر یافته به همراه1983 افزوده شده و 1214 حذف شده
  1. 63 47
      src/app/shop/admin/data/page/select.vue
  2. 26 28
      src/app/shop/admin/decorate/designer/index.vue
  3. 7 4
      src/app/shop/admin/decorate/page/component/center/common/dc-goods-select.vue
  4. 71 68
      src/app/shop/admin/decorate/page/component/center/common/dc-list.vue
  5. 45 39
      src/app/shop/admin/decorate/page/component/center/common/dc-url.vue
  6. 27 23
      src/app/shop/admin/decorate/page/component/center/comp/goodsCard/setting.vue
  7. 194 193
      src/app/shop/admin/decorate/page/component/center/comp/hotzone/edit.vue
  8. 31 36
      src/app/shop/admin/decorate/page/component/center/comp/hotzone/index.vue
  9. 8 4
      src/app/shop/admin/decorate/page/component/center/comp/hotzone/setting.vue
  10. 19 16
      src/app/shop/admin/decorate/page/component/center/comp/imageBanner/setting.vue
  11. 5 3
      src/app/shop/admin/decorate/page/component/center/comp/imageBlock/setting.vue
  12. 15 11
      src/app/shop/admin/decorate/page/component/center/comp/imageCube/setting.vue
  13. 8 5
      src/app/shop/admin/decorate/page/component/center/comp/lineBlock/setting.vue
  14. 26 22
      src/app/shop/admin/decorate/page/component/center/comp/menuButton/setting.vue
  15. 22 17
      src/app/shop/admin/decorate/page/component/center/comp/menuGrid/setting.vue
  16. 13 8
      src/app/shop/admin/decorate/page/component/center/comp/menuList/setting.vue
  17. 14 10
      src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/setting.vue
  18. 7 4
      src/app/shop/admin/decorate/page/component/center/comp/richtext/richtext-editor.vue
  19. 17 12
      src/app/shop/admin/decorate/page/component/center/comp/richtext/setting.vue
  20. 60 61
      src/app/shop/admin/decorate/page/component/center/comp/searchBlock/setting.vue
  21. 49 51
      src/app/shop/admin/decorate/page/component/center/comp/titleBlock/index.vue
  22. 10 7
      src/app/shop/admin/decorate/page/component/center/comp/titleBlock/select.vue
  23. 80 78
      src/app/shop/admin/decorate/page/component/center/comp/titleBlock/setting.vue
  24. 10 7
      src/app/shop/admin/decorate/page/component/center/comp/videoPlayer/setting.vue
  25. 29 29
      src/app/shop/admin/decorate/page/component/center/page/setting.vue
  26. 1 1
      src/app/shop/admin/decorate/page/component/left/index.scss
  27. 83 90
      src/app/shop/admin/decorate/page/component/left/index.vue
  28. 64 75
      src/app/shop/admin/decorate/page/component/left/left-comp.vue
  29. 24 37
      src/app/shop/admin/decorate/page/component/pheader.vue
  30. 54 30
      src/app/shop/admin/decorate/page/component/right/index.vue
  31. 154 141
      src/app/shop/admin/decorate/page/data.js
  32. 7 4
      src/app/shop/admin/decorate/page/index.vue
  33. 17 10
      src/app/shop/admin/decorate/template/edit.vue
  34. 12 10
      src/app/shop/admin/decorate/template/index.vue
  35. 36 32
      src/app/shop/admin/decorate/template/select.vue
  36. 338 1
      src/locales/en-US/index.json
  37. 337 0
      src/locales/zh-CN/index.json

+ 63 - 47
src/app/shop/admin/data/page/select.vue

@@ -2,7 +2,7 @@
   <el-container class="page-select-view">
     <el-header class="sa-header">
       <div class="sa-title">
-        <span>选择页面</span>
+        <span>{{ t('modules.page.selectPage') }}</span>
       </div>
     </el-header>
     <el-main class="sa-p-0">
@@ -18,7 +18,8 @@
         <div class="right">
           <template v-for="(g, i) in pageGroups" :key="i">
             <div class="group" v-if="currentGroupIndex === i">
-              <div class="group-title">{{ g.group }} (当前索引: {{ currentGroupIndex }})</div>
+              <div class="group-title">{{ g.group }} ({{ t('modules.page.currentIndex') }}: {{ currentGroupIndex }})
+              </div>
               <div class="link sa-flex sa-flex-wrap">
                 <div class="item" :class="selectedPage.path == page.path ? 'item-active' : ''"
                   v-for="page in g.children" :key="page.path" @click="selectPage(page)">
@@ -31,13 +32,16 @@
       </el-container>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button type="primary" @click="confirm">确定</el-button>
+      <el-button type="primary" @click="confirm">{{ t('common.confirm') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const emit = defineEmits(['modalCallBack']);
 const props = defineProps({
@@ -48,78 +52,78 @@ const currentGroupIndex = ref(0);
 const selectedPage = ref({});
 
 // 根据系统实际模块配置的页面路由
-const pageGroups = [
+const pageGroups = computed(() => [
   {
-    group: '主要页面',
+    group: t('modules.page.mainPages'),
     children: [
-      { name: '首页', path: '/pages/index/index' },
-      { name: '收益中心', path: '/pages/income/income' },
-      { name: '个人中心', path: '/pages/mine/mine' },
-      { name: '商品详情', path: '/pages/productDetail/productDetail?productId=xxx' },
-      { name: '搜索', path: '/pages/search/search' },
+      { name: t('modules.page.homePage'), path: '/pages/index/index' },
+      { name: t('modules.page.incomeCenter'), path: '/pages/income/income' },
+      { name: t('modules.page.personalCenter'), path: '/pages/mine/mine' },
+      { name: t('modules.page.productDetail'), path: '/pages/productDetail/productDetail?productId=xxx' },
+      { name: t('modules.page.search'), path: '/pages/search/search' },
     ],
   },
   {
-    group: '用户相关',
+    group: t('modules.page.userRelated'),
     children: [
-      { name: '登录', path: '/pages/login/login' },
-      { name: '注册', path: '/pages/register/register' },
-      { name: '忘记密码', path: '/pages/forgotPassword/forgotPassword' },
-      { name: '个人资料', path: '/pages/mine/myProfile' },
-      { name: '设置', path: '/pages/mine/setting' },
-      { name: '分享', path: '/pages/mine/share' },
-      { name: '我的收藏', path: '/pages/mine/myFavorite' },
+      { name: t('modules.page.login'), path: '/pages/login/login' },
+      { name: t('modules.page.register'), path: '/pages/register/register' },
+      { name: t('modules.page.forgotPassword'), path: '/pages/forgotPassword/forgotPassword' },
+      { name: t('modules.page.personalProfile'), path: '/pages/mine/myProfile' },
+      { name: t('modules.page.settings'), path: '/pages/mine/setting' },
+      { name: t('modules.page.share'), path: '/pages/mine/share' },
+      { name: t('modules.page.myFavorites'), path: '/pages/mine/myFavorite' },
     ],
   },
   {
-    group: '订单相关',
+    group: t('modules.page.orderRelated'),
     children: [
-      { name: '我的订单', path: '/pages/myOrders/myOrders' },
-      { name: '订单详情', path: '/pages/myOrders/orderDetail' },
-      { name: '结算', path: '/pages/productDetail/checkOut' },
+      { name: t('modules.page.myOrders'), path: '/pages/myOrders/myOrders' },
+      { name: t('modules.page.orderDetail'), path: '/pages/myOrders/orderDetail' },
+      { name: t('modules.page.checkout'), path: '/pages/productDetail/checkOut' },
     ],
   },
   {
-    group: '钱包相关',
+    group: t('modules.page.walletRelated'),
     children: [
-      { name: '我的钱包', path: '/pages/wallet/myWallet' },
-      { name: '充值', path: '/pages/wallet/recharge' },
-      { name: '提现', path: '/pages/wallet/withdraw' },
-      { name: '充值记录', path: '/pages/wallet/rechargeRecord' },
-      { name: '提现记录', path: '/pages/wallet/withdrawRecord' },
-      { name: '冻结记录', path: '/pages/wallet/frozenRecord' },
+      { name: t('modules.page.myWallet'), path: '/pages/wallet/myWallet' },
+      { name: t('modules.page.recharge'), path: '/pages/wallet/recharge' },
+      { name: t('modules.page.withdraw'), path: '/pages/wallet/withdraw' },
+      { name: t('modules.page.rechargeRecord'), path: '/pages/wallet/rechargeRecord' },
+      { name: t('modules.page.withdrawRecord'), path: '/pages/wallet/withdrawRecord' },
+      { name: t('modules.page.frozenRecord'), path: '/pages/wallet/frozenRecord' },
     ],
   },
   {
-    group: '地址管理',
+    group: t('modules.page.addressManagement'),
     children: [
-      { name: '地址簿', path: '/pages/mine/addressBook' },
-      { name: '地址操作', path: '/pages/mine/addressBookOperate' },
+      { name: t('modules.page.addressBook'), path: '/pages/mine/addressBook' },
+      { name: t('modules.page.addressOperation'), path: '/pages/mine/addressBookOperate' },
     ],
   },
   {
-    group: '活动页面',
+    group: t('modules.page.activityPages'),
     children: [
-      { name: '任务中心', path: '/pages/missionCenter/missionCenter' },
-      { name: '推荐赚钱', path: '/pages/referEarn/referEarn' },
-      { name: 'VIP会员', path: '/pages/vipMembership/vipMembership' },
-      { name: '热销商品', path: '/pages/bestSellers/bestSellers' },
-      { name: '冠军榜', path: '/pages/topChampions/topChampions' },
+      { name: t('modules.page.missionCenter'), path: '/pages/missionCenter/missionCenter' },
+      { name: t('modules.page.referEarn'), path: '/pages/referEarn/referEarn' },
+      { name: t('modules.page.vipMembership'), path: '/pages/vipMembership/vipMembership' },
+      { name: t('modules.page.bestSellers'), path: '/pages/bestSellers/bestSellers' },
+      { name: t('modules.page.topChampions'), path: '/pages/topChampions/topChampions' },
     ],
   },
   {
-    group: '其他页面',
+    group: t('modules.page.otherPages'),
     children: [
-      { name: '帮助中心', path: '/pages/mine/helpCenter' },
-      { name: '帮助详情', path: '/pages/mine/helpCenterDetail' },
-      { name: '通知', path: '/pages/notifications/notifications' },
-      { name: '网页链接', path: '/pages/webLink/webLink' },
+      { name: t('modules.page.helpCenter'), path: '/pages/mine/helpCenter' },
+      { name: t('modules.page.helpDetail'), path: '/pages/mine/helpCenterDetail' },
+      { name: t('modules.page.notifications'), path: '/pages/notifications/notifications' },
+      { name: t('modules.page.webLink'), path: '/pages/webLink/webLink' },
     ],
   },
-];
+]);
 
 function switchGroup(index) {
-  console.log('切换到分组:', index, pageGroups[index].group);
+  console.log('切换到分组:', index, pageGroups.value[index].group);
   console.log('切换前 currentGroupIndex:', currentGroupIndex.value);
   currentGroupIndex.value = index;
   console.log('切换后 currentGroupIndex:', currentGroupIndex.value);
@@ -153,7 +157,7 @@ function confirm() {
   }
 
   .el-aside {
-    --el-aside-width: 140px;
+    --el-aside-width: 180px;
     border-right: 1px solid var(--sa-border);
     padding: 20px;
   }
@@ -169,6 +173,18 @@ function confirm() {
       margin-bottom: 4px;
       cursor: pointer;
       padding: 0 12px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      display: block;
+
+      .name {
+        display: block;
+        width: 100%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
 
       &:hover {
         color: var(--el-color-primary);

+ 26 - 28
src/app/shop/admin/decorate/designer/index.vue

@@ -2,7 +2,7 @@
   <el-container class="designer-view panel-block">
     <el-header class="sa-header">
       <div class="sa-title sa-flex sa-row-between">
-        <span>设计师模板</span>
+        <span>{{ t('decorate.designerTemplate') }}</span>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
         </div>
@@ -12,13 +12,7 @@
       <div v-if="state.list.length > 0" class="designer-wrap sa-flex sa-flex-wrap">
         <template v-for="item in state.list" :key="item">
           <div class="item">
-            <el-carousel
-              trigger="click"
-              height="480px"
-              :autoplay="false"
-              :loop="false"
-              indicator-position="none"
-            >
+            <el-carousel trigger="click" height="480px" :autoplay="false" :loop="false" indicator-position="none">
               <el-carousel-item v-for="page in item.page" :key="page">
                 <sa-image :url="page.image" :suffix="{}"></sa-image>
               </el-carousel-item>
@@ -26,35 +20,24 @@
             <div class="footer">
               <div class="name">{{ item.name }}</div>
               <div class="platform sa-flex">
-                <div class="label">支持平台:</div>
+                <div class="label">{{ t('decorate.supportedPlatforms') }}:</div>
                 <div v-if="item.platform">
-                  <sa-icon
-                    class="sa-m-r-8"
-                    v-for="pl in item.platform"
-                    :key="pl"
-                    :icon="'sa-shop-decorate-' + pl"
-                    size="20"
-                    :style="{
+                  <sa-icon class="sa-m-r-8" v-for="pl in item.platform" :key="pl" :icon="'sa-shop-decorate-' + pl"
+                    size="20" :style="{
                       color: platformList.filter((pf) => {
                         return pf.type == pl;
                       })[0].color,
-                    }"
-                  />
+                    }" />
                 </div>
               </div>
               <div class="memo sa-flex">
-                <div class="label">备注:</div>
+                <div class="label">{{ t('decorate.remarks') }}:</div>
                 <div>{{ item.memo }}</div>
               </div>
               <div class="oper sa-flex sa-row-right">
-                <el-button
-                  v-auth="'shop.admin.decorate.designer.use'"
-                  class="is-link"
-                  type="primary"
-                  size="small"
-                  @click="onUse(item.id)"
-                >
-                  使用
+                <el-button v-auth="'shop.admin.decorate.designer.use'" class="is-link" type="primary" size="small"
+                  @click="onUse(item.id)">
+                  {{ t('decorate.use') }}
                 </el-button>
               </div>
             </div>
@@ -69,10 +52,13 @@
 <script setup>
 import { onMounted, reactive } from 'vue';
 import { useModal } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
 import PagePreview from './preview.vue';
 import { api } from '../decorate.service';
 import { platformList } from '../page/data';
 
+const { t } = useI18n();
+
 const state = reactive({
   loading: false,
   list: [],
@@ -105,6 +91,7 @@ onMounted(() => {
 .designer-view {
   .designer-wrap {
     padding: 10px var(--sa-padding);
+
     .item {
       position: relative;
       width: 246px;
@@ -116,21 +103,26 @@ onMounted(() => {
       margin-bottom: var(--sa-padding);
       overflow: hidden;
       margin-right: 20px;
+
       img {
         width: 100%;
       }
+
       &:hover {
         transform: scale(1.02);
         box-shadow: 0 4px 16px rgba(89, 89, 89, 0.24);
+
         .footer {
           opacity: 1;
         }
       }
+
       :deep() {
         .image-slot {
           height: 200px;
         }
       }
+
       .footer {
         position: absolute;
         bottom: 0;
@@ -140,28 +132,34 @@ onMounted(() => {
         background: var(--sa-background-assist);
         transition: all 0.5s;
         opacity: 0;
+
         .name {
           padding: 12px 0 4px;
           color: var(--sa-title);
           font-size: 16px;
         }
+
         .platform {
           height: 28px;
           font-size: 14px;
           color: var(--sa-subtitle);
         }
+
         .memo,
         .update-time {
           height: 20px;
           font-size: 12px;
           color: var(--sa-subfont);
         }
+
         .label {
           flex-shrink: 0;
         }
+
         .oper {
           height: 36px;
-          .el-button + .el-button {
+
+          .el-button+.el-button {
             margin-left: 8px;
           }
         }

+ 7 - 4
src/app/shop/admin/decorate/page/component/center/common/dc-goods-select.vue

@@ -2,7 +2,7 @@
   <div class="dc-goods-select">
     <div class="card">
       <div class="title">
-        <slot name="title">商品选择</slot>
+        <slot name="title">{{ t('modules.decorate.goodsSelect') }}</slot>
       </div>
       <div class="content">
         <sa-draggable class="sa-flex sa-flex-wrap" v-model="listData" item-key="element" :animation="300"
@@ -19,7 +19,7 @@
           </template>
         </sa-draggable>
         <slot name="add">
-          <el-button class="add-button" icon="Plus" @click="addGoods">添加</el-button>
+          <el-button class="add-button" icon="Plus" @click="addGoods">{{ t('modules.decorate.add') }}</el-button>
         </slot>
       </div>
     </div>
@@ -37,10 +37,13 @@ export default {
  * @property {Number} max - 多选最大
  */
 import { ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import SaDraggable from 'vuedraggable';
 import { useModal } from '@/sheep/hooks';
 import DecorateGoodsSelect from './decorate-goods-select.vue';
 
+const { t } = useI18n();
+
 const emit = defineEmits(['update:modelValue']);
 const props = defineProps({
   modelValue: {
@@ -78,7 +81,7 @@ function addGoods() {
     useModal(
       DecorateGoodsSelect,
       {
-        title: '选择商品',
+        title: t('modules.decorate.selectGoods'),
         multiple: props.multiple,
         max: props.max,
         ids,
@@ -136,7 +139,7 @@ function addGoods() {
     useModal(
       MpliveSelect,
       {
-        title: '选择直播间',
+        title: t('modules.decorate.selectLiveRoom'),
       },
       {
         confirm: (res) => {

+ 71 - 68
src/app/shop/admin/decorate/page/component/center/common/dc-list.vue

@@ -1,22 +1,16 @@
 <template>
   <div class="dc-list card">
     <div class="title">
-      <slot name="title">数据</slot>
+      <slot name="title">{{ t('modules.decorate.data') }}</slot>
     </div>
     <div class="content">
-      <sa-draggable
-        v-model="listData"
-        item-key="element"
-        :animation="300"
-        handle=".sortable-drag"
-        @end="updateItem"
-      >
+      <sa-draggable v-model="listData" item-key="element" :animation="300" handle=".sortable-drag" @end="updateItem">
         <template #item="{ element, index }">
           <div class="list-item">
             <div class="list-item-posi sa-flex">
               <sa-icon class="sortable-drag" icon="sa-round" />
               <slot name="deleteIcon">
-                <span class="list-delete" @click="deleteItem(index)">删除</span>
+                <span class="list-delete" @click="deleteItem(index)">{{ t('modules.decorate.delete') }}</span>
               </slot>
             </div>
             <slot name="listItem" :element="element"></slot>
@@ -24,85 +18,94 @@
         </template>
       </sa-draggable>
       <slot name="add" v-if="listData?.length != leng">
-        <el-button class="add-button" icon="Plus" @click="addItem">添加</el-button>
+        <el-button class="add-button" icon="Plus" @click="addItem">{{ t('modules.decorate.add') }}</el-button>
       </slot>
     </div>
   </div>
 </template>
 <script>
-  export default {
-    name: 'dc-list',
-  };
+export default {
+  name: 'dc-list',
+};
 </script>
 <script setup>
-  import { ref, watch } from 'vue';
-  import SaDraggable from 'vuedraggable';
-  import { cloneDeep } from 'lodash';
+import { ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import SaDraggable from 'vuedraggable';
+import { cloneDeep } from 'lodash';
 
-  const emit = defineEmits(['update:modelValue']);
-  const props = defineProps(['modelValue', 'itemProp', 'leng']);
+const { t } = useI18n();
 
-  const listData = ref(props.modelValue || []);
-  watch(
-    () => props.modelValue,
-    () => {
-      listData.value = props.modelValue;
-    },
-  );
+const emit = defineEmits(['update:modelValue']);
+const props = defineProps(['modelValue', 'itemProp', 'leng']);
 
-  function addItem() {
-    listData.value.push(cloneDeep(props.itemProp));
-  }
-  function deleteItem(index) {
-    listData.value.splice(index, 1);
-  }
-  function updateItem() {
-    emit('update:modelValue', listData.value);
-  }
+const listData = ref(props.modelValue || []);
+watch(
+  () => props.modelValue,
+  () => {
+    listData.value = props.modelValue;
+  },
+);
+
+function addItem() {
+  listData.value.push(cloneDeep(props.itemProp));
+}
+function deleteItem(index) {
+  listData.value.splice(index, 1);
+}
+function updateItem() {
+  emit('update:modelValue', listData.value);
+}
 </script>
 <style lang="scss" scoped>
-  .dc-list {
-    .title {
-      margin-bottom: 0;
+.dc-list {
+  .title {
+    margin-bottom: 0;
+  }
+
+  .list-item {
+    padding: 8px 0;
+    border-bottom: 1px solid var(--sa-space);
+    background: var(--sa-background-assist);
+
+    &:last-of-type {
+      border-bottom: none;
+      // margin-bottom: 16px;
     }
-    .list-item {
-      padding: 8px 0;
-      border-bottom: 1px solid var(--sa-space);
-      background: var(--sa-background-assist);
-      &:last-of-type {
-        border-bottom: none;
-        // margin-bottom: 16px;
-      }
-      &:hover {
-        background: var(--t-bg-hover);
-        :deep() {
-          .list-item-posi {
-            & > * {
-              display: block;
-            }
-          }
-        }
-      }
+
+    &:hover {
+      background: var(--t-bg-hover);
+
       :deep() {
         .list-item-posi {
-          padding: 0 4px 16px;
-          height: 32px;
-          & > * {
-            display: none;
+          &>* {
+            display: block;
           }
+        }
+      }
+    }
 
-          .sortable-drag {
-            font-size: 16px;
-            margin-right: 8px;
-            cursor: pointer;
-          }
+    :deep() {
+      .list-item-posi {
+        padding: 0 4px 16px;
+        height: 32px;
 
-          .list-delete {
-            color: #ff4d4f;
-            cursor: pointer;
-          }
+        &>* {
+          display: none;
+        }
+
+        .sortable-drag {
+          font-size: 16px;
+          margin-right: 8px;
+          cursor: pointer;
+        }
+
+        .list-delete {
+          color: #ff4d4f;
+          cursor: pointer;
         }
       }
     }
   }
+}
 </style>

+ 45 - 39
src/app/shop/admin/decorate/page/component/center/common/dc-url.vue

@@ -1,54 +1,60 @@
 <template>
   <div class="dc-url sa-flex">
-    <el-input v-model="path" :placeholder="placeholder" @input="changeurl">
+    <el-input v-model="path" :placeholder="computedPlaceholder" @input="changeurl">
       <template #append>
-        <span class="cursor-pointer" @click="selecturl">选择</span>
+        <span class="cursor-pointer" @click="selecturl">{{ t('modules.decorate.select') }}</span>
       </template>
     </el-input>
   </div>
 </template>
 <script>
-  export default {
-    name: 'dc-url',
-  };
+export default {
+  name: 'dc-url',
+};
 </script>
 <script setup>
-  import { ref, watch } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import PageSelect from '@/app/shop/admin/data/page/select.vue';
+import { ref, watch, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useModal } from '@/sheep/hooks';
+import PageSelect from '@/app/shop/admin/data/page/select.vue';
 
-  const emit = defineEmits(['update:modelValue']);
-  const props = defineProps({
-    modelValue: {
-      type: String,
-      default: '',
-    },
-    placeholder: {
-      type: String,
-      default: '请输入或选择',
-    },
-  });
+const { t } = useI18n();
 
-  const path = ref(props.modelValue);
-  watch(
-    () => props.modelValue,
-    () => {
-      path.value = props.modelValue;
-    },
-  );
+const emit = defineEmits(['update:modelValue']);
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: '',
+  },
+  placeholder: {
+    type: String,
+    default: '',
+  },
+});
 
-  function changeurl() {
-    emit('update:modelValue', path.value);
-  }
-  function selecturl() {
-    useModal(
-      PageSelect,
-      { title: '选择链接' },
-      {
-        confirm: (res) => {
-          emit('update:modelValue', res.data.path);
-        },
+const path = ref(props.modelValue);
+const computedPlaceholder = computed(() => {
+  return props.placeholder || t('modules.decorate.pleaseInputOrSelect');
+});
+watch(
+  () => props.modelValue,
+  () => {
+    path.value = props.modelValue;
+  },
+);
+
+function changeurl() {
+  emit('update:modelValue', path.value);
+}
+function selecturl() {
+  useModal(
+    PageSelect,
+    { title: t('modules.decorate.selectLink') },
+    {
+      confirm: (res) => {
+        emit('update:modelValue', res.data.path);
       },
-    );
-  }
+    },
+  );
+}
 </script>

+ 27 - 23
src/app/shop/admin/decorate/page/component/center/comp/goodsCard/setting.vue

@@ -3,9 +3,9 @@
     <template v-if="tabType == 'data'">
       <dc-goods-select v-model="settingData.data.goodsList" :multiple="true"></dc-goods-select>
       <div class="card">
-        <div class="title">商品样式</div>
+        <div class="title">{{ t('modules.decorate.productSettings') }}</div>
         <div class="content">
-          <el-form-item label="选择风格">
+          <el-form-item :label="t('modules.decorate.styleSettings')">
             <el-radio-group class="custom-radio-button" v-model="settingData.data.mode">
               <el-radio-button :label="1">
                 <sa-icon icon="sa-shop-decorate-goodsCard-mode-3" />
@@ -28,48 +28,49 @@
         </div>
       </div>
       <div class="card">
-        <div class="title">商品角标</div>
+        <div class="title">{{ t('modules.decorate.productBadge') }}</div>
         <div class="content">
-          <el-form-item label="角标选择">
+          <el-form-item :label="t('modules.decorate.badgeSelection')">
             <el-radio-group v-model="settingData.data.tagStyle.show">
-              <el-radio :label="0">不显示</el-radio>
-              <el-radio :label="1">显示</el-radio>
+              <el-radio :label="0">{{ t('modules.decorate.notDisplay') }}</el-radio>
+              <el-radio :label="1">{{ t('modules.decorate.display') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item v-if="settingData.data.tagStyle.show" label="上传图片">
+          <el-form-item v-if="settingData.data.tagStyle.show" :label="t('modules.decorate.uploadImageLabel')">
             <div class="sa-flex">
               <sa-upload-image v-model="settingData.data.tagStyle.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
                 :max-size="5" :direct-upload="true" :size="100" />
-              <div class="tip">建议尺寸:36*22</div>
+              <div class="tip">{{ t('modules.decorate.suggestedSize36x22') }}</div>
             </div>
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">购买按钮设置</div>
+        <div class="title">{{ t('modules.decorate.buyButton') }}</div>
         <div class="content">
-          <el-form-item label="按钮文字">
-            <el-input v-model="settingData.data.buyNowStyle.text" placeholder="Buy Now"></el-input>
+          <el-form-item :label="t('modules.decorate.buttonText')">
+            <el-input v-model="settingData.data.buyNowStyle.text"
+              :placeholder="t('modules.decorate.buyNowText')"></el-input>
           </el-form-item>
-          <el-form-item label="按钮颜色">
+          <el-form-item :label="t('modules.decorate.buttonColor')">
             <dc-color-picker v-model="settingData.data.buyNowStyle.backgroundColor"
               :default-value="'#e61b28'"></dc-color-picker>
           </el-form-item>
-          <el-form-item label="文字颜色">
+          <el-form-item :label="t('modules.decorate.textColor')">
             <dc-color-picker v-model="settingData.data.buyNowStyle.textColor" :default-value="'#fff'"></dc-color-picker>
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">样式</div>
+        <div class="title">{{ t('modules.decorate.styleLabel') }}</div>
         <div class="content">
-          <el-form-item label="上圆角">
+          <el-form-item :label="t('modules.decorate.topRadius')">
             <dc-slider v-model="settingData.data.borderRadiusTop"></dc-slider>
           </el-form-item>
-          <el-form-item label="下圆角">
+          <el-form-item :label="t('modules.decorate.bottomRadius')">
             <dc-slider v-model="settingData.data.borderRadiusBottom"></dc-slider>
           </el-form-item>
-          <el-form-item label="间距">
+          <el-form-item :label="t('modules.decorate.spacing')">
             <dc-slider v-model="settingData.data.space"></dc-slider>
           </el-form-item>
         </div>
@@ -80,22 +81,25 @@
 
 <script setup>
 import { computed, onMounted, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import dcColorPicker from '../../common/dc-color-picker.vue';
 import dcGoodsSelect from '../../common/dc-goods-select.vue';
 import dcList from '../../common/dc-list.vue';
 import dcSlider from '../../common/dc-slider.vue';
 import { api } from '@/app/shop/admin/goods/goods.service';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 
 const fieldLabel = computed(() => {
   return {
-    title: '商品标题',
-    subtitle: '副标题',
-    price: '商品价格',
-    original_price: '原价',
-    sales: '销量',
-    stock: '库存',
+    title: t('modules.decorate.productTitle'),
+    subtitle: t('modules.decorate.subtitle'),
+    price: t('modules.decorate.productPrice'),
+    original_price: t('modules.decorate.originalPrice'),
+    sales: t('modules.decorate.salesVolume'),
+    stock: t('modules.decorate.stockQuantity'),
   };
 });
 

+ 194 - 193
src/app/shop/admin/decorate/page/component/center/comp/hotzone/edit.vue

@@ -10,24 +10,18 @@
           :suffix="null"
         ></sa-image> -->
         <template v-for="(item, index) in state.mapList">
-          <div
-            v-if="!item.show"
-            class="map-item sa-flex sa-row-center"
-            :style="{
-              width: `${item.width}px`,
-              height: `${item.height}px`,
-              top: `${item.top}px`,
-              left: `${item.left}px`,
-            }"
-            data-type="item"
-            @mousedown="mousedown($event, index)"
-            @mouseup="mouseup($event, index)"
-            @click.self="onSelect(index)"
-            @dblclick="onLink(index)"
-          >
+          <div v-if="!item.show" class="map-item sa-flex sa-row-center" :style="{
+            width: `${item.width}px`,
+            height: `${item.height}px`,
+            top: `${item.top}px`,
+            left: `${item.left}px`,
+          }" data-type="item" @mousedown="mousedown($event, index)" @mouseup="mouseup($event, index)"
+            @click.self="onSelect(index)" @dblclick="onLink(index)">
             {{ item.name }}
             <div class="delete" @click.stop="onDelete(index)">
-              <el-icon><Close /></el-icon>
+              <el-icon>
+                <Close />
+              </el-icon>
             </div>
             <div class="coor" data-type="scale"></div>
           </div>
@@ -35,212 +29,219 @@
       </div>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button @click="onAdd">添加热区</el-button>
-      <el-button @click="onSave">保存</el-button>
+      <el-button @click="onAdd">{{ t('modules.decorate.addHotzone') }}</el-button>
+      <el-button @click="onSave">{{ t('modules.decorate.save') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 <script setup>
-  import { onMounted, reactive, ref } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import PageSelect from '@/app/shop/admin/data/page/select.vue';
-  import { ElMessage } from 'element-plus';
-  import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
-
-  const props = defineProps(['modal']);
-  const emit = defineEmits(['modalCallBack']);
-
-  // 内容部分宽高
-  let offsetWidth;
-  let offsetHeight;
-  let dragFlag = false;
-
-  const state = reactive({
-    mapList: [],
-    currentIndex: null,
+import { onMounted, reactive, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useModal } from '@/sheep/hooks';
+import PageSelect from '@/app/shop/admin/data/page/select.vue';
+import { ElMessage } from 'element-plus';
+import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
+
+const { t } = useI18n();
+
+const props = defineProps(['modal']);
+const emit = defineEmits(['modalCallBack']);
+
+// 内容部分宽高
+let offsetWidth;
+let offsetHeight;
+let dragFlag = false;
+
+const state = reactive({
+  mapList: [],
+  currentIndex: null,
+});
+
+const srcRef = ref();
+
+function onAdd() {
+  state.mapList.push({
+    width: 200,
+    height: 200,
+    top: 0,
+    left: 0,
+    name: t('modules.decorate.doubleClickSelectLink'),
+    url: '',
   });
-
-  const srcRef = ref();
-
-  function onAdd() {
-    state.mapList.push({
-      width: 200,
-      height: 200,
-      top: 0,
-      left: 0,
-      name: '双击选择链接',
-      url: '',
-    });
-    state.currentIndex = state.mapList.length - 1;
-  }
-
-  function onDelete(index) {
-    state.mapList[index].show = true;
-  }
-
-  function onSelect(index) {
-    state.currentIndex = index;
-  }
-
-  function onLink(index) {
-    useModal(
-      PageSelect,
-      { title: '选择链接', from: 'shop' },
-      {
-        confirm: (res) => {
-          state.mapList[index].name = res.data.name;
-          state.mapList[index].url = res.data.path;
-        },
+  state.currentIndex = state.mapList.length - 1;
+}
+
+function onDelete(index) {
+  state.mapList[index].show = true;
+}
+
+function onSelect(index) {
+  state.currentIndex = index;
+}
+
+function onLink(index) {
+  useModal(
+    PageSelect,
+    { title: t('modules.decorate.selectLink'), from: 'shop' },
+    {
+      confirm: (res) => {
+        state.mapList[index].name = res.data.name;
+        state.mapList[index].url = res.data.path;
       },
-    );
+    },
+  );
+}
+
+function mousedown(event, index) {
+  offsetWidth = srcRef.value.width || 750;
+  offsetHeight = srcRef.value.height;
+
+  dragFlag = true;
+  state.currentIndex = index;
+
+  event = event || window.event;
+  var _target = event.target;
+  var x = event.clientX - _target.offsetLeft;
+  var y = event.clientY - _target.offsetTop;
+
+  if (event.preventDefault) {
+    event.preventDefault();
+  } else {
+    event.returnValue = false;
   }
-
-  function mousedown(event, index) {
-    offsetWidth = srcRef.value.width || 750;
-    offsetHeight = srcRef.value.height;
-
-    dragFlag = true;
-    state.currentIndex = index;
-
+  document.onmousemove = (event) => {
     event = event || window.event;
-    var _target = event.target;
-    var x = event.clientX - _target.offsetLeft;
-    var y = event.clientY - _target.offsetTop;
-
-    if (event.preventDefault) {
-      event.preventDefault();
-    } else {
-      event.returnValue = false;
-    }
-    document.onmousemove = (event) => {
-      event = event || window.event;
-      if (event.target.dataset.type) {
-        if (dragFlag) {
-          var left = event.clientX - x;
-          var top = event.clientY - y;
-
-          if (_target.dataset.type === 'item') {
-            if (left <= 0) {
-              left = 0;
-            } else if (left > offsetWidth - _target.offsetWidth) {
-              left = offsetWidth - _target.offsetWidth;
-            }
-            if (top <= 0) {
-              top = 0;
-            } else if (top > offsetHeight - _target.offsetHeight) {
-              top = offsetHeight - _target.offsetHeight;
-            }
-
-            state.mapList[state.currentIndex].left = left;
-            state.mapList[state.currentIndex].top = top;
+    if (event.target.dataset.type) {
+      if (dragFlag) {
+        var left = event.clientX - x;
+        var top = event.clientY - y;
+
+        if (_target.dataset.type === 'item') {
+          if (left <= 0) {
+            left = 0;
+          } else if (left > offsetWidth - _target.offsetWidth) {
+            left = offsetWidth - _target.offsetWidth;
+          }
+          if (top <= 0) {
+            top = 0;
+          } else if (top > offsetHeight - _target.offsetHeight) {
+            top = offsetHeight - _target.offsetHeight;
           }
 
-          _target.style.left = left + 'px';
-          _target.style.top = top + 'px';
-
-          if (_target.dataset.type === 'scale') {
-            let width = _target.offsetLeft + _target.clientWidth;
-            let height = _target.offsetTop + _target.clientHeight;
+          state.mapList[state.currentIndex].left = left;
+          state.mapList[state.currentIndex].top = top;
+        }
 
-            if (width + state.mapList[state.currentIndex].left > offsetWidth) {
-              width = offsetWidth - state.mapList[state.currentIndex].left;
-            }
-            if (height + state.mapList[state.currentIndex].top > offsetHeight) {
-              height = offsetHeight - state.mapList[state.currentIndex].top;
-            }
+        _target.style.left = left + 'px';
+        _target.style.top = top + 'px';
 
-            state.mapList[state.currentIndex].width = width;
-            state.mapList[state.currentIndex].height = height;
+        if (_target.dataset.type === 'scale') {
+          let width = _target.offsetLeft + _target.clientWidth;
+          let height = _target.offsetTop + _target.clientHeight;
 
-            _target.style.left = width - _target.offsetWidth + 'px';
-            _target.style.top = height - _target.offsetHeight + 'px';
+          if (width + state.mapList[state.currentIndex].left > offsetWidth) {
+            width = offsetWidth - state.mapList[state.currentIndex].left;
           }
-        }
-      } else {
-        dragFlag = false;
-      }
-    };
-  }
-  function mouseup(e) {
-    document.onmousemove = null;
-    dragFlag = false;
-  }
+          if (height + state.mapList[state.currentIndex].top > offsetHeight) {
+            height = offsetHeight - state.mapList[state.currentIndex].top;
+          }
+
+          state.mapList[state.currentIndex].width = width;
+          state.mapList[state.currentIndex].height = height;
 
-  function onSave() {
-    let arr = state.mapList.filter((item) => !item.show);
-    let flag = false;
-    arr.forEach((item) => {
-      if (!item.url) {
-        flag = true;
+          _target.style.left = width - _target.offsetWidth + 'px';
+          _target.style.top = height - _target.offsetHeight + 'px';
+        }
       }
-    });
-    if (flag) {
-      ElMessage({
-        message: '请选择链接',
-        type: 'warning',
-      });
-      return false;
+    } else {
+      dragFlag = false;
     }
-    emit('modalCallBack', {
-      event: 'confirm',
-      data: arr,
+  };
+}
+function mouseup(e) {
+  document.onmousemove = null;
+  dragFlag = false;
+}
+
+function onSave() {
+  let arr = state.mapList.filter((item) => !item.show);
+  let flag = false;
+  arr.forEach((item) => {
+    if (!item.url) {
+      flag = true;
+    }
+  });
+  if (flag) {
+    ElMessage({
+      message: t('modules.decorate.pleaseSelectLink'),
+      type: 'warning',
     });
+    return false;
   }
-
-  onMounted(() => {
-    state.mapList = JSON.parse(JSON.stringify(props.modal.params.data.list));
+  emit('modalCallBack', {
+    event: 'confirm',
+    data: arr,
   });
+}
+
+onMounted(() => {
+  state.mapList = JSON.parse(JSON.stringify(props.modal.params.data.list));
+});
 </script>
 
 <style lang="scss" scoped>
-  .hotzone {
-    .mapContent {
-      width: 750px;
-      position: relative;
-      // border: 1px solid var(--sa-border);
-      img {
-        width: 750px !important;
-        pointer-events: none;
-      }
+.hotzone {
+  .mapContent {
+    width: 750px;
+    position: relative;
+
+    // border: 1px solid var(--sa-border);
+    img {
+      width: 750px !important;
+      pointer-events: none;
     }
-    .map-item {
+  }
+
+  .map-item {
+    position: absolute;
+    border: 1px solid var(--el-color-primary);
+    cursor: move;
+    background: var(--t-bg-active);
+    opacity: 0.8;
+    color: var(--el-color-primary);
+    font-size: 12px;
+
+    .delete {
+      display: none;
+    }
+
+    .coor {
       position: absolute;
-      border: 1px solid var(--el-color-primary);
-      cursor: move;
-      background: var(--t-bg-active);
-      opacity: 0.8;
-      color: var(--el-color-primary);
-      font-size: 12px;
+      right: 0;
+      bottom: 0;
+      z-index: 11;
+      width: 10px;
+      height: 10px;
+      background: var(--el-color-primary);
+      cursor: se-resize;
+    }
+
+    &:hover {
       .delete {
-        display: none;
-      }
-      .coor {
+        display: block;
         position: absolute;
-        right: 0;
-        bottom: 0;
-        z-index: 11;
-        width: 10px;
-        height: 10px;
+        top: -1px;
+        right: -1px;
+        width: 16px;
+        height: 16px;
         background: var(--el-color-primary);
-        cursor: se-resize;
-      }
-
-      &:hover {
-        .delete {
-          display: block;
-          position: absolute;
-          top: -1px;
-          right: -1px;
-          width: 16px;
-          height: 16px;
-          background: var(--el-color-primary);
-          border-radius: 0 0 0 100%;
-          cursor: pointer;
-          font-size: 12px;
-          color: #ffffff;
-          text-align: right;
-        }
+        border-radius: 0 0 0 100%;
+        cursor: pointer;
+        font-size: 12px;
+        color: #ffffff;
+        text-align: right;
       }
     }
   }
+}
 </style>

+ 31 - 36
src/app/shop/admin/decorate/page/component/center/comp/hotzone/index.vue

@@ -1,49 +1,44 @@
 <template>
-  <div
-    class="hotzone"
-    :style="{
-      height: compData.style.height + 'px',
-      'border-top-left-radius': compData.data.borderRadiusTop + 'px',
-      'border-top-right-radius': compData.data.borderRadiusTop + 'px',
-      'border-bottom-left-radius': compData.data.borderRadiusBottom + 'px',
-      'border-bottom-right-radius': compData.data.borderRadiusBottom + 'px',
-    }"
-  >
+  <div class="hotzone" :style="{
+    height: compData.style.height + 'px',
+    'border-top-left-radius': compData.data.borderRadiusTop + 'px',
+    'border-top-right-radius': compData.data.borderRadiusTop + 'px',
+    'border-bottom-left-radius': compData.data.borderRadiusBottom + 'px',
+    'border-bottom-right-radius': compData.data.borderRadiusBottom + 'px',
+  }">
     <sa-image :url="compData.data.src" fit="fill" radius="0" :suffix="null"></sa-image>
-    <div
-      class="map-item sa-flex sa-row-center"
-      v-for="item in compData.data.list"
-      :style="{
-        width: `${item.width / 2}px`,
-        height: `${item.height / 2}px`,
-        top: `${item.top / 2}px`,
-        left: `${item.left / 2}px`,
-      }"
-    >
+    <div class="map-item sa-flex sa-row-center" v-for="item in compData.data.list" :style="{
+      width: `${item.width / 2}px`,
+      height: `${item.height / 2}px`,
+      top: `${item.top / 2}px`,
+      left: `${item.left / 2}px`,
+    }">
       {{ item.name }}
     </div>
   </div>
 </template>
 
 <script setup>
-  const props = defineProps(['compData']);
+const props = defineProps(['compData']);
 </script>
 
 <style lang="scss" scoped>
-  .hotzone {
-    overflow: hidden;
-    position: reactive;
-    .sa-image {
-      width: 100%;
-      min-height: 30px;
-    }
-    .map-item {
-      position: absolute;
-      border: 1px solid var(--el-color-primary);
-      background: var(--t-bg-active);
-      opacity: 0.8;
-      color: var(--el-color-primary);
-      font-size: 12px;
-    }
+.hotzone {
+  overflow: hidden;
+  position: reactive;
+
+  .sa-image {
+    width: 100%;
+    min-height: 30px;
+  }
+
+  .map-item {
+    position: absolute;
+    border: 1px solid var(--el-color-primary);
+    background: var(--t-bg-active);
+    opacity: 0.8;
+    color: var(--el-color-primary);
+    font-size: 12px;
   }
+}
 </style>

+ 8 - 4
src/app/shop/admin/decorate/page/component/center/comp/hotzone/setting.vue

@@ -2,15 +2,16 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">添加图片</div>
+        <div class="title">{{ t('modules.decorate.addImage') }}</div>
         <div class="content">
-          <el-form-item label="上传图片">
+          <el-form-item :label="t('modules.decorate.uploadImageLabel')">
             <div class="sa-flex">
               <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
                 :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
-          <el-button v-if="settingData.data.src" class="add-button" icon="Plus" @click="onSetHotzone">设置热区</el-button>
+          <el-button v-if="settingData.data.src" class="add-button" icon="Plus" @click="onSetHotzone">{{
+            t('modules.decorate.setHotzone') }}</el-button>
         </div>
       </div>
     </template>
@@ -19,9 +20,12 @@
 
 <script setup>
 import { watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useModal } from '@/sheep/hooks';
 import Edit from './edit.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 
 watch(
@@ -34,7 +38,7 @@ watch(
 function onSetHotzone() {
   useModal(
     Edit,
-    { title: '设置热区', data: props.settingData.data },
+    { title: t('modules.decorate.setHotzone'), data: props.settingData.data },
     {
       confirm: (res) => {
         props.settingData.data.list = res.data;

+ 19 - 16
src/app/shop/admin/decorate/page/component/center/comp/imageBanner/setting.vue

@@ -2,9 +2,9 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">样式设置</div>
+        <div class="title">{{ t('modules.decorate.styleSettings') }}</div>
         <div class="content">
-          <!-- <el-form-item label="选择样式">
+          <!-- <el-form-item :label="t('modules.decorate.selectStyle')">
             <el-radio-group class="custom-radio-button" v-model="settingData.data.mode">
               <el-radio-button :label="1">
                 <sa-icon icon="sa-shop-decorate-imageBanner-mode-1" />
@@ -14,7 +14,7 @@
               </el-radio-button>
             </el-radio-group>
           </el-form-item> -->
-          <el-form-item label="Dot样式">
+          <el-form-item :label="t('modules.decorate.dotStyle')">
             <el-radio-group class="custom-radio-button" v-model="settingData.data.indicator">
               <el-radio-button :label="1">
                 <sa-icon icon="sa-shop-decorate-imageBanner-indicator-1" />
@@ -24,13 +24,13 @@
               </el-radio-button>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="间距">
+          <el-form-item :label="t('modules.decorate.spacing')">
             <dc-slider v-model="settingData.data.space"></dc-slider>
           </el-form-item>
-          <el-form-item label="是否轮播">
+          <el-form-item :label="t('modules.decorate.autoPlay')">
             <el-switch v-model="settingData.data.autoplay" />
           </el-form-item>
-          <el-form-item v-if="settingData.data.autoplay" label="时间间隔">
+          <el-form-item v-if="settingData.data.autoplay" :label="t('modules.decorate.timeInterval')">
             <el-input v-model="settingData.data.interval">
               <template #append>ms</template>
             </el-input>
@@ -38,27 +38,27 @@
         </div>
       </div>
       <dc-list v-model="settingData.data.list" :itemProp="{ title: '', type: 'image', src: '', poster: '', url: '' }">
-        <template #title>图片上传</template>
+        <template #title>{{ t('modules.decorate.imageUpload') }}</template>
         <template #listItem="{ element }">
-          <el-form-item label="标题">
-            <el-input v-model="element.title" placeholder="请输入标题"></el-input>
+          <el-form-item :label="t('modules.decorate.titleText')">
+            <el-input v-model="element.title" :placeholder="t('modules.decorate.pleaseInputTitle')"></el-input>
           </el-form-item>
-          <el-form-item label="选择类型">
+          <el-form-item :label="t('modules.decorate.selectType')">
             <el-radio-group v-model="element.type">
-              <el-radio label="image">图片</el-radio>
-              <el-radio label="video">视频</el-radio>
+              <el-radio label="image">{{ t('modules.decorate.image') }}</el-radio>
+              <el-radio label="video">{{ t('modules.decorate.video') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="上传">
+          <el-form-item :label="t('modules.decorate.uploadLabel')">
             <sa-upload-image v-if="element.type == 'image'" v-model="element.src" :max-count="1"
               :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
-            <el-input v-else v-model="element.src" placeholder="请输入视频链接" />
+            <el-input v-else v-model="element.src" :placeholder="t('modules.decorate.pleaseInputVideoUrl')" />
           </el-form-item>
-          <el-form-item v-if="element.type == 'video'" label="视频封面">
+          <el-form-item v-if="element.type == 'video'" :label="t('modules.decorate.videoCover')">
             <sa-upload-image v-model="element.poster" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
               :direct-upload="true" :size="100" />
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="element.url"></dc-url>
           </el-form-item>
         </template>
@@ -68,9 +68,12 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dcList from '../../common/dc-list.vue';
 import dcUrl from '../../common/dc-url.vue';
 import dcSlider from '../../common/dc-slider.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 </script>

+ 5 - 3
src/app/shop/admin/decorate/page/component/center/comp/imageBlock/setting.vue

@@ -2,15 +2,15 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">添加图片</div>
+        <div class="title">{{ t('modules.decorate.addImageTitle') }}</div>
         <div class="content">
-          <el-form-item label="上传图片">
+          <el-form-item :label="t('modules.decorate.uploadImageLabel')">
             <div class="sa-flex">
               <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
                 :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="settingData.data.url"></dc-url>
           </el-form-item>
         </div>
@@ -20,7 +20,9 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dcUrl from '../../common/dc-url.vue';
 
+const { t } = useI18n();
 const props = defineProps(['settingData', 'tabType']);
 </script>

+ 15 - 11
src/app/shop/admin/decorate/page/component/center/comp/imageCube/setting.vue

@@ -2,18 +2,19 @@
   <div class="image-cube-setting setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">魔方样式 <div class="tip">每格尺寸:187*187</div>
+        <div class="title">{{ t('modules.decorate.cubeStyle') }} <div class="tip">{{ t('modules.decorate.gridSize187')
+        }}</div>
         </div>
         <div class="content">
           <table class="image-cube-map">
             <tbody>
               <tr v-for="trItem in mapList" :key="trItem">
                 <td class="map-item" :class="activeData.minRow <= trItem &&
-                    trItem <= activeData.maxRow &&
-                    activeData.minCol <= tdItem &&
-                    tdItem <= activeData.maxCol
-                    ? 'map-item--active'
-                    : ''
+                  trItem <= activeData.maxRow &&
+                  activeData.minCol <= tdItem &&
+                  tdItem <= activeData.maxCol
+                  ? 'map-item--active'
+                  : ''
                   " :style="{
                     width: scale + 'px',
                     height: scale + 'px',
@@ -40,21 +41,21 @@
             </tbody>
           </table>
           <template v-if="selected.data.length > 0">
-            <el-form-item label="上传图片">
+            <el-form-item :label="t('modules.decorate.uploadImageLabel')">
               <sa-upload-image v-model="selected.data[selected.active].src" :max-count="1"
                 :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
             </el-form-item>
-            <el-form-item label="链接">
+            <el-form-item :label="t('modules.decorate.linkLabel')">
               <dc-url v-model="selected.data[selected.active].url"></dc-url>
             </el-form-item>
           </template>
-          <el-form-item label="上圆角">
+          <el-form-item :label="t('modules.decorate.topRadius')">
             <dc-slider v-model="settingData.data.borderRadiusTop"></dc-slider>
           </el-form-item>
-          <el-form-item label="下圆角">
+          <el-form-item :label="t('modules.decorate.bottomRadius')">
             <dc-slider v-model="settingData.data.borderRadiusBottom"></dc-slider>
           </el-form-item>
-          <el-form-item label="间距">
+          <el-form-item :label="t('modules.decorate.spacing')">
             <dc-slider v-model="settingData.data.space"></dc-slider>
           </el-form-item>
         </div>
@@ -65,10 +66,13 @@
 
 <script setup>
 import { reactive, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { cloneDeep } from 'lodash';
 import dcSlider from '../../common/dc-slider.vue';
 import dcUrl from '../../common/dc-url.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 
 watch(() => props.settingData, () => {

+ 8 - 5
src/app/shop/admin/decorate/page/component/center/comp/lineBlock/setting.vue

@@ -2,9 +2,9 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">内容设置</div>
+        <div class="title">{{ t('modules.decorate.contentSettings') }}</div>
         <div class="content">
-          <el-form-item label="选择风格">
+          <el-form-item :label="t('modules.decorate.styleSettings')">
             <el-radio-group class="custom-radio-button" v-model="settingData.data.mode">
               <el-radio-button label="solid">
                 <sa-icon icon="sa-shop-decorate-lineBlock-mode-1" />
@@ -17,7 +17,7 @@
               </el-radio-button>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="线条颜色">
+          <el-form-item :label="t('modules.decorate.lineColor')">
             <dc-color-picker v-model="settingData.data.lineColor"></dc-color-picker>
           </el-form-item>
         </div>
@@ -27,7 +27,10 @@
 </template>
 
 <script setup>
-  import dcColorPicker from '../../common/dc-color-picker.vue';
+import { useI18n } from 'vue-i18n';
+import dcColorPicker from '../../common/dc-color-picker.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const { t } = useI18n();
+
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 26 - 22
src/app/shop/admin/decorate/page/component/center/comp/menuButton/setting.vue

@@ -2,25 +2,25 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">样式选择</div>
+        <div class="title">{{ t('modules.decorate.styleSettings') }}</div>
         <div class="content">
-          <el-form-item label="菜单布局">
+          <el-form-item :label="t('modules.decorate.menuLayout')">
             <el-radio-group v-model="settingData.data.layout">
-              <el-radio :label="1">图片+文字</el-radio>
-              <el-radio :label="2">图片</el-radio>
+              <el-radio :label="1">{{ t('modules.decorate.imageAndText') }}</el-radio>
+              <el-radio :label="2">{{ t('modules.decorate.imageOnly') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="列数">
+          <el-form-item :label="t('modules.decorate.columnCount')">
             <el-radio-group v-model="settingData.data.col">
-              <el-radio :label="3">3</el-radio>
-              <el-radio :label="4">4</el-radio>
-              <el-radio :label="5">5</el-radio>
+              <el-radio :label="3">3{{ t('modules.decorate.items') }}</el-radio>
+              <el-radio :label="4">4{{ t('modules.decorate.items') }}</el-radio>
+              <el-radio :label="5">5{{ t('modules.decorate.items') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="行数">
+          <el-form-item :label="t('modules.decorate.rowCount')">
             <el-radio-group v-model="settingData.data.row">
-              <el-radio :label="1">1</el-radio>
-              <el-radio :label="2">2</el-radio>
+              <el-radio :label="1">1{{ t('modules.decorate.row') }}</el-radio>
+              <el-radio :label="2">2{{ t('modules.decorate.row') }}</el-radio>
             </el-radio-group>
           </el-form-item>
         </div>
@@ -39,30 +39,31 @@
           bgColor: '#FF6000',
         },
       }">
-        <template #title>图标设置</template>
+        <template #title>{{ t('modules.decorate.menuIcon') }}</template>
         <template #listItem="{ element }">
-          <el-form-item label="图标">
+          <el-form-item :label="t('modules.decorate.menuIcon')">
             <div class="sa-flex">
               <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
                 :direct-upload="true" :size="100" />
-              <div class="tip">建议尺寸:98*98</div>
+              <div class="tip">{{ t('modules.decorate.suggestedSize98') }}</div>
             </div>
           </el-form-item>
-          <el-form-item label="标题">
-            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
+          <el-form-item :label="t('modules.decorate.titleText')">
+            <dc-text-color v-model="element.title" :placeholder="t('modules.decorate.pleaseInputTitle')" maxlength="4"
+              show-word-limit></dc-text-color>
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="element.url"></dc-url>
           </el-form-item>
-          <el-form-item label="标签">
+          <el-form-item :label="t('modules.decorate.badge')">
             <el-switch v-model="element.badge.show" :active-value="1" :inactive-value="0" />
           </el-form-item>
           <template v-if="element.badge.show">
-            <el-form-item label="标签内容">
-              <dc-text-color v-model="element.badge" placeholder="请输入标签内容" maxlength="4"
-                show-word-limit></dc-text-color>
+            <el-form-item :label="t('modules.decorate.badgeContent')">
+              <dc-text-color v-model="element.badge" :placeholder="t('modules.decorate.pleaseInputBadgeContent')"
+                maxlength="4" show-word-limit></dc-text-color>
             </el-form-item>
-            <el-form-item label="标签背景">
+            <el-form-item :label="t('modules.decorate.badgeBackground')">
               <dc-color-picker v-model="element.badge.bgColor"></dc-color-picker>
             </el-form-item>
           </template>
@@ -73,10 +74,13 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dcColorPicker from '../../common/dc-color-picker.vue';
 import dcList from '../../common/dc-list.vue';
 import dcUrl from '../../common/dc-url.vue';
 import dcTextColor from '../../common/dc-text-color.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 </script>

+ 22 - 17
src/app/shop/admin/decorate/page/component/center/comp/menuGrid/setting.vue

@@ -2,12 +2,12 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">样式选择</div>
+        <div class="title">{{ t('modules.decorate.styleSettings') }}</div>
         <div class="content">
-          <el-form-item label="每行数量">
+          <el-form-item :label="t('modules.decorate.itemsPerRow')">
             <el-radio-group v-model="settingData.data.col">
-              <el-radio :label="3">3</el-radio>
-              <el-radio :label="4">4</el-radio>
+              <el-radio :label="3">3{{ t('modules.decorate.items') }}</el-radio>
+              <el-radio :label="4">4{{ t('modules.decorate.items') }}</el-radio>
             </el-radio-group>
           </el-form-item>
           <!-- <el-form-item label="边框">
@@ -37,33 +37,35 @@
           bgColor: '#FF6000',
         },
       }">
-        <template #title>图标设置</template>
+        <template #title>{{ t('modules.decorate.menuIcon') }}</template>
         <template #listItem="{ element }">
-          <el-form-item label="图标">
+          <el-form-item :label="t('modules.decorate.menuIcon')">
             <div class="sa-flex">
               <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
                 :direct-upload="true" :size="100" />
-              <div class="tip">建议尺寸:44*44</div>
+              <div class="tip">{{ t('modules.decorate.suggestedSize44') }}</div>
             </div>
           </el-form-item>
-          <el-form-item label="标题">
-            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
+          <el-form-item :label="t('modules.decorate.titleText')">
+            <dc-text-color v-model="element.title" :placeholder="t('modules.decorate.pleaseInputTitle')" maxlength="4"
+              show-word-limit></dc-text-color>
           </el-form-item>
-          <el-form-item label="功能提示">
-            <dc-text-color v-model="element.tip" placeholder="请输入功能提示" maxlength="4" show-word-limit></dc-text-color>
+          <el-form-item :label="t('modules.decorate.functionTip')">
+            <dc-text-color v-model="element.tip" :placeholder="t('modules.decorate.pleaseInputFunctionTip')"
+              maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="element.url"></dc-url>
           </el-form-item>
-          <el-form-item label="标签">
+          <el-form-item :label="t('modules.decorate.badge')">
             <el-switch v-model="element.badge.show" :active-value="1" :inactive-value="0" />
           </el-form-item>
           <template v-if="element.badge.show">
-            <el-form-item label="标签内容">
-              <dc-text-color v-model="element.badge" placeholder="请输入标签内容" maxlength="4"
-                show-word-limit></dc-text-color>
+            <el-form-item :label="t('modules.decorate.badgeContent')">
+              <dc-text-color v-model="element.badge" :placeholder="t('modules.decorate.pleaseInputBadgeContent')"
+                maxlength="4" show-word-limit></dc-text-color>
             </el-form-item>
-            <el-form-item label="标签背景">
+            <el-form-item :label="t('modules.decorate.badgeBackground')">
               <dc-color-picker v-model="element.badge.bgColor"></dc-color-picker>
             </el-form-item>
           </template>
@@ -74,10 +76,13 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dcColorPicker from '../../common/dc-color-picker.vue';
 import dcList from '../../common/dc-list.vue';
 import dcUrl from '../../common/dc-url.vue';
 import dcTextColor from '../../common/dc-text-color.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 </script>

+ 13 - 8
src/app/shop/admin/decorate/page/component/center/comp/menuList/setting.vue

@@ -13,22 +13,24 @@
         },
         url: '',
       }">
-        <template #title>菜单设置</template>
+        <template #title>{{ t('modules.decorate.menuSettings') }}</template>
         <template #listItem="{ element }">
-          <el-form-item label="图标">
+          <el-form-item :label="t('modules.decorate.menuIcon')">
             <div class="sa-flex">
               <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
                 :direct-upload="true" :size="100" />
-              <div class="tip">建议尺寸:44*44</div>
+              <div class="tip">{{ t('modules.decorate.suggestedSize44') }}</div>
             </div>
           </el-form-item>
-          <el-form-item label="标题">
-            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
+          <el-form-item :label="t('modules.decorate.titleText')">
+            <dc-text-color v-model="element.title" :placeholder="t('modules.decorate.pleaseInputTitle')" maxlength="4"
+              show-word-limit></dc-text-color>
           </el-form-item>
-          <el-form-item label="功能提示">
-            <dc-text-color v-model="element.tip" placeholder="请输入功能提示" maxlength="4" show-word-limit></dc-text-color>
+          <el-form-item :label="t('modules.decorate.functionTip')">
+            <dc-text-color v-model="element.tip" :placeholder="t('modules.decorate.pleaseInputFunctionTip')"
+              maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="element.url"></dc-url>
           </el-form-item>
         </template>
@@ -38,10 +40,13 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
 import dcList from '../../common/dc-list.vue';
 import dcUrl from '../../common/dc-url.vue';
 import dcColorPicker from '../../common/dc-color-picker.vue';
 import dcTextColor from '../../common/dc-text-color.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 </script>

+ 14 - 10
src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/setting.vue

@@ -2,15 +2,15 @@
   <div class="notice-block-setting setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">公告图标</div>
+        <div class="title">{{ t('modules.decorate.noticeIcon') }}</div>
         <div class="content">
-          <el-form-item label="公告样式">
+          <el-form-item :label="t('modules.decorate.noticeStyle')">
             <el-radio-group v-model="settingData.data.mode">
-              <el-radio :label="1">系统图标</el-radio>
-              <el-radio :label="2">自定义</el-radio>
+              <el-radio :label="1">{{ t('modules.decorate.systemIcon') }}</el-radio>
+              <el-radio :label="2">{{ t('modules.decorate.customIcon') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item v-if="settingData.data.mode == 1" label="公告图">
+          <el-form-item v-if="settingData.data.mode == 1" :label="t('modules.decorate.noticeImage')">
             <div class="sa-flex">
               <sa-image class="notice" :class="[
                 'notice-' + (nlindex + 1),
@@ -19,7 +19,7 @@
               </sa-image>
             </div>
           </el-form-item>
-          <el-form-item v-if="settingData.data.mode == 2" label="自定义图片">
+          <el-form-item v-if="settingData.data.mode == 2" :label="t('modules.decorate.customImage')">
             <div class="sa-flex">
               <sa-upload-image v-model="settingData.data.customSrc" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
                 :max-size="5" :direct-upload="true" :size="100" />
@@ -28,12 +28,13 @@
         </div>
       </div>
       <div class="card">
-        <div class="title">内容设置</div>
+        <div class="title">{{ t('modules.decorate.contentSettings') }}</div>
         <div class="content">
-          <el-form-item label="公告内容">
-            <dc-text-color v-model="settingData.data.title" placeholder="请输入公告内容"></dc-text-color>
+          <el-form-item :label="t('modules.decorate.noticeContent')">
+            <dc-text-color v-model="settingData.data.title"
+              :placeholder="t('modules.decorate.pleaseInputNotice')"></dc-text-color>
           </el-form-item>
-          <el-form-item label="链接">
+          <el-form-item :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="settingData.data.url"></dc-url>
           </el-form-item>
         </div>
@@ -44,11 +45,14 @@
 
 <script setup>
 import { computed, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
 import dcColorPicker from '../../common/dc-color-picker.vue';
 import dcUrl from '../../common/dc-url.vue';
 import dcTextColor from '../../common/dc-text-color.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 
 const noticeList = [

+ 7 - 4
src/app/shop/admin/decorate/page/component/center/comp/richtext/richtext-editor.vue

@@ -2,15 +2,15 @@
   <el-container class="richtext-editor-container">
     <el-main>
       <el-form :model="form" ref="formRef" label-width="80px">
-        <el-form-item label="富文本内容">
+        <el-form-item :label="t('modules.decorate.richTextContent')">
           <sa-editor v-model:content="form.content" :direct-upload="true" />
         </el-form-item>
       </el-form>
     </el-main>
 
     <el-footer class="sa-footer--submit">
-      <el-button @click="cancel">取消</el-button>
-      <el-button type="primary" @click="confirm">确定</el-button>
+      <el-button @click="cancel">{{ t('common.cancel') }}</el-button>
+      <el-button type="primary" @click="confirm">{{ t('common.confirm') }}</el-button>
     </el-footer>
   </el-container>
 </template>
@@ -18,8 +18,11 @@
 <script setup>
 import { reactive, ref, onMounted, nextTick } from 'vue';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 import saEditor from '@/sheep/components/sa-editor/sa-editor.vue';
 
+const { t } = useI18n();
+
 const emit = defineEmits(['modalCallBack']);
 const props = defineProps({
   modal: {
@@ -46,7 +49,7 @@ onMounted(() => {
 // 确认保存
 const confirm = () => {
   if (!form.content || !form.content.trim()) {
-    ElMessage.warning('请输入富文本内容');
+    ElMessage.warning(t('modules.decorate.pleaseInputRichText'));
     return;
   }
 

+ 17 - 12
src/app/shop/admin/decorate/page/component/center/comp/richtext/setting.vue

@@ -2,9 +2,9 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">富文本内容</div>
+        <div class="title">{{ t('modules.decorate.richTextContent') }}</div>
         <div class="content">
-          <el-form-item label="富文本内容">
+          <el-form-item :label="t('modules.decorate.richTextContent')">
             <div class="richtext-preview-container">
               <!-- 内容预览 -->
               <div class="content-preview" v-if="settingData.data.content" v-html="settingData.data.content"></div>
@@ -12,19 +12,21 @@
                 <el-icon size="24">
                   <Document />
                 </el-icon>
-                <span>暂无富文本内容</span>
+                <span>{{ t('modules.decorate.noRichTextContent') }}</span>
               </div>
 
               <!-- 编辑按钮 -->
               <div class="edit-actions">
-                <el-button type="primary" @click="openEditor" icon="Edit">编辑富文本内容</el-button>
-                <el-button v-if="settingData.data.content" @click="clearContent" icon="Delete">清空内容</el-button>
+                <el-button type="primary" @click="openEditor" icon="Edit">{{ t('modules.decorate.editRichText')
+                  }}</el-button>
+                <el-button v-if="settingData.data.content" @click="clearContent" icon="Delete">{{
+                  t('modules.decorate.clearContent') }}</el-button>
               </div>
             </div>
           </el-form-item>
 
           <div class="form-tip">
-            点击"编辑富文本内容"按钮在弹窗中编辑,支持完整的富文本编辑功能
+            {{ t('modules.decorate.clickToEdit') }}
           </div>
         </div>
       </div>
@@ -37,8 +39,11 @@ import { ref, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Document, Edit, Delete } from '@element-plus/icons-vue';
 import { useModal } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
 import RichtextEditor from './richtext-editor.vue';
 
+const { t } = useI18n();
+
 const props = defineProps(['settingData', 'tabType']);
 
 // 初始化默认内容
@@ -55,7 +60,7 @@ const openEditor = () => {
   useModal(
     RichtextEditor,
     {
-      title: '编辑富文本内容',
+      title: t('modules.decorate.editRichText'),
       width: '80%',
       content: props.settingData.data.content || '',
     },
@@ -65,7 +70,7 @@ const openEditor = () => {
         const content = result.data?.content || result.content;
         props.settingData.data.content = content;
         console.log('更新后的内容:', props.settingData.data.content);
-        ElMessage.success('富文本内容已更新');
+        ElMessage.success(t('modules.decorate.richTextUpdated'));
       },
     }
   );
@@ -73,13 +78,13 @@ const openEditor = () => {
 
 // 清空内容
 const clearContent = () => {
-  ElMessageBox.confirm('确定要清空富文本内容吗?', '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
+  ElMessageBox.confirm(t('modules.decorate.confirmClearContent'), t('common.tip'), {
+    confirmButtonText: t('common.confirm'),
+    cancelButtonText: t('common.cancel'),
     type: 'warning',
   }).then(() => {
     props.settingData.data.content = '';
-    ElMessage.success('内容已清空');
+    ElMessage.success(t('modules.decorate.contentCleared'));
   }).catch(() => {
     // 用户取消
   });

+ 60 - 61
src/app/shop/admin/decorate/page/component/center/comp/searchBlock/setting.vue

@@ -2,40 +2,30 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">默认文字</div>
+        <div class="title">{{ t('modules.decorate.defaultText') }}</div>
         <div class="content">
-          <el-form-item label="提示内容">
-            <el-input
-              v-model="settingData.data.placeholder"
-              placeholder="请输入提示内容"
-            ></el-input>
+          <el-form-item :label="t('modules.decorate.placeholderContent')">
+            <el-input v-model="settingData.data.placeholder"
+              :placeholder="t('modules.decorate.pleaseInputPlaceholder')"></el-input>
           </el-form-item>
-          <el-form-item label="圆角">
+          <el-form-item :label="t('modules.decorate.componentBorderRadius')">
             <dc-slider v-model="settingData.data.borderRadius"></dc-slider>
           </el-form-item>
         </div>
       </div>
-      <dc-list
-        v-model="settingData.data.keywords"
-        :leng="3"
-        :itemProp="{
-          text: '',
-          color: '#8C8C8C',
-        }"
-      >
+      <dc-list v-model="settingData.data.keywords" :leng="3" :itemProp="{
+        text: '',
+        color: '#8C8C8C',
+      }">
         <template #title>
-          搜索关键字
-          <div class="warning">最多可创建三个</div>
+          {{ t('modules.decorate.searchKeywords') }}
+          <div class="warning">{{ t('modules.decorate.maxThreeItems') }}</div>
         </template>
         <template #listItem="{ element }">
-          <el-form-item label="关键字">
+          <el-form-item :label="t('modules.decorate.keyword')">
             <div class="dc-text-color sa-flex">
-              <el-input
-                v-model="element.text"
-                placeholder="请输入关键字"
-                maxlength="4"
-                show-word-limit
-              ></el-input>
+              <el-input v-model="element.text" :placeholder="t('modules.decorate.pleaseInputKeyword')" maxlength="4"
+                show-word-limit></el-input>
               <div class="color sa-flex">
                 <img class="empty" src="/static/images/shop/decorate/picker.png" />
                 <el-color-picker v-model="element.color" />
@@ -49,50 +39,59 @@
 </template>
 
 <script setup>
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcList from '../../common/dc-list.vue';
-  import dcSlider from '../../common/dc-slider.vue';
+import { useI18n } from 'vue-i18n';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcList from '../../common/dc-list.vue';
+import dcSlider from '../../common/dc-slider.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const { t } = useI18n();
+const props = defineProps(['settingData', 'tabType']);
 </script>
 <style lang="scss" scoped>
-  .dc-text-color {
-    width: 196px;
-    :deep() {
-      .el-input {
-        flex: 1;
-        .el-input__wrapper {
-          border-radius: 4px 0 0 4px;
-        }
+.dc-text-color {
+  width: 196px;
+
+  :deep() {
+    .el-input {
+      flex: 1;
+
+      .el-input__wrapper {
+        border-radius: 4px 0 0 4px;
       }
     }
-    :deep() {
-      .el-color-picker__trigger {
-        display: flex;
-        padding: 0;
-        border-radius: 0 4px 4px 0;
-        border-left: none;
-        overflow: hidden;
-        .el-color-picker__color {
-          border: none;
-        }
-        .el-color-picker__empty {
-          display: none;
-        }
+  }
+
+  :deep() {
+    .el-color-picker__trigger {
+      display: flex;
+      padding: 0;
+      border-radius: 0 4px 4px 0;
+      border-left: none;
+      overflow: hidden;
+
+      .el-color-picker__color {
+        border: none;
       }
-    }
-    .color {
-      position: relative;
-      .empty {
-        width: 32px;
-        height: 32px;
-        border-radius: 0 4px 4px 0;
-        position: absolute;
-        top: 0;
-        right: 0;
-        bottom: 0;
-        left: 0;
+
+      .el-color-picker__empty {
+        display: none;
       }
     }
   }
+
+  .color {
+    position: relative;
+
+    .empty {
+      width: 32px;
+      height: 32px;
+      border-radius: 0 4px 4px 0;
+      position: absolute;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+    }
+  }
+}
 </style>

+ 49 - 51
src/app/shop/admin/decorate/page/component/center/comp/titleBlock/index.vue

@@ -1,38 +1,30 @@
 <template>
   <div class="title-block">
     <div class="title-block-wrap">
-      <div
-        class="title"
-        :style="{
-          'align-items': props.compData.data.location == 'center' ? 'center' : 'flex-start',
-          'margin-left': props.compData.data.skew + 'px',
-        }"
-      >
-        <div
-          class="sa-m-b-6"
-          :style="{
-            'font-size': compData.data.title.textFontSize + 'px',
-            color: compData.data.title.color,
-            'font-weight': compData.data.title.other.includes('bold') ? 'bold' : '',
-            'font-style': compData.data.title.other.includes('italic') ? 'italic' : '',
-          }"
-        >
+      <div class="title" :style="{
+        'align-items': props.compData.data.location == 'center' ? 'center' : 'flex-start',
+        'margin-left': props.compData.data.skew + 'px',
+      }">
+        <div class="sa-m-b-6" :style="{
+          'font-size': compData.data.title.textFontSize + 'px',
+          color: compData.data.title.color,
+          'font-weight': compData.data.title.other.includes('bold') ? 'bold' : '',
+          'font-style': compData.data.title.other.includes('italic') ? 'italic' : '',
+        }">
           {{ compData.data.title.text }}
         </div>
-        <div
-          :style="{
-            'font-size': compData.data.subtitle.textFontSize + 'px',
-            color: compData.data.subtitle.color,
-            'font-weight': compData.data.subtitle.other.includes('bold') ? 'bold' : '',
-            'font-style': compData.data.subtitle.other.includes('italic') ? 'italic' : '',
-          }"
-        >
+        <div :style="{
+          'font-size': compData.data.subtitle.textFontSize + 'px',
+          color: compData.data.subtitle.color,
+          'font-weight': compData.data.subtitle.other.includes('bold') ? 'bold' : '',
+          'font-style': compData.data.subtitle.other.includes('italic') ? 'italic' : '',
+        }">
           {{ compData.data.subtitle.text }}
         </div>
       </div>
       <sa-image :url="compData.data.src" radius="0" :suffix="null"></sa-image>
       <div v-if="compData.data.more.show" class="more sa-flex">
-        更多
+        {{ t('modules.decorate.more') }}
         <el-icon>
           <ArrowRight />
         </el-icon>
@@ -42,36 +34,42 @@
 </template>
 
 <script setup>
-  const props = defineProps(['compData']);
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+const props = defineProps(['compData']);
 </script>
 
 <style lang="scss" scoped>
-  .title-block {
-    .title-block-wrap {
-      position: relative;
+.title-block {
+  .title-block-wrap {
+    position: relative;
+    height: 100%;
+
+    .title {
+      position: absolute;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      z-index: 8;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+    }
+
+    .sa-image {
       height: 100%;
-      .title {
-        position: absolute;
-        top: 0;
-        right: 0;
-        bottom: 0;
-        left: 0;
-        z-index: 8;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-      }
-      .sa-image {
-        height: 100%;
-      }
-      .more {
-        position: absolute;
-        right: 10px;
-        top: 0;
-        line-height: 40px;
-        font-size: 12px;
-        color: #999;
-      }
+    }
+
+    .more {
+      position: absolute;
+      right: 10px;
+      top: 0;
+      line-height: 40px;
+      font-size: 12px;
+      color: #999;
     }
   }
+}
 </style>

+ 10 - 7
src/app/shop/admin/decorate/page/component/center/comp/titleBlock/select.vue

@@ -6,7 +6,7 @@
           <div class="style-item sa-flex sa-row-center"
             :class="sl.src == props.modal.params.src ? 'style-item--active' : ''" @click="changeStyle(sl)">
             <sa-image :url="sl.src"></sa-image>
-            <div class="title sa-flex sa-row-center">标题栏</div>
+            <div class="title sa-flex sa-row-center">{{ t('modules.decorate.titleBar') }}</div>
             <div class="check-icon sa-flex sa-row-center">
               <el-icon>
                 <Check />
@@ -17,7 +17,7 @@
       </el-row>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button type="primary" @click="confirm">确 定</el-button>
+      <el-button type="primary" @click="confirm">{{ t('common.confirm') }}</el-button>
     </el-footer>
   </el-container>
 </template>
@@ -30,26 +30,29 @@ export default {
 
 <script setup>
 import { computed } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
 
+const { t } = useI18n();
+
 const emit = defineEmits(['modalCallBack']);
 const props = defineProps(['modal']);
 
-const activeStyle = computed(() => styleList.find((s) => s.src == props.modal.params.src));
-const styleList = [
+const activeStyle = computed(() => styleList.value.find((s) => s.src == props.modal.params.src));
+const styleList = computed(() => [
   {
     type: 1,
     src: checkUrl('/static/images/shop/decorate/title-1.png'),
     location: 'left',
     skew: 0,
     title: {
-      text: '标题栏111',
+      text: t('modules.decorate.titleBarSample'),
       textColor: '',
       textFontSize: 14,
       other: [],
     },
     subtitle: {
-      text: '标题栏',
+      text: t('modules.decorate.titleBar'),
       textColor: '',
       textFontSize: 12,
       other: [],
@@ -59,7 +62,7 @@ const styleList = [
       path: '',
     },
   },
-];
+]);
 function changeStyle(url) {
   props.modal.params.src = url.src;
 }

+ 80 - 78
src/app/shop/admin/decorate/page/component/center/comp/titleBlock/setting.vue

@@ -2,79 +2,78 @@
   <div class="title-block-setting setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">选择风格 <div class="tip">建议尺寸:750*80</div> </div>
+        <div class="title">{{ t('modules.decorate.styleSettings') }} <div class="tip">{{
+          t('modules.decorate.recommendedSize750x80') }}
+          </div>
+        </div>
         <div class="content">
-          <el-form-item label="标题样式">
-            <el-button @click="selectStyle">选择样式</el-button>
+          <el-form-item :label="t('modules.decorate.selectStyle')">
+            <el-button @click="selectStyle">{{ t('modules.decorate.selectStyle') }}</el-button>
           </el-form-item>
-          <el-form-item label="背景图片">
+          <el-form-item :label="t('modules.decorate.backgroundImage')">
             <!-- TODO: 选择图片 -->
             <sa-image :url="settingData.data.src" @click="selectImage"></sa-image>
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">显示设置</div>
+        <div class="title">{{ t('modules.decorate.displaySettings') }}</div>
         <div class="content">
-          <el-form-item label="位置">
+          <el-form-item :label="t('modules.decorate.displayPosition')">
             <el-radio-group v-model="settingData.data.location">
-              <el-radio label="left">居左</el-radio>
-              <el-radio label="center">居中</el-radio>
+              <el-radio label="left">{{ t('modules.decorate.leftAlign') }}</el-radio>
+              <el-radio label="center">{{ t('modules.decorate.centerAlign') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item label="偏移">
+          <el-form-item :label="t('modules.decorate.titleOffset')">
             <el-input-number v-model="settingData.data.skew" />
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">标题设置</div>
+        <div class="title">{{ t('modules.decorate.titleSettings') }}</div>
         <div class="content">
-          <el-form-item label="文字">
-            <dc-text-color
-              v-model="settingData.data.title"
-              placeholder="请输入文字"
-            ></dc-text-color>
+          <el-form-item :label="t('modules.decorate.titleText')">
+            <dc-text-color v-model="settingData.data.title"
+              :placeholder="t('modules.decorate.pleaseInputTitle')"></dc-text-color>
           </el-form-item>
-          <el-form-item label="字号">
+          <el-form-item :label="t('modules.decorate.textSize')">
             <dc-slider v-model="settingData.data.title.textFontSize"></dc-slider>
           </el-form-item>
-          <el-form-item label="其他">
+          <el-form-item :label="t('modules.decorate.other')">
             <el-checkbox-group v-model="settingData.data.title.other">
-              <el-checkbox label="bold">加粗</el-checkbox>
-              <el-checkbox label="italic">倾斜</el-checkbox>
+              <el-checkbox label="bold">{{ t('modules.decorate.bold') }}</el-checkbox>
+              <el-checkbox label="italic">{{ t('modules.decorate.italic') }}</el-checkbox>
             </el-checkbox-group>
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">副标题设置</div>
-        <el-form-item label="文字">
-          <dc-text-color
-            v-model="settingData.data.subtitle"
-            placeholder="请输入文字"
-          ></dc-text-color>
+        <div class="title">{{ t('modules.decorate.subtitleSettings') }}</div>
+        <el-form-item :label="t('modules.decorate.subtitleText')">
+          <dc-text-color v-model="settingData.data.subtitle"
+            :placeholder="t('modules.decorate.pleaseInputSubtitle')"></dc-text-color>
         </el-form-item>
-        <el-form-item label="字号">
+        <el-form-item :label="t('modules.decorate.textSize')">
           <dc-slider v-model="settingData.data.subtitle.textFontSize"></dc-slider>
         </el-form-item>
-        <el-form-item label="其他">
+        <el-form-item :label="t('modules.decorate.other')">
           <el-checkbox-group v-model="settingData.data.subtitle.other">
-            <el-checkbox label="bold">加粗</el-checkbox>
-            <el-checkbox label="italic">倾斜</el-checkbox>
+            <el-checkbox label="bold">{{ t('modules.decorate.bold') }}</el-checkbox>
+            <el-checkbox label="italic">{{ t('modules.decorate.italic') }}</el-checkbox>
           </el-checkbox-group>
         </el-form-item>
       </div>
       <div class="card">
-        <div class="title">更多设置</div>
+        <div class="title">{{ t('modules.decorate.moreSettings') }}</div>
         <div class="content">
-          <el-form-item label="显示更多">
+          <el-form-item :label="t('modules.decorate.displayMore')">
             <el-radio-group v-model="settingData.data.more.show">
-              <el-radio :label="0">不显示</el-radio>
-              <el-radio :label="1">显示</el-radio>
+              <el-radio :label="0">{{ t('modules.decorate.notDisplay') }}</el-radio>
+              <el-radio :label="1">{{ t('modules.decorate.display') }}</el-radio>
             </el-radio-group>
           </el-form-item>
-          <el-form-item v-if="settingData.data.more.show" label="链接">
+          <el-form-item v-if="settingData.data.more.show" :label="t('modules.decorate.linkLabel')">
             <dc-url v-model="settingData.data.more.url"></dc-url>
           </el-form-item>
         </div>
@@ -84,56 +83,59 @@
 </template>
 
 <script setup>
-  import { useModal } from '@/sheep/hooks';
-  import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
+import { useModal } from '@/sheep/hooks';
+import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
+import { useI18n } from 'vue-i18n';
 
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcSlider from '../../common/dc-slider.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
-  import TitleBlockSelect from './select.vue';
-  const props = defineProps(['settingData', 'tabType']);
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcSlider from '../../common/dc-slider.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
+import TitleBlockSelect from './select.vue';
 
-  function selectStyle() {
-    useModal(
-      TitleBlockSelect,
-      {
-        title: '风格选择',
-        src: props.settingData.data.src,
-      },
-      {
-        confirm: (res) => {
-          props.settingData.data.src = res.data.src;
-          for (var key in props.settingData.data) {
-            props.settingData.data[key] = res.data[key];
-          }
-        },
-      },
-    );
-  }
-  function selectImage() {
-    useFile(
-      {
-        fileType: 'image',
+const { t } = useI18n();
+const props = defineProps(['settingData', 'tabType']);
+
+function selectStyle() {
+  useModal(
+    TitleBlockSelect,
+    {
+      title: t('modules.decorate.selectStyle'),
+      src: props.settingData.data.src,
+    },
+    {
+      confirm: (res) => {
+        props.settingData.data.src = res.data.src;
+        for (var key in props.settingData.data) {
+          props.settingData.data[key] = res.data[key];
+        }
       },
-      {
-        confirm: (data) => {
-          props.settingData.data.src = data.url;
-        },
+    },
+  );
+}
+function selectImage() {
+  useFile(
+    {
+      fileType: 'image',
+    },
+    {
+      confirm: (data) => {
+        props.settingData.data.src = data.url;
       },
-    );
-  }
+    },
+  );
+}
 </script>
 
 <style lang="scss" scoped>
-  .title-block-setting {
-    .sa-image {
-      width: 100%;
-      height: 32px;
-      line-height: 32px;
-      text-align: center;
-      border-radius: 4px;
-      border: 1px solid var(--sa-border);
-    }
+.title-block-setting {
+  .sa-image {
+    width: 100%;
+    height: 32px;
+    line-height: 32px;
+    text-align: center;
+    border-radius: 4px;
+    border: 1px solid var(--sa-border);
   }
+}
 </style>

+ 10 - 7
src/app/shop/admin/decorate/page/component/center/comp/videoPlayer/setting.vue

@@ -2,22 +2,22 @@
   <div class="setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">内容设置</div>
+        <div class="title">{{ t('modules.decorate.contentSettings') }}</div>
         <div class="content">
-          <el-form-item label="上传视频">
+          <el-form-item :label="t('modules.decorate.uploadVideo')">
             <div class="sa-flex">
               <sa-upload-video v-model="settingData.data.videoUrl" :max-count="1"
                 :accept="['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm']" :max-size="100" :direct-upload="true" :size="120"
-                placeholder="点击上传视频" />
+                :placeholder="t('modules.decorate.clickUploadVideo')" />
             </div>
-            <div class="form-tip">支持 MP4、AVI、MOV、WMV、FLV、WebM 格式,文件大小不超过100MB</div>
+            <div class="form-tip">{{ t('modules.decorate.videoFormatTip') }}</div>
           </el-form-item>
-          <el-form-item label="视频封面">
+          <el-form-item :label="t('modules.decorate.videoCover')">
             <div class="sa-flex">
               <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
-                :max-size="5" :direct-upload="true" :size="100" placeholder="上传视频封面" />
+                :max-size="5" :direct-upload="true" :size="100" :placeholder="t('modules.decorate.uploadVideoCover')" />
             </div>
-            <div class="form-tip">视频封面图片,建议尺寸:16:9 比例</div>
+            <div class="form-tip">{{ t('modules.decorate.videoCoverTip') }}</div>
           </el-form-item>
         </div>
       </div>
@@ -26,6 +26,9 @@
 </template>
 
 <script setup>
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 const props = defineProps(['settingData', 'tabType']);
 </script>
 

+ 29 - 29
src/app/shop/admin/decorate/page/component/center/page/setting.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="setting">
     <div class="card">
-      <div class="title">页面背景</div>
+      <div class="title">{{ t('decorate.pageBackground') }}</div>
       <div class="content">
-        <el-form-item label="背景色">
+        <el-form-item :label="t('decorate.backgroundColor')">
           <dc-color-picker v-model="settingData.background.color"></dc-color-picker>
         </el-form-item>
-        <el-form-item label="背景图片">
+        <el-form-item :label="t('decorate.backgroundImage')">
           <div class="sa-flex">
             <sa-upload-image v-model="settingData.background.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
               :max-size="5" :direct-upload="true" :size="100" />
@@ -15,15 +15,15 @@
       </div>
     </div>
     <div class="card">
-      <div class="title">头部设置</div>
+      <div class="title">{{ t('decorate.headerSettings') }}</div>
       <div class="content">
-        <el-form-item label="头部样式">
+        <el-form-item :label="t('decorate.headerStyle')">
           <el-radio-group v-model="settingData.navbar.mode">
-            <el-radio label="normal">标准</el-radio>
+            <el-radio label="normal">{{ t('decorate.standard') }}</el-radio>
             <el-radio label="inner">
-              沉浸式
+              {{ t('decorate.immersive') }}
               <el-popover popper-class="title-popover" placement="top-start" :width="232" trigger="hover"
-                :popper-options="{ boundariesElement: 'body' }" content="沉侵式头部仅支持微信小程序、APP 建议页面第一个组件为图片展示类组件">
+                :popper-options="{ boundariesElement: 'body' }" :content="t('decorate.immersiveHeaderTip')">
                 <template #reference>
                   <el-icon class="popover-tip">
                     <Warning />
@@ -33,13 +33,13 @@
             </el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item v-if="settingData.navbar.mode == 'inner'" label="常驻显示">
+        <el-form-item v-if="settingData.navbar.mode == 'inner'" :label="t('decorate.alwaysShow')">
           <el-radio-group v-model="settingData.navbar.alwaysShow">
-            <el-radio :label="0">关闭</el-radio>
+            <el-radio :label="0">{{ t('decorate.disable') }}</el-radio>
             <el-radio :label="1">
-              开启
+              {{ t('decorate.enable') }}
               <el-popover popper-class="title-popover" placement="top-start" :width="232" trigger="hover"
-                :popper-options="{ boundariesElement: 'body' }" content="常驻显示关闭后,头部小组件将在页面滑动时淡入">
+                :popper-options="{ boundariesElement: 'body' }" :content="t('decorate.alwaysShowTip')">
                 <template #reference>
                   <el-icon class="popover-tip">
                     <Warning />
@@ -49,16 +49,16 @@
             </el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="背景">
+        <el-form-item :label="t('decorate.background')">
           <el-radio-group v-model="settingData.navbar.type">
-            <el-radio label="color">纯色</el-radio>
-            <el-radio label="image">图片</el-radio>
+            <el-radio label="color">{{ t('decorate.solidColor') }}</el-radio>
+            <el-radio label="image">{{ t('decorate.image') }}</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item v-if="settingData.navbar.type == 'color'" label="选择颜色">
+        <el-form-item v-if="settingData.navbar.type == 'color'" :label="t('decorate.selectColor')">
           <dc-color-picker v-model="settingData.navbar.color"></dc-color-picker>
         </el-form-item>
-        <el-form-item v-if="settingData.navbar.type == 'image'" label="选择图片">
+        <el-form-item v-if="settingData.navbar.type == 'image'" :label="t('decorate.selectImage')">
           <sa-upload-image v-model="settingData.navbar.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
             :max-size="5" :direct-upload="true" :size="100" />
         </el-form-item>
@@ -66,7 +66,7 @@
     </div>
     <div class="card">
       <div class="title sa-flex">
-        头部内容
+        {{ t('decorate.headerContent') }}
         <!-- <div class="warning">
           可切换到
           {{ platformType == 'WechatMiniProgram' ? '其他' : '小程序' }}
@@ -114,40 +114,40 @@
           </tbody>
         </table>
         <template v-if="state.selectedData.length > 0">
-          <el-form-item label="头部类型">
+          <el-form-item :label="t('decorate.headerType')">
             <el-radio-group v-model="state.selectedData[state.selectedIndex].type">
-              <el-radio label="text">文字</el-radio>
-              <el-radio label="image">图片</el-radio>
-              <el-radio label="search">搜索框</el-radio>
+              <el-radio label="text">{{ t('decorate.text') }}</el-radio>
+              <el-radio label="image">{{ t('decorate.image') }}</el-radio>
+              <el-radio label="search">{{ t('decorate.searchBox') }}</el-radio>
             </el-radio-group>
           </el-form-item>
           <template v-if="state.selectedData[state.selectedIndex].type == 'text'">
-            <el-form-item label="文字内容">
+            <el-form-item :label="t('decorate.textContent')">
               <el-input v-model="state.selectedData[state.selectedIndex].text" maxlength="10"
                 show-word-limit></el-input>
             </el-form-item>
-            <el-form-item label="文字颜色">
+            <el-form-item :label="t('decorate.textColor')">
               <dc-color-picker v-model="state.selectedData[state.selectedIndex].textColor"></dc-color-picker>
             </el-form-item>
           </template>
           <template v-if="state.selectedData[state.selectedIndex].type == 'image'">
-            <el-form-item label="上传图片">
+            <el-form-item :label="t('decorate.uploadImage')">
               <div class="sa-flex">
                 <sa-upload-image v-model="state.selectedData[state.selectedIndex].src" :max-count="1"
                   :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
-                <div class="tip">建议尺寸:56*56</div>
+                <div class="tip">{{ t('decorate.recommendedSize') }}:56*56</div>
               </div>
             </el-form-item>
-            <el-form-item label="选择链接">
+            <el-form-item :label="t('decorate.selectLink')">
               <dc-url v-model="state.selectedData[state.selectedIndex].url"></dc-url>
             </el-form-item>
           </template>
           <template v-if="state.selectedData[state.selectedIndex].type == 'search'">
-            <el-form-item label="提示文字">
+            <el-form-item :label="t('decorate.placeholderText')">
               <el-input v-model="state.selectedData[state.selectedIndex].placeholder" maxlength="10"
                 show-word-limit></el-input>
             </el-form-item>
-            <el-form-item label="圆角">
+            <el-form-item :label="t('decorate.borderRadius')">
               <dc-slider v-model="state.selectedData[state.selectedIndex].borderRadius"></dc-slider>
             </el-form-item>
           </template>

+ 1 - 1
src/app/shop/admin/decorate/page/component/left/index.scss

@@ -47,7 +47,7 @@
   }
 
   .item {
-    width: 86px;
+    width: 100px;
     height: 86px;
     display: flex;
     flex-direction: column;

+ 83 - 90
src/app/shop/admin/decorate/page/component/left/index.vue

@@ -4,121 +4,114 @@
       <template v-if="collapseLeftPanel">
         <!-- 基础 -->
         <template v-if="pageType == 'basic'">
-          <left-basic
-            :fromType="fromType"
-            :basicData="templateData[pageType]"
-            @leftBasicBack="leftBasicBack"
-            @changeTheme="changeTheme"
-          ></left-basic>
+          <left-basic :fromType="fromType" :basicData="templateData[pageType]" @leftBasicBack="leftBasicBack"
+            @changeTheme="changeTheme"></left-basic>
         </template>
         <!-- 单页 -->
         <template v-if="pageType != 'basic'">
-          <left-comp
-            :type="pageType"
-            :index="index"
-            :leng="
-              templateData[pageType] && templateData[pageType].data
-                ? templateData[pageType].data.length
-                : 0
-            "
-            :theme="templateData.basic ? templateData.basic.theme : 'orange'"
-            @leftCompBack="leftCompBack"
-          ></left-comp>
+          <left-comp :type="pageType" :index="index" :leng="templateData[pageType] && templateData[pageType].data
+            ? templateData[pageType].data.length
+            : 0
+            " :theme="templateData.basic ? templateData.basic.theme : 'orange'"
+            @leftCompBack="leftCompBack"></left-comp>
         </template>
       </template>
     </el-scrollbar>
     <!-- 折叠 -->
-    <div
-      class="collapse-icon left-close sa-flex sa-row-center"
-      @click="collapseLeftPanel = !collapseLeftPanel"
-    >
-      <el-icon v-if="collapseLeftPanel"><ArrowLeft /></el-icon>
-      <el-icon v-else><ArrowRight /></el-icon>
+    <div class="collapse-icon left-close sa-flex sa-row-center" @click="collapseLeftPanel = !collapseLeftPanel">
+      <el-icon v-if="collapseLeftPanel">
+        <ArrowLeft />
+      </el-icon>
+      <el-icon v-else>
+        <ArrowRight />
+      </el-icon>
     </div>
   </div>
 </template>
 <script setup>
-  /**
-   * @property {String} pageType 页面类型
-   * @property {Object} templateData 页面数据
-   * @property {Number} index 索引
-   * @property {String} compType 组件类型
-   */
-  import { ref } from 'vue';
-  import { cloneDeep, isEmpty } from 'lodash';
-  import leftComp from './left-comp.vue';
-  import LeftBasic from './left-basic.vue';
+/**
+ * @property {String} pageType 页面类型
+ * @property {Object} templateData 页面数据
+ * @property {Number} index 索引
+ * @property {String} compType 组件类型
+ */
+import { ref } from 'vue';
+import { cloneDeep, isEmpty } from 'lodash';
+import leftComp from './left-comp.vue';
+import LeftBasic from './left-basic.vue';
 
-  const emit = defineEmits(['update:index', 'update:compType', 'pageLeftBack']);
-  const props = defineProps(['fromType', 'pageType', 'templateData', 'index', 'compType']);
+const emit = defineEmits(['update:index', 'update:compType', 'pageLeftBack']);
+const props = defineProps(['fromType', 'pageType', 'templateData', 'index', 'compType']);
 
-  // 左侧基础返回
-  function leftBasicBack(data) {
-    if (isEmpty(props.templateData[props.pageType])) {
-      props.templateData[props.pageType] = {};
-    }
-    props.templateData[props.pageType][data.type] = data.template;
-    emit('update:index', data.index);
-    emit('update:compType', data.type);
-    emit('pageLeftBack', data.template);
+// 左侧基础返回
+function leftBasicBack(data) {
+  if (isEmpty(props.templateData[props.pageType])) {
+    props.templateData[props.pageType] = {};
   }
+  props.templateData[props.pageType][data.type] = data.template;
+  emit('update:index', data.index);
+  emit('update:compType', data.type);
+  emit('pageLeftBack', data.template);
+}
 
-  // 左侧组件返回
-  function leftCompBack(data) {
-    props.templateData[props.pageType].data;
-    if (data.trigger == 'drag') {
-      // 拖拽
-      props.templateData[props.pageType].data.splice(data.index, 0, data.template);
-      props.templateData[props.pageType].data.splice(cloneDeep(data.index) + 1, 1);
-    } else if (data.trigger == 'click') {
-      // 选择
-      props.templateData[props.pageType].data.splice(data.index, 0, data.template);
-    }
-    emit('update:index', data.index);
-    emit('update:compType', data.type);
-    emit('pageLeftBack', data.template);
+// 左侧组件返回
+function leftCompBack(data) {
+  props.templateData[props.pageType].data;
+  if (data.trigger == 'drag') {
+    // 拖拽
+    props.templateData[props.pageType].data.splice(data.index, 0, data.template);
+    props.templateData[props.pageType].data.splice(cloneDeep(data.index) + 1, 1);
+  } else if (data.trigger == 'click') {
+    // 选择
+    props.templateData[props.pageType].data.splice(data.index, 0, data.template);
   }
+  emit('update:index', data.index);
+  emit('update:compType', data.type);
+  emit('pageLeftBack', data.template);
+}
 
-  // 修改主题
-  function changeTheme(data) {
-    props.templateData.basic.theme = data.theme;
-  }
+// 修改主题
+function changeTheme(data) {
+  props.templateData.basic.theme = data.theme;
+}
 
-  const collapseLeftPanel = ref(true);
+const collapseLeftPanel = ref(true);
 </script>
 <style lang="scss" scoped>
-  .page-left {
-    flex-shrink: 0;
-    width: 260px;
-    height: 100%;
-    background: var(--sa-background-assist);
-    box-shadow: 0px 0px 0.24rem rgb(0 0 0 / 16%);
-    transition: all 0.2s;
-    position: relative;
-    z-index: 1;
-    .left-close {
-      right: -20px;
-    }
+.page-left {
+  flex-shrink: 0;
+  width: 301px;
+  height: 100%;
+  background: var(--sa-background-assist);
+  box-shadow: 0px 0px 0.24rem rgb(0 0 0 / 16%);
+  transition: all 0.2s;
+  position: relative;
+  z-index: 1;
 
-    &--collapse {
-      width: 0;
-    }
+  .left-close {
+    right: -20px;
+  }
+
+  &--collapse {
+    width: 0;
   }
-  @media only screen and (max-width: 768px) {
-    .page-left {
-      position: absolute !important;
-      left: 0;
-      z-index: 100;
-      transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
-      box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.04);
-      filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.08)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.06));
+}
 
-      &--collapse {
-        width: 0 !important;
-      }
+@media only screen and (max-width: 768px) {
+  .page-left {
+    position: absolute !important;
+    left: 0;
+    z-index: 100;
+    transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
+    box-shadow: 0 4px 8px 2px rgba(0, 0, 0, 0.04);
+    filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.08)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.06));
+
+    &--collapse {
+      width: 0 !important;
     }
   }
+}
 </style>
 <style lang="scss">
-  @import './index.scss';
+@import './index.scss';
 </style>

+ 64 - 75
src/app/shop/admin/decorate/page/component/left/left-comp.vue

@@ -2,11 +2,8 @@
   <div class="left-comp left-tools">
     <el-collapse v-model="activeCollapse">
       <template v-for="item in compList" :key="item.id">
-        <el-collapse-item
-          v-if="!item.show || (item.show && item.show.includes(type))"
-          :title="item.name"
-          :name="item.type"
-        >
+        <el-collapse-item v-if="!item.show || (item.show && item.show.includes(type))" :title="item.name"
+          :name="item.type">
           <template #title>
             <el-icon class="caret-bottom">
               <CaretBottom />
@@ -16,22 +13,12 @@
             </el-icon>
             <span class="sa-m-l-8">{{ item.name }}</span>
           </template>
-          <sa-draggable
-            class="sa-flex sa-flex-wrap"
-            v-model="item.data"
-            item-key="element"
-            :group="{
-              name: 'draggableName',
-              pull: 'clone',
-              put: false,
-            }"
-            ghostClass="sortable-ghost"
-            fallbackClass="clone-item"
-            :fallbackOnBody="true"
-            :move="leftMove"
-            @start="leftStart"
-            @end="leftEnd"
-          >
+          <sa-draggable class="sa-flex sa-flex-wrap" v-model="item.data" item-key="element" :group="{
+            name: 'draggableName',
+            pull: 'clone',
+            put: false,
+          }" ghostClass="sortable-ghost" fallbackClass="clone-item" :fallbackOnBody="true" :move="leftMove"
+            @start="leftStart" @end="leftEnd">
             <template #item="{ element }">
               <div class="item" :class="element.type" @click.stop="selectComp(element.type)">
                 <img :src="'./static/images/shop/decorate/' + element.type + '.png'" />
@@ -45,69 +32,71 @@
   </div>
 </template>
 <script>
-  export default {
-    name: 'left-comp',
-  };
+export default {
+  name: 'left-comp',
+};
 </script>
 <script setup>
-  /**
-   * @property {String} type 页面类型
-   * @property {Number} index 索引
-   * @property {Number} leng 数组的长度
-   * @property {String} theme 主题
-   */
-  import { reactive, ref } from 'vue';
-  import SaDraggable from 'vuedraggable';
-  import { compList, cloneComponent } from '../../data';
+/**
+ * @property {String} type 页面类型
+ * @property {Number} index 索引
+ * @property {Number} leng 数组的长度
+ * @property {String} theme 主题
+ */
+import { reactive, ref, computed } from 'vue';
+import SaDraggable from 'vuedraggable';
+import { getCompList, cloneComponent } from '../../data';
 
-  const emit = defineEmits(['leftCompBack']);
-  const props = defineProps(['type', 'index', 'leng', 'theme']);
+const compList = computed(() => getCompList());
 
-  // 折叠面板展开
-  const activeCollapse = reactive(['0', '1', '2', '3', '4']);
+const emit = defineEmits(['leftCompBack']);
+const props = defineProps(['type', 'index', 'leng', 'theme']);
 
-  // 拖动元素
-  const saveHtml = ref();
-  // 开始拖动
-  function leftStart(e) {
-    saveHtml.value = e.clone.innerHTML;
-  }
-  // 拖动
-  function leftMove(e) {
-    if (e.to.className.indexOf('comp-wrap') != -1) {
-      e.dragged.innerHTML = `<div style="padding:0 50px;width:100%;height:50px;line-height:50px;background:#f00">新添加的元素</div>`;
-    } else {
-      return false;
-    }
-  }
-  // 结束拖动赋值
-  function leftEnd(e) {
-    if (e.to.className.indexOf('comp-wrap') != -1) {
-      e.item.innerHTML = saveHtml.value;
-      let type = e.item.classList[1];
-      emit('leftCompBack', {
-        trigger: 'drag',
-        type: type,
-        index: e.newIndex,
-        template: cloneComponent(type, props.theme),
-      });
-    }
-  }
+// 折叠面板展开
+const activeCollapse = reactive(['0', '1', '2', '3', '4']);
 
-  // 选择组件
-  function selectComp(type) {
-    let index;
-    if (props.index == 0 || props.index > 0) {
-      index = props.index;
-      index++;
-    } else {
-      index = props.leng;
-    }
+// 拖动元素
+const saveHtml = ref();
+// 开始拖动
+function leftStart(e) {
+  saveHtml.value = e.clone.innerHTML;
+}
+// 拖动
+function leftMove(e) {
+  if (e.to.className.indexOf('comp-wrap') != -1) {
+    e.dragged.innerHTML = `<div style="padding:0 50px;width:100%;height:50px;line-height:50px;background:#f00">新添加的元素</div>`;
+  } else {
+    return false;
+  }
+}
+// 结束拖动赋值
+function leftEnd(e) {
+  if (e.to.className.indexOf('comp-wrap') != -1) {
+    e.item.innerHTML = saveHtml.value;
+    let type = e.item.classList[1];
     emit('leftCompBack', {
-      trigger: 'click',
+      trigger: 'drag',
       type: type,
-      index: index,
+      index: e.newIndex,
       template: cloneComponent(type, props.theme),
     });
   }
+}
+
+// 选择组件
+function selectComp(type) {
+  let index;
+  if (props.index == 0 || props.index > 0) {
+    index = props.index;
+    index++;
+  } else {
+    index = props.leng;
+  }
+  emit('leftCompBack', {
+    trigger: 'click',
+    type: type,
+    index: index,
+    template: cloneComponent(type, props.theme),
+  });
+}
 </script>

+ 24 - 37
src/app/shop/admin/decorate/page/component/pheader.vue

@@ -1,13 +1,8 @@
 <template>
   <div class="header-left sa-flex">
     <template v-if="pageType != 'diypage'">
-      <div
-        class="oper-button"
-        :class="pageType == ptl.type ? 'is-active' : ''"
-        v-for="ptl in pageTypeList"
-        :key="ptl"
-        @click="emit('changePageType', ptl.type)"
-      >
+      <div class="oper-button" :class="pageType == ptl.type ? 'is-active' : ''" v-for="ptl in pageTypeList" :key="ptl"
+        @click="emit('changePageType', ptl.type)">
         <el-popover popper-class="sa-popper" trigger="hover" :content="ptl.label">
           <template #reference>
             <sa-icon :icon="'sa-shop-decorate-' + ptl.type" size="24" />
@@ -21,12 +16,8 @@
     <template v-for="sl in systemList" :key="sl">
       <el-popover popper-class="sa-popper" trigger="hover" :content="sl.label">
         <template #reference>
-          <sa-icon
-            :icon="'sa-shop-decorate-' + sl.type"
-            size="24"
-            :style="{ color: systemType == sl.type ? sl.color : '' }"
-            @click="emit('update:systemType', sl.type)"
-          />
+          <sa-icon :icon="'sa-shop-decorate-' + sl.type" size="24"
+            :style="{ color: systemType == sl.type ? sl.color : '' }" @click="emit('update:systemType', sl.type)" />
         </template>
       </el-popover>
     </template>
@@ -34,12 +25,9 @@
     <template v-for="pl in platformList" :key="pl">
       <el-popover popper-class="sa-popper" trigger="hover" :content="pl.label">
         <template #reference>
-          <sa-icon
-            :icon="'sa-shop-decorate-' + pl.type"
-            size="24"
+          <sa-icon :icon="'sa-shop-decorate-' + pl.type" size="24"
             :style="{ color: platformType == pl.type ? pl.color : '' }"
-            @click="emit('update:platformType', pl.type)"
-          ></sa-icon>
+            @click="emit('update:platformType', pl.type)"></sa-icon>
         </template>
       </el-popover>
     </template>
@@ -47,13 +35,9 @@
     <el-popover popper-class="sa-popper" trigger="hover" content="页面设置">
       <template #reference>
         <div>
-          <sa-icon
-            v-if="pageType != 'basic'"
-            icon="sa-shop-decorate-page"
-            size="24"
+          <sa-icon v-if="pageType != 'basic'" icon="sa-shop-decorate-page" size="24"
             :style="{ color: currentCompIndex == -1 ? 'var(--el-color-primary)' : '' }"
-            @click="emit('updatePageSetting')"
-          ></sa-icon>
+            @click="emit('updatePageSetting')"></sa-icon>
         </div>
       </template>
     </el-popover>
@@ -63,18 +47,21 @@
   </div>
 </template>
 <script setup>
-  /**
-   * @property {String} pageType 页面类型
-   * @property {String} systemType 系统类型
-   * @property {String} platformType 平台类型
-   */
-  import { pageTypeList, systemList, platformList } from '../data';
+/**
+ * @property {String} pageType 页面类型
+ * @property {String} systemType 系统类型
+ * @property {String} platformType 平台类型
+ */
+import { computed } from 'vue';
+import { getPageTypeList, systemList, platformList } from '../data';
 
-  const emit = defineEmits([
-    'changePageType',
-    'update:systemType',
-    'update:platformType',
-    'updatePageSetting',
-  ]);
-  const props = defineProps(['pageType', 'systemType', 'platformType', 'currentCompIndex']);
+const pageTypeList = computed(() => getPageTypeList());
+
+const emit = defineEmits([
+  'changePageType',
+  'update:systemType',
+  'update:platformType',
+  'updatePageSetting',
+]);
+const props = defineProps(['pageType', 'systemType', 'platformType', 'currentCompIndex']);
 </script>

+ 54 - 30
src/app/shop/admin/decorate/page/component/right/index.vue

@@ -1,5 +1,8 @@
 <template>
-  <div class="page-right" :class="panelCollapse ? 'page-right--collapse' : ''">
+  <div class="page-right" :class="[
+    panelCollapse ? 'page-right--collapse' : '',
+    getCurrentLanguage() === 'en-US' ? 'page-right--en' : 'page-right--zh'
+  ]">
     <el-scrollbar height="100%">
       <template v-if="type">
         <!-- name -->
@@ -8,7 +11,7 @@
           <span>{{ compNameObj[type] }}</span>
         </div>
         <!-- data -->
-        <el-form :model="rightData" label-width="84px">
+        <el-form :model="rightData" :label-width="getCurrentLanguage() === 'en-US' ? '140px' : '84px'">
           <div class="setting">
             <!-- type==page 不显示/type==tabbar 显示 -->
             <div class="tab">
@@ -18,15 +21,15 @@
                     ts.type == 'data' &&
                     !['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(type)
                   " :class="[ts.type, tabType == ts.type ? 'is-active' : '']" @click="tabType = ts.type">
-                    {{ ts.name }}
+                    {{ t(`modules.decorate.${ts.type}`) }}
                   </div>
                   <div v-if="ts.type == 'style' && !['floatMenu', 'popupImage', 'page'].includes(type)"
                     :class="[ts.type, tabType == ts.type ? 'is-active' : '']" @click="tabType = ts.type">
-                    {{ ts.name }}
+                    {{ t(`modules.decorate.${ts.type}`) }}
                   </div>
                   <div v-if="ts.type == 'css'" :class="[ts.type, tabType == ts.type ? 'is-active' : '']"
                     @click="tabType = ts.type">
-                    {{ ts.name }}
+                    {{ t(`modules.decorate.${ts.type}`) }}
                   </div>
                 </template>
               </div>
@@ -35,48 +38,56 @@
               :platformType="platformType" :templateDetailType="templateDetailType"></compSetting>
             <template v-if="tabType == 'style' && rightData.style">
               <div class="card">
-                <div class="title">组件样式</div>
+                <div class="title">{{ t('modules.decorate.componentStyle') }}</div>
                 <div class="content">
                   <template v-if="rightData.style.background">
-                    <el-form-item label="组件背景">
+                    <el-form-item :label="t('modules.decorate.background')">
                       <el-radio-group v-model="rightData.style.background.type">
-                        <el-radio label="color">纯色</el-radio>
-                        <el-radio label="image">图片</el-radio>
+                        <el-radio label="color">{{ t('modules.decorate.solidColor') }}</el-radio>
+                        <el-radio label="image">{{ t('modules.decorate.image') }}</el-radio>
                       </el-radio-group>
                     </el-form-item>
-                    <el-form-item v-if="rightData.style.background.type == 'color'" label="选择颜色">
+                    <el-form-item v-if="rightData.style.background.type == 'color'"
+                      :label="t('modules.decorate.selectColor')">
                       <dc-color-picker v-model="rightData.style.background.bgColor"></dc-color-picker>
                     </el-form-item>
-                    <el-form-item v-if="rightData.style.background.type == 'image'" label="选择图片">
+                    <el-form-item v-if="rightData.style.background.type == 'image'"
+                      :label="t('modules.decorate.selectImage')">
                       <sa-upload-image v-model="rightData.style.background.bgImage" :max-count="1"
                         :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
                     </el-form-item>
                   </template>
-                  <el-form-item v-if="rightData.style.marginTop || rightData.style.marginTop == 0" label="上间距">
+                  <el-form-item v-if="rightData.style.marginTop || rightData.style.marginTop == 0"
+                    :label="t('modules.decorate.topMargin')">
                     <dc-slider v-model="rightData.style.marginTop"></dc-slider>
                   </el-form-item>
-                  <el-form-item v-if="rightData.style.marginRight || rightData.style.marginRight == 0" label="右间距">
+                  <el-form-item v-if="rightData.style.marginRight || rightData.style.marginRight == 0"
+                    :label="t('modules.decorate.rightMargin')">
                     <dc-slider v-model="rightData.style.marginRight"></dc-slider>
                   </el-form-item>
-                  <el-form-item v-if="rightData.style.marginBottom || rightData.style.marginBottom == 0" label="下间距">
+                  <el-form-item v-if="rightData.style.marginBottom || rightData.style.marginBottom == 0"
+                    :label="t('modules.decorate.bottomMargin')">
                     <dc-slider v-model="rightData.style.marginBottom"></dc-slider>
                   </el-form-item>
-                  <el-form-item v-if="rightData.style.marginLeft || rightData.style.marginLeft == 0" label="左间距">
+                  <el-form-item v-if="rightData.style.marginLeft || rightData.style.marginLeft == 0"
+                    :label="t('modules.decorate.leftMargin')">
                     <dc-slider v-model="rightData.style.marginLeft"></dc-slider>
                   </el-form-item>
                   <el-form-item v-if="rightData.style.borderRadiusTop || rightData.style.borderRadiusTop == 0"
-                    label="上圆角">
+                    :label="t('modules.decorate.topRadius')">
                     <dc-slider v-model="rightData.style.borderRadiusTop"></dc-slider>
                   </el-form-item>
                   <el-form-item v-if="
                     rightData.style.borderRadiusBottom || rightData.style.borderRadiusBottom == 0
-                  " label="下圆角">
+                  " :label="t('modules.decorate.bottomRadius')">
                     <dc-slider v-model="rightData.style.borderRadiusBottom"></dc-slider>
                   </el-form-item>
-                  <el-form-item v-if="rightData.style.padding || rightData.style.padding == 0" label="内间距">
+                  <el-form-item v-if="rightData.style.padding || rightData.style.padding == 0"
+                    :label="t('modules.decorate.innerPadding')">
                     <dc-slider v-model="rightData.style.padding"></dc-slider>
                   </el-form-item>
-                  <el-form-item v-if="rightData.style.height || rightData.style.height == 0" label="高度">
+                  <el-form-item v-if="rightData.style.height || rightData.style.height == 0"
+                    :label="t('modules.decorate.height')">
                     <dc-slider v-model="rightData.style.height" :mult="6"></dc-slider>
                   </el-form-item>
                 </div>
@@ -84,19 +95,21 @@
             </template>
             <template v-if="tabType == 'style' && rightData.background">
               <div class="card">
-                <div class="title">组件样式</div>
+                <div class="title">{{ t('modules.decorate.componentStyle') }}</div>
                 <div class="content">
                   <template v-if="rightData.background">
-                    <el-form-item label="组件背景">
+                    <el-form-item :label="t('modules.decorate.background')">
                       <el-radio-group v-model="rightData.background.type">
-                        <el-radio label="color">纯色</el-radio>
-                        <el-radio label="image">图片</el-radio>
+                        <el-radio label="color">{{ t('modules.decorate.solidColor') }}</el-radio>
+                        <el-radio label="image">{{ t('modules.decorate.image') }}</el-radio>
                       </el-radio-group>
                     </el-form-item>
-                    <el-form-item v-if="rightData.background.type == 'color'" label="选择颜色">
+                    <el-form-item v-if="rightData.background.type == 'color'"
+                      :label="t('modules.decorate.selectColor')">
                       <dc-color-picker v-model="rightData.background.bgColor"></dc-color-picker>
                     </el-form-item>
-                    <el-form-item v-if="rightData.background.type == 'image'" label="选择图片">
+                    <el-form-item v-if="rightData.background.type == 'image'"
+                      :label="t('modules.decorate.selectImage')">
                       <sa-upload-image v-model="rightData.background.bgImage" :max-count="1"
                         :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
                     </el-form-item>
@@ -134,14 +147,17 @@
  * @property {String} type 组件类型
  * @property {Object} rightData 组件数据
  */
-import { ref, watch } from 'vue';
-import { compNameObj } from '../../data';
-
+import { ref, watch, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { isString } from 'lodash';
+import { getCompNameObj } from '../../data';
+import { getCurrentLanguage } from '@/locales/index.js';
 import compSetting from './setting.vue';
-
 import dcColorPicker from '../center/common/dc-color-picker.vue';
 import dcSlider from '../center/common/dc-slider.vue';
-import { isString } from 'lodash';
+
+const { t } = useI18n();
+const compNameObj = computed(() => getCompNameObj());
 
 const emit = defineEmits(['update:panelCollapse']);
 const props = defineProps([
@@ -191,6 +207,14 @@ watch(
   position: relative;
   z-index: 1;
 
+  &--zh {
+    width: 400px;
+  }
+
+  &--en {
+    width: 480px;
+  }
+
   .name {
     padding: 8px 12px;
     color: var(--sa-subtitle);

+ 154 - 141
src/app/shop/admin/decorate/page/data.js

@@ -1,6 +1,7 @@
 import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
 import { api as goodsApi } from '@/app/shop/admin/goods/goods.service';
 import { api as dataApi } from '@/app/shop/admin/data/data.service';
+import i18n from '@/locales/index.js';
 
 // 主题
 export const themeColor = {
@@ -30,13 +31,17 @@ export const themeColor = {
   },
 };
 
-// 页面列表
-export const pageTypeList = [
-  {
-    type: 'diypage',
-    label: '自定义页面',
-  },
-];
+// 页面列表 - 动态获取翻译
+export const getPageTypeList = () => {
+  const t = (key) => i18n.global.t(key);
+
+  return [
+    {
+      type: 'diypage',
+      label: t('modules.decorate.customPage'),
+    },
+  ];
+};
 
 // 系统列表
 export const systemList = [
@@ -79,110 +84,114 @@ export const platformList = [
 // 基础列表
 export const basicList = [];
 
-// 组件列表
-export const compList = [
-  // {
-  //   name: '会员组件',
-  //   type: '0',
-  //   show: ['user', 'diypage'],
-  //   data: [
-  //     {
-  //       name: '会员卡片',
-  //       type: 'userCard',
-  //     },
-  //     {
-  //       name: '订单卡片',
-  //       type: 'orderCard',
-  //     },
-  //     {
-  //       name: '资产卡片',
-  //       type: 'walletCard',
-  //     },
-  //     {
-  //       name: '卡券卡片',
-  //       type: 'couponCard',
-  //     },
-  //   ],
-  // },
-  {
-    name: '基础组件',
-    type: '1',
-    data: [
-      {
-        name: '搜索框',
-        type: 'searchBlock',
-      },
-      {
-        name: '公告栏',
-        type: 'noticeBlock',
-      },
-      {
-        name: '菜单导航',
-        type: 'menuButton',
-      },
-      {
-        name: '列表导航',
-        type: 'menuList',
-      },
-      {
-        name: '宫格导航',
-        type: 'menuGrid',
-      },
-    ],
-  },
-  {
-    name: '商品组件',
-    type: '2',
-    data: [
-      {
-        name: '商品卡片',
-        type: 'goodsCard',
-      },
-      // {
-      //   name: '商品栏',
-      //   type: 'goodsShelves',
-      // },
-    ],
-  },
-  {
-    name: '图文组件',
-    type: '3',
-    data: [
-      {
-        name: '图片展示',
-        type: 'imageBlock',
-      },
-      {
-        name: '图片轮播',
-        type: 'imageBanner',
-      },
-      {
-        name: '标题栏',
-        type: 'titleBlock',
-      },
-      {
-        name: '广告魔方',
-        type: 'imageCube',
-      },
-      {
-        name: '视频播放',
-        type: 'videoPlayer',
-      },
-      {
-        name: '辅助线',
-        type: 'lineBlock',
-      },
-      {
-        name: '富文本',
-        type: 'richtext',
-      },
-      {
-        name: '热区',
-        type: 'hotzone',
-      },
-    ],
-  },
-];
+// 组件列表 - 动态获取翻译
+export const getCompList = () => {
+  const t = (key) => i18n.global.t(key);
+
+  return [
+    // {
+    //   name: t('modules.decorate.memberComponents'),
+    //   type: '0',
+    //   show: ['user', 'diypage'],
+    //   data: [
+    //     {
+    //       name: t('modules.decorate.userCard'),
+    //       type: 'userCard',
+    //     },
+    //     {
+    //       name: t('modules.decorate.orderCard'),
+    //       type: 'orderCard',
+    //     },
+    //     {
+    //       name: t('modules.decorate.walletCard'),
+    //       type: 'walletCard',
+    //     },
+    //     {
+    //       name: t('modules.decorate.couponCard'),
+    //       type: 'couponCard',
+    //     },
+    //   ],
+    // },
+    {
+      name: t('modules.decorate.basicComponents'),
+      type: '1',
+      data: [
+        {
+          name: t('modules.decorate.searchBlock'),
+          type: 'searchBlock',
+        },
+        {
+          name: t('modules.decorate.noticeBlock'),
+          type: 'noticeBlock',
+        },
+        {
+          name: t('modules.decorate.menuButton'),
+          type: 'menuButton',
+        },
+        {
+          name: t('modules.decorate.menuList'),
+          type: 'menuList',
+        },
+        {
+          name: t('modules.decorate.menuGrid'),
+          type: 'menuGrid',
+        },
+      ],
+    },
+    {
+      name: t('modules.decorate.goodsComponents'),
+      type: '2',
+      data: [
+        {
+          name: t('modules.decorate.goodsCard'),
+          type: 'goodsCard',
+        },
+        // {
+        //   name: t('modules.decorate.goodsShelves'),
+        //   type: 'goodsShelves',
+        // },
+      ],
+    },
+    {
+      name: t('modules.decorate.mediaComponents'),
+      type: '3',
+      data: [
+        {
+          name: t('modules.decorate.imageBlock'),
+          type: 'imageBlock',
+        },
+        {
+          name: t('modules.decorate.imageBanner'),
+          type: 'imageBanner',
+        },
+        {
+          name: t('modules.decorate.titleBlock'),
+          type: 'titleBlock',
+        },
+        {
+          name: t('modules.decorate.imageCube'),
+          type: 'imageCube',
+        },
+        {
+          name: t('modules.decorate.videoPlayerComp'),
+          type: 'videoPlayer',
+        },
+        {
+          name: t('modules.decorate.lineBlock'),
+          type: 'lineBlock',
+        },
+        {
+          name: t('modules.decorate.richtext'),
+          type: 'richtext',
+        },
+        {
+          name: t('modules.decorate.hotzone'),
+          type: 'hotzone',
+        },
+      ],
+    },
+  ];
+};
 
 // 页面默认数据
 export const initTemplateData = {
@@ -476,7 +485,7 @@ export function cloneComponent(type, theme = 'orange') {
         },
         buyNowStyle: {
           mode: 1,
-          text: '立即购买',
+          text: i18n.global.t('modules.decorate.buyNowText'),
           color1: themeColor[theme].color1,
           color2: themeColor[theme].color2,
           src: '',
@@ -608,13 +617,13 @@ export function cloneComponent(type, theme = 'orange') {
         location: 'left', // left=居左 center=居中
         skew: 0,
         title: {
-          text: '标题栏',
+          text: i18n.global.t('modules.decorate.titleBlockDefault'),
           color: '#111111',
           textFontSize: 14,
           other: [], // bold=加粗 italic=倾斜
         },
         subtitle: {
-          text: '副标题',
+          text: i18n.global.t('modules.decorate.subtitleDefault'),
           color: '#8c8c8c',
           textFontSize: 12,
           other: [], // bold=加粗 italic=倾斜
@@ -716,8 +725,8 @@ export function cloneComponent(type, theme = 'orange') {
     richtext: {
       type: 'richtext',
       data: {
-        title: '富文本内容',
-        content: '<p>请输入富文本内容...</p>',
+        title: i18n.global.t('modules.decorate.richTextTitle'),
+        content: `<p>${i18n.global.t('modules.decorate.defaultRichTextContent')}</p>`,
       },
       style: {
         background: {
@@ -783,29 +792,33 @@ export function cloneComponent(type, theme = 'orange') {
   return comp[type];
 }
 
-// 组件名称
-export const compNameObj = {
-  page: '页面设置',
-  userCard: '会员卡片',
-  orderCard: '订单卡片',
-  walletCard: '资产卡片',
-  couponCard: '卡券卡片',
-  searchBlock: '搜索框',
-  noticeBlock: '公告栏',
-  menuButton: '菜单导航',
-  menuList: '列表导航',
-  menuGrid: '宫格导航',
-  goodsCard: '商品卡片',
-  goodsShelves: '商品栏',
-  imageBlock: '图片展示',
-  imageBanner: '图片轮播',
-  titleBlock: '标题栏',
-  imageCube: '广告魔方',
-  videoPlayer: '视频播放',
-  lineBlock: '辅助线',
-  richtext: '富文本',
-  hotzone: '热区',
-  // subscribeWechatOfficialAccount: '关注公众号',
+// 组件名称 - 动态获取翻译
+export const getCompNameObj = () => {
+  const t = (key) => i18n.global.t(key);
+
+  return {
+    page: t('modules.decorate.pageSettings'),
+    userCard: t('modules.decorate.userCard'),
+    orderCard: t('modules.decorate.orderCard'),
+    walletCard: t('modules.decorate.walletCard'),
+    couponCard: t('modules.decorate.couponCard'),
+    searchBlock: t('modules.decorate.searchBlock'),
+    noticeBlock: t('modules.decorate.noticeBlock'),
+    menuButton: t('modules.decorate.menuButton'),
+    menuList: t('modules.decorate.menuList'),
+    menuGrid: t('modules.decorate.menuGrid'),
+    goodsCard: t('modules.decorate.goodsCard'),
+    goodsShelves: t('modules.decorate.goodsShelves'),
+    imageBlock: t('modules.decorate.imageBlock'),
+    imageBanner: t('modules.decorate.imageBanner'),
+    titleBlock: t('modules.decorate.titleBlock'),
+    imageCube: t('modules.decorate.imageCube'),
+    videoPlayer: t('modules.decorate.videoPlayerComp'),
+    lineBlock: t('modules.decorate.lineBlock'),
+    richtext: t('modules.decorate.richtext'),
+    hotzone: t('modules.decorate.hotzone'),
+    // subscribeWechatOfficialAccount: '关注公众号',
+  };
 };
 
 // 初始化暂存数据
@@ -841,10 +854,10 @@ export function handleTempData(data) {
     } else if (t.type == 'richtext') {
       // 富文本组件现在直接使用 content 字段,无需额外处理
       if (!t.data.content) {
-        t.data.content = '<p>请输入富文本内容...</p>';
+        t.data.content = `<p>${i18n.global.t('modules.decorate.defaultRichTextContent')}</p>`;
       }
       if (!t.data.title) {
-        t.data.title = '富文本内容';
+        t.data.title = i18n.global.t('modules.decorate.richTextTitle');
       }
     }
   });

+ 7 - 4
src/app/shop/admin/decorate/page/index.vue

@@ -96,6 +96,7 @@ import {
   onMounted,
   reactive,
   ref,
+  computed,
   onUpdated,
   onBeforeUnmount,
 } from 'vue';
@@ -107,7 +108,7 @@ import { api } from '../decorate.service';
 import adminApi from '@/app/admin/api';
 import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
 import { isEmpty } from 'lodash';
-import { compNameObj, initTemplateData, handleTempData, handleSubmitData } from './data';
+import { getCompNameObj, initTemplateData, handleTempData, handleSubmitData } from './data';
 import pageHeader from './component/pheader.vue';
 import pageLeft from './component/left/index.vue';
 import pageRight from './component/right/index.vue';
@@ -116,6 +117,8 @@ import basic from './component/center/basic/index.vue';
 import comp from './component/center/comp/index.vue';
 import { cloneDeep } from 'lodash';
 import { ElMessageBox, ElMessage } from 'element-plus';
+
+const compNameObj = computed(() => getCompNameObj());
 // import PagePreview from './preview.vue';
 
 const { proxy } = getCurrentInstance();
@@ -141,9 +144,9 @@ function changePageType(type) {
     pageType.value = type;
     return false;
   }
-  ElMessageBox.confirm('是否保存数据', {
-    confirmButtonText: '保存',
-    cancelButtonText: '放弃',
+  ElMessageBox.confirm(t('decorate.saveData'), {
+    confirmButtonText: t('common.save'),
+    cancelButtonText: t('decorate.abandon'),
     type: 'warning',
   })
     .then(async () => {

+ 17 - 10
src/app/shop/admin/decorate/template/edit.vue

@@ -2,28 +2,33 @@
   <el-container>
     <el-main>
       <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="页面名称" prop="title">
-          <el-input v-model="form.model.title" placeholder="请输入页面名称"></el-input>
+        <el-form-item :label="t('modules.decorate.pageName')" prop="title">
+          <el-input v-model="form.model.title" :placeholder="t('modules.decorate.pleaseInputPageName')"></el-input>
         </el-form-item>
-        <el-form-item label="排序">
-          <el-input-number v-model="form.model.sort" :min="0" placeholder="请输入排序"></el-input-number>
+        <el-form-item :label="t('modules.decorate.sortOrder')">
+          <el-input-number v-model="form.model.sort" :min="0"
+            :placeholder="t('modules.decorate.pleaseInputSort')"></el-input-number>
         </el-form-item>
-        <el-form-item label="状态">
-          <el-switch v-model="form.model.status" active-text="启用" inactive-text="禁用"></el-switch>
+        <el-form-item :label="t('common.status')">
+          <el-switch v-model="form.model.status" :active-text="t('common.enable')"
+            :inactive-text="t('common.disable')"></el-switch>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">保存</el-button>
-      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">更新</el-button>
+      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">{{ t('common.save') }}</el-button>
+      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">{{ t('common.update') }}</el-button>
     </el-footer>
   </el-container>
 </template>
 <script setup>
 import { onMounted, reactive, ref, unref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { api } from '../decorate.service';
 import { cloneDeep } from 'lodash';
 
+const { t } = useI18n();
+
 const emit = defineEmits(['modalCallBack']);
 const props = defineProps({
   modal: {
@@ -41,8 +46,10 @@ const form = reactive({
     status: true,
     diyData: '',
   },
-  rules: {
-    title: [{ required: true, message: '请输入模板名称', trigger: 'blur' }],
+  get rules() {
+    return {
+      title: [{ required: true, message: t('modules.decorate.pleaseInputTemplateName'), trigger: 'blur' }],
+    };
   },
 });
 const loading = ref(false);

+ 12 - 10
src/app/shop/admin/decorate/template/index.vue

@@ -2,13 +2,13 @@
   <el-container class="template-view panel-block">
     <el-header class="sa-header">
       <el-tabs class="sa-tabs" v-model="template.type" @tab-change="getData">
-        <el-tab-pane label="自定义页面" name="diypage"></el-tab-pane>
+        <el-tab-pane :label="t('modules.decorate.customPage')" name="diypage"></el-tab-pane>
       </el-tabs>
       <div class="sa-title sa-flex sa-row-between">
-        <span>列表</span>
+        <span>{{ t('modules.decorate.templateList') }}</span>
         <div>
           <el-button class="sa-button-refresh" icon="RefreshRight" @click="getData()"></el-button>
-          <el-button icon="Plus" type="primary" @click="addTemplate">新建模板</el-button>
+          <el-button icon="Plus" type="primary" @click="addTemplate">{{ t('modules.decorate.newTemplate') }}</el-button>
         </div>
       </div>
     </el-header>
@@ -22,14 +22,14 @@
                 <el-icon :size="48">
                   <Picture />
                 </el-icon>
-                <div class="text">暂无封面</div>
+                <div class="text">{{ t('modules.decorate.noCover') }}</div>
               </div>
             </div>
             <div class="footer">
               <div class="header-info sa-flex sa-row-between sa-col-center">
                 <div class="name">{{ temp.title }}</div>
                 <el-tag :type="temp.status ? 'success' : 'info'" size="small">
-                  {{ temp.status ? '启用' : '禁用' }}
+                  {{ temp.status ? t('common.enabled') : t('common.disabled') }}
                 </el-tag>
               </div>
               <div class="update-time">
@@ -40,15 +40,15 @@
               </div>
               <div class="oper sa-flex sa-row-between">
                 <el-button class="is-link" type="primary" size="small" @click="decorateTemplate(temp.id)">
-                  装修
+                  {{ t('modules.decorate.decorateAction') }}
                 </el-button>
                 <div class="sa-flex">
                   <el-button class="is-link" type="primary" size="small" @click="editTemplate(temp.id)">
-                    编辑
+                    {{ t('modules.decorate.editAction') }}
                   </el-button>
                   <el-button class="is-link" :type="temp.status ? 'warning' : 'success'" size="small"
                     @click="toggleStatus(temp)">
-                    {{ temp.status ? '禁用' : '启用' }}
+                    {{ temp.status ? t('common.disable') : t('common.enable') }}
                   </el-button>
                 </div>
               </div>
@@ -68,12 +68,14 @@ export default {
 <script setup>
 import { onMounted, reactive } from 'vue';
 import { useRouter } from 'vue-router';
+import { useI18n } from 'vue-i18n';
 import { api } from '../decorate.service';
 import { useModal } from '@/sheep/hooks';
 import { platformList } from '../page/data';
 import TemplateEdit from './edit.vue';
 import { Picture, Clock } from '@element-plus/icons-vue';
 
+const { t } = useI18n();
 const router = useRouter();
 
 const template = reactive({
@@ -96,7 +98,7 @@ async function getData() {
 function addTemplate() {
   useModal(
     TemplateEdit,
-    { title: '新建', type: 'add', templateType: template.type },
+    { title: t('modules.decorate.newAction'), type: 'add', templateType: template.type },
     {
       confirm: () => {
         getData();
@@ -111,7 +113,7 @@ function editTemplate(id) {
   useModal(
     TemplateEdit,
     {
-      title: '编辑',
+      title: t('modules.decorate.editAction'),
       type: 'edit',
       id: id,
       templateType: template.type,

+ 36 - 32
src/app/shop/admin/decorate/template/select.vue

@@ -2,18 +2,19 @@
   <el-container class="template-view panel-block">
     <el-main>
       <el-table class="sa-table" :data="template.list" stripe>
-        <el-table-column prop="id" label="ID" min-width="80" />
-        <el-table-column prop="name" label="名称" min-width="180">
+        <el-table-column prop="id" :label="t('modules.decorate.templateId')" min-width="80" />
+        <el-table-column prop="name" :label="t('common.name')" min-width="180">
           <template #default="scope">
             <div class="sa-line-1">
               {{ scope.row.name }}
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="type_text" label="类型" min-width="80" />
-        <el-table-column prop="name" label="名称" min-width="100" fixed="right">
+        <el-table-column prop="type_text" :label="t('modules.decorate.templateType')" min-width="80" />
+        <el-table-column prop="name" :label="t('common.actions')" min-width="100" fixed="right">
           <template #default="scope">
-            <el-button class="is-link" type="primary" @click="onSelect(scope.row)">选择</el-button>
+            <el-button class="is-link" type="primary" @click="onSelect(scope.row)">{{
+              t('modules.decorate.selectTemplate') }}</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -22,34 +23,37 @@
 </template>
 
 <script>
-  export default {
-    name: 'TemplateSelect',
-  };
+export default {
+  name: 'TemplateSelect',
+};
 </script>
 
 <script setup>
-  import { onMounted, reactive } from 'vue';
-  import { api } from '../decorate.service';
-
-  const emit = defineEmits(['modalCallBack']);
-
-  const template = reactive({
-    loading: false,
-    list: [],
-  });
-
-  async function getData() {
-    template.loading = true;
-    const { data } = await api.template.select();
-    template.list = data;
-    template.loading = false;
-  }
-
-  function onSelect(row) {
-    emit('modalCallBack', { event: 'confirm', data: row });
-  }
-
-  onMounted(() => {
-    getData();
-  });
+import { onMounted, reactive } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { api } from '../decorate.service';
+
+const { t } = useI18n();
+
+const emit = defineEmits(['modalCallBack']);
+
+const template = reactive({
+  loading: false,
+  list: [],
+});
+
+async function getData() {
+  template.loading = true;
+  const { data } = await api.template.select();
+  template.list = data;
+  template.loading = false;
+}
+
+function onSelect(row) {
+  emit('modalCallBack', { event: 'confirm', data: row });
+}
+
+onMounted(() => {
+  getData();
+});
 </script>

+ 338 - 1
src/locales/en-US/index.json

@@ -1695,7 +1695,344 @@
       "enTitleRequired": "Please enter English title",
       "bnContentRequired": "Please enter Bengali content",
       "enContentRequired": "Please enter English content",
-      "categoryRequired": "Please select category"
+      "categoryRequired": "Please select a category"
+    },
+    "decorate": {
+      "pageDesign": "Page Design",
+      "templateManagement": "Template Management",
+      "designerTemplate": "Designer Template",
+      "pageBackground": "Page Background",
+      "backgroundColor": "Background Color",
+      "backgroundImage": "Background Image",
+      "headerSettings": "Header Settings",
+      "headerStyle": "Header Style",
+      "standard": "Standard",
+      "immersive": "Immersive",
+      "alwaysShow": "Always Show",
+      "enable": "Enable",
+      "disable": "Disable",
+      "background": "Background",
+      "solidColor": "Solid Color",
+      "image": "Image",
+      "selectColor": "Select Color",
+      "selectImage": "Select Image",
+      "headerContent": "Header Content",
+      "headerType": "Header Type",
+      "text": "Text",
+      "searchBox": "Search Box",
+      "textContent": "Text Content",
+      "textColor": "Text Color",
+      "uploadImage": "Upload Image",
+      "selectLink": "Select Link",
+      "placeholderText": "Placeholder Text",
+      "borderRadius": "Border Radius",
+      "componentStyle": "Component Style",
+      "componentBackground": "Component Background",
+      "topMargin": "Top Margin",
+      "rightMargin": "Right Margin",
+      "bottomMargin": "Bottom Margin",
+      "leftMargin": "Left Margin",
+      "topRadius": "Top Radius",
+      "bottomRadius": "Bottom Radius",
+      "innerPadding": "Inner Padding",
+      "height": "Height",
+      "content": "Content",
+      "style": "Style",
+      "data": "Data",
+      "css": "Data",
+      "selectStyle": "Select Style",
+      "styleSelection": "Style Selection",
+      "displaySettings": "Display Settings",
+      "position": "Position",
+      "left": "Left",
+      "center": "Center",
+      "offset": "Offset",
+      "titleSettings": "Title Settings",
+      "fontSize": "Font Size",
+      "other": "Other",
+      "bold": "Bold",
+      "italic": "Italic",
+      "subtitleSettings": "Subtitle Settings",
+      "moreSettings": "More Settings",
+      "showMore": "Show More",
+      "notShow": "Not Show",
+      "show": "Show",
+      "link": "Link",
+      "richTextContent": "Rich Text Content",
+      "editRichText": "Edit Rich Text Content",
+      "clearContent": "Clear Content",
+      "noRichTextContent": "No Rich Text Content",
+      "clickToEdit": "Click \"Edit Rich Text Content\" button to edit in popup with full rich text editing features",
+      "confirmClearContent": "Are you sure you want to clear the rich text content?",
+      "contentCleared": "Content cleared",
+      "richTextUpdated": "Rich text content updated",
+      "pleaseInputRichText": "Please input rich text content",
+      "videoPlayer": "Video Player",
+      "videoUpload": "Video Upload",
+      "videoCover": "Video Cover",
+      "supportedPlatforms": "Supported Platforms",
+      "remarks": "Remarks",
+      "use": "Use",
+      "recommendedSize": "Recommended Size",
+      "titleStyle": "Title Style",
+      "pleaseInputText": "Please input text",
+      "immersiveHeaderTip": "Immersive header only supports WeChat Mini Program and APP. It is recommended that the first component of the page is an image display component",
+      "alwaysShowTip": "After always show is turned off, the header widget will fade in when the page scrolls",
+      "saveData": "Save data?",
+      "abandon": "Abandon",
+      "customPage": "Custom Page",
+      "templateList": "List",
+      "newTemplate": "New Template",
+      "noCover": "No Cover",
+      "decorateAction": "Decorate",
+      "pageName": "Page Name",
+      "pleaseInputPageName": "Please input page name",
+      "sortOrder": "Sort Order",
+      "pleaseInputSort": "Please input sort order",
+      "pleaseInputTemplateName": "Please input template name",
+      "templateName": "Template Name",
+      "templateId": "ID",
+      "templateType": "Type",
+      "selectTemplate": "Select",
+      "newAction": "New",
+      "editAction": "Edit",
+      "basicComponents": "Basic Components",
+      "goodsComponents": "Product Components",
+      "mediaComponents": "Media Components",
+      "memberComponents": "Member Components",
+      "searchBlock": "Search Box",
+      "noticeBlock": "Notice Bar",
+      "menuButton": "Menu Navigation",
+      "menuList": "List Navigation",
+      "menuGrid": "Grid Navigation",
+      "goodsCard": "Product Card",
+      "goodsShelves": "Product Shelf",
+      "imageBlock": "Image Display",
+      "imageBanner": "Image Banner",
+      "titleBlock": "Title Bar",
+      "imageCube": "Image Cube",
+      "videoPlayerComp": "Video Player",
+      "lineBlock": "Divider Line",
+      "richtext": "Rich Text",
+      "hotzone": "Hotzone",
+      "userCard": "User Card",
+      "orderCard": "Order Card",
+      "walletCard": "Wallet Card",
+      "couponCard": "Coupon Card",
+      "pageSettings": "Page Settings",
+      "defaultRichTextContent": "Please enter rich text content...",
+      "richTextTitle": "Rich Text Content",
+      "titleBlockDefault": "Title Bar",
+      "subtitleDefault": "Subtitle",
+      "buyNowText": "Buy Now",
+      "defaultText": "Default Text",
+      "placeholderContent": "Placeholder Content",
+      "pleaseInputPlaceholder": "Please enter placeholder content",
+      "componentBorderRadius": "Border Radius",
+      "searchKeywords": "Search Keywords",
+      "maxThreeItems": "Maximum of three items can be created",
+      "keyword": "Keyword",
+      "pleaseInputKeyword": "Please enter keyword",
+      "addImageTitle": "Add Image",
+      "uploadImageLabel": "Upload Image",
+      "linkLabel": "Link",
+      "addTitle": "Add Title",
+      "titleText": "Title Text",
+      "pleaseInputTitle": "Please enter title",
+      "subtitleText": "Subtitle Text",
+      "pleaseInputSubtitle": "Please enter subtitle",
+      "displayMore": "Display More",
+      "moreLink": "More Link",
+      "styleSettings": "Style Settings",
+      "displayPosition": "Display Position",
+      "leftAlign": "Left Align",
+      "centerAlign": "Center Align",
+      "titleOffset": "Title Offset",
+      "fontSettings": "Font Settings",
+      "textSize": "Text Size",
+      "textColor": "Text Color",
+      "bold": "Bold",
+      "italic": "Italic",
+      "noticeContent": "Notice Content",
+      "pleaseInputNotice": "Please enter notice content",
+      "scrollSpeed": "Scroll Speed",
+      "menuSettings": "Menu Settings",
+      "menuItems": "Menu Items",
+      "maxFiveItems": "Maximum of five items can be created",
+      "menuName": "Menu Name",
+      "pleaseInputMenuName": "Please enter menu name",
+      "menuIcon": "Menu Icon",
+      "noticeIcon": "Notice Icon",
+      "noticeStyle": "Notice Style",
+      "systemIcon": "System Icon",
+      "customIcon": "Custom",
+      "noticeImage": "Notice Image",
+      "customImage": "Custom Image",
+      "contentSettings": "Content Settings",
+      "videoSettings": "Video Settings",
+      "videoUrl": "Video URL",
+      "pleaseInputVideoUrl": "Please enter video URL",
+      "videoCover": "Video Cover",
+      "autoPlay": "Auto Play",
+      "showControls": "Show Controls",
+      "bannerSettings": "Banner Settings",
+      "bannerImages": "Banner Images",
+      "addBannerImage": "Add Banner Image",
+      "switchInterval": "Switch Interval",
+      "seconds": "Seconds",
+      "showIndicators": "Show Indicators",
+      "cubeSettings": "Cube Settings",
+      "cubeLayout": "Layout Style",
+      "twoByTwo": "2x2",
+      "oneByThree": "1x3",
+      "threeByOne": "3x1",
+      "cubeItems": "Cube Items",
+      "lineSettings": "Line Settings",
+      "lineStyle": "Line Style",
+      "solid": "Solid",
+      "dashed": "Dashed",
+      "dotted": "Dotted",
+      "lineWidth": "Line Width",
+      "lineColor": "Line Color",
+      "hotzoneSettings": "Hotzone Settings",
+      "hotzoneAreas": "Hotzone Areas",
+      "addHotzone": "Add Hotzone",
+      "hotzonePosition": "Hotzone Position",
+      "hotzoneSize": "Hotzone Size",
+      "productSettings": "Product Settings",
+      "productDisplay": "Product Display",
+      "productList": "Product List",
+      "selectProducts": "Select Products",
+      "displayCount": "Display Count",
+      "showPrice": "Show Price",
+      "showStock": "Show Stock",
+      "showSales": "Show Sales",
+      "priceColor": "Price Color",
+      "buyButton": "Buy Button",
+      "buttonText": "Button Text",
+      "buttonStyle": "Button Style",
+      "setHotzone": "Set Hotzone",
+      "lineColor": "Line Color",
+      "uploadVideo": "Upload Video",
+      "clickUploadVideo": "Click to upload video",
+      "videoFormatTip": "Supports MP4, AVI, MOV, WMV, FLV, WebM formats, file size not exceeding 100MB",
+      "uploadVideoCover": "Upload video cover",
+      "videoCoverTip": "Video cover image, recommended size: 16:9 ratio",
+      "addHotzone": "Add Hotzone",
+      "save": "Save",
+      "doubleClickSelectLink": "Double click to select link",
+      "selectLink": "Select Link",
+      "pleaseSelectLink": "Please select a link",
+      "itemsPerRow": "Items Per Row",
+      "items": " items",
+      "functionTip": "Function Tip",
+      "pleaseInputFunctionTip": "Please enter function tip",
+      "badge": "Badge",
+      "badgeContent": "Badge Content",
+      "pleaseInputBadgeContent": "Please enter badge content",
+      "badgeBackground": "Badge Background",
+      "suggestedSize44": "Suggested size: 44*44",
+      "dotStyle": "Dot Style",
+      "spacing": "Spacing",
+      "timeInterval": "Time Interval",
+      "imageUpload": "Image Upload",
+      "selectType": "Select Type",
+      "video": "Video",
+      "uploadLabel": "Upload",
+      "pleaseInputVideoUrl": "Please enter video URL",
+      "cubeStyle": "Cube Style",
+      "gridSize187": "Grid size: 187*187",
+      "data": "Data",
+      "delete": "Delete",
+      "add": "Add",
+      "goodsSelect": "Goods Selection",
+      "selectGoods": "Select Goods",
+      "selectLiveRoom": "Select Live Room",
+      "select": "Select",
+      "pleaseInputOrSelect": "Please input or select",
+      "recommendedSize750x80": "Recommended size: 750*80",
+      "selectStyle": "Select Style",
+      "backgroundImage": "Background Image",
+      "displayPosition": "Display Position",
+      "leftAlign": "Left Align",
+      "centerAlign": "Center Align",
+      "titleOffset": "Title Offset",
+      "titleSettings": "Title Settings",
+      "subtitleSettings": "Subtitle Settings",
+      "subtitleText": "Subtitle Text",
+      "pleaseInputSubtitle": "Please enter subtitle",
+      "other": "Other",
+      "bold": "Bold",
+      "italic": "Italic",
+      "moreSettings": "More Settings",
+      "displayMore": "Display More",
+      "notDisplay": "Not Display",
+      "display": "Display",
+      "menuSettings": "Menu Settings",
+      "menuLayout": "Menu Layout",
+      "imageAndText": "Image + Text",
+      "imageOnly": "Image Only",
+      "columnCount": "Column Count",
+      "rowCount": "Row Count",
+      "row": " rows",
+      "suggestedSize98": "Suggested size: 98*98",
+      "productBadge": "Product Badge",
+      "badgeSelection": "Badge Selection",
+      "suggestedSize36x22": "Suggested size: 36*22",
+      "buttonColor": "Button Color",
+      "styleLabel": "Style",
+      "productTitle": "Product Title",
+      "subtitle": "Subtitle",
+      "productPrice": "Product Price",
+      "originalPrice": "Original Price",
+      "salesVolume": "Sales Volume",
+      "stockQuantity": "Stock Quantity",
+      "titleBar": "Title Bar",
+      "titleBarSample": "Title Bar Sample",
+      "more": "More"
+    },
+    "page": {
+      "selectPage": "Select Page",
+      "currentIndex": "Current Index",
+      "mainPages": "Main Pages",
+      "homePage": "Home Page",
+      "incomeCenter": "Income Center",
+      "personalCenter": "Personal Center",
+      "productDetail": "Product Detail",
+      "search": "Search",
+      "userRelated": "User Related",
+      "login": "Login",
+      "register": "Register",
+      "forgotPassword": "Forgot Password",
+      "personalProfile": "Personal Profile",
+      "settings": "Settings",
+      "share": "Share",
+      "myFavorites": "My Favorites",
+      "orderRelated": "Order Related",
+      "myOrders": "My Orders",
+      "orderDetail": "Order Detail",
+      "checkout": "Checkout",
+      "walletRelated": "Wallet Related",
+      "myWallet": "My Wallet",
+      "recharge": "Recharge",
+      "withdraw": "Withdraw",
+      "rechargeRecord": "Recharge Record",
+      "withdrawRecord": "Withdraw Record",
+      "frozenRecord": "Frozen Record",
+      "addressManagement": "Address Management",
+      "addressBook": "Address Book",
+      "addressOperation": "Address Operation",
+      "activityPages": "Activity Pages",
+      "missionCenter": "Mission Center",
+      "referEarn": "Refer & Earn",
+      "vipMembership": "VIP Membership",
+      "bestSellers": "Best Sellers",
+      "topChampions": "Top Champions",
+      "otherPages": "Other Pages",
+      "helpCenter": "Help Center",
+      "helpDetail": "Help Detail",
+      "notifications": "Notifications",
+      "webLink": "Web Link"
     }
   }
 }

+ 337 - 0
src/locales/zh-CN/index.json

@@ -1709,6 +1709,343 @@
       "bnContentRequired": "请输入孟加拉语内容",
       "enContentRequired": "请输入英文内容",
       "categoryRequired": "请选择分类"
+    },
+    "decorate": {
+      "pageDesign": "页面装修",
+      "templateManagement": "模板管理",
+      "designerTemplate": "设计师模板",
+      "pageBackground": "页面背景",
+      "backgroundColor": "背景色",
+      "backgroundImage": "背景图片",
+      "headerSettings": "头部设置",
+      "headerStyle": "头部样式",
+      "standard": "标准",
+      "immersive": "沉浸式",
+      "alwaysShow": "常驻显示",
+      "enable": "开启",
+      "disable": "关闭",
+      "background": "背景",
+      "solidColor": "纯色",
+      "image": "图片",
+      "selectColor": "选择颜色",
+      "selectImage": "选择图片",
+      "headerContent": "头部内容",
+      "headerType": "头部类型",
+      "text": "文字",
+      "searchBox": "搜索框",
+      "textContent": "文字内容",
+      "textColor": "文字颜色",
+      "uploadImage": "上传图片",
+      "selectLink": "选择链接",
+      "placeholderText": "提示文字",
+      "borderRadius": "圆角",
+      "componentStyle": "组件样式",
+      "componentBackground": "组件背景",
+      "topMargin": "上间距",
+      "rightMargin": "右间距",
+      "bottomMargin": "下间距",
+      "leftMargin": "左间距",
+      "topRadius": "上圆角",
+      "bottomRadius": "下圆角",
+      "innerPadding": "内间距",
+      "height": "高度",
+      "content": "内容",
+      "style": "样式",
+      "data": "数据",
+      "css": "数据",
+      "selectStyle": "选择风格",
+      "styleSelection": "风格选择",
+      "displaySettings": "显示设置",
+      "position": "位置",
+      "left": "居左",
+      "center": "居中",
+      "offset": "偏移",
+      "titleSettings": "标题设置",
+      "fontSize": "字号",
+      "other": "其他",
+      "bold": "加粗",
+      "italic": "倾斜",
+      "subtitleSettings": "副标题设置",
+      "moreSettings": "更多设置",
+      "showMore": "显示更多",
+      "notShow": "不显示",
+      "show": "显示",
+      "link": "链接",
+      "richTextContent": "富文本内容",
+      "editRichText": "编辑富文本内容",
+      "clearContent": "清空内容",
+      "noRichTextContent": "暂无富文本内容",
+      "clickToEdit": "点击\"编辑富文本内容\"按钮在弹窗中编辑,支持完整的富文本编辑功能",
+      "confirmClearContent": "确定要清空富文本内容吗?",
+      "contentCleared": "内容已清空",
+      "richTextUpdated": "富文本内容已更新",
+      "pleaseInputRichText": "请输入富文本内容",
+      "videoPlayer": "视频播放器",
+      "videoUpload": "视频上传",
+      "videoCover": "视频封面",
+      "supportedPlatforms": "支持平台",
+      "remarks": "备注",
+      "use": "使用",
+      "recommendedSize": "建议尺寸",
+      "titleStyle": "标题样式",
+      "pleaseInputText": "请输入文字",
+      "immersiveHeaderTip": "沉侵式头部仅支持微信小程序、APP 建议页面第一个组件为图片展示类组件",
+      "alwaysShowTip": "常驻显示关闭后,头部小组件将在页面滑动时淡入",
+      "saveData": "是否保存数据",
+      "abandon": "放弃",
+      "customPage": "自定义页面",
+      "templateList": "列表",
+      "newTemplate": "新建模板",
+      "noCover": "暂无封面",
+      "decorateAction": "装修",
+      "pageName": "页面名称",
+      "pleaseInputPageName": "请输入页面名称",
+      "sortOrder": "排序",
+      "pleaseInputSort": "请输入排序",
+      "pleaseInputTemplateName": "请输入模板名称",
+      "templateName": "模板名称",
+      "templateId": "ID",
+      "templateType": "类型",
+      "selectTemplate": "选择",
+      "newAction": "新建",
+      "editAction": "编辑",
+      "basicComponents": "基础组件",
+      "goodsComponents": "商品组件",
+      "mediaComponents": "图文组件",
+      "memberComponents": "会员组件",
+      "searchBlock": "搜索框",
+      "noticeBlock": "公告栏",
+      "menuButton": "菜单导航",
+      "menuList": "列表导航",
+      "menuGrid": "宫格导航",
+      "goodsCard": "商品卡片",
+      "goodsShelves": "商品栏",
+      "imageBlock": "图片展示",
+      "imageBanner": "图片轮播",
+      "titleBlock": "标题栏",
+      "imageCube": "广告魔方",
+      "videoPlayerComp": "视频播放",
+      "lineBlock": "辅助线",
+      "richtext": "富文本",
+      "hotzone": "热区",
+      "userCard": "会员卡片",
+      "orderCard": "订单卡片",
+      "walletCard": "资产卡片",
+      "couponCard": "卡券卡片",
+      "pageSettings": "页面设置",
+      "defaultRichTextContent": "请输入富文本内容...",
+      "richTextTitle": "富文本内容",
+      "titleBlockDefault": "标题栏",
+      "subtitleDefault": "副标题",
+      "buyNowText": "立即购买",
+      "defaultText": "默认文字",
+      "placeholderContent": "提示内容",
+      "pleaseInputPlaceholder": "请输入提示内容",
+      "componentBorderRadius": "圆角",
+      "searchKeywords": "搜索关键字",
+      "maxThreeItems": "最多可创建三个",
+      "keyword": "关键字",
+      "pleaseInputKeyword": "请输入关键字",
+      "addImageTitle": "添加图片",
+      "uploadImageLabel": "上传图片",
+      "linkLabel": "链接",
+      "addTitle": "添加标题",
+      "titleText": "标题文字",
+      "pleaseInputTitle": "请输入标题",
+      "subtitleText": "副标题文字",
+      "pleaseInputSubtitle": "请输入副标题",
+      "displayMore": "显示更多",
+      "moreLink": "更多链接",
+      "styleSettings": "风格设置",
+      "displayPosition": "显示位置",
+      "leftAlign": "居左",
+      "centerAlign": "居中",
+      "titleOffset": "标题偏移",
+      "fontSettings": "字体设置",
+      "textSize": "字号",
+      "textColor": "文字颜色",
+      "bold": "加粗",
+      "italic": "倾斜",
+      "noticeContent": "公告内容",
+      "pleaseInputNotice": "请输入公告内容",
+      "scrollSpeed": "滚动速度",
+      "menuSettings": "菜单设置",
+      "menuItems": "菜单项",
+      "maxFiveItems": "最多可创建五个",
+      "menuName": "菜单名称",
+      "pleaseInputMenuName": "请输入菜单名称",
+      "menuIcon": "菜单图标",
+      "noticeIcon": "公告图标",
+      "noticeStyle": "公告样式",
+      "systemIcon": "系统图标",
+      "customIcon": "自定义",
+      "noticeImage": "公告图",
+      "customImage": "自定义图片",
+      "contentSettings": "内容设置",
+      "videoSettings": "视频设置",
+      "videoUrl": "视频地址",
+      "pleaseInputVideoUrl": "请输入视频地址",
+      "videoCover": "视频封面",
+      "autoPlay": "自动播放",
+      "showControls": "显示控制栏",
+      "bannerSettings": "轮播设置",
+      "bannerImages": "轮播图片",
+      "addBannerImage": "添加轮播图",
+      "switchInterval": "切换间隔",
+      "seconds": "秒",
+      "showIndicators": "显示指示器",
+      "cubeSettings": "魔方设置",
+      "cubeLayout": "布局方式",
+      "twoByTwo": "2x2",
+      "oneByThree": "1x3",
+      "threeByOne": "3x1",
+      "cubeItems": "魔方项目",
+      "lineSettings": "线条设置",
+      "lineStyle": "线条样式",
+      "solid": "实线",
+      "dashed": "虚线",
+      "dotted": "点线",
+      "lineWidth": "线条宽度",
+      "lineColor": "线条颜色",
+      "hotzoneSettings": "热区设置",
+      "hotzoneAreas": "热区区域",
+      "addHotzone": "添加热区",
+      "hotzonePosition": "热区位置",
+      "hotzoneSize": "热区大小",
+      "productSettings": "商品设置",
+      "productDisplay": "商品展示",
+      "productList": "商品列表",
+      "selectProducts": "选择商品",
+      "displayCount": "显示数量",
+      "showPrice": "显示价格",
+      "showStock": "显示库存",
+      "showSales": "显示销量",
+      "priceColor": "价格颜色",
+      "buyButton": "购买按钮",
+      "buttonText": "按钮文字",
+      "buttonStyle": "按钮样式",
+      "setHotzone": "设置热区",
+      "lineColor": "线条颜色",
+      "uploadVideo": "上传视频",
+      "clickUploadVideo": "点击上传视频",
+      "videoFormatTip": "支持 MP4、AVI、MOV、WMV、FLV、WebM 格式,文件大小不超过100MB",
+      "uploadVideoCover": "上传视频封面",
+      "videoCoverTip": "视频封面图片,建议尺寸:16:9 比例",
+      "addHotzone": "添加热区",
+      "save": "保存",
+      "doubleClickSelectLink": "双击选择链接",
+      "selectLink": "选择链接",
+      "pleaseSelectLink": "请选择链接",
+      "itemsPerRow": "每行数量",
+      "items": "个",
+      "functionTip": "功能提示",
+      "pleaseInputFunctionTip": "请输入功能提示",
+      "badge": "标签",
+      "badgeContent": "标签内容",
+      "pleaseInputBadgeContent": "请输入标签内容",
+      "badgeBackground": "标签背景",
+      "suggestedSize44": "建议尺寸:44*44",
+      "dotStyle": "Dot样式",
+      "spacing": "间距",
+      "timeInterval": "时间间隔",
+      "imageUpload": "图片上传",
+      "selectType": "选择类型",
+      "video": "视频",
+      "uploadLabel": "上传",
+      "pleaseInputVideoUrl": "请输入视频链接",
+      "cubeStyle": "魔方样式",
+      "gridSize187": "每格尺寸:187*187",
+      "data": "数据",
+      "delete": "删除",
+      "add": "添加",
+      "goodsSelect": "商品选择",
+      "selectGoods": "选择商品",
+      "selectLiveRoom": "选择直播间",
+      "select": "选择",
+      "pleaseInputOrSelect": "请输入或选择",
+      "recommendedSize750x80": "推荐尺寸:750*80",
+      "selectStyle": "选择风格",
+      "backgroundImage": "背景图片",
+      "displayPosition": "显示位置",
+      "leftAlign": "左对齐",
+      "centerAlign": "居中对齐",
+      "titleOffset": "标题偏移",
+      "titleSettings": "标题设置",
+      "subtitleSettings": "副标题设置",
+      "subtitleText": "副标题文字",
+      "pleaseInputSubtitle": "请输入副标题",
+      "other": "其他",
+      "bold": "加粗",
+      "italic": "斜体",
+      "moreSettings": "更多设置",
+      "displayMore": "显示更多",
+      "notDisplay": "不显示",
+      "display": "显示",
+      "menuSettings": "菜单设置",
+      "menuLayout": "菜单布局",
+      "imageAndText": "图片+文字",
+      "imageOnly": "图片",
+      "columnCount": "列数",
+      "rowCount": "行数",
+      "row": "行",
+      "suggestedSize98": "建议尺寸:98*98",
+      "productBadge": "商品角标",
+      "badgeSelection": "角标选择",
+      "suggestedSize36x22": "建议尺寸:36*22",
+      "buttonColor": "按钮颜色",
+      "styleLabel": "样式",
+      "productTitle": "商品标题",
+      "subtitle": "副标题",
+      "productPrice": "商品价格",
+      "originalPrice": "原价",
+      "salesVolume": "销量",
+      "stockQuantity": "库存",
+      "titleBar": "标题栏",
+      "titleBarSample": "标题栏示例",
+      "more": "更多"
+    },
+    "page": {
+      "selectPage": "选择页面",
+      "currentIndex": "当前索引",
+      "mainPages": "主要页面",
+      "homePage": "首页",
+      "incomeCenter": "收益中心",
+      "personalCenter": "个人中心",
+      "productDetail": "商品详情",
+      "search": "搜索",
+      "userRelated": "用户相关",
+      "login": "登录",
+      "register": "注册",
+      "forgotPassword": "忘记密码",
+      "personalProfile": "个人资料",
+      "settings": "设置",
+      "share": "分享",
+      "myFavorites": "我的收藏",
+      "orderRelated": "订单相关",
+      "myOrders": "我的订单",
+      "orderDetail": "订单详情",
+      "checkout": "结算",
+      "walletRelated": "钱包相关",
+      "myWallet": "我的钱包",
+      "recharge": "充值",
+      "withdraw": "提现",
+      "rechargeRecord": "充值记录",
+      "withdrawRecord": "提现记录",
+      "frozenRecord": "冻结记录",
+      "addressManagement": "地址管理",
+      "addressBook": "地址簿",
+      "addressOperation": "地址操作",
+      "activityPages": "活动页面",
+      "missionCenter": "任务中心",
+      "referEarn": "推荐赚钱",
+      "vipMembership": "VIP会员",
+      "bestSellers": "热销商品",
+      "topChampions": "冠军榜",
+      "otherPages": "其他页面",
+      "helpCenter": "帮助中心",
+      "helpDetail": "帮助详情",
+      "notifications": "通知",
+      "webLink": "网页链接"
     }
   }
 }