feat: 国际化

topwar
guofei 2024-06-01 14:44:16 +08:00
parent f0d66aff65
commit e87316cc50
14 changed files with 1017 additions and 714 deletions

294
package-lock.json generated
View File

@ -5,6 +5,7 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "playable-preview",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
@ -15,15 +16,21 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"localstorage-slim": "^2.2.0", "localstorage-slim": "^2.2.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.4", "react-copy-to-clipboard": "^5.0.4",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-i18next": "^14.1.2",
"react-modal-login": "^2.0.7", "react-modal-login": "^2.0.7",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.2" "web-vitals": "^2.1.2"
},
"devDependencies": {
"http-proxy-middleware": "^3.0.0"
} }
}, },
"node_modules/@apideck/better-ajv-errors": { "node_modules/@apideck/better-ajv-errors": {
@ -1531,11 +1538,11 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.18.9", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.14.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -1553,6 +1560,11 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.16.0", "version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz",
@ -3344,9 +3356,9 @@
"integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="
}, },
"node_modules/@types/http-proxy": { "node_modules/@types/http-proxy": {
"version": "1.17.8", "version": "1.17.14",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz",
"integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==",
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@ -4696,11 +4708,11 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -5606,14 +5618,19 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.3", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
} }
}, },
"node_modules/decamelize": { "node_modules/decamelize": {
@ -7206,9 +7223,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -7952,6 +7969,14 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/html-webpack-plugin": { "node_modules/html-webpack-plugin": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@ -8055,18 +8080,20 @@
} }
}, },
"node_modules/http-proxy-middleware": { "node_modules/http-proxy-middleware": {
"version": "2.0.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz",
"integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==",
"dev": true,
"dependencies": { "dependencies": {
"@types/http-proxy": "^1.17.5", "@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0", "is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2" "micromatch": "^4.0.5"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/https-proxy-agent": { "node_modules/https-proxy-agent": {
@ -8097,6 +8124,36 @@
"ms": "^2.0.0" "ms": "^2.0.0"
} }
}, },
"node_modules/i18next": {
"version": "23.11.5",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz",
"integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -10668,12 +10725,12 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.4", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": { "dependencies": {
"braces": "^3.0.1", "braces": "^3.0.3",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -11410,11 +11467,14 @@
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pirates": { "node_modules/pirates": {
@ -13058,6 +13118,27 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz",
"integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==" "integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA=="
}, },
"node_modules/react-i18next": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz",
"integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -15137,6 +15218,14 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/w3c-hr-time": { "node_modules/w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@ -15357,6 +15446,29 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
"integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
"dependencies": {
"@types/http-proxy": "^1.17.8",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"@types/express": "^4.17.13"
},
"peerDependenciesMeta": {
"@types/express": {
"optional": true
}
}
},
"node_modules/webpack-dev-server/node_modules/json-schema-traverse": { "node_modules/webpack-dev-server/node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@ -17185,11 +17297,18 @@
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.18.9", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.14.0"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
}
} }
}, },
"@babel/runtime-corejs3": { "@babel/runtime-corejs3": {
@ -18570,9 +18689,9 @@
"integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="
}, },
"@types/http-proxy": { "@types/http-proxy": {
"version": "1.17.8", "version": "1.17.14",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz",
"integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==",
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
@ -19747,11 +19866,11 @@
} }
}, },
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"requires": { "requires": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
} }
}, },
"browser-process-hrtime": { "browser-process-hrtime": {
@ -20496,9 +20615,9 @@
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI="
}, },
"debug": { "debug": {
"version": "4.3.3", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"requires": { "requires": {
"ms": "2.1.2" "ms": "2.1.2"
} }
@ -21794,9 +21913,9 @@
"integrity": "sha512-sHvRqTiwdmcuzqet7iVwsbwF6UrV3wIgDf2SHNdY1Hgl8PC45HZg/0xtdw6U2izIV4lccnrY9ftl6wZFNdjYMg==" "integrity": "sha512-sHvRqTiwdmcuzqet7iVwsbwF6UrV3wIgDf2SHNdY1Hgl8PC45HZg/0xtdw6U2izIV4lccnrY9ftl6wZFNdjYMg=="
}, },
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"requires": { "requires": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
} }
@ -22395,6 +22514,14 @@
} }
} }
}, },
"html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"requires": {
"void-elements": "3.1.0"
}
},
"html-webpack-plugin": { "html-webpack-plugin": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@ -22488,15 +22615,17 @@
} }
}, },
"http-proxy-middleware": { "http-proxy-middleware": {
"version": "2.0.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz",
"integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==",
"dev": true,
"requires": { "requires": {
"@types/http-proxy": "^1.17.5", "@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0", "is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2" "micromatch": "^4.0.5"
} }
}, },
"https-proxy-agent": { "https-proxy-agent": {
@ -22521,6 +22650,22 @@
"ms": "^2.0.0" "ms": "^2.0.0"
} }
}, },
"i18next": {
"version": "23.11.5",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz",
"integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==",
"requires": {
"@babel/runtime": "^7.23.2"
}
},
"i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"requires": {
"@babel/runtime": "^7.23.2"
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -24547,12 +24692,12 @@
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
}, },
"micromatch": { "micromatch": {
"version": "4.0.4", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.3",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
} }
}, },
"mime": { "mime": {
@ -25134,9 +25279,9 @@
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
}, },
"picomatch": { "picomatch": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
}, },
"pirates": { "pirates": {
"version": "4.0.4", "version": "4.0.4",
@ -26387,6 +26532,15 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz",
"integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==" "integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA=="
}, },
"react-i18next": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz",
"integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==",
"requires": {
"@babel/runtime": "^7.23.9",
"html-parse-stringify": "^3.0.1"
}
},
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -26431,6 +26585,7 @@
"postcss-preset-env": "^7.0.1", "postcss-preset-env": "^7.0.1",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"prop-types": "^15.8.0", "prop-types": "^15.8.0",
"react": "^17.0.2",
"react-app-polyfill": "^3.0.0", "react-app-polyfill": "^3.0.0",
"react-dev-utils": "^12.0.0", "react-dev-utils": "^12.0.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
@ -28103,6 +28258,11 @@
} }
} }
}, },
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
},
"w3c-hr-time": { "w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@ -28311,6 +28471,18 @@
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
}, },
"http-proxy-middleware": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
"integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
"requires": {
"@types/http-proxy": "^1.17.8",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
}
},
"json-schema-traverse": { "json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",

View File

@ -12,12 +12,15 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"localstorage-slim": "^2.2.0", "localstorage-slim": "^2.2.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.4", "react-copy-to-clipboard": "^5.0.4",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-i18next": "^14.1.2",
"react-modal-login": "^2.0.7", "react-modal-login": "^2.0.7",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.2" "web-vitals": "^2.1.2"
@ -45,5 +48,8 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"http-proxy-middleware": "^3.0.0"
} }
} }

