Browse Source

多语言

lipenghui 3 months ago
parent
commit
c43a61d8fd

+ 144 - 0
src/components/language/LangInput.vue

@@ -0,0 +1,144 @@
+
+<template>
+  <div class="lang-input-wrapper">
+    <el-input
+      v-model="displayValue"
+      v-bind="$attrs"
+      :type="type"
+    >
+      <template #suffix>
+        <el-popover
+          placement="right"
+          trigger="click"
+          :width="300"
+          popper-class="lang-popover"
+          ref="popoverRef"
+        >
+          <template #reference>
+            <Icon icon="fa:language" class="lang-icon" />
+          </template>
+          <div class="lang-popover-content">
+            <div class="lang-item mt-10">
+              <span class="flag-icon cn-flag"></span>
+              <el-input v-model="chineseValue" disabled placeholder="中文" />
+            </div>
+            <div class="lang-item mt-10">
+              <span class="flag-icon uk-flag"></span>
+              <el-input v-model="englishValue" placeholder="英文" />
+            </div>
+            <div class="popover-footer">
+              <el-button size="small" @click="closePopover">取消</el-button>
+              <el-button size="small" type="primary" @click="saveAndClose">确认</el-button>
+            </div>
+          </div>
+        </el-popover>
+      </template>
+    </el-input>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch } from 'vue'
+import { Edit } from '@element-plus/icons-vue'
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  type: {
+    type: String,
+    default: 'text'
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+const popoverRef = ref(null)
+
+const chineseValue = ref('')
+const englishValue = ref('')
+
+const displayValue = computed({
+  get() {
+    return props.modelValue.split('~~')[0] || ''
+  },
+  set(val) {
+    emit('update:modelValue', val + '~~' + englishValue.value)
+  }
+})
+
+watch(() => props.modelValue, (newVal) => {
+  const [chinese, english] = newVal.split('~~')
+  chineseValue.value = chinese || ''
+  englishValue.value = english || ''
+}, { immediate: true })
+
+const saveLang = () => {
+  emit('update:modelValue', chineseValue.value + '~~' + 'en**'+englishValue.value)
+}
+
+const closePopover = () => {
+  popoverRef.value?.hide()
+}
+
+const saveAndClose = () => {
+  saveLang()
+  closePopover()
+}
+</script>
+
+<style scoped>
+.lang-input-wrapper {
+  position: relative;
+  display: inline-block;
+  width: 100%;
+}
+.lang-icon {
+  cursor: pointer;
+  color: var(--el-color-primary);
+  margin-left: 8px;
+  font-size: 20px;
+}
+.lang-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.flag-icon {
+  display: inline-block;
+  width: 24px;
+  height: 24px;
+  background-size: contain;
+  background-repeat: no-repeat;
+  flex-shrink: 0;
+}
+.cn-flag {
+  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3crect width='512' height='512' fill='%23de2910'/%3e%3cpath fill='%23ffde00' d='M106.7 106.7h298.7v298.7H106.7z'/%3e%3cpath fill='%23de2910' d='M256 167.5l-33.2 102-87.5-63.5h108.1L289.2 269.5z'/%3e%3c/svg%3e");
+}
+.uk-flag {
+  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 30'%3e%3cpath fill='%23012169' d='M0 0h60v30H0z'/%3e%3cpath fill='%23fff' d='M0 0l60 30m0-30L0 30' stroke='%23fff' stroke-width='6'/%3e%3cpath fill='%23c8102e' d='M0 0l60 30m0-30L0 30' stroke='%23c8102e' stroke-width='4'/%3e%3cpath fill='%23c8102e' d='M30 0v30M0 15h60' stroke='%23fff' stroke-width='10'/%3e%3c/svg%3e");
+}
+.mt-10 {
+  margin-top: 10px;
+}
+.popover-footer {
+  margin-top: 10px;
+  display: flex;
+  justify-content: flex-end;
+  //gap: 3px;
+}
+</style>
+
+<style>
+.lang-popover {
+  padding: 15px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15);
+  border-radius: 8px;
+}
+.lang-popover .el-input__wrapper {
+  //padding: 8px 12px;
+}
+.lang-popover .el-input__inner {
+  font-size: 12px;
+}
+</style>

+ 45 - 3
src/config/axios/service.ts

@@ -8,10 +8,11 @@ import errorCode from './errorCode'
 
 import { resetRouter } from '@/router'
 import { deleteUserCache } from '@/hooks/web/useCache'
