jinx 2 năm trước cách đây
mục cha
commit
a2b01f5219

+ 50 - 22
package-lock.json

@@ -3176,7 +3176,6 @@
       "version": "1.0.2",
       "resolved": "http://192.168.0.47:4873/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
@@ -4447,7 +4446,6 @@
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/deep-equal/-/deep-equal-1.1.1.tgz",
       "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
-      "dev": true,
       "requires": {
         "is-arguments": "^1.0.4",
         "is-date-object": "^1.0.1",
@@ -4591,7 +4589,6 @@
       "version": "1.1.4",
       "resolved": "http://192.168.0.47:4873/define-properties/-/define-properties-1.1.4.tgz",
       "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
-      "dev": true,
       "requires": {
         "has-property-descriptors": "^1.0.0",
         "object-keys": "^1.1.1"
@@ -5349,8 +5346,7 @@
     "extend": {
       "version": "3.0.2",
       "resolved": "http://192.168.0.47:4873/extend/-/extend-3.0.2.tgz",
-      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
-      "dev": true
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
     },
     "extend-shallow": {
       "version": "3.0.2",
@@ -5450,6 +5446,11 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
+    "fast-diff": {
+      "version": "1.1.2",
+      "resolved": "http://192.168.0.47:4873/fast-diff/-/fast-diff-1.1.2.tgz",
+      "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
+    },
     "fast-glob": {
       "version": "2.2.7",
       "resolved": "http://192.168.0.47:4873/fast-glob/-/fast-glob-2.2.7.tgz",
@@ -5739,8 +5740,7 @@
     "function-bind": {
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
     "function.prototype.name": {
       "version": "1.1.5",
@@ -5757,8 +5757,7 @@
     "functions-have-names": {
       "version": "1.2.3",
       "resolved": "http://192.168.0.47:4873/functions-have-names/-/functions-have-names-1.2.3.tgz",
-      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-      "dev": true
+      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
     },
     "gensync": {
       "version": "1.0.0-beta.2",
@@ -5776,7 +5775,6 @@
       "version": "1.2.0",
       "resolved": "http://192.168.0.47:4873/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
       "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
@@ -5950,7 +5948,6 @@
       "version": "1.0.3",
       "resolved": "http://192.168.0.47:4873/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1"
       }
@@ -5971,7 +5968,6 @@
       "version": "1.0.0",
       "resolved": "http://192.168.0.47:4873/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
       "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-      "dev": true,
       "requires": {
         "get-intrinsic": "^1.1.1"
       }
@@ -5985,14 +5981,12 @@
     "has-symbols": {
       "version": "1.0.3",
       "resolved": "http://192.168.0.47:4873/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
     },
     "has-tostringtag": {
       "version": "1.0.0",
       "resolved": "http://192.168.0.47:4873/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
       "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
       "requires": {
         "has-symbols": "^1.0.2"
       }
@@ -6643,7 +6637,6 @@
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/is-arguments/-/is-arguments-1.1.1.tgz",
       "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -6753,7 +6746,6 @@
       "version": "1.0.5",
       "resolved": "http://192.168.0.47:4873/is-date-object/-/is-date-object-1.0.5.tgz",
       "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-      "dev": true,
       "requires": {
         "has-tostringtag": "^1.0.0"
       }
@@ -6900,7 +6892,6 @@
       "version": "1.1.4",
       "resolved": "http://192.168.0.47:4873/is-regex/-/is-regex-1.1.4.tgz",
       "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -7836,7 +7827,6 @@
       "version": "1.1.5",
       "resolved": "http://192.168.0.47:4873/object-is/-/object-is-1.1.5.tgz",
       "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3"
@@ -7845,8 +7835,7 @@
     "object-keys": {
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/object-keys/-/object-keys-1.1.1.tgz",
-      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-      "dev": true
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
     },
     "object-visit": {
       "version": "1.0.1",
@@ -8066,6 +8055,11 @@
         "no-case": "^2.2.0"
       }
     },
+    "parchment": {
+      "version": "1.1.4",
+      "resolved": "http://192.168.0.47:4873/parchment/-/parchment-1.1.4.tgz",
+      "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
+    },
     "parse-asn1": {
       "version": "5.1.6",
       "resolved": "http://192.168.0.47:4873/parse-asn1/-/parse-asn1-5.1.6.tgz",
@@ -9583,6 +9577,41 @@
       "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
       "dev": true
     },
+    "quill": {
+      "version": "1.3.6",
+      "resolved": "http://192.168.0.47:4873/quill/-/quill-1.3.6.tgz",
+      "integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
+      "requires": {
+        "clone": "^2.1.1",
+        "deep-equal": "^1.0.1",
+        "eventemitter3": "^2.0.3",
+        "extend": "^3.0.1",
+        "parchment": "^1.1.4",
+        "quill-delta": "^3.6.2"
+      },
+      "dependencies": {
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "http://192.168.0.47:4873/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
+        },
+        "eventemitter3": {
+          "version": "2.0.3",
+          "resolved": "http://192.168.0.47:4873/eventemitter3/-/eventemitter3-2.0.3.tgz",
+          "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
+        }
+      }
+    },
+    "quill-delta": {
+      "version": "3.6.3",
+      "resolved": "http://192.168.0.47:4873/quill-delta/-/quill-delta-3.6.3.tgz",
+      "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
+      "requires": {
+        "deep-equal": "^1.0.1",
+        "extend": "^3.0.2",
+        "fast-diff": "1.1.2"
+      }
+    },
     "randombytes": {
       "version": "2.1.0",
       "resolved": "http://192.168.0.47:4873/randombytes/-/randombytes-2.1.0.tgz",
@@ -9707,7 +9736,6 @@
       "version": "1.4.3",
       "resolved": "http://192.168.0.47:4873/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
       "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3",

+ 3 - 1
package.json

@@ -5,12 +5,14 @@
   "scripts": {
     "serve": "vue-cli-service serve",
     "build": "vue-cli-service build",
-    "build:test": "vue-cli-service build --mode test"
+    "build:test": "vue-cli-service build --mode test",
+    "langs": "node ./scripts/update-i18n.js"
   },
   "dependencies": {
     "axios": "^0.21.1",
     "clipboard": "^2.0.11",
     "core-js": "^3.6.5",
+    "quill": "^1.3.6",
     "vant": "^3.6.4",
     "vue": "^3.2.26",
     "vue-i18n": "9",

+ 184 - 0
public/__langs/index.html

@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <title>配置国际化</title>
+    </head>
+    <style>
+        .locales-setting {
+            position: fixed;
+            z-index: 20000;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.5);
+            pointer-events: all;
+        }
+        .locales-setting > div {
+            padding: 40px 20px;
+            display: flex;
+            align-items: flex-start;
+            position: absolute;
+            left: 30px;
+            top: 30px;
+            bottom: 30px;
+            right: 30px;
+            background: #efefef;
+            box-shadow: 0 0 8px #666;
+        }
+        .locales-setting > div aside {
+            width: 200px;
+            height: 100%;
+            border-right: solid 1px #999;
+        }
+        .locales-setting > div aside li {
+            margin-bottom: 10px;
+            cursor: pointer;
+        }
+        .locales-setting > div aside li.active {
+            color: #f60;
+        }
+        .locales-setting > div main {
+            flex: 1;
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+            overflow-y: auto;
+        }
+        .locales-setting > div main li {
+            display: flex;
+            align-items: center;
+            margin-bottom: 10px;
+        }
+        .locales-setting > div main li input {
+            height: 24px;
+            border: solid 1px #666;
+            width: 100%;
+            padding: 0 4px;
+        }
+        .locales-setting > div main li > div:first-child {
+            width: 400px;
+            /* text-align: right; */
+            padding-right: 3px;
+        }
+        .locales-setting > div main li > div:last-child {
+            width: 100%;
+        }
+        .locales-setting .save {
+            position: absolute;
+            right: 5px;
+            top: 5px;
+        }
+        .locales-setting .save select {
+            border: solid 1px #666;
+            width: 70px;
+            height: 24px;
+            text-align: center;
+            background: #fff;
+        }
+        .locales-setting .save button {
+            border: solid 1px #666;
+            width: 70px;
+            height: 24px;
+            text-align: center;
+            background: #fff;
+            margin-left: 10px;
+        }
+    </style>
+    <body>
+        <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
+        <div id="app">
+            <div class="locales-setting">
+                <div>
+                    <div class="save">
+                        <select v-model="locale" @change="onLocaleChange">
+                            <option value="zh">中文</option>
+                            <option value="en">英文</option>
+                            <option value="kr">韩文</option>
+                            <option value="fr">法语</option>
+                            <option value="ja">日语</option>
+                        </select>
+                        <button @click="onSave">保存</button>
+                    </div>
+
+                    <aside>
+                        <ul>
+                            <li v-for="menu in menus" @click="onModuleChange(menu.name)" :class="{ active: menu.name == module }">{{ menu.text }}</li>
+                        </ul>
+                    </aside>
+                    <main>
+                        <ul>
+                            <li v-for="locale in locales">
+                                <div>{{ locale.key }}:</div>
+                                <div><input type="text" v-model="locale.value" /></div>
+                            </li>
+                        </ul>
+                    </main>
+                </div>
+            </div>
+        </div>
+
+        <script>
+            const { createApp } = Vue
+
+            createApp({
+                data() {
+                    return {
+                        locale: 'zh',
+                        locales: [],
+                        info: {},
+                        menus: [],
+                        module: 'home',
+                        respone: null,
+                    }
+                },
+                methods: {
+                    async onLocaleChange() {
+                        this.locales = []
+                        this.respone = await this.fetchLocale()
+                        this.initData(this.respone)
+                    },
+                    onModuleChange(name) {
+                        this.locales = []
+                        this.module = name
+                        console.log(this.module)
+                        console.log(this.respone)
+                        this.initData(this.respone)
+                    },
+                    initData(data, init = false) {
+                        for (let key in data) {
+                            if (key.split('.').pop() == 'name') {
+                                if (init) {
+                                    this.menus.push({ name: key.split('.')[0], text: data[key] })
+                                }
+                            } else if (typeof data[key] == 'object' && key == this.module) {
+                                console.log(this.module)
+                                this.initData(data[key])
+                            } else if (typeof data[key] == 'string') {
+                                this.locales.push({ key: key.split('.')[0], value: data[key] })
+                                // if (!this.info[key]) {
+                                //     this.info[key] = []
+                                // }
+                                // this.info[key].push({ key: key.split('.')[0], value: data[key] })
+                                // this.locales.push({ key: key.split('.')[0], value: data[key] })
+                            }
+                        }
+                    },
+                    async fetchLocale(locale) {
+                        return await (await fetch(`./locales/${this.locale}.json?${Date.now()}`)).json()
+                    },
+                },
+                mounted() {
+                    this.fetchLocale('zh').then(res => {
+                        this.respone = res
+                        this.initData(res, true)
+                    })
+                    // this.onLocaleChange()
+                },
+            }).mount('#app')
+        </script>
+    </body>
+</html>

+ 35 - 0
public/__langs/locales/en.json

@@ -0,0 +1,35 @@
+{
+    "home": {
+        "tag": "标注",
+        "splitScreen": "分屏",
+        "fullScreen": "全屏"
+    },
+    "home.name": "首页",
+    "header": {
+        "setting": "设为",
+        "reset": "重设",
+        "userInfo": "个人信息",
+        "loginout": "退出登录",
+        "adhustText1": "为场景设置关联位置",
+        "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+        "userLogin": "用户登录",
+        "rememberPassword": "记住密码",
+        "forgetPassword": "忘记密码",
+        "resigter": "官网注册",
+        "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+        "pointUpdate": "关联位置已更新"
+    },
+    "header.name": "头部",
+    "common": {
+        "login": "login",
+        "cancel": "cancel",
+        "sync": "同步",
+        "copySuccess": "复制成功",
+        "syncSuccess": "同步成功"
+    },
+    "common.name": "通用",
+    "code": {
+        "failed": "连接服务器失败"
+    },
+    "code.name": "状态码"
+}

+ 35 - 0
public/__langs/locales/ja.json

@@ -0,0 +1,35 @@
+{
+    "home": {
+        "tag": "标注",
+        "splitScreen": "分屏",
+        "fullScreen": "全屏"
+    },
+    "home.name": "首页",
+    "header": {
+        "setting": "设为",
+        "reset": "重设",
+        "userInfo": "个人信息",
+        "loginout": "退出登录",
+        "adhustText1": "为场景设置关联位置",
+        "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+        "userLogin": "用户登录",
+        "rememberPassword": "记住密码",
+        "forgetPassword": "忘记密码",
+        "resigter": "官网注册",
+        "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+        "pointUpdate": "关联位置已更新"
+    },
+    "header.name": "头部",
+    "common": {
+        "login": "登录",
+        "cancel": "取消",
+        "sync": "同步",
+        "copySuccess": "复制成功",
+        "syncSuccess": "同步成功"
+    },
+    "common.name": "通用",
+    "code": {
+        "failed": "连接服务器失败"
+    },
+    "code.name": "状态码"
+}

+ 35 - 0
public/__langs/locales/zh.json

@@ -0,0 +1,35 @@
+{
+    "home": {
+        "tag": "标注1",
+        "splitScreen": "分屏",
+        "fullScreen": "全屏"
+    },
+    "home.name": "首页",
+    "header": {
+        "setting": "设为",
+        "reset": "重设",
+        "userInfo": "个人信息",
+        "loginout": "退出登录",
+        "adhustText1": "为场景设置关联位置",
+        "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+        "userLogin": "用户登录",
+        "rememberPassword": "记住密码",
+        "forgetPassword": "忘记密码",
+        "resigter": "官网注册",
+        "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+        "pointUpdate": "关联位置已更新"
+    },
+    "header.name": "头部",
+    "common": {
+        "login": "登录",
+        "cancel": "取消",
+        "sync": "同步",
+        "copySuccess": "复制成功",
+        "syncSuccess": "同步成功"
+    },
+    "common.name": "通用",
+    "code": {
+        "failed": "连接服务器失败"
+    },
+    "code.name": "状态码"
+}

+ 52 - 0
scripts/update-i18n.js

@@ -0,0 +1,52 @@
+const fs = require('fs')
+const path = require('path')
+const locales = ['en', 'ja']
+
+const merge = (source, resource, target) => {
+    for (let key in source) {
+        if (typeof source[key] === 'string') {
+            target[key] = resource[key] == void 0 ? source[key] : resource[key]
+        } else {
+            target[key] = {}
+            resource[key] = resource[key] || {}
+            merge(source[key], resource[key], target[key])
+        }
+    }
+}
+
+const combine = (source, resource, target) => {
+    merge(source, resource, target)
+}
+
+function exec() {
+    var source = null
+    fs.readFile(path.join(__dirname, '..', 'src', 'locales', 'zh.json'), (err, data) => {
+        if (err) {
+            return
+        }
+        source = JSON.parse(data.toString())
+        fs.writeFile(path.join(__dirname, '..', 'public', '__langs', 'locales', 'zh.json'), JSON.stringify(source, null, 4), () => {})
+
+        locales.forEach(locale => {
+            try {
+                fs.readFile(path.join(__dirname, '..', 'src', 'locales', locale + '.json'), (err, response) => {
+                    fs.readFile(path.join(__dirname, '..', 'src', 'locales', 'zh.json'), (err, data) => {
+                        if (err) {
+                            return
+                        }
+                        var target = {}
+                        // var source = JSON.parse(data.toString())
+                        var resource = JSON.parse(response.toString())
+                        combine(source, resource, target)
+
+                        fs.writeFile(path.join(__dirname, '..', 'src', 'locales', locale + '.json'), JSON.stringify(target, null, 4), () => {})
+                        fs.writeFile(path.join(__dirname, '..', 'public', '__langs', 'locales', locale + '.json'), JSON.stringify(target, null, 4), () => {})
+                    })
+                })
+            } catch (error) {
+                console.log(error)
+            }
+        })
+    })
+}
+exec()

+ 1 - 0
src/components/form/medias/Image.vue

@@ -94,6 +94,7 @@ onMounted(() => {
     // }
 
     // notify.value.media['image'] = images.value
+    console.log(swiper)
 })
 </script>
 <style lang="scss" scoped>

+ 5 - 5
src/components/header/Login.vue

@@ -4,7 +4,7 @@
             <div class="login-box">
                 <span class="close" @click="emits('close')"><i class="iconfont icon-close"></i></span>
                 <div class="area">
-                    <h4>用户登录</h4>
+                    <h4>{{$t('header.userLogin')}}</h4>
                     <div class="input">
                         <span class="icon">
                             <i class="iconfont icon-user"></i>
@@ -25,16 +25,16 @@
                     <div class="remember">
                         <div @click="remember = !remember">
                             <div class="checkbox" :class="{ checked: remember }"></div>
-                            <div class="checkbox-label">记住密码</div>
+                            <div class="checkbox-label">{{$t('header.rememberPassword')}}</div>
                         </div>
                     </div>
                     <div class="button">
-                        <button type="submit" @click="onLogin">登录</button>
+                        <button type="submit" @click="onLogin">{{$t('common.login')}}</button>
                         <div class="tips" v-show="errors.message">{{ errors.message }}</div>
                     </div>
                     <div class="links">
-                        <a href="http://test.4dkankan.com/#/login/forget?from=%2F">忘记密码</a>
-                        <a href="http://test.4dkankan.com/#/login/register?from=%2F">官网注册</a>
+                        <a href="http://test.4dkankan.com/#/login/forget?from=%2F">{{$t('header.forgetPassword')}}</a>
+                        <a href="http://test.4dkankan.com/#/login/register?from=%2F">{{$t('header.resigter')}}</a>
                     </div>
                 </div>
             </div>

+ 24 - 17
src/components/header/index.vue

@@ -2,8 +2,8 @@
     <header v-if="props.showAdjust">
         <div v-if="project">{{ project.projectName }}</div>
         <div class="sync">
-            <button @click="onCancel">取消</button>
-            <button type="submit" @click="onSubmit" :class="{ active: points.p1 && points.p2 }">同步</button>
+            <button @click="onCancel">{{ $t('common.cancel') }}</button>
+            <button type="submit" @click="onSubmit" :class="{ active: points.p1 && points.p2 }">{{ $t('common.sync') }}</button>
         </div>
     </header>
     <header v-else>
@@ -18,25 +18,31 @@
                     <img :src="user.head + '&x-oss-process=image/resize,m_fill,w_64,h_64/quality,q_70'" alt="" />
                     <div class="menu">
                         <ul>
-                            <li><a href="/smarts/#/personal">个人信息</a></li>
+                            <li>
+                                <a href="/smarts/#/personal">{{ $t('header.userInfo') }}</a>
+                            </li>
                             <li class="split"></li>
-                            <li><a href="javascript:;" @click="onLogout">退出登录</a></li>
+                            <li>
+                                <a href="javascript:;" @click="onLogout">{{ $t('header.loginout') }}</a>
+                            </li>
                         </ul>
                     </div>
                 </li>
-                <li v-else class="login" @click="showLogin = true"><span>登录</span></li>
+                <li v-else class="login" @click="showLogin = true">
+                    <span>{{ $t('common.login') }}</span>
+                </li>
             </ul>
         </div>
     </header>
     <footer v-if="props.showAdjust">
-        <h4>为场景设置关联位置</h4>
-        <div>请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。</div>
+        <h4>{{ $t('header.adhustText1') }}</h4>
+        <div>{{ $t('header.adhustText2') }}</div>
         <div class="points">
-            <button @click="onSetP1" :class="{ active: points.p1 }">{{ points.p1 ? '重设P1' : '设为P1' }}</button>
-            <button @click="onSetP2" :class="{ active: points.p2 }">{{ points.p2 ? '重设P2' : '设为P2' }}</button>
+            <button @click="onSetP1" :class="{ active: points.p1 }">{{ points.p1 ? `${t('header.reset')}P1` : `${t('header.setting')}P1` }}</button>
+            <button @click="onSetP2" :class="{ active: points.p2 }">{{ points.p2 ? `${t('header.reset')}P2` : `${t('header.setting')}P2` }}</button>
         </div>
     </footer>
-    <Toast v-if="showCopyDone" content="复制成功" />
+    <Toast v-if="showCopyDone" :content="t('common.copySuccess')" />
     <Toast v-if="showTips" :content="showTips" :close="() => (showTips = null)" />
     <Login v-if="showLogin" @close="showLogin = false" @user="info => (user = info)" />
     <Loading v-if="showLoading" />
@@ -51,7 +57,8 @@ import Loading from '@/components/loading/Loading'
 import Login from './Login'
 import CopyLink from './CopyLink'
 import sync from '@/utils/sync'
-
+import { useI18n, getLocale } from '@/i18n'
+const { t } = useI18n({ useScope: 'global' })
 const props = defineProps({
     project: Object,
     showAdjust: Boolean,
@@ -89,10 +96,10 @@ const getCurPosInfo = () => {
 const onSetP1 = () => {
     let p1 = getCurPosInfo()
     if (points.value.p2 && points.value.p2.id == p1.id) {
-        return (showTips.value = '匹配失败,请选择不同点位进行同步')
+        return (showTips.value = t('header.setPointfaidText'))
     }
     if (points.value.p1) {
-        showTips.value = '关联位置已更新'
+        showTips.value = t('header.pointUpdate')
     }
     points.value.p1 = p1
     emits('update', 'p1', points.value.p1)
@@ -100,10 +107,10 @@ const onSetP1 = () => {
 const onSetP2 = () => {
     let p2 = getCurPosInfo()
     if (points.value.p1 && points.value.p1.id == p2.id) {
-        return (showTips.value = '匹配失败,请选择不同点位进行同步')
+        return (showTips.value = t('header.setPointfaidText'))
     }
     if (points.value.p2) {
-        showTips.value = '关联位置已更新'
+        showTips.value = t('header.pointUpdate')
     }
     points.value.p2 = p2
     emits('update', 'p2', points.value.p2)
@@ -154,7 +161,7 @@ const onSubmit = () => {
         .then(response => {
             showLoading.value = false
             if (response.success) {
-                showTips.value = 'BIM同步成功'
+                showTips.value = 'BIM' + t('common.syncSuccess')
                 setTimeout(() => {
                     window.location.href = window.location.href.replace('&adjust', '&split')
                 }, 4000)
@@ -166,7 +173,7 @@ const onSubmit = () => {
         })
         .catch(() => {
             showLoading.value = false
-            showTips.value = '连接服务器失败'
+            showTips.value = t('code.failed')
         })
 }
 

+ 68 - 0
src/i18n/index.js

@@ -0,0 +1,68 @@
+import { nextTick } from 'vue'
+import { useI18n, createI18n } from 'vue-i18n/index'
+import browser from '@/utils/browser'
+
+export { useI18n }
+export const SUPPORT_LOCALES = ['zh', 'en', 'ja']
+
+export function getLocale() {
+    let lang = browser.getURLParam('lang')
+    if (!lang) {
+        lang = window.navigator.language || window.navigator.userLanguage || null
+        if (lang && !/^zh/.test(lang)) {
+            console.log('自动获取浏览器语言:' + lang)
+            lang = 'en'
+        } else {
+            lang = 'zh'
+        }
+    }
+    return lang
+}
+
+export function setupI18n(options = { locale: 'zh' }) {
+    const i18n = createI18n(options)
+    setI18nLanguage(i18n, options.locale)
+    return i18n
+}
+
+export function setI18nLanguage(i18n, locale) {
+    if (i18n.mode === 'legacy') {
+        i18n.global.locale = locale
+    } else {
+        i18n.global.locale.value = locale
+    }
+    /**
+     * NOTE:
+     * If you need to specify the language setting for headers, such as the `fetch` API, set it here.
+     * The following is an example for axios.
+     *
+     * axios.defaults.headers.common['Accept-Language'] = locale
+     */
+    document.querySelector('html').setAttribute('lang', locale)
+}
+
+export async function loadLocaleMessages(i18n, locale) {
+    // load locale messages with dynamic import
+    // set locale and locale message
+    if (window.location.search.includes('i18n')) {
+        try {
+            const messages = await (await fetch(`https://4dkk.4dage.com/v4/www/locales/${locale}.json?_=${Date.now()}`)).json()
+            i18n.global.setLocaleMessage(locale, messages)
+        } catch (error) {
+            const messages = await import(/* webpackChunkName: "locale-[request]" */ `../locales/zh.json`)
+            i18n.global.setLocaleMessage(locale, messages.default)
+        }
+    } else {
+        const messages = await import(/* webpackChunkName: "locale-[request]" */ `../locales/${locale}.json`)
+        i18n.global.setLocaleMessage(locale, messages.default)
+    }
+
+    return nextTick()
+}
+
+export default setupI18n({
+    globalInjection: true,
+    legacy: false,
+    locale: '',
+    fallbackLocale: 'zh',
+})

+ 35 - 0
src/locales/en.json

@@ -0,0 +1,35 @@
+{
+    "home": {
+        "tag": "标注",
+        "splitScreen": "分屏",
+        "fullScreen": "全屏"
+    },
+    "home.name": "首页",
+    "header": {
+        "setting": "设为",
+        "reset": "重设",
+        "userInfo": "个人信息",
+        "loginout": "退出登录",
+        "adhustText1": "为场景设置关联位置",
+        "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+        "userLogin": "用户登录",
+        "rememberPassword": "记住密码",
+        "forgetPassword": "忘记密码",
+        "resigter": "官网注册",
+        "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+        "pointUpdate": "关联位置已更新"
+    },
+    "header.name": "头部",
+    "common": {
+        "login": "login",
+        "cancel": "cancel",
+        "sync": "同步",
+        "copySuccess": "复制成功",
+        "syncSuccess": "同步成功"
+    },
+    "common.name": "通用",
+    "code": {
+        "failed": "连接服务器失败"
+    },
+    "code.name": "状态码"
+}

+ 35 - 0
src/locales/ja.json

@@ -0,0 +1,35 @@
+{
+    "home": {
+        "tag": "标注",
+        "splitScreen": "分屏",
+        "fullScreen": "全屏"
+    },
+    "home.name": "首页",
+    "header": {
+        "setting": "设为",
+        "reset": "重设",
+        "userInfo": "个人信息",
+        "loginout": "退出登录",
+        "adhustText1": "为场景设置关联位置",
+        "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+        "userLogin": "用户登录",
+        "rememberPassword": "记住密码",
+        "forgetPassword": "忘记密码",
+        "resigter": "官网注册",
+        "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+        "pointUpdate": "关联位置已更新"
+    },
+    "header.name": "头部",
+    "common": {
+        "login": "登录",
+        "cancel": "取消",
+        "sync": "同步",
+        "copySuccess": "复制成功",
+        "syncSuccess": "同步成功"
+    },
+    "common.name": "通用",
+    "code": {
+        "failed": "连接服务器失败"
+    },
+    "code.name": "状态码"
+}

+ 35 - 0
src/locales/zh.json

@@ -0,0 +1,35 @@
+{
+  "home": {
+    "tag": "标注1",
+    "splitScreen": "分屏",
+    "fullScreen": "全屏"
+  },
+  "home.name": "首页",
+  "header": {
+    "setting": "设为",
+    "reset": "重设",
+    "userInfo": "个人信息",
+    "loginout": "退出登录",
+    "adhustText1": "为场景设置关联位置",
+    "adhustText2": "请选择位置,确认左右视图中的场景在同一位置后,单击右侧按钮将其设为关联位置。",
+    "userLogin": "用户登录",
+    "rememberPassword": "记住密码",
+    "forgetPassword": "忘记密码",
+    "resigter": "官网注册",
+    "setPointfaidText": "匹配失败,请选择不同点位进行同步",
+    "pointUpdate": "关联位置已更新"
+  },
+  "header.name": "头部",
+  "common": {
+    "login": "登录",
+    "cancel": "取消",
+    "sync": "同步",
+    "copySuccess": "复制成功",
+    "syncSuccess": "同步成功"
+  },
+  "common.name": "通用",
+  "code": {
+    "failed": "连接服务器失败"
+  },
+  "code.name": "状态码"
+}

+ 3 - 3
src/pages/Viewer.vue

@@ -73,7 +73,7 @@
                 <div class="file" :class="{ active: fileChecked, disable: fileDisable }" v-show="!fscChecked && !showBim">
                     <div @click="onFileChecked">
                         <i class="iconfont icon-note1"></i>
-                        <span>标注</span>
+                        <span>{{ $t('home.tag') }}</span>
                     </div>
                 </div>
                 <div class="bim" :class="{ active: bimChecked, disable: bimDisable }" v-show="!fscChecked && !showBim">
@@ -85,12 +85,12 @@
                 <div class="dbs" :class="{ active: dbsChecked, disable: dbsDisable }" v-show="!fscChecked && !showBim">
                     <div @click="onDbsChecked">
                         <i class="iconfont icon-split_screen"></i>
-                        <span>分屏</span>
+                        <span>{{ $t('home.splitScreen') }}</span>
                     </div>
                 </div>
                 <div class="fsc" :class="{ active: fscChecked }" @click="onFscChecked">
                     <i class="iconfont" :class="[fscChecked ? 'icon-full_screen_selected' : 'icon-full_screen_normal']"></i>
-                    <span>全屏</span>
+                    <span>{{ $t('home.fullScreen') }}</span>
                 </div>
             </div>
             <TagManager />

+ 14 - 2
src/pages/viewer.js

@@ -4,6 +4,11 @@ import { createApp } from 'vue'
 import { setup } from '../utils/request'
 import ClickOutSide from '../utils/ClickOutSide'
 import App from './Viewer.vue'
+import i18n, { getLocale, setI18nLanguage, loadLocaleMessages } from '../i18n'
+const local = getLocale()
+
+
+
 
 Date.prototype.format = function(fmt = 'YYYY-mm-dd HH:MM:SS') {
     var res = ''
@@ -43,5 +48,12 @@ document.oncontextmenu = function (event){
 
 setup()
 const app = createApp(App)
-app.directive('click-outside', ClickOutSide)
-app.mount('#app')
+
+
+
+loadLocaleMessages(i18n, local).then(() => {
+  setI18nLanguage(i18n, local)
+  app.use(i18n)
+  app.directive('click-outside', ClickOutSide)
+  app.mount('#app')
+})

+ 14 - 0
src/utils/browser.js

@@ -11,6 +11,20 @@ var browser = {
             }
         )
     },
+    getURLParam: function (key) {
+      let querys = window.location.search.substring(1).split('&')
+      for (let i = 0; i < querys.length; i++) {
+          let keypair = querys[i].split('=')
+          if (keypair.length === 2 && keypair[0] === key) {
+              try {
+                  return decodeURIComponent(keypair[1])
+              } catch (error) {
+                  return keypair[1]
+              }
+          }
+      }
+      return ''
+  },
     isFullscreen() {
         return document.fullscreenElement || document.mozFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement
     },