View File

@ -1,48 +1,100 @@
.App { .App {
text-align: center; text-align: center;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
} }
.App-logo { .App-logo {
height: 40vmin; height: 40vmin;
pointer-events: none; pointer-events: none;
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
.App-logo { .App-logo {
animation: App-logo-spin infinite 20s linear; animation: App-logo-spin infinite 20s linear;
} }
} }
.App-header { .App-header {
background-color: #282c34; background-color: #282c34;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white; color: white;
} }
.App-link { .App-link {
color: #61dafb; color: #61dafb;
} }
@keyframes App-logo-spin { @keyframes App-logo-spin {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.MuiFormControlLabel-label { .MuiFormControlLabel-label {
font-size: 13px !important; font-size: 13px !important;
} }
.MuiDialog-root { .MuiDialog-root {
background-color: white; background-color: white;
} }
.pane-btn {
border-radius: 4px;
border: 0;
display: inline-block;
height: 30px;
width: 150px;
cursor: pointer;
outline: none;
font-weight: bold;
}
.pane-btn.checked {
background-color: #02b564;
color: white;
}
.pane-btn + .pane-btn {
margin-left: 10px;
}
.generate-btn {
border-radius: 4px;
display: inline-block;
height: 40px;
width: 150px;
cursor: pointer;
outline: none;
font-weight: bold;
font-size: 22px;
background-color: rgb(82, 146, 242);
border: 1px #367fed solid;
}
.language-select-container {
display: flex;
align-items: center;
position: absolute;
right: 40px;
top: 30px;
z-index: 9999;
}
.language-select-container .language-select {
margin-left: 10px;
width: 120px;
height: 30px;
}
.language-select-container .language-select fieldset {
display: none;
}

View File

@ -8,12 +8,9 @@ import waterMarkPng from "./images/water-mark.png";
import React from "react"; import React from "react";
import SettingFrame from "./Components/settingsFrame"; import SettingFrame from "./Components/settingsFrame";
import QrCode from "qrcode"; import QrCode from "qrcode";
import { import { getLoginInfo, getProjectSettingValue, setLoginInfo, setProjectSetting } from "./storage";
getLoginInfo, import Select from "@mui/material/Select";
getProjectSettingValue, import MenuItem from "@mui/material/MenuItem";
setLoginInfo,
setProjectSetting,
} from "./storage";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Dialog from "@mui/material/Dialog"; import Dialog from "@mui/material/Dialog";
@ -23,271 +20,271 @@ import DialogTitle from "@mui/material/DialogTitle";
import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";
import queryString from "query-string"; import queryString from "query-string";
import { withTranslation } from "react-i18next";
import i18n from "i18next";
class App extends React.Component { class App extends React.Component {
constructor() { constructor(props) {
super(); super();
this.state = { this.state = {
mode: "normal", mode: "normal",
projects: [], projects: [],
selectedProject: {}, selectedProject: {},
selectedProjectName: "", selectedProjectName: "",
device: "android-h", device: "android-h",
htmlUrl: "", htmlUrl: "",
dataUrl: "", dataUrl: "",
loading: false, loading: false,
loginOpen: false, loginOpen: false,
loginErrorMessage: "", loginErrorMessage: "",
hideLogo: false, hideLogo: false,
loginForm: { loginForm: {
account: "", account: "",
password: "", password: "",
keepLogin: false, keepLogin: false,
}, },
}; language: localStorage.getItem("lang") ?? "zh",
} };
componentDidMount() { this.t = props.t;
const loginInfo = getLoginInfo(); }
var tenant = utils.getTenant(); componentDidMount() {
const hideLogo = queryString.parseUrl(window.location.href)?.query const loginInfo = getLoginInfo();
?.hideLogo; var tenant = utils.getTenant();
this.setState({ hideLogo: !!hideLogo }); const hideLogo = queryString.parseUrl(window.location.href)?.query?.hideLogo;
if (loginInfo && loginInfo.permissionPages.includes(tenant)) { this.setState({ hideLogo: !!hideLogo });
this.fetchData(); if (loginInfo && loginInfo.permissionPages.includes(tenant)) {
this.refreshQrCode(); this.fetchData();
} else { this.refreshQrCode();
this.setState({ loginOpen: true }); } else {
loginInfo && this.setState({ loginOpen: true });
this.setState({ loginErrorMessage: "无权限访问,请切换账号" }); loginInfo && this.setState({ loginErrorMessage: "noAuth" });
} }
} }
login = async () => { login = async () => {
var { account, password, keepLogin } = this.state.loginForm; var { account, password, keepLogin } = this.state.loginForm;
var user = await LoginApi.UserLogin({ account, password }); var user = await LoginApi.UserLogin({ account, password });
var tenant = utils.getTenant(); var tenant = utils.getTenant();
if (user && user.permissionPages.includes(tenant)) { if (user && user.permissionPages.includes(tenant)) {
this.setState({ this.setState({
loginOpen: false, loginOpen: false,
}); });
if (keepLogin) { if (keepLogin) {
setLoginInfo(user); setLoginInfo(user);
} }
this.fetchData(); this.fetchData();
this.refreshQrCode(); this.refreshQrCode();
} else { } else {
this.setState({ this.setState({
loginErrorMessage: user loginErrorMessage: user ? "noAuth" : "loginError",
? `无权限访问${tenant},请切换账号` });
: "账号或密码错误", }
}); };
}
};
refreshQrCode() { refreshQrCode() {
var { selectedProject } = this.state; var { selectedProject } = this.state;
selectedProject.HtmlUrl && selectedProject.HtmlUrl &&
QrCode.toDataURL([{ data: this.selectedProjectUrl, mode: "byte" }], { QrCode.toDataURL([{ data: this.selectedProjectUrl, mode: "byte" }], {
width: 120, width: 120,
}).then((dataUrl) => { }).then((dataUrl) => {
this.setState({ this.setState({
dataUrl: dataUrl, dataUrl: dataUrl,
}); });
}); });
} }
get selectedProjectUrl() { get selectedProjectUrl() {
var { selectedProject } = this.state; var { selectedProject } = this.state;
var settingValue = getProjectSettingValue(selectedProject?.Name) || [ var settingValue = getProjectSettingValue(selectedProject?.Name) || [1, 1, 1];
1, 1, 1, var rawUrl = selectedProject?.HtmlUrl ? encodeURI(selectedProject?.HtmlUrl) : "";
]; if (rawUrl) {
var rawUrl = selectedProject?.HtmlUrl rawUrl += `?datanumber=${settingValue[0]}&datanumber1=${settingValue[1]}&datanumber2=${settingValue[2]}&lunaOrHtml=false`;
? encodeURI(selectedProject?.HtmlUrl) }
: "";
if (rawUrl) {
rawUrl += `?datanumber=${settingValue[0]}&datanumber1=${settingValue[1]}&datanumber2=${settingValue[2]}&lunaOrHtml=false`;
}
return rawUrl; return rawUrl;
} }
async setSelectedProject(selectedProjectName) { async setSelectedProject(selectedProjectName) {
this.setState({ this.setState({
selectedProjectName: selectedProjectName, selectedProjectName: selectedProjectName,
}); });
console.log("selected:", selectedProjectName); console.log("selected:", selectedProjectName);
await this.updateSelectedProject(selectedProjectName); await this.updateSelectedProject(selectedProjectName);
} }
async updateSelectedProject(projectName) { async updateSelectedProject(projectName) {
var projectSetting = await ProjectApi.getProjectSetting(projectName); var projectSetting = await ProjectApi.getProjectSetting(projectName);
this.setState({ if (projectSetting.TextObjStr) {
selectedProject: projectSetting, const none = `${this.t("none")}|${this.t("none")}|${this.t("none")}`;
}); if (projectSetting.TextObjStr.CentText === "无|无|无") {
} projectSetting.TextObjStr.CentText = none;
}
if (projectSetting.TextObjStr.MiddText === "无|无|无") {
projectSetting.TextObjStr.MiddText = none;
}
if (projectSetting.TextObjStr.TopText === "无|无|无") {
projectSetting.TextObjStr.TopText = none;
}
}
this.setState({
selectedProject: projectSetting,
});
}
setDevice(selected) { setDevice(selected) {
this.setState({ this.setState({
device: selected, device: selected,
}); });
} }
setMode(mode) { setMode(mode) {
this.setState({ this.setState({
mode: mode, mode: mode,
}); });
} }
setLoginForm(form) { setLoginForm(form) {
this.setState({ this.setState({
loginForm: { loginForm: {
...this.state.loginForm, ...this.state.loginForm,
...form, ...form,
}, },
}); });
} }
async fetchData() { async fetchData() {
this.setState({ this.setState({
loading: true, loading: true,
}); });
var projectNames = (await this.fetchProjects()) || ["无项目"]; var projectNames = (await this.fetchProjects()) || ["无项目"];
this.setState({ this.setState({
projects: projectNames, projects: projectNames,
selectedProjectName: projectNames[0], selectedProjectName: projectNames[0],
loading: false, loading: false,
}); });
await this.updateSelectedProject(projectNames[0]); await this.updateSelectedProject(projectNames[0]);
this.refreshQrCode(); this.refreshQrCode();
} }
async fetchProjects() { async fetchProjects() {
return await ProjectApi.getProjects(); return await ProjectApi.getProjects();
} }
get projectSettingValue() { get projectSettingValue() {
return getProjectSettingValue(this.state.selectedProjectName); return getProjectSettingValue(this.state.selectedProjectName);
} }
UpdateSetting(obj) { UpdateSetting(obj) {
setProjectSetting(this.state.selectedProject.Name, [ setProjectSetting(this.state.selectedProject.Name, [+obj.topType, +obj.centreType, +obj.middleType]);
+obj.topType, }
+obj.centreType,
+obj.middleType,
]);
}
render() { render() {
var { var { mode, device, projects, selectedProject, dataUrl, loading, loginOpen, loginForm, hideLogo, language } = this.state;
mode, const { t } = this.props;
device,
projects,
selectedProject,
dataUrl,
loading,
loginOpen,
loginForm,
hideLogo,
} = this.state;
return ( return (
<div className="App"> <div className="App">
<Dialog open={loginOpen}> <Dialog open={loginOpen}>
<DialogTitle>登录</DialogTitle> <DialogTitle>{t("login")}</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
autoFocus autoFocus
margin="dense" margin="dense"
id="name" id="name"
label="账号" label={t("account")}
fullWidth fullWidth
value={loginForm.account} value={loginForm.account}
variant="standard" variant="standard"
onChange={(e) => this.setLoginForm({ account: e.target.value })} onChange={(e) => this.setLoginForm({ account: e.target.value })}
onKeyDown={(e) => e.keyCode === 13 && this.login()} onKeyDown={(e) => e.keyCode === 13 && this.login()}
/> />
<TextField <TextField
autoFocus autoFocus
margin="dense" margin="dense"
id="name" id="name"
label="密码" label={t("password")}
type={"password"} type={"password"}
fullWidth fullWidth
value={loginForm.password} value={loginForm.password}
variant="standard" variant="standard"
onChange={(e) => this.setLoginForm({ password: e.target.value })} onChange={(e) => this.setLoginForm({ password: e.target.value })}
onKeyDown={(e) => e.keyCode === 13 && this.login()} onKeyDown={(e) => e.keyCode === 13 && this.login()}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
{this.state.loginErrorMessage && ( {this.state.loginErrorMessage && (
<span <span
style={{ style={{
color: "red", color: "red",
fontSize: "12px", fontSize: "12px",
margin: "0 auto 0 10px", margin: "0 auto 0 10px",
}} }}>
> {t(this.state.loginErrorMessage)}
{this.state.loginErrorMessage} </span>
</span> )}
)} <FormControlLabel
<FormControlLabel control={<Checkbox checked={loginForm.keepLogin} onChange={(_, checked) => this.setLoginForm({ keepLogin: checked })} />}
control={ label={t("keepLoginStatus")}
<Checkbox />
checked={loginForm.keepLogin} <Button onClick={() => this.login()}>{t("submit")}</Button>
onChange={(_, checked) => </DialogActions>
this.setLoginForm({ keepLogin: checked }) </Dialog>
} <Pane
/> mode={mode}
} onModeChange={(_mode) => this.setMode(_mode)}
label="保持登录状态7天" projects={projects}
/> device={device}
<Button onClick={() => this.login()}>提交</Button> loading={loading}
</DialogActions> hideLogo={hideLogo}
</Dialog> onProjectSelect={(project) => {
<Pane this.setSelectedProject(project);
mode={mode} }}
onModeChange={(_mode) => this.setMode(_mode)} onDeviceChange={(_device) => this.setDevice(_device)}
projects={projects} />
device={device} <div className="language-select-container">
loading={loading} <div>{t("language")}:</div>
hideLogo={hideLogo} <Select
onProjectSelect={(project) => { className="language-select"
this.setSelectedProject(project); value={language}
}} label={t("language")}
onDeviceChange={(_device) => this.setDevice(_device)} sx={{
/> background: "rgb(115, 158, 211)",
<div width: "80%",
style={{ color: "white",
display: "flex", }}
flex: 1, onChange={(e) => {
alignItems: "center", let lang = e.target.value;
backgroundImage: hideLogo ? "" : `url(${waterMarkPng})`, this.setState({
backgroundPositionX: "right", language: lang,
backgroundPositionY: "bottom", });
backgroundRepeat: "no-repeat", i18n.changeLanguage(lang);
}} localStorage.setItem("lang", lang);
> }}>
{mode === "normal" ? ( <MenuItem value="en">English</MenuItem>
<DeviceFrame <MenuItem value="zh">Chinese</MenuItem>
htmlUrl={this.selectedProjectUrl} </Select>
device={device} </div>
qrDataUrl={dataUrl} <div
refreshQrCode={() => this.refreshQrCode()} style={{
/> display: "flex",
) : ( flex: 1,
<SettingFrame alignItems: "center",
setting={selectedProject?.TextObjStr} backgroundImage: hideLogo ? "" : `url(${waterMarkPng})`,
settingValue={this.projectSettingValue} backgroundPositionX: "right",
generate={(value) => this.UpdateSetting(value)} backgroundPositionY: "bottom",
/> backgroundRepeat: "no-repeat",
)} }}>
</div> {mode === "normal" ? (
</div> <DeviceFrame htmlUrl={this.selectedProjectUrl} device={device} qrDataUrl={dataUrl} refreshQrCode={() => this.refreshQrCode()} />
); ) : (
} <SettingFrame setting={selectedProject?.TextObjStr} settingValue={this.projectSettingValue} generate={(value) => this.UpdateSetting(value)} />
)}
</div>
</div>
);
}
} }
export default App; export default withTranslation()(App);

View File

@ -18,6 +18,6 @@
.qrCode { .qrCode {
position: absolute; position: absolute;
right: 30px; right: 40px;
top: 30px; top: 100px;
} }

View File

@ -1,117 +1,124 @@
import './index.css' import "./index.css";
import androidHPng from '../../images/android-h.png' import androidHPng from "../../images/android-h.png";
import androidVPng from '../../images/android-v.png' import androidVPng from "../../images/android-v.png";
import iphoneHPng from '../../images/iphone-h.png' import iphoneHPng from "../../images/iphone-h.png";
import iphoneVPng from '../../images/iphone-v.png' import iphoneVPng from "../../images/iphone-v.png";
import ipadHPng from '../../images/iPad-h.png' import ipadHPng from "../../images/iPad-h.png";
import ipadVPng from '../../images/iPad-v.png' import ipadVPng from "../../images/iPad-v.png";
import copyPng from '../../images/copy.png' import copyPng from "../../images/copy.png";
import refreshButtonPng from '../../images/refresh-button.png' import refreshButtonPng from "../../images/refresh-button.png";
import {CopyToClipboard} from 'react-copy-to-clipboard'; import { CopyToClipboard } from "react-copy-to-clipboard";
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from "react";
import { BaseUrl } from '../../constants' import { BaseUrl } from "../../constants";
import { Alert, Snackbar } from '@mui/material' import { Alert, Snackbar } from "@mui/material";
import { useTranslation } from "react-i18next";
var configMap = { var configMap = {
"android-h": { "android-h": {
bkg: androidHPng, bkg: androidHPng,
width: '512px', width: "512px",
height: '286px', height: "286px",
offsetv: '5px', offsetv: "5px",
}, },
"android-v": { "android-v": {
bkg: androidVPng, bkg: androidVPng,
width: '286px', width: "286px",
height: '512px', height: "512px",
offsetv: '5px', offsetv: "5px",
}, },
"iphone-h": { "iphone-h": {
bkg: iphoneHPng, bkg: iphoneHPng,
width: '628px', width: "628px",
height: '322px' height: "322px",
}, },
"iphone-v": { "iphone-v": {
bkg: iphoneVPng, bkg: iphoneVPng,
width: '322px', width: "322px",
height: '628px' height: "628px",
}, },
"ipad-h": { "ipad-h": {
bkg: ipadHPng, bkg: ipadHPng,
width: '433px', width: "433px",
height: '326px', height: "326px",
offsetv: '5px', offsetv: "5px",
}, },
"ipad-v": { "ipad-v": {
bkg: ipadVPng, bkg: ipadVPng,
width: '324px', width: "324px",
height: '433px', height: "433px",
offsetv: '5px', offsetv: "5px",
}, },
} };
function DeviceFrame(props) { function DeviceFrame(props) {
var [config, setConfig] = useState({}) var [config, setConfig] = useState({});
var [cacheKey, setCacheKey] = useState(new Date().getTime()); var [cacheKey, setCacheKey] = useState(new Date().getTime());
var [openPopup, setOpenPopup] = useState(false); var [openPopup, setOpenPopup] = useState(false);
var [absoluteUrl, setAbsoluteUrl] = useState(''); var [absoluteUrl, setAbsoluteUrl] = useState("");
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
setConfig(configMap[props.device]) setConfig(configMap[props.device]);
}, [props.device, config]) }, [props.device, config]);
var refresh = () => setCacheKey(new Date().getTime()); var refresh = () => setCacheKey(new Date().getTime());
useEffect(() => { useEffect(() => {
console.log('触发更新', props.htmlUrl); console.log("触发更新", props.htmlUrl);
props.htmlUrl && setAbsoluteUrl(!props.htmlUrl.startsWith("http") ? BaseUrl + props.htmlUrl : props.htmlUrl); props.htmlUrl && setAbsoluteUrl(!props.htmlUrl.startsWith("http") ? BaseUrl + props.htmlUrl : props.htmlUrl);
refresh() refresh();
props.refreshQrCode() props.refreshQrCode();
}, [props.htmlUrl]) }, [props.htmlUrl]);
return ( return (
<div className='deviceFrameContainer'> <div className="deviceFrameContainer">
<img src={refreshButtonPng} alt='refresh' onClick={() => refresh()} className='refreshButton'/> <img src={refreshButtonPng} alt="refresh" onClick={() => refresh()} className="refreshButton" />
{ props.qrDataUrl && {props.qrDataUrl && (
<div className='qrCode'> <div className="qrCode">
<p style={{ margin: '0' }}>扫一扫,手机预览 <p style={{ margin: "0" }}>
<CopyToClipboard text={absoluteUrl} onCopy={() => setOpenPopup(true)}> {t("scan")}
<img style={{ cursor: 'pointer', width: '20px', height: '20px' }} src={copyPng} alt='复制链接'/> <CopyToClipboard text={absoluteUrl} onCopy={() => setOpenPopup(true)}>
</CopyToClipboard> <img style={{ cursor: "pointer", width: "20px", height: "20px" }} src={copyPng} alt="复制链接" />
</p> </CopyToClipboard>
<img src={props.qrDataUrl} alt='qr code'/> </p>
</div> <img src={props.qrDataUrl} alt="qr code" />
} </div>
<Snackbar open={openPopup} onClose={() => setOpenPopup(false)} autoHideDuration={500} anchorOrigin={{ )}
vertical: 'top', <Snackbar
horizontal: 'right' open={openPopup}
}}> onClose={() => setOpenPopup(false)}
<Alert severity="success" sx={{ width: '100%' }}> autoHideDuration={500}
链接复制成功 anchorOrigin={{
</Alert> vertical: "top",
</Snackbar> horizontal: "right",
<div> }}>
<img src={config.bkg} alt='background'/> <Alert severity="success" sx={{ width: "100%" }}>
{ {t("copySuccess")}
props.htmlUrl </Alert>
&& <iframe </Snackbar>
id='deviceFrame' <div>
style={{ <img src={config.bkg} alt="background" />
position: 'absolute', {props.htmlUrl && (
top: '50%', <iframe
left: '50%', id="deviceFrame"
transform: 'translate(-50%, -50%)', style={{
paddingBottom: config.offsetv position: "absolute",
}} top: "50%",
allow="clipboard-read; clipboard-write *" left: "50%",
title='preview' transform: "translate(-50%, -50%)",
src={absoluteUrl + '&cacheKey=' + cacheKey} paddingBottom: config.offsetv,
width={config.width} }}
height={config.height} allow="clipboard-read; clipboard-write *"
frameBorder={'0'}/> title="preview"
} src={absoluteUrl + "&cacheKey=" + cacheKey}
</div> width={config.width}
</div> height={config.height}
) frameBorder={"0"}
/>
)}
</div>
</div>
);
} }
export default DeviceFrame; export default DeviceFrame;