-
+import {langHelper} from '@/utils/langHelper'
+import { useLocaleStore } from '@/store/modules/locale'
 const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
 const { result_code, base_url, request_timeout } = config
-
+// const localeStore = useLocaleStore()
 // 需要忽略的提示。忽略后,自动 Promise.reject('error')
 const ignoreMsgs = [
   '无效的刷新令牌', // 刷新令牌被删除时,不用提示
@@ -175,7 +176,40 @@ service.interceptors.response.use(
       }
       return Promise.reject('error')
     } else {
-      return data
+      // const lang = localeStore.getCurrentLocale.lang
+
+      const requestUrl = response.config.url || ''
+      // 判断是否包含rq/iot路径
+      if (requestUrl.includes('rq/')||requestUrl.includes('system/dict')) {
+        const localeStore = useLocaleStore()
+        const lang = localeStore.getCurrentLocale.lang
+        console.log('检测到rq/:', requestUrl)
+        // const lang = 'en'
+        debugger
+        if (data&& data.data) {
+          if (data.data.list) {
+            if (Array.isArray(data.data.list)) {
+              const list = langHelper.transformArray(data.data.list, lang)
+              data.data.list = list;
+              return data;
+            }
+          }else if (data &&Array.isArray(data.data)) {
+            const list = langHelper.transformArray(data.data, lang)
+            data.data = list;
+            return data;
+          }else if (data && typeof data.data === 'object') {
+            const object =  langHelper.transformObject(data, lang)
+            data = object
+            return data
+          } else {
+            return data
+          }
+        }
+
+      }else {
+        return data
+      }
+      // return data
     }
   },
   (error: AxiosError) => {
@@ -194,6 +228,14 @@ service.interceptors.response.use(
   }
 )
 
+const isSystemPagePath = (path: string): boolean=> {
+  // 正则说明:
+  // ^.*system/ 匹配开头任意字符直到system/
+  // (?:[^/]+/)* 匹配零个或多个非斜杠字符组成的路径段
+  // page(?:\?.*)?$ 匹配page结尾,可带查询参数
+  const pattern = /^.*system\/(?:[^/]+\/)*page(?:\?.*)?$/i
+  return pattern.test(path)
+}
 const refreshToken = async () => {
   axios.defaults.headers.common['tenant-id'] = getTenantId()
   return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())

+ 22 - 1
src/locales/en.ts

