diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..9575e9d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index 98d443f..900e1fc 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"jsqr": "^1.3.1",
+ "jszip": "^3.7.1",
"medium-zoom": "^1.0.5",
"prop-types": "^15.7.2",
"react": "^16.13.1",
diff --git a/src/actions/index.js b/src/actions/index.js
index d092a62..f90064b 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -1,10 +1,19 @@
import {actionTypes} from "../constant/ActionTypes";
import {handleStyle} from "../utils/gaHelper";
-export const genQRInfo = text => ({
- type: actionTypes.GENERATE_QR_INFO,
- text
-})
+export const genQRInfo = text => {
+ if (Array.isArray(text)) {
+ return {
+ type: actionTypes.GENERATE_QR_INFO,
+ text: text[0],
+ textArray: text
+ }
+ }
+ return {
+ type: actionTypes.GENERATE_QR_INFO,
+ text
+ }
+}
export const changeStyle = (rendererIndex, value) => {
handleStyle(value);
diff --git a/src/components/Qrcode.css b/src/components/Qrcode.css
index a0adebc..f7879db 100644
--- a/src/components/Qrcode.css
+++ b/src/components/Qrcode.css
@@ -318,7 +318,7 @@ td {
.Qr-input-upload {
display: flex;
max-width: calc(100vw - 46px);
- width: 20em;
+ width: 22em;
}
.Qr-input-upload-div {
@@ -360,7 +360,7 @@ td {
height: calc(2em + 6px);
font-size: calc(10px + 2vmin);
border: var(--border-color) solid 2px;
- margin-left: 10px;
+ margin: 0 10px;
flex: 1;
}
@@ -372,6 +372,7 @@ td {
.Qr-upload {
color: var(--upload-color);
+ fill: var(--upload-color);
font-size: calc(10px + 2vmin);
border: var(--border-color) solid 2px;
width: calc(2em + 6px)!important;
@@ -409,6 +410,7 @@ td {
.Qr-upload:hover {
border-color: var(--border-color-focus);
color: var(--border-color-focus);
+ fill: var(--border-color-focus);
}
.Qr-upload:active {
@@ -417,6 +419,7 @@ td {
-moz-transition-duration: 0s;
border-color: #3BBC9F;
color: #3BBC9F;
+ fill: #3BBC9F;
}
.Qr-upload-svg {
diff --git a/src/components/app/PartDownload.js b/src/components/app/PartDownload.js
index f881e4e..1584182 100644
--- a/src/components/app/PartDownload.js
+++ b/src/components/app/PartDownload.js
@@ -4,6 +4,12 @@ import PropTypes from 'prop-types';
import {isWeiXin} from "../../utils/navigatorUtils";
import PresetModalViewer from "../../containers/preset/PresetModalViewer";
+const CountComponent = ({ value }) => {
+ if (isNaN(value)) return null;
+ if (value >= 10000) value = (value / 10000).toFixed(1) + "万";
+ return {value}
+}
+
const WxMessage = () => {
if (isWeiXin()) {
return (
@@ -13,7 +19,7 @@ const WxMessage = () => {
)
}
- return null
+ return null;
}
const ImgBox = ({ imgData }) => {
@@ -29,7 +35,7 @@ const ImgBox = ({ imgData }) => {
return null
}
-const PartDownload = ({ value, downloadCount, onSvgDownload, onImgDownload, savePreset }) => {
+const PartDownload = ({ value, batchMode, downloadCount, onSvgDownload, onImgDownload, savePreset }) => {
const [imgData, setImgData] = useState('');
const [visible, setVisible] = useState(false);
@@ -38,8 +44,10 @@ const PartDownload = ({ value, downloadCount, onSvgDownload, onImgDownload, save
Downloads
- 下载二维码 — {value}
- {downloadCount}
+ 下载二维码
+ ({batchMode ? "批量模式" : "单文件模式"})
+ — {value}
+
diff --git a/src/components/app/PartMore.js b/src/components/app/PartMore.js
index e25f8b1..aae1891 100644
--- a/src/components/app/PartMore.js
+++ b/src/components/app/PartMore.js
@@ -51,6 +51,7 @@ const PartMore = () => {
最新消息
+
2021.10.5
新增批量生成二维码。
2020.9.1
新增 C3 样式、图标插入、 PNG 下载。
2020.6.29
新的反馈渠道!我们开始征集好玩的二维码设计啦,可以是推送尾图、海报等等,快来上传吧。 (
key={"input_" + rendererIndex + "_" + paramIndex}
id="image_upload"
hidden={true}
- accept=".jpg, .jpeg, .png"
+ accept="image/*"
onChange={onChange}
/>
diff --git a/src/components/svg/FileImport.js b/src/components/svg/FileImport.js
new file mode 100644
index 0000000..6991079
--- /dev/null
+++ b/src/components/svg/FileImport.js
@@ -0,0 +1,15 @@
+import * as React from "react"
+
+function FileImport(props) {
+ return (
+
+ );
+}
+
+export default FileImport;
\ No newline at end of file
diff --git a/src/containers/app/InputText.js b/src/containers/app/InputText.js
index 2c98a53..1a19047 100644
--- a/src/containers/app/InputText.js
+++ b/src/containers/app/InputText.js
@@ -4,6 +4,7 @@ import React, {useRef} from "react";
import {isPicture} from "../../utils/imageUtils";
import {decodeData} from "../../utils/qrcodeHandler";
import { handleUpload, handleInputUrl } from "../../utils/gaHelper";
+import FileImport from "../../components/svg/FileImport";
const InputText = ({dispatch}) => {
const textRef = useRef();
@@ -41,7 +42,6 @@ const InputText = ({dispatch}) => {
decodeData(file).then((res) => {
if (res) {
textRef.current.value = res.data;
- console.log(res.data)
dispatch(genQRInfo(res.data))
}
}).catch(console.err);
@@ -65,9 +65,39 @@ const InputText = ({dispatch}) => {
}
}}
/>
+
+ e.target.value = null}
+ onChange={(e) => {
+ if (e.target.files.length > 0) {
+ const file = e.target.files[0];
+ if (file.type === "text/plain") {
+ const fileReader = new FileReader();
+ fileReader.onload = function(event) {
+ const lines = event.target.result
+ .split(/[\r\n]+/g)
+ .map(line => line.trim())
+ .filter(line => line.length > 0);
+ console.log(lines)
+ if (lines.length > 0) {
+ textRef.current.value = lines[0];
+ dispatch(genQRInfo(lines))
+ }
+ }
+ fileReader.readAsText(file, "UTF-8");
+ }
+ }
+ }}
+ />
- 上传普通二维码或输入网址
+ 上传普通二维码、输入网址或上传 txt 文本批量生成
);
diff --git a/src/containers/app/PartDownloadViewer.js b/src/containers/app/PartDownloadViewer.js
index 260a4f2..0bf9161 100644
--- a/src/containers/app/PartDownloadViewer.js
+++ b/src/containers/app/PartDownloadViewer.js
@@ -1,11 +1,15 @@
import {connect} from 'react-redux';
import PartDownload from "../../components/app/PartDownload";
-import {saveImg, saveSvg} from "../../utils/downloader";
+import {saveImg, saveSvg, startTask} from "../../utils/downloader";
import {getDownloadCount, increaseDownloadData, recordDownloadDetail} from "../../api/TcbHandler";
-import {getParamDetailedValue, outerHtml} from "../../utils/util";
+import {fillEmptyWith, getParamDetailedValue, outerHtml} from "../../utils/util";
import {handleDownloadImg, handleDownloadSvg} from "../../utils/gaHelper";
import {appendPreset} from "../../utils/storageUtils";
import {svgToBase64} from "../../utils/imageUtils";
+import React from "react";
+import ReactDOMServer from "react-dom/server";
+import JSZip from "jszip";
+import {encodeData} from "../../utils/qrcodeHandler";
function saveDB(state, type, updateDownloadData) {
return new Promise(resolve => {
@@ -42,20 +46,66 @@ function saveDB(state, type, updateDownloadData) {
const mapStateToProps = (state, ownProps) => ({
value: state.value,
downloadCount: state.downloadData[state.value],
- onSvgDownload: () => {
- saveSvg(state.value, outerHtml(state.selectedIndex));
- saveDB(state, 'svg', ownProps.updateDownloadData).catch(console.error);
- handleDownloadSvg(state.value);
- },
- onImgDownload: (type) => {
- return new Promise(resolve => {
- saveImg(state.value, outerHtml(state.selectedIndex), 1500, 1500, type).then((res) => {
- saveDB(state, type, ownProps.updateDownloadData).then(() => {
- handleDownloadImg(state.value, type);
- resolve(res)
+ batchMode: state.textUrlArray.length > 0,
+ onSvgDownload: async () => {
+ let data;
+ if (state.textUrlArray.length === 0) {
+ data = saveSvg(state.value, outerHtml(state.selectedIndex));
+ const filename = "QRcode_" + state.value + ".svg";
+ startTask(URL.createObjectURL(data), filename);
+ } else {
+ const zip = new JSZip();
+ for (let i = 0; i < state.textUrlArray.length; i++) {
+ const qrcode = encodeData({ text: state.textUrlArray[i], correctLevel: state.correctLevel });
+ const el = React.createElement(state.rendererType, {
+ qrcode: qrcode,
+ params: fillEmptyWith(state.paramValue[state.selectedIndex].slice(), 0),
+ title: state.title,
+ icon: state.icon,
+ setParamInfo: () => {}
});
- });
- });
+ data = saveSvg(state.value, ReactDOMServer.renderToString(el));
+ console.log(data)
+ const filename = "QRcode_" + state.value + "_" + i + ".svg";
+ zip.file(filename, data);
+ }
+ const href = URL.createObjectURL(await zip.generateAsync({ type: "blob" }));
+ startTask(href, "QRcode_" + state.value + ".zip");
+ }
+
+ await saveDB(state, 'svg', ownProps.updateDownloadData);
+ handleDownloadSvg(state.value);
+ return data;
+ },
+ onImgDownload: async (type) => {
+ let data;
+ if (state.textUrlArray.length === 0) {
+ data = await saveImg(state.value, outerHtml(state.selectedIndex), 1500, 1500, type);
+ const filename = "QRcode_" + state.value + "." + type;
+ startTask(data, filename);
+ } else {
+ const zip = new JSZip();
+ for (let i = 0; i < state.textUrlArray.length; i++) {
+ const qrcode = encodeData({ text: state.textUrlArray[i], correctLevel: state.correctLevel });
+ const el = React.createElement(state.rendererType, {
+ qrcode: qrcode,
+ params: fillEmptyWith(state.paramValue[state.selectedIndex].slice(), 0),
+ title: state.title,
+ icon: state.icon,
+ setParamInfo: () => {}
+ });
+ data = await saveImg(state.value, ReactDOMServer.renderToString(el), 1500, 1500, type);
+ const startIdx = data.indexOf("base64,") + "base64,".length;
+ const filename = "QRcode_" + state.value + "_" + i + "." + type;
+ zip.file(filename, data.substring(startIdx), {base64: true});
+ }
+ const href = URL.createObjectURL(await zip.generateAsync({ type: "blob" }));
+ startTask(href, "QRcode_" + state.value + ".zip");
+ }
+
+ await saveDB(state, type, ownProps.updateDownloadData)
+ handleDownloadImg(state.value, type);
+ return data;
},
savePreset: () => {
let preset = {
diff --git a/src/reducers/index.js b/src/reducers/index.js
index cb7c468..fe990a0 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -8,6 +8,7 @@ const initialState = {
value: 'A1',
correctLevel: 0,
textUrl: QRBTF_URL,
+ textUrlArray: [],
history: [],
downloadData: [],
qrcode: encodeData({text: QRBTF_URL, correctLevel: 0}),
@@ -24,7 +25,8 @@ export default function appReducer(state = initialState, action) {
if (!text || text.length === 0) text = QRBTF_URL;
return Object.assign({}, state, {
textUrl: text,
- qrcode: encodeData({text: text, correctLevel: state.correctLevel})
+ qrcode: encodeData({text: text, correctLevel: state.correctLevel}),
+ textUrlArray: action.textArray || []
});
}
case actionTypes.CHANGE_STYLE: {
diff --git a/src/utils/downloader.js b/src/utils/downloader.js
index cbbd01c..e690082 100644
--- a/src/utils/downloader.js
+++ b/src/utils/downloader.js
@@ -3,23 +3,25 @@ const svgHead = "\n " +
const MIME = { "jpg": "image/jpeg", "png": "image/png" };
+export function startTask(data, filename) {
+ let a = document.createElement('a');
+ a.setAttribute('href', data)
+ a.setAttribute('target', 'download')
+ a.setAttribute('download', filename);
+ a.setAttribute('hidden', true);
+ a.click();
+}
+
export function saveSvg(value, content) {
let htmlContent = [svgHead + content]
let bl = new Blob(htmlContent, {type: "image/svg+xml"})
- let a = document.createElement("a")
- let filename = "QRcode_" + value + ".svg"
-
- a.href = URL.createObjectURL(bl)
- a.download = filename
- a.hidden = true
- a.click()
+ return bl;
}
export function saveImg(value, content, width, height, type) {
if (!MIME[type]) throw "Error image type";
// Finish creating downloadable data
- let filename = "QRcode_" + value + "." + type;
const wrap = document.createElement('div');
wrap.innerHTML = content;
@@ -51,12 +53,8 @@ export function saveImg(value, content, width, height, type) {
// Will result in a download popup for chrome and the
// image opening in a new tab for others.
- let a = document.createElement('a');
let data = canvas.toDataURL(MIME[type], 0.8);
- a.setAttribute('href', data)
- a.setAttribute('target', 'download')
- a.setAttribute('download', filename);
- a.click();
+ // startTask(data, filename);
resolve(data)
};
diff --git a/src/utils/imageUtils.js b/src/utils/imageUtils.js
index 665d777..7377327 100644
--- a/src/utils/imageUtils.js
+++ b/src/utils/imageUtils.js
@@ -39,7 +39,6 @@ export function toBase64(file, aspectRatio) {
height = img.height;
width = height * aspectRatio;
}
- console.log(width + ' ' + height)
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
diff --git a/yarn.lock b/yarn.lock
index dc61e95..8094262 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5279,6 +5279,11 @@ ignore@^4.0.6:
resolved "https://registry.npm.taobao.org/ignore/download/ignore-4.0.6.tgz?cache=0&sync_timestamp=1565775199290&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fignore%2Fdownload%2Fignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=
+immediate@~3.0.5:
+ version "3.0.6"
+ resolved "https://registry.nlark.com/immediate/download/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+ integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
+
immer@1.10.0:
version "1.10.0"
resolved "https://registry.npm.taobao.org/immer/download/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
@@ -6397,6 +6402,16 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3:
array-includes "^3.0.3"
object.assign "^4.1.0"
+jszip@^3.7.1:
+ version "3.7.1"
+ resolved "https://registry.nlark.com/jszip/download/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9"
+ integrity sha1-vWNAEiHBViWhIoxVbKimjab9o9k=
+ dependencies:
+ lie "~3.3.0"
+ pako "~1.0.2"
+ readable-stream "~2.3.6"
+ set-immediate-shim "~1.0.1"
+
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -6488,6 +6503,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+lie@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.nlark.com/lie/download/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+ integrity sha1-3Pgt7lRfRgdNryAMfBxaCOD0D2o=
+ dependencies:
+ immediate "~3.0.5"
+
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.npm.taobao.org/lines-and-columns/download/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@@ -7483,7 +7505,7 @@ p-try@^2.0.0:
resolved "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=
-pako@~1.0.5:
+pako@~1.0.2, pako@~1.0.5:
version "1.0.11"
resolved "https://registry.npm.taobao.org/pako/download/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=
@@ -9503,6 +9525,11 @@ set-blocking@^2.0.0:
resolved "https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+set-immediate-shim@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.nlark.com/set-immediate-shim/download/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+ integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
+
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.npm.taobao.org/set-value/download/set-value-2.0.1.tgz?cache=0&sync_timestamp=1585775409029&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fset-value%2Fdownload%2Fset-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"