View File

@ -12,161 +12,136 @@ import horizentalButton from "../../images/horizental-button.png";
import verticalChecked from "../../images/vertical-button-checked.png"; import verticalChecked from "../../images/vertical-button-checked.png";
import verticalButton from "../../images/vertical-button.png"; import verticalButton from "../../images/vertical-button.png";
import modeSelectPng from "../../images/mode-select.png"; import modeSelectPng from "../../images/mode-select.png";
import { useTranslation } from "react-i18next";
var deviceConfigs = [ var deviceConfigs = [
{ {
name: "iPhone", name: "iPhone",
icon: iphonePng, icon: iphonePng,
vertical: "iphone-v", vertical: "iphone-v",
horizental: "iphone-h", horizental: "iphone-h",
}, },
{ {
name: "Android Phone", name: "Android Phone",
icon: androidPng, icon: androidPng,
vertical: "android-v", vertical: "android-v",
horizental: "android-h", horizental: "android-h",
}, },
{ {
name: "iPad", name: "iPad",
icon: ipadPng, icon: ipadPng,
vertical: "ipad-v", vertical: "ipad-v",
horizental: "ipad-h", horizental: "ipad-h",
}, },
]; ];
function Pane(props) { function Pane(props) {
var [mode, setMode] = useState(props.mode); var [mode, setMode] = useState(props.mode);
var [device, setDevice] = useState(props.device); var [device, setDevice] = useState(props.device);
var [project, setProject] = useState(props.project); var [project, setProject] = useState(props.project);
const { t } = useTranslation();
var modeChange = (e) => { var modeChange = (e) => {
var _mode = e.target.value; var _mode = e.target.value;
setMode(_mode); setMode(_mode);
props.onModeChange(e.target.value); props.onModeChange(e.target.value);
}; };
var deviceChange = (_device) => { var deviceChange = (_device) => {
setDevice(_device); setDevice(_device);
props.onDeviceChange(_device); props.onDeviceChange(_device);
}; };
var projectSelect = (e) => { var projectSelect = (e) => {
var projectName = e.target.value; var projectName = e.target.value;
setProject(projectName); setProject(projectName);
props.onProjectSelect(projectName); props.onProjectSelect(projectName);
}; };
useEffect(() => { useEffect(() => {
props.projects.length && !project && setProject(props.projects[0]); props.projects.length && !project && setProject(props.projects[0]);
}, [props.projects]); }, [props.projects]);
return ( return (
<div className="Pane"> <div className="Pane">
<Select <Select
id="mode-select" id="mode-select"
value={mode} value={mode}
onChange={(e) => modeChange(e)} onChange={(e) => modeChange(e)}
startAdornment={ startAdornment={<img src={modeSelectPng} height={"30px"} alt="selected" />}
<img src={modeSelectPng} height={"30px"} alt="selected" /> sx={{
} width: "100%",
sx={{ border: "none",
width: "100%", background: "rgb(69, 115, 191)",
border: "none", color: "white",
background: "rgb(69, 115, 191)", fontWeight: "bold",
color: "white", }}
fontWeight: "bold", MenuProps={{
}} classes: {
MenuProps={{ paper: "selectPager",
classes: { },
paper: "selectPager", }}>
}, <MenuItem value={"normal"}>Normal Preview</MenuItem>
}} <MenuItem value={"dynamic"}>Dynamic Preview</MenuItem>
> </Select>
<MenuItem value={"normal"}>Normal Preview</MenuItem> <List>
<MenuItem value={"dynamic"}>Dynamic Preview</MenuItem> {deviceConfigs.map((_device) => (
</Select> <ListItem
<List> key={_device.name}
{deviceConfigs.map((_device) => ( sx={{
<ListItem display: "flex",
key={_device.name} textAlign: "center",
sx={{ }}>
display: "flex", <div style={{ width: "120px", alignItems: "center" }}>
textAlign: "center", <img className="deviceIcon" src={_device.icon} alt={_device.name} />
}} </div>
> <div className="deviceContent">
<div style={{ width: "120px", alignItems: "center" }}> <p style={{ margin: "14px 0" }}>{_device.name}</p>
<img <div className="deviceButtons">
className="deviceIcon" <button className={`pane-btn ${device === _device.horizental ? "checked" : ""}`} onClick={() => deviceChange(_device.horizental)}>
src={_device.icon} {t("horizontal")}
alt={_device.name} </button>
/> <button className={`pane-btn ${device === _device.vertical ? "checked" : ""}`} onClick={() => deviceChange(_device.vertical)}>
</div> {t("vertical")}
<div className="deviceContent"> </button>
<p style={{ margin: "14px 0" }}>{_device.name}</p> {/* <img src={device === _device.horizental ? horizentalChecked : horizentalButton} alt="horizental" onClick={() => deviceChange(_device.horizental)} />
<div className="deviceButtons"> <img src={device === _device.vertical ? verticalChecked : verticalButton} alt="vertical" onClick={() => deviceChange(_device.vertical)} /> */}
<img </div>
src={ </div>
device === _device.horizental </ListItem>
? horizentalChecked ))}
: horizentalButton </List>
} <div
alt="horizental" style={{
onClick={() => deviceChange(_device.horizental)} marginTop: "50px",
/> color: "white",
<img }}>
src={ {props.loading ? (
device === _device.vertical <CircularProgress color="inherit" />
? verticalChecked ) : (
: verticalButton project && (
} <Select
alt="vertical" id="project-select"
onClick={() => deviceChange(_device.vertical)} value={project}
/> onChange={(e) => projectSelect(e)}
</div> placeholder="选择项目"
</div> sx={{
</ListItem> background: "rgb(115, 158, 211)",
))} width: "80%",
</List> color: "white",
<div }}>
style={{ {props.projects.map((pName, i) => (
marginTop: "50px", <MenuItem key={i} value={pName}>
color: "white", {pName}
}} </MenuItem>
> ))}
{props.loading ? ( </Select>
<CircularProgress color="inherit" /> )
) : ( )}
project && ( </div>
<Select {!props.hideLogo && <img src={logoPanePng} alt="logo" width={"100%"} className="bottomLogo" />}
id="project-select" </div>
value={project} );
onChange={(e) => projectSelect(e)}
placeholder="选择项目"
sx={{
background: "rgb(115, 158, 211)",
width: "80%",
color: "white",
}}
>
{props.projects.map((pName, i) => (
<MenuItem key={i} value={pName}>
{pName}
</MenuItem>
))}
</Select>
)
)}
</div>
{!props.hideLogo && (
<img
src={logoPanePng}
alt="logo"
width={"100%"}
className="bottomLogo"
/>
)}
</div>
);
} }
export default Pane; export default Pane;

