Bläddra i källkod

feat:国际化

jinx 2 år sedan
förälder
incheckning
61b77394c4

+ 49 - 100
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "@kankan/smart-bim",
-  "version": "4.4.4",
+  "version": "4.7.0-alpha.14",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2324,7 +2324,6 @@
       "version": "1.3.8",
       "resolved": "http://192.168.0.47:4873/accepts/-/accepts-1.3.8.tgz",
       "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "dev": true,
       "requires": {
         "mime-types": "~2.1.34",
         "negotiator": "0.6.3"
@@ -2466,8 +2465,7 @@
     "array-flatten": {
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/array-flatten/-/array-flatten-1.1.1.tgz",
-      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
-      "dev": true
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
     },
     "array-union": {
       "version": "1.0.2",
@@ -2836,7 +2834,6 @@
       "version": "1.20.1",
       "resolved": "http://192.168.0.47:4873/body-parser/-/body-parser-1.20.1.tgz",
       "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
-      "dev": true,
       "requires": {
         "bytes": "3.1.2",
         "content-type": "~1.0.4",
@@ -2856,7 +2853,6 @@
           "version": "2.6.9",
           "resolved": "http://192.168.0.47:4873/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           }
@@ -2864,14 +2860,12 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "http://192.168.0.47:4873/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-          "dev": true
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "qs": {
           "version": "6.11.0",
           "resolved": "http://192.168.0.47:4873/qs/-/qs-6.11.0.tgz",
           "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-          "dev": true,
           "requires": {
             "side-channel": "^1.0.4"
           }
@@ -3093,8 +3087,7 @@
     "bytes": {
       "version": "3.1.2",
       "resolved": "http://192.168.0.47:4873/bytes/-/bytes-3.1.2.tgz",
-      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
-      "dev": true
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
     },
     "cacache": {
       "version": "12.0.4",
@@ -3741,7 +3734,6 @@
       "version": "0.5.4",
       "resolved": "http://192.168.0.47:4873/content-disposition/-/content-disposition-0.5.4.tgz",
       "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
-      "dev": true,
       "requires": {
         "safe-buffer": "5.2.1"
       }
@@ -3749,8 +3741,7 @@
     "content-type": {
       "version": "1.0.5",
       "resolved": "http://192.168.0.47:4873/content-type/-/content-type-1.0.5.tgz",
-      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
-      "dev": true
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
     },
     "convert-source-map": {
       "version": "1.9.0",
@@ -3761,14 +3752,12 @@
     "cookie": {
       "version": "0.5.0",
       "resolved": "http://192.168.0.47:4873/cookie/-/cookie-0.5.0.tgz",
-      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
-      "dev": true
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
     },
     "cookie-signature": {
       "version": "1.0.6",
       "resolved": "http://192.168.0.47:4873/cookie-signature/-/cookie-signature-1.0.6.tgz",
-      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
-      "dev": true
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
     },
     "copy-concurrently": {
       "version": "1.0.5",
@@ -3976,6 +3965,15 @@
       "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
       "dev": true
     },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "http://192.168.0.47:4873/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
     "cosmiconfig": {
       "version": "5.2.1",
       "resolved": "http://192.168.0.47:4873/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
@@ -4687,8 +4685,7 @@
     "depd": {
       "version": "2.0.0",
       "resolved": "http://192.168.0.47:4873/depd/-/depd-2.0.0.tgz",
-      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
-      "dev": true
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
     },
     "des.js": {
       "version": "1.0.1",
@@ -4703,8 +4700,7 @@
     "destroy": {
       "version": "1.2.0",
       "resolved": "http://192.168.0.47:4873/destroy/-/destroy-1.2.0.tgz",
-      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
-      "dev": true
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
     },
     "detect-node": {
       "version": "2.1.0",
@@ -4889,8 +4885,7 @@
     "ee-first": {
       "version": "1.1.1",
       "resolved": "http://192.168.0.47:4873/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
-      "dev": true
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
     },
     "ejs": {
       "version": "2.7.4",
@@ -4942,8 +4937,7 @@
     "encodeurl": {
       "version": "1.0.2",
       "resolved": "http://192.168.0.47:4873/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
-      "dev": true
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
     },
     "end-of-stream": {
       "version": "1.4.4",
@@ -5088,8 +5082,7 @@
     "escape-html": {
       "version": "1.0.3",
       "resolved": "http://192.168.0.47:4873/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "dev": true
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
     },
     "escape-string-regexp": {
       "version": "1.0.5",
@@ -5176,8 +5169,7 @@
     "etag": {
       "version": "1.8.1",
       "resolved": "http://192.168.0.47:4873/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
-      "dev": true
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
     },
     "event-pubsub": {
       "version": "4.3.0",
@@ -5282,7 +5274,6 @@
       "version": "4.18.2",
       "resolved": "http://192.168.0.47:4873/express/-/express-4.18.2.tgz",
       "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
-      "dev": true,
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
@@ -5321,7 +5312,6 @@
           "version": "2.6.9",
           "resolved": "http://192.168.0.47:4873/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           }
@@ -5329,14 +5319,12 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "http://192.168.0.47:4873/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-          "dev": true
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "qs": {
           "version": "6.11.0",
           "resolved": "http://192.168.0.47:4873/qs/-/qs-6.11.0.tgz",
           "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
-          "dev": true,
           "requires": {
             "side-channel": "^1.0.4"
           }
@@ -5574,7 +5562,6 @@
       "version": "1.2.0",
       "resolved": "http://192.168.0.47:4873/finalhandler/-/finalhandler-1.2.0.tgz",
       "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
-      "dev": true,
       "requires": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
@@ -5589,7 +5576,6 @@
           "version": "2.6.9",
           "resolved": "http://192.168.0.47:4873/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           }
@@ -5597,8 +5583,7 @@
         "ms": {
           "version": "2.0.0",
           "resolved": "http://192.168.0.47:4873/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-          "dev": true
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         }
       }
     },
@@ -5673,8 +5658,7 @@
     "forwarded": {
       "version": "0.2.0",
       "resolved": "http://192.168.0.47:4873/forwarded/-/forwarded-0.2.0.tgz",
-      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
-      "dev": true
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
     },
     "fragment-cache": {
       "version": "0.2.1",
@@ -5688,8 +5672,7 @@
     "fresh": {
       "version": "0.5.2",
       "resolved": "http://192.168.0.47:4873/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
-      "dev": true
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
     },
     "from2": {
       "version": "2.3.0",
@@ -6272,7 +6255,6 @@
       "version": "2.0.0",
       "resolved": "http://192.168.0.47:4873/http-errors/-/http-errors-2.0.0.tgz",
       "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
-      "dev": true,
       "requires": {
         "depd": "2.0.0",
         "inherits": "2.0.4",
@@ -6383,7 +6365,6 @@
       "version": "0.4.24",
       "resolved": "http://192.168.0.47:4873/iconv-lite/-/iconv-lite-0.4.24.tgz",
       "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-      "dev": true,
       "requires": {
         "safer-buffer": ">= 2.1.2 < 3"
       }
@@ -6553,8 +6534,7 @@
     "inherits": {
       "version": "2.0.4",
       "resolved": "http://192.168.0.47:4873/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
     "internal-ip": {
       "version": "4.3.0",
@@ -6604,8 +6584,7 @@
     "ipaddr.js": {
       "version": "1.9.1",
       "resolved": "http://192.168.0.47:4873/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
-      "dev": true
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
     },
     "is-absolute-url": {
       "version": "2.1.0",
@@ -7302,8 +7281,7 @@
     "media-typer": {
       "version": "0.3.0",
       "resolved": "http://192.168.0.47:4873/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
-      "dev": true
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
     },
     "memory-fs": {
       "version": "0.4.1",
@@ -7318,8 +7296,7 @@
     "merge-descriptors": {
       "version": "1.0.1",
       "resolved": "http://192.168.0.47:4873/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
-      "dev": true
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
     },
     "merge-source-map": {
       "version": "1.1.0",
@@ -7345,8 +7322,7 @@
     "methods": {
       "version": "1.1.2",
       "resolved": "http://192.168.0.47:4873/methods/-/methods-1.1.2.tgz",
-      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
-      "dev": true
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
     },
     "micromatch": {
       "version": "3.1.10",
@@ -7396,14 +7372,12 @@
     "mime-db": {
       "version": "1.52.0",
       "resolved": "http://192.168.0.47:4873/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
     },
     "mime-types": {
       "version": "2.1.35",
       "resolved": "http://192.168.0.47:4873/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "requires": {
         "mime-db": "1.52.0"
       }
@@ -7637,8 +7611,7 @@
     "negotiator": {
       "version": "0.6.3",
       "resolved": "http://192.168.0.47:4873/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-      "dev": true
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
     },
     "neo-async": {
       "version": "2.6.2",
@@ -7783,8 +7756,7 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "http://192.168.0.47:4873/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "dev": true
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
     },
     "object-copy": {
       "version": "0.1.0",
@@ -7820,8 +7792,7 @@
     "object-inspect": {
       "version": "1.12.3",
       "resolved": "http://192.168.0.47:4873/object-inspect/-/object-inspect-1.12.3.tgz",
-      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
-      "dev": true
+      "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
     },
     "object-is": {
       "version": "1.1.5",
@@ -7900,7 +7871,6 @@
       "version": "2.4.1",
       "resolved": "http://192.168.0.47:4873/on-finished/-/on-finished-2.4.1.tgz",
       "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
-      "dev": true,
       "requires": {
         "ee-first": "1.1.1"
       }
@@ -8111,8 +8081,7 @@
     "parseurl": {
       "version": "1.3.3",
       "resolved": "http://192.168.0.47:4873/parseurl/-/parseurl-1.3.3.tgz",
-      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
-      "dev": true
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
     },
     "pascalcase": {
       "version": "0.1.1",
@@ -8165,8 +8134,7 @@
     "path-to-regexp": {
       "version": "0.1.7",
       "resolved": "http://192.168.0.47:4873/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
-      "dev": true
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
     },
     "path-type": {
       "version": "3.0.0",
@@ -9452,7 +9420,6 @@
       "version": "2.0.7",
       "resolved": "http://192.168.0.47:4873/proxy-addr/-/proxy-addr-2.0.7.tgz",
       "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
-      "dev": true,
       "requires": {
         "forwarded": "0.2.0",
         "ipaddr.js": "1.9.1"
@@ -9634,14 +9601,12 @@
     "range-parser": {
       "version": "1.2.1",
       "resolved": "http://192.168.0.47:4873/range-parser/-/range-parser-1.2.1.tgz",
-      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
-      "dev": true
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
     },
     "raw-body": {
       "version": "2.5.1",
       "resolved": "http://192.168.0.47:4873/raw-body/-/raw-body-2.5.1.tgz",
       "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
-      "dev": true,
       "requires": {
         "bytes": "3.1.2",
         "http-errors": "2.0.0",
@@ -10032,8 +9997,7 @@
     "safe-buffer": {
       "version": "5.2.1",
       "resolved": "http://192.168.0.47:4873/safe-buffer/-/safe-buffer-5.2.1.tgz",
-      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "dev": true
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
     },
     "safe-regex": {
       "version": "1.1.0",
@@ -10058,8 +10022,7 @@
     "safer-buffer": {
       "version": "2.1.2",
       "resolved": "http://192.168.0.47:4873/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "dev": true
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "sass": {
       "version": "1.57.1",
@@ -10154,7 +10117,6 @@
       "version": "0.18.0",
       "resolved": "http://192.168.0.47:4873/send/-/send-0.18.0.tgz",
       "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
-      "dev": true,
       "requires": {
         "debug": "2.6.9",
         "depd": "2.0.0",
@@ -10175,7 +10137,6 @@
           "version": "2.6.9",
           "resolved": "http://192.168.0.47:4873/debug/-/debug-2.6.9.tgz",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "dev": true,
           "requires": {
             "ms": "2.0.0"
           },
@@ -10183,22 +10144,19 @@
             "ms": {
               "version": "2.0.0",
               "resolved": "http://192.168.0.47:4873/ms/-/ms-2.0.0.tgz",
-              "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-              "dev": true
+              "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
             }
           }
         },
         "mime": {
           "version": "1.6.0",
           "resolved": "http://192.168.0.47:4873/mime/-/mime-1.6.0.tgz",
-          "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
-          "dev": true
+          "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
         },
         "ms": {
           "version": "2.1.3",
           "resolved": "http://192.168.0.47:4873/ms/-/ms-2.1.3.tgz",
-          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-          "dev": true
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
         }
       }
     },
@@ -10283,7 +10241,6 @@
       "version": "1.15.0",
       "resolved": "http://192.168.0.47:4873/serve-static/-/serve-static-1.15.0.tgz",
       "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
-      "dev": true,
       "requires": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
@@ -10329,8 +10286,7 @@
     "setprototypeof": {
       "version": "1.2.0",
       "resolved": "http://192.168.0.47:4873/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
-      "dev": true
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
     },
     "sha.js": {
       "version": "2.4.11",
@@ -10376,7 +10332,6 @@
       "version": "1.0.4",
       "resolved": "http://192.168.0.47:4873/side-channel/-/side-channel-1.0.4.tgz",
       "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.0",
         "get-intrinsic": "^1.0.2",
@@ -10799,8 +10754,7 @@
     "statuses": {
       "version": "2.0.1",
       "resolved": "http://192.168.0.47:4873/statuses/-/statuses-2.0.1.tgz",
-      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
-      "dev": true
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
     },
     "stream-browserify": {
       "version": "2.0.2",
@@ -11290,8 +11244,7 @@
     "toidentifier": {
       "version": "1.0.1",
       "resolved": "http://192.168.0.47:4873/toidentifier/-/toidentifier-1.0.1.tgz",
-      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
-      "dev": true
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
     },
     "toposort": {
       "version": "1.0.7",
@@ -11352,7 +11305,6 @@
       "version": "1.6.18",
       "resolved": "http://192.168.0.47:4873/type-is/-/type-is-1.6.18.tgz",
       "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
-      "dev": true,
       "requires": {
         "media-typer": "0.3.0",
         "mime-types": "~2.1.24"
@@ -11484,8 +11436,7 @@
     "unpipe": {
       "version": "1.0.0",
       "resolved": "http://192.168.0.47:4873/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
-      "dev": true
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
     },
     "unquote": {
       "version": "1.1.1",
@@ -11681,8 +11632,7 @@
     "utils-merge": {
       "version": "1.0.1",
       "resolved": "http://192.168.0.47:4873/utils-merge/-/utils-merge-1.0.1.tgz",
-      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
-      "dev": true
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
     },
     "uuid": {
       "version": "3.4.0",
@@ -11713,8 +11663,7 @@
     "vary": {
       "version": "1.1.2",
       "resolved": "http://192.168.0.47:4873/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-      "dev": true
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
     },
     "vendors": {
       "version": "1.0.4",

+ 2 - 0
package.json

@@ -12,6 +12,8 @@
     "axios": "^0.21.1",
     "clipboard": "^2.0.11",
     "core-js": "^3.6.5",
+    "cors": "^2.8.5",
+    "express": "^4.18.2",
     "quill": "^1.3.6",
     "vant": "^3.6.4",
     "vue": "^3.2.26",

+ 80 - 0
public/__langs/css/index.css

@@ -0,0 +1,80 @@
+.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;
+}

+ 21 - 83
public/__langs/index.html

@@ -6,88 +6,7 @@
         <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>
+    <link rel="stylesheet" href="./css/index.css" />
     <body>
         <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
         <div id="app">
@@ -113,7 +32,7 @@
                         <ul>
                             <li v-for="locale in locales">
                                 <div>{{ locale.key }}:</div>
-                                <div><input type="text" v-model="locale.value" /></div>
+                                <div><input type="text" @input="onChangeVal(locale)" v-model="locale.value" /></div>
                             </li>
                         </ul>
                     </main>
@@ -136,6 +55,25 @@
                     }
                 },
                 methods: {
+                    async onSave() {
+                        console.log(this.respone)
+                        let params = this.respone
+                        fetch('http://192.168.0.130:9091/save', {
+                            method: 'post',
+                            headers: {
+                                'Content-Type': 'application/x-www-form-urlencoded',
+                            },
+                            body: `q=${JSON.stringify(params)}&locale=${this.locale}`,
+                        }).then(async res => {
+                            let json = await res.json()
+                            if (json.success) {
+                                alert('保存成功')
+                            }
+                        })
+                    },
+                    onChangeVal(item) {
+                        this.respone[this.module][item.key] = item.value
+                    },
                     async onLocaleChange() {
                         this.locales = []
                         this.respone = await this.fetchLocale()

+ 64 - 3
public/__langs/locales/en.json

@@ -1,11 +1,16 @@
 {
     "home": {
-        "tag": "标注",
-        "splitScreen": "分屏",
+        "tag": "tag",
+        "splitScreen": "分屏12",
         "fullScreen": "全屏"
     },
     "home.name": "首页",
     "header": {
+        "passwordText1": "密码不能为空",
+        "phoneText1": "手机号码不能为空",
+        "phoneText2": "请输入正确手机号",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
         "setting": "设为",
         "reset": "重设",
         "userInfo": "个人信息",
@@ -19,16 +24,72 @@
         "setPointfaidText": "匹配失败,请选择不同点位进行同步",
         "pointUpdate": "关联位置已更新"
     },
+    "components": {
+        "uploadVideo": "上传视频",
+        "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+        "linkView": "网页展示区",
+        "continueAdd": "继续添加",
+        "uploadImg": "上传图片",
+        "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+        "TipsImgLength": "图片数量最多支持上传9张",
+        "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+        "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+        "uploadAudio": "上传音频",
+        "year": "年",
+        "month": "月",
+        "day": "日",
+        "chooseTime": "选择时间"
+    },
+    "tag": {
+        "addComment": "发一条评论吧",
+        "addCommentTips": "请输入内容",
+        "deletetCommentTips": "确定要删除吗?",
+        "noComment": "暂无评论",
+        "unkownUser": "未知用户",
+        "reply": "reply",
+        "comment": "comment",
+        "creater": "创建人",
+        "createTime": "创建时间",
+        "statusText1": "待处理",
+        "statusText2": "进行中",
+        "statusText3": "未解决",
+        "statusText4": "已解决",
+        "uploadFile": "上传附件",
+        "desc": "描述",
+        "member": "涉及的成员",
+        "status": "状态",
+        "inputDesc": "请输入描述",
+        "inputMember": "请选择需要通知的项目人员",
+        "inputStatus": "请选择处理状态",
+        "inputTagName": "请输入标注名称",
+        "tagName": "标注名称",
+        "creatTag": "新建标注",
+        "addTag": "添加标注",
+        "isAddTag": "已添加标注",
+        "deleteTagText": "确定要删除资料吗?"
+    },
+    "tag.name": "标注",
+    "components.name": "组件",
     "header.name": "头部",
     "common": {
+        "input": "请输入",
+        "publish": "发布",
+        "submit": "提交",
+        "exit": "退出",
+        "delete": "删除",
+        "edit": "编辑",
+        "confirm": "确定",
+        "tips": "提示",
         "login": "login",
         "cancel": "cancel",
-        "sync": "同步",
+        "sync": "sync",
+        "deleteSuccess": "删除成功",
         "copySuccess": "复制成功",
         "syncSuccess": "同步成功"
     },
     "common.name": "通用",
     "code": {
+        "4008": "用户未登录",
         "failed": "连接服务器失败"
     },
     "code.name": "状态码"

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

@@ -6,6 +6,11 @@
     },
     "home.name": "首页",
     "header": {
+        "passwordText1": "密码不能为空",
+        "phoneText1": "手机号码不能为空",
+        "phoneText2": "请输入正确手机号",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
         "setting": "设为",
         "reset": "重设",
         "userInfo": "个人信息",
@@ -19,16 +24,72 @@
         "setPointfaidText": "匹配失败,请选择不同点位进行同步",
         "pointUpdate": "关联位置已更新"
     },
+    "components": {
+        "uploadVideo": "上传视频",
+        "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+        "linkView": "网页展示区",
+        "continueAdd": "继续添加",
+        "uploadImg": "上传图片",
+        "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+        "TipsImgLength": "图片数量最多支持上传9张",
+        "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+        "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+        "uploadAudio": "上传音频",
+        "year": "年",
+        "month": "月",
+        "day": "日",
+        "chooseTime": "选择时间"
+    },
+    "tag": {
+        "addComment": "发一条评论吧",
+        "addCommentTips": "请输入内容",
+        "deletetCommentTips": "确定要删除吗?",
+        "noComment": "暂无评论",
+        "unkownUser": "未知用户",
+        "reply": "回复",
+        "comment": "评论",
+        "creater": "创建人",
+        "createTime": "创建时间",
+        "statusText1": "待处理",
+        "statusText2": "进行中",
+        "statusText3": "未解决",
+        "statusText4": "已解决",
+        "uploadFile": "上传附件",
+        "desc": "描述",
+        "member": "涉及的成员",
+        "status": "状态",
+        "inputDesc": "请输入描述",
+        "inputMember": "请选择需要通知的项目人员",
+        "inputStatus": "请选择处理状态",
+        "inputTagName": "请输入标注名称",
+        "tagName": "标注名称",
+        "creatTag": "新建标注",
+        "addTag": "添加标注",
+        "isAddTag": "已添加标注",
+        "deleteTagText": "确定要删除资料吗?"
+    },
+    "tag.name": "标注",
+    "components.name": "组件",
     "header.name": "头部",
     "common": {
+        "input": "请输入",
+        "publish": "发布",
+        "submit": "提交",
+        "exit": "退出",
+        "delete": "删除",
+        "edit": "编辑",
+        "confirm": "确定",
+        "tips": "提示",
         "login": "登录",
         "cancel": "取消",
         "sync": "同步",
+        "deleteSuccess": "删除成功",
         "copySuccess": "复制成功",
         "syncSuccess": "同步成功"
     },
     "common.name": "通用",
     "code": {
+        "4008": "用户未登录",
         "failed": "连接服务器失败"
     },
     "code.name": "状态码"

+ 62 - 1
public/__langs/locales/zh.json

@@ -1,11 +1,16 @@
 {
     "home": {
-        "tag": "标注1",
+        "tag": "标注",
         "splitScreen": "分屏",
         "fullScreen": "全屏"
     },
     "home.name": "首页",
     "header": {
+        "passwordText1": "密码不能为空",
+        "phoneText1": "手机号码不能为空",
+        "phoneText2": "请输入正确手机号",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
         "setting": "设为",
         "reset": "重设",
         "userInfo": "个人信息",
@@ -19,16 +24,72 @@
         "setPointfaidText": "匹配失败,请选择不同点位进行同步",
         "pointUpdate": "关联位置已更新"
     },
+    "components": {
+        "uploadVideo": "上传视频",
+        "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+        "linkView": "网页展示区",
+        "continueAdd": "继续添加",
+        "uploadImg": "上传图片",
+        "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+        "TipsImgLength": "图片数量最多支持上传9张",
+        "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+        "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+        "uploadAudio": "上传音频",
+        "year": "年",
+        "month": "月",
+        "day": "日",
+        "chooseTime": "选择时间"
+    },
+    "tag": {
+        "addComment": "发一条评论吧",
+        "addCommentTips": "请输入内容",
+        "deletetCommentTips": "确定要删除吗?",
+        "noComment": "暂无评论",
+        "unkownUser": "未知用户",
+        "reply": "回复",
+        "comment": "评论",
+        "creater": "创建人",
+        "createTime": "创建时间",
+        "statusText1": "待处理",
+        "statusText2": "进行中",
+        "statusText3": "未解决",
+        "statusText4": "已解决",
+        "uploadFile": "上传附件",
+        "desc": "描述",
+        "member": "涉及的成员",
+        "status": "状态",
+        "inputDesc": "请输入描述",
+        "inputMember": "请选择需要通知的项目人员",
+        "inputStatus": "请选择处理状态",
+        "inputTagName": "请输入标注名称",
+        "tagName": "标注名称",
+        "creatTag": "新建标注",
+        "addTag": "添加标注",
+        "isAddTag": "已添加标注",
+        "deleteTagText": "确定要删除资料吗?"
+    },
+    "tag.name": "标注",
+    "components.name": "组件",
     "header.name": "头部",
     "common": {
+        "input": "请输入",
+        "publish": "发布",
+        "submit": "提交",
+        "exit": "退出",
+        "delete": "删除",
+        "edit": "编辑",
+        "confirm": "确定",
+        "tips": "提示",
         "login": "登录",
         "cancel": "取消",
         "sync": "同步",
+        "deleteSuccess": "删除成功",
         "copySuccess": "复制成功",
         "syncSuccess": "同步成功"
     },
     "common.name": "通用",
     "code": {
+        "4008": "用户未登录",
         "failed": "连接服务器失败"
     },
     "code.name": "状态码"

+ 38 - 0
public/__langs/script/saveLange.js

@@ -0,0 +1,38 @@
+// 导入 express
+const express = require('express')
+const fs = require('fs')
+const path = require('path')
+// 创建服务器实例
+const app = express()
+
+// 配置解析表单数据的中间件
+app.use(express.urlencoded({ extended: false }))
+
+// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
+const cors = require('cors')
+app.use(cors())
+
+// 必须在配置 cors 中间件之前,配置 JSONP 的接口
+app.post('/save', (req, res) => {
+    // TODO: 定义 JSONP 接口具体的实现过程
+    // 1. 得到函数的名称
+    const data = JSON.parse(req.body.q)
+    const locale = req.body.locale
+    fs.writeFile(path.join(__dirname, '..', 'locales', locale + '.json'), JSON.stringify(data, null, 4), err => {
+        if (err) {
+            return
+        }
+        fs.writeFile(path.join(__dirname, '..', '..', '..', 'src', 'locales', locale + '.json'), JSON.stringify(data, null, 4), () => {
+            res.send({ success: true, code: 1, data }).end()
+        })
+    })
+})
+// 导入路由模块
+// const router = require('./16.apiRouter')
+// // 把路由模块,注册到 app 上
+// app.use('/api', router)
+
+// 启动服务器
+app.listen(9091, () => {
+    console.log('express server running at http://127.0.0.1')
+})

+ 25 - 13
src/components/calendar/mobile.vue

@@ -1,17 +1,29 @@
 <template>
     <div class="calendar" v-show="!showCalendar">
         <span class="prev" @click="emits('prev')"><i class="iconfont icon-arrows_left"></i></span>
-        <span class="cale" @click="onPickDate()">{{ date }}<i style="display: none;" class="iconfont icon-date"></i></span>
+        <span class="cale" @click="onPickDate()">{{ date }}<i style="display: none" class="iconfont icon-date"></i></span>
         <span class="next" @click="emits('next')"><i class="iconfont icon-arrows_right"></i></span>
     </div>
     <div class="calendar-list" v-if="showCalendar" @click="showCalendar = false">
         <div @click.stop>
-            <van-datetime-picker v-model="props.value" type="date" title="选择时间" confirm-button-text="确定" :filter="onFilter" :formatter="onFormatter" @confirm="onConfirm" @cancel="onCancel" />
+            <van-datetime-picker
+                v-model="props.value"
+                type="date"
+                :title="$t('components.chooseTime')"
+                :confirm-button-text="$t('common.confirm')"
+                :filter="onFilter"
+                :formatter="onFormatter"
+                @confirm="onConfirm"
+                @cancel="onCancel"
+            />
         </div>
     </div>
 </template>
 <script setup>
 import { ref, defineProps, computed } from 'vue'
+import { components } from '../../../../4dkankan_v3/src/lang/_zh'
+import { useI18n, getLocale } from '@/i18n'
+const { t } = useI18n({ useScope: 'global' })
 const props = defineProps({
     value: Date,
     highlighted: Array,
@@ -30,34 +42,34 @@ const onPickDate = () => {
 }
 const onFormatter = (type, val) => {
     if (type === 'year') {
-        return `${val}年`
+        return val + t('components.year')
     }
     if (type === 'month') {
-        return `${val}月`
+        return val + t('components.month')
     }
     if (type === 'day') {
-        return `${val}日`
+        return val + t('components.day')
     }
     return val
 }
-const onFilter = (type, options)=>{
+const onFilter = (type, options) => {
     const days = props.highlighted
-    if(!days.year.length){
+    if (!days.year.length) {
         return options
     }
-    if(type == 'year') {
+    if (type == 'year') {
         return days.year
-    }else if(type == 'month'){
+    } else if (type == 'month') {
         return days.month
-    } else if(type == 'day'){
+    } else if (type == 'day') {
         return days.day
     }
     return options
 }
-const onCancel = ()=>{
+const onCancel = () => {
     showCalendar.value = false
 }
-const onConfirm = payload=>{
+const onConfirm = payload => {
     emits('selected', { payload })
     showCalendar.value = false
 }
@@ -99,7 +111,7 @@ const onConfirm = payload=>{
     bottom: 0;
     height: 100vh;
     z-index: 1000;
-    background: rgba(0,0,0,0.5);
+    background: rgba(0, 0, 0, 0.5);
     display: flex;
     align-items: flex-end;
     justify-content: center;

+ 1 - 1
src/components/datepicker/PickerDay.vue

@@ -16,7 +16,7 @@
           >&lt;</span
         >
         <span class="day__month_btn" @click="showMonthCalendar" :class="allowedToShowView('month') ? 'up' : ''"
-          >{{ isYmd ? currMonthName : currYearName }} {{ isYmd ? currYearName : currMonthName }}</span
+          >{{ isYmd ? currMonthName : currYearName }}{{$t('components.year')}} {{ isYmd ? currYearName : currMonthName }}</span
         >
         <span @click="isRtl ? previousMonth() : nextMonth()" class="next" :class="{ disabled: isRightNavDisabled }"
           >&gt;</span

+ 21 - 19
src/components/files/TagEditor.vue

@@ -2,33 +2,33 @@
     <div class="tag-editor">
         <div class="tag-editor-content" :style="{ height: height + 'px' }">
             <header>
-                <span>新建标注</span>
+                <span>{{ $t('tag.creatTag') }}</span>
                 <i class="iconfont icon-close" @click="onClose"></i>
             </header>
             <article>
                 <div>
-                    <h4><span>*</span>标注名称</h4>
-                    <UiInput v-model="form.title" type="text" placeholder="请输入标注名称" :maxlength="20" />
+                    <h4><span>*</span>{{ $t('tag.tagName') }}</h4>
+                    <UiInput v-model="form.title" type="text" :placeholder="$t('tag.inputTagName')" :maxlength="20" />
                 </div>
                 <div>
-                    <h4><span>*</span>状态</h4>
-                    <UiInput v-model="form.status" type="select" placeholder="请选择处理状态" :data="data.status" />
+                    <h4><span>*</span>{{ $t('tag.status') }}</h4>
+                    <UiInput v-model="form.status" type="select" :placeholder="$t('tag.inputStatus')" :data="data.status" />
                 </div>
                 <div>
-                    <h4>涉及的成员</h4>
-                    <UiSelectList v-model="form.members" placeholder="请选择需要通知的项目人员" :data="data.members" />
+                    <h4>{{ $t('tag.member') }}</h4>
+                    <UiSelectList v-model="form.members" :placeholder="$t('tag.inputMember')" :data="data.members" />
                 </div>
                 <div>
-                    <h4>描述</h4>
-                    <UiArea v-model="form.describe" type="text" placeholder="请输入描述" :maxlength="50" />
+                    <h4>{{ $t('tag.desc') }}</h4>
+                    <UiArea v-model="form.describe" type="text" :placeholder="$t('tag.inputDesc')" :maxlength="50" />
                 </div>
                 <div>
-                    <h4>上传附件</h4>
+                    <h4>{{ $t('tag.uploadFile') }}</h4>
                     <UiMedias />
                 </div>
             </article>
             <footer>
-                <button @click="onSubmit">提交</button>
+                <button @click="onSubmit">{{ $t('common.submit') }}</button>
             </footer>
         </div>
         <Toast v-if="showTips" type="warn" :content="showTips" :close="() => (showTips = null)" />
@@ -47,6 +47,8 @@ import UiInput from '../form/Input.vue'
 import UiMedias from '../form/medias'
 import UiSelectList from '../form/SelectList.vue'
 import { from } from 'readable-stream'
+import i18n from '@/i18n'
+const { t } = i18n.global
 const showLoading = ref(false)
 const showTips = ref(null)
 const projectId = browser.valueFromUrl('projectId') || 1
@@ -65,10 +67,10 @@ let mediaList = []
 let tag = null
 const data = ref({
     status: [
-        { text: '待处理', value: 1 },
-        { text: '进行中', value: 2 },
-        { text: '未解决', value: 3 },
-        { text: '已解决', value: 4 },
+        { text: t('tag.statusText1'), value: 1 },
+        { text: t('tag.statusText2'), value: 2 },
+        { text: t('tag.statusText3'), value: 3 },
+        { text: t('tag.statusText4'), value: 4 },
     ],
     members: [],
 })
@@ -87,10 +89,10 @@ const onClose = () => {
 let pushData = null
 const onSubmit = async () => {
     if (!form.value.title) {
-        return (showTips.value = '请输入标注名称')
+        return (showTips.value = t('tag.inputTagName'))
     }
     if (!form.value.status) {
-        return (showTips.value = '请选择处理状态')
+        return (showTips.value = t('tag.inputStatus'))
     }
     notify.value.title = form.value.title
     notify.value.content = form.value.describe
@@ -134,7 +136,7 @@ const handlerUpload = async data => {
                     tag.media[notify.value.type][i].src = res.data
                     notify.value.media[notify.value.type][i].src = res.data
                 } else if (res.code == 4008) {
-                    showTips.value = '请先登录'
+                    showTips.value = t('code.4008')
                 } else {
                     showTips.value = res.message
                 }
@@ -173,7 +175,7 @@ const handlerUpload = async data => {
             notify.value.id = response.data
             notify.value = null
         } else if (response.code == 4008) {
-            showTips.value = '请先登录'
+            showTips.value = t('code.4008')
         } else {
             showTips.value = response.message
         }

+ 22 - 19
src/components/files/content/Comment.vue

@@ -2,13 +2,15 @@
 <template>
     <div class="aside-item right-item">
         <div class="comment-content" ref="slider" v-if="slideHeigt" :style="`height:${slideHeigt - 84}px;`">
-            <div class="comment-header"><span>评论</span></div>
+            <div class="comment-header">
+                <span>{{ $t('tag.comment') }}</span>
+            </div>
             <div class="comment-msg">
                 <div class="comment-item" v-for="(i, index) in commentList">
                     <div class="avatar-box" :style="i.head ? `background-image:url(${i.head});` : `background-image:url(${emptyAvatar});`"></div>
                     <div class="comment-box">
                         <div class="view-box view-top">
-                            <span class="user-name">{{ i.nickName || '未知用户' }}</span>
+                            <span class="user-name">{{ i.nickName || $t('tag.unkownUser') }}</span>
                             <i class="iconfont icon-del" v-if="i.userId == userId" @click="delComment({ commentId: i.commentId, index })"></i>
                         </div>
                         <div class="view-box view-middle">
@@ -16,29 +18,29 @@
                         </div>
                         <div class="view-box view-bottom">
                             <span class="comment-time">{{ i.createTime }}</span>
-                            <span class="reply-btn" @click="handlerReply({ parentId: i.commentId, nickName: i.nickName }, index)">回复</span>
+                            <span class="reply-btn" @click="handlerReply({ parentId: i.commentId, nickName: i.nickName }, index)">{{ $t('tag.reply') }}</span>
                         </div>
                         <div class="reply-content" v-if="i.children">
                             <div class="reply-item" v-for="(j, j_index) in i.children">
                                 <div class="avatar-box" :style="j.head ? `background-image:url(${j.head});` : `background-image:url(${emptyAvatar});`"></div>
                                 <div class="reply-box">
                                     <div class="view-box view-top">
-                                        <span class="user-name">{{ j.nickName || '未知用户' }}</span>
+                                        <span class="user-name">{{ j.nickName || $t('tag.unkownUser') }}</span>
                                         <i class="iconfont icon-del" v-if="j.userId == userId" @click="delComment({ commentId: j.commentId, index: j_index, parentIndex: index })"></i>
                                     </div>
                                     <div class="view-box view-middle">
                                         <span class="reply-text"
                                             ><span v-if="j.replyId"
-                                                >回复<span class="reply-tips">@{{ j.replyNickName || '未知用户' }}</span></span
+                                                >{{ $t('tag.reply') }}<span class="reply-tips">@{{ j.replyNickName || $t('tag.unkownUser') }}</span></span
                                             >
                                             {{ j.content }}</span
                                         >
                                     </div>
                                     <div class="view-box view-bottom">
                                         <span class="reply-time">{{ j.createTime }}</span>
-                                        <span class="reply-btn" @click="handlerReply({ parentId: i.commentId, replyId: j.commentId, parentUserId: j.userId, nickName: j.nickName }, j_index)"
-                                            >回复</span
-                                        >
+                                        <span class="reply-btn" @click="handlerReply({ parentId: i.commentId, replyId: j.commentId, parentUserId: j.userId, nickName: j.nickName }, j_index)">{{
+                                            $t('tag.reply')
+                                        }}</span>
                                     </div>
                                 </div>
                             </div>
@@ -48,20 +50,20 @@
             </div>
             <div class="empty-box" v-if="!commentList.length">
                 <div class="pic"></div>
-                <div>暂无评论</div>
+                <div>{{ $t('tag.noComment') }}</div>
             </div>
         </div>
 
         <div class="input-content">
             <div class="input-box"><input ref="input$" @input="handlerInput" v-model="inputText" :placeholder="placeholderText" type="text" /></div>
-            <div class="send-btn" @click="hanlderSubmit">发布</div>
+            <div class="send-btn" @click="hanlderSubmit">{{ $t('common.publish') }}</div>
         </div>
     </div>
     <Toast v-if="showTips" :type="tipsType" :content="showTips" :close="() => (showTips = null)" />
 
     <ui-confirm v-if="delComfirm" @ok="handlerDel" @no="handlerDel">
         <template #content>
-            <div>确定要删除吗?</div>
+            <div>{{ $t('tag.deletetCommentTips') }}</div>
         </template>
     </ui-confirm>
 </template>
@@ -72,6 +74,8 @@ import Toast from '@/components/dialog/Toast'
 import { http } from '@/utils/request'
 import avatar from '@/assets/img/avatar@2x.png'
 import UiConfirm from '@/components/dialog/Confirm.vue'
+import i18n from '@/i18n'
+const { t } = i18n.global
 const props = defineProps({
     slideHeigt: Number,
 })
@@ -82,7 +86,7 @@ const notify = inject('notify')
 const emits = defineEmits(['action'])
 const input$ = ref(null)
 const inputText = ref('')
-const placeholderText = ref('发一条评论吧')
+const placeholderText = ref(t('tag.addComment'))
 const replyInfo = ref(null)
 const tipsType = ref('warn')
 const showTips = ref(null)
@@ -90,11 +94,10 @@ const slider = ref(null)
 
 const handlerReply = (data, index) => {
     inputText.value = ''
-    let name = data.nickName ? data.nickName : '未知用户'
+    let name = data.nickName ? data.nickName : t('tag.unkownUser')
     placeholderText.value = '@' + name
     delete data.nickName
     replyInfo.value = data
-
 }
 
 const handlerInput = () => {
@@ -110,7 +113,7 @@ const commentList = ref([])
 const hanlderSubmit = () => {
     if (inputText.value == '') {
         tipsType.value = 'warn'
-        showTips.value = '请输入内容'
+        showTips.value = t('tag.addCommentTips')
         return
     }
 
@@ -147,7 +150,7 @@ const hanlderSubmit = () => {
 
                     replyInfo.value = null
                     inputText.value = ''
-                    placeholderText.value = '发一条评论吧'
+                    placeholderText.value = t('tag.addComment')
                 } else {
                     tipsType.value = 'error'
                     showTips.value = response.message
@@ -189,10 +192,10 @@ const handlerDel = status => {
                 if (replyInfo.value?.parentId == delComfirm.value.commentId) {
                     replyInfo.value = null
                     inputText.value = ''
-                    placeholderText.value = '发一条评论吧'
+                    placeholderText.value = t('tag.addComment')
                 }
                 tipsType.value = 'success'
-                showTips.value = '删除成功'
+                showTips.value = t('common.deleteSuccess')
             } else {
                 tipsType.value = 'error'
                 showTips.value = response.message
@@ -227,7 +230,7 @@ onMounted(() => {
             if (e.keyCode == 8) {
                 if (replyInfo.value && !inputText.value.length) {
                     replyInfo.value = null
-                    placeholderText.value = '发一条评论吧'
+                    placeholderText.value = t('tag.addComment')
                 }
             }
         })

+ 21 - 12
src/components/files/content/TagMsg.vue

@@ -4,28 +4,28 @@
         <UiAudio v-if="notify.type == 'audio'" :src="notify.media?.[notify.type][0].src" />
 
         <div class="content-item">
-            <div class="item-title">创建时间</div>
+            <div class="item-title">{{$t('tag.createTime')}}</div>
             <span class="content-desc">{{ notify.createTime }}</span>
         </div>
         <div class="content-item">
-            <div class="item-title">创建人</div>
+            <div class="item-title">{{$t('tag.creater')}}</div>
             <span class="content-desc">{{ notify.lastCreateBy }}</span>
         </div>
         <div class="content-item">
-            <div class="item-title">状态</div>
+            <div class="item-title">{{$t('tag.status')}}</div>
             <span class="content-desc" v-for="i in data.status">
                 <span v-if="i.value == form.status">{{ i.text }}</span></span
             >
         </div>
         <div class="content-item">
-            <div class="item-title">涉及的成员</div>
+            <div class="item-title">{{$t('tag.member')}}</div>
             <span class="content-desc" v-for="(i, index) in form.members"
                 ><span>{{ i.text }}</span>
                 <span v-if="index < form.members.length - 1">、</span>
             </span>
         </div>
         <div class="content-item">
-            <div class="item-title">描述</div>
+            <div class="item-title">{{$t('tag.desc')}}</div>
             <span class="content-desc">{{ form.describe }}</span>
         </div>
         <div class="media-box" :class="{ nor: notify.type == 'link', 'zoom-in': notify.type != 'video' }" @click="openView = true" v-if="notify.media?.[notify.type]?.length && notify.type != 'audio'">
@@ -60,6 +60,8 @@ import Link from '@/components/form/medias/Link.vue'
 import UiAudio from '@/components/audio/index.vue'
 import Toast from '@/components/dialog/Toast'
 import { nextTick } from 'process'
+import i18n from '@/i18n'
+const { t } = i18n.global
 const projectId = browser.valueFromUrl('projectId') || 1
 const notify = inject('notify')
 // const props = defineProps(['notify'])
@@ -74,10 +76,10 @@ const form = ref({
 const openView = ref(false)
 const data = ref({
     status: [
-        { text: '待处理', value: 1 },
-        { text: '进行中', value: 2 },
-        { text: '未解决', value: 3 },
-        { text: '已解决', value: 4 },
+        { text: t('tag.statusText1'), value: 1 },
+        { text: t('tag.statusText2'), value: 2 },
+        { text: t('tag.statusText3'), value: 3 },
+        { text: t('tag.statusText4'), value: 4 },
     ],
     members: [],
 })
@@ -139,9 +141,16 @@ onMounted(() => {
                 emits('setShow')
             })
         } else {
-            showTips.value = {
-                type: 'warn',
-                text: response.message,
+            if (response.code == 4008) {
+                showTips.value = {
+                    type: 'warn',
+                    text: t('code.4008'),
+                }
+            } else {
+                showTips.value = {
+                    type: 'warn',
+                    text: response.message,
+                }
             }
         }
     })

+ 8 - 8
src/components/files/index.vue

@@ -2,9 +2,9 @@
     <transition name="slide-right" mode="in-out">
         <div class="files" v-if="showFiles && !isEdit">
             <div class="info" ref="add$">
-                <button @click="onAdd">添加标注</button>
+                <button @click="onAdd">{{ $t('tag.addTag') }}</button>
                 <div>
-                    已添加的标注(<span>{{ tags.length }}</span
+                    {{ $t('tag.isAddTag') }}(<span>{{ tags.length }}</span
                     >)
                 </div>
             </div>
@@ -15,27 +15,27 @@
                         <div class="more" @click.stop="onShowMore(tag)">
                             <i class="iconfont icon-more"></i>
                             <div v-if="showMoreSid == tag.id" v-click-outside="onOutside">
-                                <div @click.stop="onMoreHandler('modify', tag)">编辑</div>
-                                <div @click.stop="onMoreHandler('delete', tag)">删除</div>
+                                <div @click.stop="onMoreHandler('modify', tag)">{{ $t('common.edit') }}</div>
+                                <div @click.stop="onMoreHandler('delete', tag)">{{ $t('common.delete') }}</div>
                             </div>
                         </div>
                     </li>
                 </ul>
             </div>
             <div class="exit" ref="exit$">
-                <button type="button" @click="emits('exit')">退出</button>
+                <button type="button" @click="emits('exit')">{{ $t('common.exit') }}</button>
             </div>
         </div>
     </transition>
     <transition name="slide-up" mode="in-out">
         <div class="toolbar" v-if="showToolbar">
-            <button type="button" @click="onAddCancel">取消</button>
-            <button type="submit" @click="onAddConfirm">确定</button>
+            <button type="button" @click="onAddCancel">{{ $t('common.cancel') }}</button>
+            <button type="submit" @click="onAddConfirm">{{ $t('common.confirm') }}</button>
         </div>
     </transition>
     <ui-confirm v-if="delComfirm" @ok="handlerDel" @no="handlerDel">
         <template #content>
-            <div>确定要删除资料吗?</div>
+            <div>{{ $t('tag.deleteTagText') }}</div>
         </template>
     </ui-confirm>
 </template>

+ 5 - 3
src/components/form/medias/Audio.vue

@@ -6,9 +6,9 @@
     <div class="placeholder" @click="file.click()" v-if="!media.length">
         <div class="icon">
             <i class="iconfont icon-add"></i>
-            <span>上传音频</span>
+            <span>{{ $t('components.uploadAudio') }}</span>
         </div>
-        <div class="tips">支持 mp3/wav 文件:≤ 5MB</div>
+        <div class="tips">{{ $t('components.limitFileSize', { file: 'mp3/wav', size: '5' }) }}</div>
         <input ref="file" type="file" style="display: none" accept=".mp3, .wav" @change="onChange" />
     </div>
     <div class="del-btn" v-if="isEdit && notify.media?.[notify.type]?.length" @click="delMedia">
@@ -18,6 +18,8 @@
 <script setup>
 import { ref, onMounted, inject } from 'vue'
 import { checkSizeLimitFree, base64ToDataURL } from '@/utils/file'
+import i18n from '@/i18n'
+const { t } = i18n.global
 const notify = inject('notify')
 const isEdit = inject('isEdit')
 const emits = defineEmits(['tips'])
@@ -38,7 +40,7 @@ const onChange = e => {
         }
         reader.readAsDataURL(file)
     } else {
-        emits('tips', '请上传 5MB 以内的 jpg/png 文件')
+        emits('tips', t('components.FileSizeTips', { size: '5', file: 'mp3/wav' }))
     }
     e.target.value = ''
 }

+ 8 - 5
src/components/form/medias/Image.vue

@@ -8,16 +8,17 @@
             <div class="swiper-button-next"></div>
         </div>
         <div v-if="isEdit" class="add" @click="file.click()" :class="{ disable: images.length >= 9 }">
-            <span style="color: #fff" v-if="images.length < 9">继续添加</span>&nbsp;<span>{{ images.length }}</span
+            <span style="color: #fff" v-if="images.length < 9">{{ $t('components.continueAdd') }}</span
+            >&nbsp;<span>{{ images.length }}</span
             >&nbsp;/&nbsp;9
         </div>
     </div>
     <div v-if="isEdit" class="placeholder" @click="file.click()" v-show="images.length == 0">
         <div class="icon">
             <i class="iconfont icon-add"></i>
-            <span>上传图片</span>
+            <span>{{ $t('components.uploadImg') }}</span>
         </div>
-        <div class="tips">支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。</div>
+        <div class="tips">{{ $t('components.limitImgLength') }}</div>
         <input ref="file" multiple type="file" style="display: none" accept="image/jpg,image/jpeg,image/png" @change="onChange" />
     </div>
 
@@ -29,6 +30,8 @@
 import { ref, onMounted, inject } from 'vue'
 import { checkSizeLimitFree, base64ToDataURL, convertBlob2File, base64ToBlob } from '@/utils/file'
 import common from '@/utils/common'
+import i18n from '@/i18n'
+const { t } = i18n.global
 const notify = inject('notify')
 const isEdit = inject('isEdit')
 const emits = defineEmits(['tips'])
@@ -53,7 +56,7 @@ const onChange = e => {
                 if (images.value.length >= 9) {
                     if (!frist) {
                         frist = true
-                        emits('tips', '图片数量最多支持上传9张')
+                        emits('tips', t('components.TipsImgLength'))
                     }
                 } else {
                     images.value.push({ src: base64ToDataURL(reader.result), file })
@@ -62,7 +65,7 @@ const onChange = e => {
             }
             reader.readAsDataURL(file)
         } else {
-            emits('tips', '请上传 5MB 以内的 jpg/png 文件')
+            emits('tips', t('components.FileSizeTips', { size: '5', file: 'jpg/png' }))
         }
         console.log(images.value.length)
     }

+ 1 - 1
src/components/form/medias/Link.vue

@@ -6,7 +6,7 @@
     </div>
     <div class="placeholder" v-show="url == null">
         <div class="icon">
-            <span>网页展示区</span>
+            <span>{{$t('components.linkView')}}</span>
         </div>
         <div class="link">
             <input type="text" placeholder="https://" v-model.trim="href" />

+ 3 - 3
src/components/form/medias/Video.vue

@@ -15,9 +15,9 @@
     <div class="placeholder" @click="file.click()" v-if="!media.length">
         <div class="icon">
             <i class="iconfont icon-add"></i>
-            <span>上传视频</span>
+            <span>{{$t('components.uploadVideo')}}</span>
         </div>
-        <div class="tips">支持 mp4/mov 文件:≤ 20MB,≤ 2Mbps</div>
+        <div class="tips">{{$t('components.limitFileSizeBit',{file:"mp4/mov",size:"20",bit:"2"})}}</div>
         <input ref="file" type="file" style="display: none" accept=".mp4, .mov" @change="onChange" />
     </div>
     <div class="del-btn" v-if="notify.media?.[notify.type]?.length && isEdit" @click="delMedia">
@@ -47,7 +47,7 @@ const onChange = e => {
         }
         reader.readAsDataURL(file)
     } else {
-        emits('tips', '请上传 5MB 以内的 jpg/png 文件')
+      emits('tips', t('components.FileSizeTips', { size: '5', file: 'mp4/mov' }))
     }
     e.target.value = ''
 }

+ 4 - 4
src/components/header/CopyLink.vue

@@ -3,15 +3,15 @@
         <div class="login-layer">
             <div class="login-box">
                 <header>
-                    <h4>分享链接</h4>
+                    <h4>{{ $t('header.shareLink') }}</h4>
                     <span class="close" @click="emits('close')"><i class="iconfont icon-close"></i></span>
                 </header>
                 <main>
                     <input readonly v-model="shareURL" />
                 </main>
                 <footer>
-                    <button @click="emits('close')">取消</button>
-                    <button type="submit" ref="copy" :data-clipboard-text="shareURL">复制链接</button>
+                    <button @click="emits('close')">{{ $t('common.cancel') }}</button>
+                    <button type="submit" ref="copy" :data-clipboard-text="shareURL">{{$t('header.copyLink')}}</button>
                 </footer>
             </div>
         </div>
@@ -21,7 +21,7 @@
 import ClipboardJS from 'clipboard'
 import { ref, onMounted } from 'vue'
 import Toast from '@/components/dialog/Toast'
-const emits = defineEmits(['close','done'])
+const emits = defineEmits(['close', 'done'])
 const shareURL = ref(window.location.href.replace('&split', '').replace('&adjust', ''))
 const showToast = ref(false)
 const copy = ref(null)

+ 11 - 9
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>{{$t('header.userLogin')}}</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">{{$t('header.rememberPassword')}}</div>
+                            <div class="checkbox-label">{{ $t('header.rememberPassword') }}</div>
                         </div>
                     </div>
                     <div class="button">
-                        <button type="submit" @click="onLogin">{{$t('common.login')}}</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">{{$t('header.forgetPassword')}}</a>
-                        <a href="http://test.4dkankan.com/#/login/register?from=%2F">{{$t('header.resigter')}}</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>
@@ -45,6 +45,8 @@
 import { ref, onMounted } from 'vue'
 import { http } from '@/utils/request'
 import common from '@/utils/common'
+import { useI18n, getLocale } from '@/i18n'
+const { t } = useI18n({ useScope: 'global' })
 const emits = defineEmits(['close', 'user'])
 const showpass = ref(false)
 const remember = ref(false)
@@ -54,15 +56,15 @@ const errors = ref({})
 const onLogin = () => {
     errors.value = {}
     if (!username.value) {
-        errors.value.username = '手机号码不能为空'
+        errors.value.username = t('header.phoneText1')
         return
     }
     if (!/^1[3-9]\d{9}$/.test(username.value)) {
-        errors.value.username = '请输入正确手机号'
+        errors.value.username = t('header.phoneText2')
         return
     }
     if (!password.value) {
-        errors.value.password = '密码不能为空'
+        errors.value.username = t('header.passwordText1')
         return
     }
 
@@ -96,7 +98,7 @@ const onLogin = () => {
             }
         })
         .catch(() => {
-            errors.value.message = '服务器连接失败'
+            errors.value.message = t('code.failed')
         })
 }
 onMounted(() => {

+ 64 - 3
src/locales/en.json

@@ -1,11 +1,16 @@
 {
     "home": {
-        "tag": "标注",
-        "splitScreen": "分屏",
+        "tag": "tag",
+        "splitScreen": "分屏12",
         "fullScreen": "全屏"
     },
     "home.name": "首页",
     "header": {
+        "passwordText1": "密码不能为空",
+        "phoneText1": "手机号码不能为空",
+        "phoneText2": "请输入正确手机号",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
         "setting": "设为",
         "reset": "重设",
         "userInfo": "个人信息",
@@ -19,16 +24,72 @@
         "setPointfaidText": "匹配失败,请选择不同点位进行同步",
         "pointUpdate": "关联位置已更新"
     },
+    "components": {
+        "uploadVideo": "上传视频",
+        "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+        "linkView": "网页展示区",
+        "continueAdd": "继续添加",
+        "uploadImg": "上传图片",
+        "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+        "TipsImgLength": "图片数量最多支持上传9张",
+        "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+        "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+        "uploadAudio": "上传音频",
+        "year": "年",
+        "month": "月",
+        "day": "日",
+        "chooseTime": "选择时间"
+    },
+    "tag": {
+        "addComment": "发一条评论吧",
+        "addCommentTips": "请输入内容",
+        "deletetCommentTips": "确定要删除吗?",
+        "noComment": "暂无评论",
+        "unkownUser": "未知用户",
+        "reply": "reply",
+        "comment": "comment",
+        "creater": "创建人",
+        "createTime": "创建时间",
+        "statusText1": "待处理",
+        "statusText2": "进行中",
+        "statusText3": "未解决",
+        "statusText4": "已解决",
+        "uploadFile": "上传附件",
+        "desc": "描述",
+        "member": "涉及的成员",
+        "status": "状态",
+        "inputDesc": "请输入描述",
+        "inputMember": "请选择需要通知的项目人员",
+        "inputStatus": "请选择处理状态",
+        "inputTagName": "请输入标注名称",
+        "tagName": "标注名称",
+        "creatTag": "新建标注",
+        "addTag": "添加标注",
+        "isAddTag": "已添加标注",
+        "deleteTagText": "确定要删除资料吗?"
+    },
+    "tag.name": "标注",
+    "components.name": "组件",
     "header.name": "头部",
     "common": {
+        "input": "请输入",
+        "publish": "发布",
+        "submit": "提交",
+        "exit": "退出",
+        "delete": "删除",
+        "edit": "编辑",
+        "confirm": "确定",
+        "tips": "提示",
         "login": "login",
         "cancel": "cancel",
-        "sync": "同步",
+        "sync": "sync",
+        "deleteSuccess": "删除成功",
         "copySuccess": "复制成功",
         "syncSuccess": "同步成功"
     },
     "common.name": "通用",
     "code": {
+        "4008": "用户未登录",
         "failed": "连接服务器失败"
     },
     "code.name": "状态码"

+ 61 - 0
src/locales/ja.json

@@ -6,6 +6,11 @@
     },
     "home.name": "首页",
     "header": {
+        "passwordText1": "密码不能为空",
+        "phoneText1": "手机号码不能为空",
+        "phoneText2": "请输入正确手机号",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
         "setting": "设为",
         "reset": "重设",
         "userInfo": "个人信息",
@@ -19,16 +24,72 @@
         "setPointfaidText": "匹配失败,请选择不同点位进行同步",
         "pointUpdate": "关联位置已更新"
     },
+    "components": {
+        "uploadVideo": "上传视频",
+        "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+        "linkView": "网页展示区",
+        "continueAdd": "继续添加",
+        "uploadImg": "上传图片",
+        "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+        "TipsImgLength": "图片数量最多支持上传9张",
+        "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+        "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+        "uploadAudio": "上传音频",
+        "year": "年",
+        "month": "月",
+        "day": "日",
+        "chooseTime": "选择时间"
+    },
+    "tag": {
+        "addComment": "发一条评论吧",
+        "addCommentTips": "请输入内容",
+        "deletetCommentTips": "确定要删除吗?",
+        "noComment": "暂无评论",
+        "unkownUser": "未知用户",
+        "reply": "回复",
+        "comment": "评论",
+        "creater": "创建人",
+        "createTime": "创建时间",
+        "statusText1": "待处理",
+        "statusText2": "进行中",
+        "statusText3": "未解决",
+        "statusText4": "已解决",
+        "uploadFile": "上传附件",
+        "desc": "描述",
+        "member": "涉及的成员",
+        "status": "状态",
+        "inputDesc": "请输入描述",
+        "inputMember": "请选择需要通知的项目人员",
+        "inputStatus": "请选择处理状态",
+        "inputTagName": "请输入标注名称",
+        "tagName": "标注名称",
+        "creatTag": "新建标注",
+        "addTag": "添加标注",
+        "isAddTag": "已添加标注",
+        "deleteTagText": "确定要删除资料吗?"
+    },
+    "tag.name": "标注",
+    "components.name": "组件",
     "header.name": "头部",
     "common": {
+        "input": "请输入",
+        "publish": "发布",
+        "submit": "提交",
+        "exit": "退出",
+        "delete": "删除",
+        "edit": "编辑",
+        "confirm": "确定",
+        "tips": "提示",
         "login": "登录",
         "cancel": "取消",
         "sync": "同步",
+        "deleteSuccess": "删除成功",
         "copySuccess": "复制成功",
         "syncSuccess": "同步成功"
     },
     "common.name": "通用",
     "code": {
+        "4008": "用户未登录",
         "failed": "连接服务器失败"
     },
     "code.name": "状态码"

+ 62 - 1
src/locales/zh.json

@@ -6,6 +6,11 @@
   },
   "home.name": "首页",
   "header": {
+    "passwordText1": "密码不能为空",
+    "phoneText1": "手机号码不能为空",
+    "phoneText2": "请输入正确手机号",
+    "copyLink": "复制链接",
+    "shareLink": "分享链接",
     "setting": "设为",
     "reset": "重设",
     "userInfo": "个人信息",
@@ -19,17 +24,73 @@
     "setPointfaidText": "匹配失败,请选择不同点位进行同步",
     "pointUpdate": "关联位置已更新"
   },
+  "components": {
+    "uploadVideo": "上传视频",
+    "limitFileSizeBit": "支持 {file} 文件:≤ {size}MB,≤ {bit}Mbps",
+    "linkView": "网页展示区",
+    "continueAdd": "继续添加",
+    "uploadImg": "上传图片",
+    "limitImgLength": "支持JPG、PNG等图片格式,单张不超过5MB,最多支持上传9张。",
+    "TipsImgLength": "图片数量最多支持上传9张",
+    "limitFileSize": "支持 {file} 文件:≤ {size}MB",
+    "FileSizeTips": "请上传 {size}MB 以内的 {file} 文件",
+    "uploadAudio": "上传音频",
+    "year": "年",
+    "month": "月",
+    "day": "日",
+    "chooseTime": "选择时间"
+  },
+  "tag": {
+    "addComment": "发一条评论吧",
+    "addCommentTips": "请输入内容",
+    "deletetCommentTips": "确定要删除吗?",
+    "noComment": "暂无评论",
+    "unkownUser": "未知用户",
+    "reply": "回复",
+    "comment": "评论",
+    "creater": "创建人",
+    "createTime": "创建时间",
+    "statusText1": "待处理",
+    "statusText2": "进行中",
+    "statusText3": "未解决",
+    "statusText4": "已解决",
+    "uploadFile": "上传附件",
+    "desc": "描述",
+    "member": "涉及的成员",
+    "status": "状态",
+    "inputDesc": "请输入描述",
+    "inputMember": "请选择需要通知的项目人员",
+    "inputStatus": "请选择处理状态",
+    "inputTagName": "请输入标注名称",
+    "tagName": "标注名称",
+    "creatTag": "新建标注",
+    "addTag": "添加标注",
+    "isAddTag": "已添加标注",
+    "deleteTagText": "确定要删除资料吗?"
+  },
+  "tag.name": "标注",
+  "components.name": "组件",
   "header.name": "头部",
   "common": {
+    "input": "请输入",
+    "publish": "发布",
+    "submit": "提交",
+    "exit": "退出",
+    "delete": "删除",
+    "edit": "编辑",
+    "confirm": "确定",
+    "tips": "提示",
     "login": "登录",
     "cancel": "取消",
     "sync": "同步",
+    "deleteSuccess": "删除成功",
     "copySuccess": "复制成功",
     "syncSuccess": "同步成功"
   },
   "common.name": "通用",
   "code": {
-    "failed": "连接服务器失败"
+    "failed": "连接服务器失败",
+    "4008": "用户未登录"
   },
   "code.name": "状态码"
 }