@@ -1,5 +1,6 @@
 export default {
   common: {
+    sort:'sort',
     inputText: 'Please input',
     selectText: 'Please select',
     startTimeText: 'Start time',
@@ -291,6 +292,7 @@ export default {
     header: 'Header'
   },
   action: {
+    search:'Search',
     create: 'Create',
     add: 'Add',
     del: 'Delete',
@@ -456,5 +458,24 @@ export default {
     btn_zoom_in: 'Zoom in',
     btn_zoom_out: 'Zoom out',
     preview: 'Preivew'
-  }
+  },
+  dict:{
+    no:'dictNo',
+    name:'dictName',
+    type:'dictType',
+    status:'status',
+    remark:'remark',
+    createTime:'createTime',
+    data:'DictData',
+    label:'dictLabel',
+    typePlace:'please enter dictType',
+    labelPlace:'please enter dictLabel',
+    statusPlace:'please enter dictStatus',
+    value:'dictValue',
+    color:'colorType',
+    dataPlace:'please enter dataLabel',
+    valuePlace:'please enter value',
+    dataLabel:'dataLabel',
+    dataValue:'dataValue'
+  },
 }

+ 21 - 0
src/locales/zh-CN.ts

@@ -1,5 +1,6 @@
 export default {
   common: {
+    sort:'排序',
     inputText: '请输入',
     selectText: '请选择',
     startTimeText: '开始时间',
@@ -293,6 +294,7 @@ export default {
     header: '头部'
   },
   action: {
+    search:'搜索',
     create: '新增',
     add: '新增',
     del: '删除',
@@ -452,5 +454,24 @@ export default {
     btn_zoom_out: '缩小',
     preview: '预览'
   },
+  dict:{
+    no:'字典编号',
+    name:'字典名称',
+    type:'字典类型',
+    status:'状态',
+    remark:'备注',
+    createTime:'创建时间',
+    data:'数据',
+    label:'字典标签',
+    labelPlace:'请输入字典标签',
+    statusPlace:'请输入状态',
+    value:'字典键值',
+    color:'颜色类型',
+    typePlace:'请输入字典类型',
+    dataPlace:'请输入数据标签',
+    valuePlace:'请输入数据键值',
+    dataLabel:'数据标签',
+    dataValue:'数据键值'
+  },
   'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错
 }

+ 1 - 0
src/store/modules/dict.ts

@@ -71,6 +71,7 @@ export const useDictStore = defineStore('dict', {
       if (!this.isSetDict) {
         this.setDictMap()
       }
+      debugger
       return this.dictMap[type]
     },
     async resetDict() {

+ 37 - 0
src/utils/langHelper.ts

@@ -0,0 +1,37 @@
+
+interface LangPair {
+  zh: string
+  en: string
+}
+
+export const langHelper = {
+  parseLangString(str: string): LangPair {
+    if (!str.includes('~~')) return { zh: str, en: str }
+    const [zhPart, enPart] = str.split('~~')
+    return {
+      zh: zhPart.replace('zh-CN**', ''),
+      en: enPart?.replace('en**', '') || zhPart.replace('zh-CN**', '')
+    }
+  },
+
+  getDisplayText(str: string, currentLang: string): string {
+    const { zh, en } = this.parseLangString(str)
+    return currentLang === 'zh-CN' ? zh : (en || zh)
+  },
+
+  transformObject<T extends Record<string, any>>(obj: T, currentLang: string): T {
+    const result = { ...obj }
+    for (const key in result) {
+      if (typeof result[key] === 'string') {
+        result[key] = this.getDisplayText(result[key], currentLang)
+      } else if (typeof result[key] === 'object' && result[key] !== null) {
+        result[key] = this.transformObject(result[key], currentLang)
+      }
+    }
+    return result
+  },
+
+  transformArray<T extends Record<string, any>>(arr: T[], currentLang: string): T[] {
+    return arr.map(item => this.transformObject(item, currentLang))
+  }
+}

+ 1 - 0
src/views/Home/Index.vue

@@ -258,6 +258,7 @@ const safe = ref()
 const getStats = async () => {
   // 获取基础统计数据
   IotStatApi.getDeviceCount().then((res) => {
+    debugger
     device.value = res
   })
   IotStatApi.getMaintainCount().then((res) => {

+ 7 - 5
src/views/system/dict/DictTypeForm.vue

@@ -7,17 +7,18 @@
       :rules="formRules"
       label-width="80px"
     >
-      <el-form-item label="字典名称" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入字典名称" />
+      <el-form-item :label="t('dict.name')" prop="name">
+<!--        <el-input v-model="formData.name" placeholder="请输入字典名称" />-->
+        <lang-input v-model="formData.name" placeholder="请输入字典名称" />
       </el-form-item>
-      <el-form-item label="字典类型" prop="type">
+      <el-form-item :label="t('dict.type')" prop="type">
         <el-input
           v-model="formData.type"
           :disabled="typeof formData.id !== 'undefined'"
           placeholder="请输入参数名称"
         />
       </el-form-item>
-      <el-form-item label="状态" prop="status">
+      <el-form-item :label="t('dict.status')" prop="status">
         <el-radio-group v-model="formData.status">
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@@ -28,7 +29,7 @@
           </el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="备注" prop="remark">
+      <el-form-item :label="t('dict.remark')" prop="remark">
         <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
       </el-form-item>
     </el-form>
@@ -42,6 +43,7 @@
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as DictTypeApi from '@/api/system/dict/dict.type'
 import { CommonStatusEnum } from '@/utils/constants'
+import LangIn from '@/components/language/LangInput.vue'
 
 defineOptions({ name: 'SystemDictTypeForm' })
 

+ 12 - 11
src/views/system/dict/data/DictDataForm.vue

@@ -7,23 +7,24 @@
       :rules="formRules"
       label-width="80px"
     >
-      <el-form-item label="字典类型" prop="type">
+      <el-form-item :label="t('dict.type')" prop="type">
         <el-input
           v-model="formData.dictType"
           :disabled="typeof formData.id !== 'undefined'"
           placeholder="请输入参数名称"
         />
       </el-form-item>
-      <el-form-item label="数据标签" prop="label">
-        <el-input v-model="formData.label" placeholder="请输入数据标签" />
+      <el-form-item :label="t('dict.dataLabel')" prop="label">
+<!--        <el-input v-model="formData.label" :placeholder="t('dict.dataPlace')" />-->
+        <lang-input v-model="formData.label" :placeholder="t('dict.dataPlaceholder')" />
       </el-form-item>
-      <el-form-item label="数据键值" prop="value">
-        <el-input v-model="formData.value" placeholder="请输入数据键值" />
+      <el-form-item :label="t('dict.dataValue')" prop="value">
+        <el-input v-model="formData.value" :placeholder="t('dict.valuePlace')" />
       </el-form-item>
-      <el-form-item label="显示排序" prop="sort">
+      <el-form-item :label="t('common.sort')" prop="sort">
         <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
       </el-form-item>
-      <el-form-item label="状态" prop="status">
+      <el-form-item :label="t('dict.status')" prop="status">
         <el-radio-group v-model="formData.status">
           <el-radio
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@@ -34,7 +35,7 @@
           </el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="颜色类型" prop="colorType">
+      <el-form-item :label="t('dict.color')" prop="colorType">
         <el-select v-model="formData.colorType">
           <el-option
             v-for="item in colorTypeOptions"
@@ -45,10 +46,10 @@
         </el-select>
       </el-form-item>
       <el-form-item label="CSS Class" prop="cssClass">
-        <el-input v-model="formData.cssClass" placeholder="请输入 CSS Class" />
+        <el-input v-model="formData.cssClass" placeholder="CSS Class" />
       </el-form-item>
-      <el-form-item label="备注" prop="remark">
-        <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" />
+      <el-form-item :label="t('dict.remark')" prop="remark">
+        <el-input v-model="formData.remark" type="textarea" />
       </el-form-item>
     </el-form>
     <template #footer>

+ 19 - 18
src/views/system/dict/data/index.vue

@@ -7,7 +7,7 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="字典名称" prop="dictType">
+      <el-form-item :label="t('dict.name')" label-width="80px" prop="dictType">
         <el-select v-model="queryParams.dictType" class="!w-240px">
           <el-option
             v-for="item in dictTypeList"
@@ -17,17 +17,17 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="字典标签" prop="label">
+      <el-form-item :label="t('dict.label')" prop="label">
         <el-input
           v-model="queryParams.label"
-          placeholder="请输入字典标签"
+          :placeholder="t('dict.labelPlace')"
           clearable
           @keyup.enter="handleQuery"
           class="!w-240px"
         />
       </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="数据状态" clearable class="!w-240px">
+      <el-form-item :label="t('dict.status')" prop="status">
+        <el-select v-model="queryParams.status" :placeholder="t('dict.statusPlace')" clearable class="!w-240px">
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="dict.value"
@@ -37,15 +37,15 @@
         </el-select>
       </el-form-item>
       <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{t('action.search')}}</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{t('common.reset')}}</el-button>
         <el-button
           type="primary"
           plain
           @click="openForm('create')"
           v-hasPermi="['system:dict:create']"
         >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
+          <Icon icon="ep:plus" class="mr-5px" /> {{t('action.create')}}
         </el-button>
         <el-button
           type="success"
@@ -54,7 +54,7 @@
           :loading="exportLoading"
           v-hasPermi="['system:dict:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          <Icon icon="ep:download" class="mr-5px" /> {{t('action.export')}}
         </el-button>
       </el-form-item>
     </el-form>
@@ -63,26 +63,26 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="字典编码" align="center" prop="id" />
-      <el-table-column label="字典标签" align="center" prop="label" />
-      <el-table-column label="字典键值" align="center" prop="value" />
-      <el-table-column label="字典排序" align="center" prop="sort" />
-      <el-table-column label="状态" align="center" prop="status">
+      <el-table-column :label="t('dict.no')" align="center" prop="id" />
+      <el-table-column :label="t('dict.label')" align="center" prop="label" />
+      <el-table-column :label="t('dict.value')" align="center" prop="value" />
+      <el-table-column :label="t('common.sort')" align="center" prop="sort" />
+      <el-table-column :label="t('dict.status')" align="center" prop="status">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column label="颜色类型" align="center" prop="colorType" />
+      <el-table-column :label="t('dict.color')" align="center" prop="colorType" />
       <el-table-column label="CSS Class" align="center" prop="cssClass" />
-      <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+      <el-table-column :label="t('dict.remark')" align="center" prop="remark" show-overflow-tooltip />
       <el-table-column
-        label="创建时间"
+        :label="t('dict.createTime')"
         align="center"
         prop="createTime"
         width="180"
         :formatter="dateFormatter"
       />
-      <el-table-column label="操作" align="center">
+      <el-table-column :label="t('table.action')" align="center">
         <template #default="scope">
           <el-button
             link
@@ -204,6 +204,7 @@ const handleExport = async () => {
 /** 初始化 **/
 onMounted(async () => {
   await getList()
+  console.log(route.params.dictType)
   // 查询字典(精简)列表
   dictTypeList.value = await DictTypeApi.getSimpleDictTypeList()
 })

+ 18 - 18
src/views/system/dict/index.vue

@@ -8,7 +8,7 @@
       class="-mb-15px"
       label-width="68px"
     >
-      <el-form-item label="字典名称" prop="name">
+      <el-form-item :label="t('dict.name')" label-width="80px" prop="name">
         <el-input
           v-model="queryParams.name"
           class="!w-240px"
@@ -17,7 +17,7 @@
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="字典类型" prop="type">
+      <el-form-item :label="t('dict.type')" prop="type">
         <el-input
           v-model="queryParams.type"
           class="!w-240px"
@@ -26,7 +26,7 @@
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="状态" prop="status">
+      <el-form-item :label="t('dict.status')" prop="status">
         <el-select
           v-model="queryParams.status"
           class="!w-240px"
@@ -41,7 +41,7 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
+      <el-form-item :label="t('dict.createTime')" prop="createTime">
         <el-date-picker
           v-model="queryParams.createTime"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
@@ -55,11 +55,11 @@
       <el-form-item>
         <el-button @click="handleQuery">
           <Icon class="mr-5px" icon="ep:search" />
-          搜索
+          {{t('action.search')}}
         </el-button>
         <el-button @click="resetQuery">
           <Icon class="mr-5px" icon="ep:refresh" />
-          重置
+          {{t('common.reset')}}
         </el-button>
         <el-button
           v-hasPermi="['system:dict:create']"
@@ -68,7 +68,7 @@
           @click="openForm('create')"
         >
           <Icon class="mr-5px" icon="ep:plus" />
-          新增
+          {{t('action.create')}}
         </el-button>
         <el-button
           v-hasPermi="['system:dict:export']"
@@ -78,7 +78,7 @@
           @click="handleExport"
         >
           <Icon class="mr-5px" icon="ep:download" />
-          导出
+          {{t('action.export')}}
         </el-button>
       </el-form-item>
     </el-form>
@@ -87,23 +87,23 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column align="center" label="字典编号" prop="id" />
-      <el-table-column align="center" label="字典名称" prop="name" show-overflow-tooltip />
-      <el-table-column align="center" label="字典类型" prop="type" width="300" />
-      <el-table-column align="center" label="状态" prop="status">
+      <el-table-column align="center" :label="t('dict.no')" prop="id" />
+      <el-table-column align="center" :label="t('dict.name')" prop="name" show-overflow-tooltip />
+      <el-table-column align="center" :label="t('dict.type')" prop="type" width="300" />
+      <el-table-column align="center" :label="t('dict.status')" prop="status">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column align="center" label="备注" prop="remark" />
+      <el-table-column align="center" :label="t('dict.remark')" prop="remark" />
       <el-table-column
         :formatter="dateFormatter"
         align="center"
-        label="创建时间"
+        :label="t('dict.createTime')"
         prop="createTime"
         width="180"
       />
-      <el-table-column align="center" label="操作">
+      <el-table-column align="center" :label="t('table.action')">
         <template #default="scope">
           <el-button
             v-hasPermi="['system:dict:update']"
@@ -111,10 +111,10 @@
             type="primary"
             @click="openForm('update', scope.row.id)"
           >
-            修改
+            {{t('action.update')}}
           </el-button>
           <router-link :to="'/dict/type/data/' + scope.row.type">
-            <el-button link type="primary">数据</el-button>
+            <el-button link type="primary">{{t('dict.data')}}</el-button>
           </router-link>
           <el-button
             v-hasPermi="['system:dict:delete']"
@@ -122,7 +122,7 @@
             type="danger"
             @click="handleDelete(scope.row.id)"
           >
-            删除
+            {{t('action.delete')}}
           </el-button>
         </template>
       </el-table-column>