View File

@ -1,96 +1,103 @@
import { Alert, Grid, List, ListItem, Snackbar } from '@mui/material'; import { Alert, Grid, List, ListItem, Snackbar } from "@mui/material";
import { useEffect, useState } from 'react' import { useEffect, useState } from "react";
import './index.css' import "./index.css";
import checkPng from '../../images/check.png'; import checkPng from "../../images/check.png";
import generateButtonPng from '../../images/generate-button.png'; import generateButtonPng from "../../images/generate-button.png";
import { useTranslation } from "react-i18next";
var rows = ['TopText', 'CentText', 'MiddText'] var rows = ["TopText", "CentText", "MiddText"];
var rowTitles = ['开头', '中间', '结尾'] var rowTitles = ["start", "middle", "end"];
function SettingFrame(props) { function SettingFrame(props) {
var [settingArr, setSettingArr] = useState([1, 2, 3]) var [settingArr, setSettingArr] = useState([1, 2, 3]);
var [openPopup, setOpenPopup] = useState(false); var [openPopup, setOpenPopup] = useState(false);
console.log(props.settingValue) const { t } = useTranslation();
useEffect(() => {
setSettingArr(props.settingValue)
}, [props.settingValue])
return ( useEffect(() => {
<div style={{ setSettingArr(props.settingValue);
flex: 1 }, [props.settingValue]);
}}>
<Snackbar open={openPopup} onClose={() => setOpenPopup(false)} autoHideDuration={3000} anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}>
<Alert severity="success" sx={{ width: '100%' }}>
保存成功
</Alert>
</Snackbar>
<List>
{
rows.map((rowKey, rowIndex) => (
<ListItem key={rowIndex}>
<span>{rowTitles[rowIndex]}</span>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 4, sm: 8, md: 12 }}>
{(props.setting && props.setting[rowKey]
? Array.from(props.setting[rowKey].split('|'))
: ['无', '无', '无'])
.map((text, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<div className='setting-card' style={{
position: 'relative',
height: '120px',
textAlign: 'center',
justifyContent: 'center',
display: 'flex',
alignItems: 'center',
cursor: 'pointer'
}}
onClick={() => {
var newSetting = settingArr.slice()
newSetting[rowIndex] = index + 1;
setSettingArr(newSetting);
}}>
{text}
<img src={checkPng} alt='check' style={{
position: 'absolute',
right: '10px',
top: '10px',
display: (index + 1) === settingArr[rowIndex] ? 'block' : 'none'
}}/>
</div>
</Grid>
))}
</Grid>
</ListItem>
))
}
</List>
<div style={{
alignItems: 'center',
cursor: 'pointer',
marginTop: '30px'
}}>
{
props.setting && <img
src={generateButtonPng}
alt='generate'
style={{
cursor: 'pointer'
}}
onClick={async () => {
await props.generate({
topType: settingArr[0]+'',
centreType: settingArr[1]+'',
middleType: settingArr[2]+''
})
setOpenPopup(true); return (
}}/> <div
} style={{
</div> flex: 1,
</div> }}>
) <Snackbar
open={openPopup}
onClose={() => setOpenPopup(false)}
autoHideDuration={3000}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}>
<Alert severity="success" sx={{ width: "100%" }}>
{t("saveSuccess")}
</Alert>
</Snackbar>
<List>
{rows.map((rowKey, rowIndex) => (
<ListItem key={rowIndex}>
<span>{t(rowTitles[rowIndex])}</span>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 4, sm: 8, md: 12 }}>
{(props.setting && props.setting[rowKey] ? Array.from(t(props.setting[rowKey]).split("|")) : [t("none"), t("none"), t("none")]).map((text, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<div
className="setting-card"
style={{
position: "relative",
height: "120px",
textAlign: "center",
justifyContent: "center",
display: "flex",
alignItems: "center",
cursor: "pointer",
}}
onClick={() => {
var newSetting = settingArr.slice();
newSetting[rowIndex] = index + 1;
setSettingArr(newSetting);
}}>
{text}
<img
src={checkPng}
alt="check"
style={{
position: "absolute",
right: "10px",
top: "10px",
display: index + 1 === settingArr[rowIndex] ? "block" : "none",
}}
/>
</div>
</Grid>
))}
</Grid>
</ListItem>
))}
</List>
<div
style={{
alignItems: "center",
cursor: "pointer",
marginTop: "30px",
}}>
{props.setting && (
<button
className="generate-btn"
onClick={async () => {
await props.generate({
topType: settingArr[0] + "",
centreType: settingArr[1] + "",
middleType: settingArr[2] + "",
});
setOpenPopup(true);
}}>
{t("generate")}
</button>
)}
</div>
</div>
);
} }
export default SettingFrame; export default SettingFrame;

View File

@ -1,23 +1,21 @@
var BaseUrl = "http://api.soyootech.com/"; var BaseUrl = "/api/";
export async function UserLogin(loginForm) { export async function UserLogin(loginForm) {
try { try {
var response = await fetch(BaseUrl + "SoyooUser/previewlogin", { var response = await fetch(BaseUrl + "SoyooUser/previewlogin", {
body: JSON.stringify({ body: JSON.stringify({
account: loginForm.account, account: loginForm.account,
password: loginForm.password, password: loginForm.password,
}), }),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
method: "POST", method: "POST",
}); });
var result = await response.json(); var result = await response.json();
result.permissionPages = result.permissionPages.map((permission) => result.permissionPages = result.permissionPages.map((permission) => permission.toLowerCase());
permission.toLowerCase() return result;
); } catch (error) {
return result; console.log(error);
} catch (error) { return null;
console.log(error); }
return null;
}
} }

32
src/i18n.js 100644
View File

@ -0,0 +1,32 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import en from "./locales/en.json";
import zh from "./locales/zh.json";
const resources = {
en: {
translation: en,
},
zh: {
translation: zh,
},
};
const lang = localStorage.getItem("lang") ?? "zh";
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: lang,
lng: lang,
debug: true,
resources: resources,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
});
export default i18n;

View File

@ -1,14 +1,19 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import './index.css'; import "./index.css";
import App from './App'; import App from "./App";
import reportWebVitals from './reportWebVitals'; import reportWebVitals from "./reportWebVitals";
import "./i18n";
import { I18nextProvider } from "react-i18next";
import i18n from "./i18n";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <I18nextProvider i18n={i18n}>
</React.StrictMode>, <App />
document.getElementById('root') </I18nextProvider>
</React.StrictMode>,
document.getElementById("root")
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View File

@ -0,0 +1,20 @@
{
"login": "Login",
"account": "Account",
"password": "Password",
"keepLoginStatus": "Keep the login status for 7 days",
"submit": "Submit",
"noAuth": "No permission",
"loginError": "Account or password is incorrect",
"horizontal": "Horizontal",
"vertical": "Vertical",
"scan": "Mobile preview",
"none": "None",
"start": "Start",
"middle": "Mid",
"end": "End",
"generate": "Generate",
"copySuccess": "link copy success",
"saveSuccess": "save success",
"language": "language"
}

View File

@ -0,0 +1,20 @@
{
"login": "登录",
"account": "账号",
"password": "密码",
"keepLoginStatus": "保持登录状态7天",
"submit": "提交",
"loginError": "账号或密码错误",
"noAuth": "无权限访问,请切换账号",
"horizontal": "横屏",
"vertical": "竖屏",
"scan": "扫一扫,手机预览",
"none": "无",
"start": "开头",
"middle": "中间",
"end": "结尾",
"generate": "一键生成",
"copySuccess": "链接复制成功",
"saveSuccess": "保存成功",
"language": "语言"
}

12
src/setupProxy.js 100644
View File

@ -0,0 +1,12 @@
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api", // 这是路径模式
createProxyMiddleware({
target: "https://api.soyootech.com/", // 目标服务器地址
changeOrigin: true,
pathRewrite: { "^/api": "" },
})
);
};