Compare commits

...

13 Commits

Author SHA1 Message Date
887019ed5e
feat: client log 2023-06-12 16:51:37 +08:00
aba40a5445
feat: shop log
fix: update client address when paying
2023-06-12 16:25:56 +08:00
78c786ff7e
fix: change QRcode scanner's size 2023-06-12 15:41:50 +08:00
abe480a526
feat: the shop can generate QRcode to receive ETH 2023-06-12 15:41:07 +08:00
3047758550
feat: the shop can create a order into DB 2023-06-11 22:50:45 +08:00
26bc81aa56
feat: shop's nav block 2023-06-11 17:18:11 +08:00
ebe58b6d08
feat: complete payment flow 2023-06-11 15:22:32 +08:00
367d837e2e
fix: ignore trace DB 2023-06-11 02:17:20 +08:00
c075e080b5
fix: set index so that the event can be filtered 2023-06-11 02:16:40 +08:00
69e634ded0
feat: save order in database (backent) 2023-06-11 02:16:01 +08:00
4dd0a9c1e8
fix: the method to get account address 2023-06-10 22:20:14 +08:00
e3f7145f91
feat: credit page 2023-06-10 21:33:17 +08:00
10fdb644fa
feat: client info page 2023-06-10 21:19:58 +08:00
24 changed files with 2201 additions and 285 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ coverage
*.sln
*.sw?
.env
backend/main.db

148
backend/main.py Normal file
View File

@ -0,0 +1,148 @@
from flask import Flask, request, jsonify
import sqlite3
import os
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
DATABASE = './main.db'
def initDB():
if os.path.isfile(DATABASE):
db = sqlite3.connect(DATABASE)
else:
with open('schema.sql', 'r') as file:
sql_statements = file.read()
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
cursor.executescript(sql_statements)
db.commit()
db.close()
@app.route('/')
def hello():
return 'Hello, Flask!'
@app.route('/order', methods=['POST'])
def process_data():
data = request.get_json()
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
cursor.execute("SELECT id from `shops` WHERE address=?", (data['from'], ))
shop_id = cursor.fetchone()[0]
filter = []
params = []
for product in data['products']:
product_id = product['product_id']
filter.append('?')
params.append(product_id)
filter_str = ', '.join(filter)
query = "SELECT id, price FROM products WHERE id IN ({})".format(filter_str)
cursor.execute(query, params)
prices = cursor.fetchall()
amount = 0
for index, product in enumerate(data['products']):
count = int(product['count'])
amount += count * int(prices[index][1])
cursor.execute('INSERT INTO "orders"("id","shop_id","client_addr","amount") VALUES (NULL,?,NULL,?);', (shop_id, amount))
order_id = cursor.lastrowid
for index, product in enumerate(data['products']):
product_id = product['product_id']
count = int(product['count'])
cursor.execute('INSERT INTO "order_products"("id","order_id","product_id","count") VALUES (NULL,?,?,?);', (order_id, product_id, count))
db.commit()
db.close()
return str(order_id)
@app.route('/order/<id>', methods=['GET'])
def get_order(id):
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
ans = {}
# products
cursor.execute("SELECT `name`, `price`, `count` FROM `order_products`, `products` \
WHERE `order_products`.`order_id`=? and `order_products`.`product_id`=`products`.`id`", (id, ))
products = cursor.fetchall()
ans['products'] = []
for product in products:
ans['products'].append({
'name': product[0],
'price': str(product[1]),
'count': product[2]
})
# shop name
cursor.execute("SELECT `address`, `name`, `amount`, `client_addr` FROM `shops`, `orders` WHERE `orders`.`shop_id`=`shops`.`id` and `orders`.`id`= ?", (id, ))
result = cursor.fetchone()
ans['shop'] = {
'name': result[1],
'address': result[0]
}
ans['amount'] = str(result[2])
ans['client'] = result[3]
return jsonify(ans)
@app.route('/order/<id>', methods=['PATCH'])
def update_client(id):
client = request.get_json()['client']
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
ans = {}
# products
cursor.execute("UPDATE `orders` SET `client_addr`=? WHERE `id`=?", (client, id ))
db.commit()
db.close()
return jsonify({'status': 'OK'})
@app.route('/shop/check', methods=['POST'])
def shop_check():
address = request.get_json()['address']
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
cursor.execute("SELECT COUNT(*) FROM `shops` WHERE `address`=?", (address, ))
result = cursor.fetchone()[0]
print(result, type(result))
if result != 0:
return jsonify({'status': True})
else:
return jsonify({'status': False})
@app.route('/shop/<address>/products', methods=['GET'])
def shop_products(address):
db = sqlite3.connect(DATABASE)
cursor = db.cursor()
cursor.execute("SELECT `products`.`id`, `products`.`name`, `products`.`price`, `products`.`code` FROM `shops`, `products` \
WHERE `shops`.id = `products`.`shop_id`and `shops`.`address`=?", (address, ))
result = cursor.fetchall()
ans = {'products': {}}
for product in result:
ans['products'][str(product[3])] = {
'id': product[0],
'name': product[1],
'price': str(product[2]),
}
return jsonify(ans)
if __name__ == '__main__':
initDB()
app.run(host="0.0.0.0")

52
backend/schema.sql Normal file
View File

@ -0,0 +1,52 @@
CREATE TABLE "shops" (
"id" INTEGER,
"address" TEXT,
"name" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
);
---------------------------------------------------
CREATE TABLE "products" (
"id" INTEGER,
"shop_id" INTEGER,
"name" TEXT,
"code" TEXT,
"price" INTEGER,
FOREIGN KEY("shop_id") REFERENCES "shops"("id"),
PRIMARY KEY("id" AUTOINCREMENT)
);
---------------------------------------------------
CREATE TABLE "orders" (
"id" INTEGER,
"shop_id" INTEGER,
"client_addr" TEXT,
"amount" TEXT,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("shop_id") REFERENCES "shops"("id")
);
---------------------------------------------------
CREATE TABLE "order_products" (
"id" INTEGER,
"order_id" INTEGER,
"product_id" INTEGER,
"count" INTEGER,
FOREIGN KEY("product_id") REFERENCES "products"("id"),
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("order_id") REFERENCES "orders"("id")
);
---------------------------------------------------
INSERT INTO "main"."shops"("id","address","name") VALUES (NULL,"0x73D081e82b35D6E6f9f5e72EBBB6b637d4f46992","全家便利商店");
INSERT INTO "main"."shops"("id","address","name") VALUES (NULL,"0xDa68136fcB885a2ec44db6dcE946F656aF457A76","暨大圖文部");
INSERT INTO "main"."products"("id","shop_id","name","code", "price") VALUES (NULL,1,"濃辛咖哩飯","7233957360139", "10000000000000000");
INSERT INTO "main"."products"("id","shop_id","name","code", "price") VALUES (NULL,1,"霜淇淋","4718022345288", "5000000000000000");
INSERT INTO "main"."products"("id","shop_id","name","code", "price") VALUES (NULL,2,"證件套","748009345271", "25000000000000000");
INSERT INTO "main"."products"("id","shop_id","name","code", "price") VALUES (NULL,2,"便條紙","4979274503226", "4000000000000000");

446
package-lock.json generated
View File

@ -11,6 +11,8 @@
"@metamask/detect-provider": "^2.0.0",
"html5-qrcode": "^2.3.8",
"pinia": "^2.0.36",
"qrcode": "^1.5.3",
"qrcode-vue": "^1.2.0",
"vue": "^3.3.2",
"vue-cookies": "^1.8.3",
"vue-router": "^4.2.0",
@ -1195,7 +1197,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -1204,7 +1205,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@ -1626,6 +1626,14 @@
"node": ">=6"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -1693,6 +1701,16 @@
"resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz",
"integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw=="
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@ -1708,7 +1726,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -1719,8 +1736,7 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
@ -1910,6 +1926,14 @@
}
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
@ -1998,6 +2022,11 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -2043,6 +2072,16 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encode-utf8": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -3054,6 +3093,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@ -3632,6 +3679,14 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-function": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
@ -4461,6 +4516,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -4490,7 +4553,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -4599,6 +4661,14 @@
}
}
},
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
@ -4690,6 +4760,36 @@
"node": ">=6"
}
},
"node_modules/qr.js": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
},
"node_modules/qrcode": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
"integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
"dependencies": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode-vue": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/qrcode-vue/-/qrcode-vue-1.2.0.tgz",
"integrity": "sha512-djGZRrXCVA/mxHJvhYXdYN2eeKrVtu+Yi0pYRuE02dbj5R3Xt4uyXa5iM3dgiEQ85r5p7m6nYFRAJaayMMwbqg==",
"dependencies": {
"qr.js": "0.0.0"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@ -4847,6 +4947,19 @@
"node": ">=0.6"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@ -5128,6 +5241,11 @@
"node": ">=6"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@ -5280,6 +5398,19 @@
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
@ -5329,7 +5460,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@ -6308,6 +6438,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"node_modules/which-typed-array": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
@ -6336,6 +6471,19 @@
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -6406,6 +6554,11 @@
"node": ">=0.4"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
@ -6420,6 +6573,87 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yargs/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@ -7148,14 +7382,12 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
@ -7479,6 +7711,11 @@
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -7536,6 +7773,16 @@
"resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz",
"integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@ -7548,7 +7795,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
@ -7556,8 +7802,7 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"combined-stream": {
"version": "1.0.8",
@ -7706,6 +7951,11 @@
"ms": "2.1.2"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
},
"decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
@ -7762,6 +8012,11 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
},
"dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -7804,6 +8059,16 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"encode-utf8": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -8622,6 +8887,11 @@
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@ -9030,6 +9300,11 @@
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-function": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
@ -9641,6 +9916,11 @@
"p-limit": "^3.0.2"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -9663,8 +9943,7 @@
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"path-is-absolute": {
"version": "1.0.1",
@ -9728,6 +10007,11 @@
}
}
},
"pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
@ -9787,6 +10071,30 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
},
"qr.js": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
},
"qrcode": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
"integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
"requires": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
}
},
"qrcode-vue": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/qrcode-vue/-/qrcode-vue-1.2.0.tgz",
"integrity": "sha512-djGZRrXCVA/mxHJvhYXdYN2eeKrVtu+Yi0pYRuE02dbj5R3Xt4uyXa5iM3dgiEQ85r5p7m6nYFRAJaayMMwbqg==",
"requires": {
"qr.js": "0.0.0"
}
},
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@ -9895,6 +10203,16 @@
}
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@ -10099,6 +10417,11 @@
"xhr": "^2.3.3"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@ -10207,6 +10530,16 @@
"safe-buffer": "~5.2.0"
}
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
@ -10244,7 +10577,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
@ -11004,6 +11336,11 @@
"is-symbol": "^1.0.3"
}
},
"which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"which-typed-array": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
@ -11023,6 +11360,16 @@
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -11089,6 +11436,11 @@
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
@ -11100,6 +11452,68 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"dependencies": {
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
}
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -11,6 +11,8 @@
"@metamask/detect-provider": "^2.0.0",
"html5-qrcode": "^2.3.8",
"pinia": "^2.0.36",
"qrcode": "^1.5.3",
"qrcode-vue": "^1.2.0",
"vue": "^3.3.2",
"vue-cookies": "^1.8.3",
"vue-router": "^4.2.0",

View File

@ -1,239 +1,257 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "SBT_addr",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "recv",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "sbt",
"outputs": [
{
"internalType": "contract SoulboundToken",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"stateMutability": "payable",
"type": "receive",
"payable": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "setCredit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "number",
"type": "uint256"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "shop",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "pay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "repay",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function",
"payable": true
},
{
"inputs": [],
"name": "start_recv",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "stop_recv",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getCredit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getArrear",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getClientOrders",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "isFinished",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "shop",
"type": "address"
}
],
"internalType": "struct Order[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getSBTNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
}
]
{
"inputs": [
{
"internalType": "address",
"name": "SBT_addr",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "recv",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "sbt",
"outputs": [
{
"internalType": "contract SoulboundToken",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"stateMutability": "payable",
"type": "receive",
"payable": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "setCredit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "number",
"type": "uint256"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "shop",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "pay",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "repay",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function",
"payable": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "warning",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "start_recv",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "stop_recv",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getCredit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getArrear",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getClientOrders",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "isFinished",
"type": "bool"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "shop",
"type": "address"
}
],
"internalType": "struct Order[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "client",
"type": "address"
}
],
"name": "getSBTNumber",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
}
]

View File

@ -58,19 +58,19 @@
"anonymous": false,
"inputs": [
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "shop",
"type": "address"
},
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "bank",
"type": "address"
@ -114,13 +114,13 @@
"anonymous": false,
"inputs": [
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "bank",
"type": "address"
@ -170,13 +170,13 @@
"anonymous": false,
"inputs": [
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "client",
"type": "address"
},
{
"indexed": false,
"indexed": true,
"internalType": "address",
"name": "bank",
"type": "address"

View File

@ -30,6 +30,17 @@ hr {
background: white;
}
.svg-container {
width: 100%; /* 設定容器寬度,根據需要調整 */
height: 50%;
padding-bottom: 50%; /* 設定高度,根據需要調整,這裡使用 75% 做為示例 */
position: relative;
}
p.block {
color: lightslategray;
}
/*
.message-header {
background: white;

View File

@ -2,18 +2,19 @@
export default {
name: 'ClientNav',
props: ['path'],
mounted () {
console.log(this.path)
this.navCSS[this.path] += ' is-active'
console.log(this.navCSS)
mounted() {
if (this.path in this.navCSS) {
this.navCSS[this.path] += ' is-active'
}
},
data () {
data() {
return {
navCSS: {
navCSS: {
main: 'panel-block',
pay: 'panel-block',
credit: 'panel-block',
info: 'panel-block',
log: 'panel-block'
}
}
}
@ -59,15 +60,26 @@ export default {
<p>掃描支付</p>
</template>
</RouterLink>
<RouterLink to="/client/log" :class="this.navCSS['log']">
<span class="panel-icon">
<i class="fas fa-book" aria-hidden="true"></i>
</span>
<template v-if="this.path == 'log'">
<strong>借款紀錄</strong>
</template>
<template v-else>
<p>借款紀錄</p>
</template>
</RouterLink>
<RouterLink to="/client/credit" :class="this.navCSS['credit']">
<span class="panel-icon">
<i class="fas fa-book" aria-hidden="true"></i>
</span>
<template v-if="this.path == 'credit'">
<strong>信用紀錄</strong>
<strong>SBT信用紀錄</strong>
</template>
<template v-else>
<p>信用紀錄</p>
<p>SBT信用紀錄</p>
</template>
</RouterLink>
</nav>

View File

@ -44,6 +44,15 @@ export default {
this.web3.eth.defaultAccount = clientAddr
var returnNumber = await token.methods.getSBTNumber(clientAddr).call({from: clientAddr})
if (returnNumber != 0){
var result = await fetch(import.meta.env.VITE_BACKEND_PREFIX+"/shop/check", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'address': clientAddr})
})
var result = await result.json()
this.$cookies.set('isShop', result.status)
this.$cookies.set('address', clientAddr)
this.$cookies.set('linked', true)
this.$cookies.set('SBTNumber', returnNumber)
@ -71,6 +80,7 @@ export default {
this.$cookies.remove('linked')
this.$cookies.remove('address')
this.$cookies.remove('SBTNumber')
this.$cookies.remove('shop')
this.$router.push('/')
}
}

View File

@ -0,0 +1,62 @@
<script>
export default {
name: 'ShopNav',
props: ['path'],
mounted() {
if (this.path in this.navCSS) {
this.navCSS[this.path] += ' is-active'
}
},
data() {
return {
navCSS: {
shoppay: 'panel-block',
shoplog: 'panel-block',
shopproducts: 'panel-block',
}
}
}
}
</script>
<template>
<nav class="panel">
<p class="panel-heading">
店家選項
</p>
<RouterLink to="/shop/pay" :class="this.navCSS['shoppay']">
<span class="panel-icon">
<i class="fas fa-book" aria-hidden="true"></i>
</span>
<template v-if="this.path == 'shoppay'">
<strong>店家結帳</strong>
</template>
<template v-else>
<p>店家結帳</p>
</template>
</RouterLink>
<RouterLink to="/shop/log" :class="this.navCSS['shoplog']">
<span class="panel-icon">
<i class="fas fa-book" aria-hidden="true"></i>
</span>
<template v-if="this.path == 'shoplog'">
<strong>店家收款紀錄</strong>
</template>
<template v-else>
<p>店家收款紀錄</p>
</template>
</RouterLink>
<RouterLink to="/shop/products" :class="this.navCSS['shopproducts']">
<span class="panel-icon">
<i class="fas fa-book" aria-hidden="true"></i>
</span>
<template v-if="this.path == 'shopproducts'">
<strong>店家商品管理</strong>
</template>
<template v-else>
<p>店家商品管理</p>
</template>
</RouterLink>
</nav>
</template>

View File

@ -7,6 +7,12 @@ import ClientMainView from '../views/ClientMainView.vue'
import ClientInfoView from '../views/ClientInfoView.vue'
import ClientCreditView from '../views/ClientCreditView.vue'
import ClientPayView from '../views/ClientPayView.vue'
import PaymentView from '../views/PaymentView.vue'
import ShopPayView from '../views/ShopPayView.vue'
import ShopPayQRcodeView from '../views/ShopPayQRcodeView.vue'
import ShopLogView from '../views/ShopLogView.vue'
import OrderView from '../views/OrderView.vue'
import ClientLogView from '../views/ClientLogView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -51,6 +57,36 @@ const router = createRouter({
name: 'clientpay',
component: ClientPayView
},
{
path: '/client/pay/:id',
name: 'clientpayment',
component: PaymentView
},
{
path: '/shop/pay',
name: 'shoppay',
component: ShopPayView
},
{
path: '/shop/pay/:id',
name: 'shopayqrcode',
component: ShopPayQRcodeView
},
{
path: '/shop/log',
name: 'shopaylog',
component: ShopLogView
},
{
path: '/order/:id',
name: 'order',
component: OrderView
},
{
path: '/client/log',
name: 'log',
component: ClientLogView
},
]
})

View File

@ -0,0 +1,183 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import PageTitle from '../components/PageTitle.vue'
import detectEthereumProvider from '@metamask/detect-provider'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import { useClientStore } from '../stores/Client.js'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
// To use Html5QrcodeScanner (more info below)
import { Html5QrcodeScanner } from "html5-qrcode";
// To use Html5Qrcode (more info below)
import { Html5Qrcode } from "html5-qrcode";
export default {
components: { PageTitle, WarningModal, SuccessModal, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
number: 0,
warningModalStatus: false,
successModalStatus: false,
msg: '',
clientAddr: '',
web3: null,
token: null,
isWaiting: false,
log: [],
warningLog: [],
scanner: null
}
},
async mounted() {
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
var borrow = await this.token.getPastEvents("Borrow", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr } })
for (let i of borrow) {
let result = i.returnValues
let obj = {
bank: result['bank'],
shop: result['shop'],
id: result['id'],
amount: this.web3.utils.fromWei(result['amount'], 'ether'),
repay: false
}
this.log.push(obj)
}
var repay = await this.token.getPastEvents("Repay", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr } })
for (let i of repay) {
let result = i.returnValues
for (let j of this.log) {
if ((result['bank'] == j.bank) && (result['id'] == j.id)) {
j.repay = true
}
}
}
console.log(this.log)
var warning = await this.token.getPastEvents("Warning", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr } })
for (let i of warning) {
let result = i.returnValues
this.warningLog.push(result.bank)
}
console.log(this.warningLog)
},
methods: {
onScanSuccess(decodedText, decodedResult) {
// handle the scanned code as you like, for example:
console.log(`Code matched = ${decodedText}`, decodedResult);
this.scanner.clear()
},
scan() {
this.scanner = new Html5QrcodeScanner(
"reader",
{ fps: 10, qrbox: { width: 250, height: 250 } },
/* verbose= */ false);
this.scanner.render(this.onScanSuccess);
}
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="credit"></ClientNav>
<template v-if="this.$cookies.get('isShop') == 'true'">
<ShopNav path="credit"></ShopNav>
</template>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="SBT 信用紀錄" subtitle="根據 SBT 信用紀錄設定額度"></PageTitle>
</div>
<div class="block">
<div class="box">
<div class="content">
<h5 class="title is-5">說明</h5>
<ul>
<li>我們會根據您提供的 SBT 查詢相關信用紀錄我們會根據紀錄設定給予的每月額度</li>
<li>若擁有新的 SBT代表您不曾擁有過信用紀錄下表為空</li>
<li>若您不曾擁有過信用交易紀錄我們提供最低額度 1 ETH/1 month</li>
<li>請確認下表紀錄我們將依照該紀錄表計算</li>
</ul>
</div>
</div>
</div>
<br>
<div class="block">
<h1 class="title is-4">支付紀錄</h1>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>#</th>
<th>銀行</th>
<th>銀行帳款編號</th>
<th>商店</th>
<th>金額</th>
<th>已結清帳款</th>
</tr>
</thead>
<tbody>
<template v-for="(value, index) of log">
<tr>
<th>{{ index }}</th>
<td>{{ value.bank }}</td>
<td>#{{ value.id }}</td>
<td>{{ value.shop }}</td>
<td>{{ value.amount }} ETH</td>
<td v-if="value.repay">
<span class="icon has-text-success">
<i class="fas fa-check-square"></i>
</span>
</td>
<td v-else>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="block">
<h1 class="title is-4">警告紀錄</h1>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>#</th>
<th>發起預警之銀行</th>
</tr>
</thead>
<tbody>
<template v-for="(value, index) of warningLog">
<tr>
<th>{{ index }}</th>
<td>{{ value }}</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false"
link="/signup/linksbt" btnName="繼續"></SuccessModal>
</template>

View File

@ -0,0 +1,135 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import Bank from '@/assets/Bank.json'
import PageTitle from '../components/PageTitle.vue'
import detectEthereumProvider from '@metamask/detect-provider'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import { useClientStore } from '../stores/Client.js'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
// To use Html5QrcodeScanner (more info below)
import { Html5QrcodeScanner } from "html5-qrcode";
// To use Html5Qrcode (more info below)
import { Html5Qrcode } from "html5-qrcode";
export default {
components: { PageTitle, WarningModal, SuccessModal, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
bank: null,
picName: '',
pic: '',
credit: '',
arrear: '',
borrowNum: 0,
repayNum: 0,
warningNum: 0
}
},
async mounted() {
if (!this.$cookies.isKey('linked')) {
this.$router.push('/')
}
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
this.bank = new this.web3.eth.Contract(Bank, this.BankAddress)
// get svg image
var base64Data = await this.token.methods.tokenURI(this.$cookies.get('SBTNumber')).call()
const commaIndex = base64Data.indexOf(',');
if (commaIndex !== -1) {
base64Data = base64Data.substr(commaIndex + 1);
}
const decodedData = JSON.parse(atob(base64Data));
this.picName = decodedData.name
this.pic = decodedData.image_data
this.pic = this.pic.replace('<svg', '<svg class="svg-container"')
// get credit
this.credit = await this.bank.methods.getCredit(this.clientAddr).call()
this.credit = await this.web3.utils.fromWei(this.credit, 'ether')
// get arrear
this.arrear = await this.bank.methods.getArrear(this.clientAddr).call()
this.arrear = await this.web3.utils.fromWei(this.arrear, 'ether')
var borrow = await this.token.getPastEvents("Borrow", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr, bank: this.BankAddress } })
var repay = await this.token.getPastEvents("Repay", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr, bank: this.BankAddress } })
var warning = await this.token.getPastEvents("Warning", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr, bank: this.BankAddress } })
this.borrowNum = borrow.length
this.repayNum = repay.length
this.warningNum = warning.length
},
methods: {
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="info"></ClientNav>
<template v-if="this.$cookies.get('isShop') == 'true'">
<ShopNav path="info"></ShopNav>
</template>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="個人資料" subtitle="紀錄在區塊鏈上的相關資料"></PageTitle>
</div>
<div class="block">
<table class="table is-fullwidth">
<tbody>
<tr>
<th>地址</th>
<td>{{ this.clientAddr }}</td>
<!-- <td>{{ this.clientAddr.slice(0, 8)+"..."+this.clientAddr.slice(33, 42) }}</td> -->
</tr>
<tr>
<th>目前信用額度</th>
<td>{{ this.credit }} ETH (尚可使用 {{ this.credit - this.arrear }} ETH)</td>
<!-- <td>{{ this.clientAddr.slice(0, 8)+"..."+this.clientAddr.slice(33, 42) }}</td> -->
</tr>
<tr>
<th>目前欠款</th>
<td>{{ this.arrear }} ETH</td>
<!-- <td>{{ this.clientAddr.slice(0, 8)+"..."+this.clientAddr.slice(33, 42) }}</td> -->
</tr>
<tr>
<th>本銀行借款紀錄簡覽</th>
<td>共計借款 {{ this.borrowNum }} 已還款 {{ this.repayNum }} 被本銀行預警 {{ this.warningNum }} </td>
</tr>
<tr>
<th>Soulbound Token 號碼</th>
<td>{{ this.$cookies.get('SBTNumber') }}</td>
</tr>
<tr>
<th>Soulbound Token NFT 證書</th>
<td>{{ picName }}<div class="block is-fullwidth" v-html="pic"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
</template>

View File

@ -0,0 +1,93 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import PageTitle from '../components/PageTitle.vue'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
export default {
components: { PageTitle, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
log: [],
}
},
async mounted() {
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
var borrow = await this.token.getPastEvents("Borrow", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr, bank: this.BankAddress } })
console.log(borrow)
for (let i of borrow) {
let result = i.returnValues
let obj = {
bank: result['bank'],
shop: result['shop'],
client: result['client'],
id: result['id'],
amount: this.web3.utils.fromWei(result['amount'], 'ether'),
}
this.log.push(obj)
}
},
methods: {
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="shoplog"></ClientNav>
<template v-if="this.$cookies.get('isShop') == 'true'">
<ShopNav path="shoplog"></ShopNav>
</template>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="借款紀錄" subtitle="查詢透過本銀行進行支付的所有紀錄"></PageTitle>
</div>
<div class="block">
<h1 class="title is-4">收款紀錄</h1>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>#</th>
<th>店家</th>
<th>銀行</th>
<th>帳款編號</th>
<th>金額</th>
<th>詳細訂單狀況</th>
</tr>
</thead>
<tbody>
<template v-for="(value, index) of log">
<tr>
<th>{{ index }}</th>
<td>{{ value.shop }}</td>
<td>{{ value.bank }}</td>
<td>#{{ value.id }}</td>
<td>{{ value.amount }} ETH</td>
<td><RouterLink :to="'/order/'+value.id" class="button is-info is-outlined">查詢</RouterLink></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
</template>

View File

@ -30,13 +30,18 @@ export default {
token: null,
isWaiting: false,
log: [],
scanner: null
scanner: null,
isShop: false
}
},
async mounted() {
if (!this.$cookies.isKey('linked')) {
this.$router.push('/')
}
this.web3 = new Web3(window.ethereum)
this.clientAddr = (await this.web3.eth.getAccounts())[0]
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.isShop = (this.$cookies.get('isShop') == 'true')
},
methods: {
onScanSuccess(decodedText, decodedResult) {
@ -44,7 +49,7 @@ export default {
console.log(`Code matched = ${decodedText}`, decodedResult);
this.scanner.clear()
},
scan () {
scan() {
this.scanner = new Html5QrcodeScanner(
"reader",
{ fps: 10, qrbox: { width: 250, height: 250 } },
@ -87,12 +92,41 @@ export default {
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
<RouterLink to="/client/credit" class="tile is-child notification is-info">
<RouterLink to="/client/log" class="tile is-child notification is-info">
<article>
<p class="title"><i class="fas fa-history"></i> 信用紀錄</p>
<p class="title"><i class="fas fa-history"></i> 借款紀錄</p>
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
<RouterLink to="/client/credit" class="tile is-child notification is-info">
<article>
<p class="title"><i class="fas fa-cubes"></i> SBT信用紀錄</p>
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
<template v-if="this.isShop">
<RouterLink to="/shop/pay" class="tile is-child notification is-info">
<article>
<p class="title"><i class="fas fa-cash-register"></i> 店家結帳</p>
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
<RouterLink to="/shop/log" class="tile is-child notification is-info">
<article>
<p class="title"><i class="fas fa-receipt"></i> 店家收款紀錄</p>
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
<RouterLink to="/shop/products" class="tile is-child notification is-info">
<article>
<p class="title"><i class="fas fa-box-open"></i> 店家商品管理</p>
<!-- <p class="subtitle">Top tile</p> -->
</article>
</RouterLink>
</template>
</div>
</div>
</div>

102
src/views/ClientPayView.vue Normal file
View File

@ -0,0 +1,102 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import Bank from '@/assets/Bank.json'
import PageTitle from '../components/PageTitle.vue'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
// To use Html5QrcodeScanner (more info below)
import { Html5QrcodeScanner } from "html5-qrcode";
export default {
components: { PageTitle, WarningModal, SuccessModal, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
bank: null,
picName: '',
pic: '',
credit: '',
arrear: '',
}
},
async mounted() {
// if (!this.$cookies.isKey('linked')) {
// this.$router.push('/')
// }
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
this.bank = new this.web3.eth.Contract(Bank, this.BankAddress)
// get credit
this.credit = await this.bank.methods.getCredit(this.clientAddr).call()
this.credit = await this.web3.utils.fromWei(this.credit, 'ether')
// get arrear
this.arrear = await this.bank.methods.getArrear(this.clientAddr).call()
this.arrear = await this.web3.utils.fromWei(this.arrear, 'ether')
},
methods: {
onScanSuccess(decodedText, decodedResult) {
// handle the scanned code as you like, for example:
console.log(`Code matched = ${decodedText}`, decodedResult);
var last_id = decodedText.split('/').pop()
this.scanner.clear()
this.$router.push('/client/pay/'+last_id)
},
scan() {
this.scanner = new Html5QrcodeScanner(
"reader",
{ fps: 10, qrbox: { width: 250, height: 250 } },
/* verbose= */ false);
this.scanner.render(this.onScanSuccess);
}
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="pay"></ClientNav>
<template v-if="this.$cookies.get('isShop') == 'true'">
<ShopNav path="pay"></ShopNav>
</template>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="掃描支付" subtitle="掃描店家給的訂單 QRcode 進行支付"></PageTitle>
</div>
<div class="block">
<p class="block">使用地址{{ this.clientAddr }}</p>
<p class="block">剩餘額度{{ this.credit-this.arrear }} ETH (總額度 {{ this.credit }} ETH )</p>
</div>
<div class="block">
<button class="button is-success is-outlined is-large" @click="scan">Pay</button>
<div id="reader" width="300px"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false"
link="/signup/linksbt" btnName="繼續"></SuccessModal>
</template>

View File

@ -20,12 +20,13 @@ export default {
web3: null,
token: null,
isWaiting: false,
log: []
log: [],
warningLog: []
}
},
async mounted() {
this.web3 = new Web3(window.ethereum)
this.clientAddr = (await this.web3.eth.getAccounts())[0]
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
@ -51,7 +52,13 @@ export default {
}
}
}
console.log(this.log)
var warning = await this.token.getPastEvents("Warning", { fromBlock: 0, toBlock: 'latest', filter: { client: this.clientAddr } })
for (let i of warning) {
let result = i.returnValues
this.warningLog.push(result.bank)
}
console.log(this.warningLog)
},
methods: {
@ -72,7 +79,7 @@ export default {
<ul>
<li>我們會根據您提供的 SBT 查詢相關信用紀錄我們會根據紀錄設定給予的每月額度</li>
<li>若在前個步驟您 mint 了新的 SBT代表您不曾擁有過信用紀錄下表為空</li>
<li>若您不曾擁有過信用交易紀錄我們提供最低額度 3 ETH/1 month</li>
<li>若您不曾擁有過信用交易紀錄我們提供最低額度 1 ETH/1 month</li>
<li>請確認下表紀錄我們將依照該紀錄表計算</li>
</ul>
</div>
@ -120,10 +127,10 @@ export default {
</tr>
</thead>
<tbody>
<template v-for="(value, index) of log">
<template v-for="(value, index) of warningLog">
<tr>
<th>{{ index }}</th>
<td>{{ value.bank }}</td>
<td>{{ value }}</td>
</tr>
</template>
</tbody>
@ -134,7 +141,8 @@ export default {
<div class="columns">
<div class="column is-2 is-offset-5">
<RouterLink to="/client" class="button is-primary is-fullwidth is-medium is-outlined">確認</RouterLink>
<p class="block">確認完成註冊請重新登入</p>
<RouterLink to="/" class="button is-primary is-fullwidth is-medium is-outlined">確認並完成註冊</RouterLink>
</div>
</div>
</div>

109
src/views/OrderView.vue Normal file
View File

@ -0,0 +1,109 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import PageTitle from '../components/PageTitle.vue'
import Bank from '@/assets/Bank.json'
export default {
components: { PageTitle },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
bank: null,
link: '',
orderId: '',
products: [],
amount: "0",
amountWei: "0",
client: '',
shop: {
'address': '',
'name': '',
},
waiting: false
}
},
async mounted() {
// if (!this.$cookies.isKey('linked')) {
// this.$router.push('/')
// }
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
this.bank = new this.web3.eth.Contract(Bank, this.BankAddress)
// get order
this.orderId = this.$route.params.id
console.log("id: ", this.orderId)
const response = await fetch(`${import.meta.env.VITE_BACKEND_PREFIX}/order/${this.orderId}`);
var orderData = await response.json();
console.log(orderData)
this.products = orderData.products
this.amountWei = orderData.amount
this.client = orderData.client
this.amount = this.web3.utils.fromWei(orderData.amount, 'ether')
this.shop = orderData.shop
},
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="block">
<PageTitle title="訂單資訊" subtitle=""></PageTitle>
</div>
<div class="block">
<table class="table is-fullwidth">
<tbody>
<tr>
<th colspan="4" style="text-align: center;">商品清單</th>
</tr>
<template v-for="product in products">
<tr>
<td><b>{{ product.name }}</b></td>
<td>{{ web3.utils.fromWei(product.price, 'ether') }} ETH/</td>
<td> * {{ product.count }} </td>
<td> 合計 {{ web3.utils.fromWei(product.price, 'ether') * product.count }} ETH</td>
</tr>
</template>
<tr>
<td colspan="3"></td>
<td>共計 {{ amount }} ETH</td>
</tr>
</tbody>
</table>
<table class="table is-fullwidth">
<tbody>
<tr>
<th>支付地址</th>
<td colspan="3">{{ this.client }}</td>
</tr>
<tr>
<th>店家地址</th>
<td colspan="3">{{ this.shop.address }} ( {{ this.shop.name }} )</td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<div class="columns">
<div class="column is-2 is-offset-5">
<button @click="this.$router.go(-1)" class="button is-danger is-fullwidth is-medium is-outlined">返回</button>
</div>
</div>
</div>
</div>
</section>
</template>

166
src/views/PaymentView.vue Normal file
View File

@ -0,0 +1,166 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import PageTitle from '../components/PageTitle.vue'
import Bank from '@/assets/Bank.json'
export default {
components: { WarningModal, SuccessModal, PageTitle },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
warningModalStatus: false,
successModalStatus: false,
clientAddr: '',
web3: null,
token: null,
bank: null,
link: '',
msg: '',
credit: '',
arrear: '',
orderId: '',
products: [],
amount: "0",
amountWei: "0",
creditWei: "0",
arrearWei: "0",
shop: {
'address': '',
'name': '',
},
waiting: false
}
},
async mounted() {
// if (!this.$cookies.isKey('linked')) {
// this.$router.push('/')
// }
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
this.bank = new this.web3.eth.Contract(Bank, this.BankAddress)
// get credit
this.credit = await this.bank.methods.getCredit(this.clientAddr).call()
this.creditWei = this.credit
this.credit = await this.web3.utils.fromWei(this.credit, 'ether')
// get arrear
this.arrear = await this.bank.methods.getArrear(this.clientAddr).call()
this.arrearWei = this.arrear
this.arrear = await this.web3.utils.fromWei(this.arrear, 'ether')
// get order
this.orderId = this.$route.params.id
console.log("id: ", this.orderId)
const response = await fetch(`${import.meta.env.VITE_BACKEND_PREFIX}/order/${this.orderId}`);
var orderData = await response.json();
this.products = orderData.products
this.amountWei = orderData.amount
this.amount = this.web3.utils.fromWei(orderData.amount, 'ether')
this.shop = orderData.shop
console.log(this.products)
console.log(this.amount)
console.log(this.shop)
},
methods: {
async pay () {
this.waiting = true
// await this.bank.methods.pay(this.number, this.shop.address, this.amountWei).send({ from: this.clientAddr })
try {
await this.bank.methods.pay(this.orderId, this.shop.address, this.amountWei).send({ from: this.clientAddr })
await fetch(import.meta.env.VITE_BACKEND_PREFIX+"/order/"+this.orderId, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({'client': this.clientAddr})
})
this.link = '/client/info'
this.msg = '支付成功!<br>已經支付'+this.amount+" ETH 給"+this.shop.name+" ("+this.shop.address+")"
this.successModalStatus = true
} catch (error) {
this.msg = '支付失敗'
this.warningModalStatus = true
}
this.waiting = false
}
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="block">
<PageTitle title="確認付款資訊" subtitle="確認後由銀行先向店家支付 ETH"></PageTitle>
</div>
<div class="block">
<table class="table is-fullwidth">
<tbody>
<tr>
<th colspan="4" style="text-align: center;">商品清單</th>
</tr>
<template v-for="product in products">
<tr>
<td><b>{{ product.name }}</b></td>
<td>{{ web3.utils.fromWei(product.price, 'ether') }} ETH/</td>
<td> * {{ product.count }} </td>
<td> 合計 {{ web3.utils.fromWei(product.price, 'ether') * product.count }} ETH</td>
</tr>
</template>
<tr>
<td colspan="3"></td>
<td>共計 {{ amount }} ETH</td>
</tr>
</tbody>
</table>
<table class="table is-fullwidth">
<tbody>
<tr>
<th>支付地址</th>
<td colspan="3">{{ this.clientAddr }}</td>
</tr>
<tr>
<th>店家地址</th>
<td colspan="3">{{ this.shop.address }} ( {{ this.shop.name }} )</td>
</tr>
<tr>
<th>目前信用額度</th>
<td colspan="4">{{ this.credit }} ETH (尚可使用 {{ this.credit - this.arrear }} ETH)</td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<div class="columns">
<div class="column is-2 is-offset-5">
<template v-if="(this.creditWei - this.arrearWei) >= this.amountWei">
<template v-if="!waiting">
<button @click="pay" class="button is-primary is-fullwidth is-medium is-outlined">支付</button>
</template>
<template v-else>
<button class="button is-primary is-fullwidth is-medium is-outlined is-loading">支付</button>
</template>
</template>
<template v-else>
<RouterLink to="/client" class="button is-danger is-fullwidth is-medium is-outlined">額度不足取消</RouterLink>
</template>
</div>
</div>
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false" :link="link"
btnName="繼續"></SuccessModal>
</template>

93
src/views/ShopLogView.vue Normal file
View File

@ -0,0 +1,93 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import PageTitle from '../components/PageTitle.vue'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
export default {
components: { PageTitle, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
log: [],
}
},
async mounted() {
this.web3 = new Web3(window.ethereum)
this.clientAddr = this.$cookies.get('address')
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
var borrow = await this.token.getPastEvents("Borrow", { fromBlock: 0, toBlock: 'latest', filter: { shop: this.clientAddr, bank: this.BankAddress } })
console.log(borrow)
for (let i of borrow) {
let result = i.returnValues
let obj = {
bank: result['bank'],
shop: result['shop'],
client: result['client'],
id: result['id'],
amount: this.web3.utils.fromWei(result['amount'], 'ether'),
}
this.log.push(obj)
}
},
methods: {
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="shoplog"></ClientNav>
<template v-if="this.$cookies.get('isShop') == 'true'">
<ShopNav path="shoplog"></ShopNav>
</template>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="店家收款紀錄" subtitle="查詢客戶付款狀況"></PageTitle>
</div>
<div class="block">
<h1 class="title is-4">收款紀錄</h1>
<table class="table is-fullwidth is-striped is-hoverable">
<thead>
<tr>
<th>#</th>
<th>客戶</th>
<th>銀行</th>
<th>帳款編號</th>
<th>金額</th>
<th>詳細訂單狀況</th>
</tr>
</thead>
<tbody>
<template v-for="(value, index) of log">
<tr>
<th>{{ index }}</th>
<td>{{ value.client }}</td>
<td>{{ value.bank }}</td>
<td>#{{ value.id }}</td>
<td>{{ value.amount }} ETH</td>
<td><RouterLink :to="'/order/'+value.id" class="button is-info is-outlined">查詢</RouterLink></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
</template>

View File

@ -0,0 +1,55 @@
<script>
import PageTitle from '../components/PageTitle.vue'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
import QRCode from 'qrcode';
export default {
components: { PageTitle, ClientNav, ShopNav },
data() {
return {
size: 300,
value: "www.google.com"
}
},
async mounted() {
const qrcodeCanvas = this.$refs.qrcodeCanvas;
const qrCodeUrl = import.meta.env.VITE_FRONTEND_PREFIX+"/client/pay/"+this.$route.params.id
QRCode.toCanvas(qrcodeCanvas, qrCodeUrl, (error) => {
if (error) {
console.error(error);
}
});
},
methods: {
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="shoppay"></ClientNav>
<ShopNav path="shoppay"></ShopNav>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="店家收款 QRcode" subtitle="提供顧客掃描以進行支付"></PageTitle>
</div>
<div class="block">
<canvas id="canvas" ref="qrcodeCanvas"></canvas>
</div>
<div class="block">
<RouterLink to="/shop/pay" class="button is-info is-outlined is-large" @click="send">繼續下一訂單</RouterLink>
</div>
</div>
</div>
</div>
</div>
</section>
</template>

171
src/views/ShopPayView.vue Normal file
View File

@ -0,0 +1,171 @@
<script>
import Web3 from 'web3';
import SBT from '@/assets/SBT.json'
import Bank from '@/assets/Bank.json'
import PageTitle from '../components/PageTitle.vue'
import WarningModal from '../components/WarningModal.vue'
import SuccessModal from '../components/SuccessModal.vue'
import ClientNav from '../components/ClientNav.vue'
import ShopNav from '../components/ShopNav.vue'
// To use Html5QrcodeScanner (more info below)
import { Html5QrcodeScanner } from "html5-qrcode";
export default {
components: { PageTitle, WarningModal, SuccessModal, ClientNav, ShopNav },
data() {
return {
SBTAddress: import.meta.env.VITE_SBT_ADDR,
BankAddress: import.meta.env.VITE_BANK_ADDR,
clientAddr: '',
web3: null,
token: null,
bank: null,
warningModalStatus: false,
successModalStatus: false,
msg: '',
isWaiting: false,
products: {},
productCar: [],
orderId: '0'
}
},
async mounted() {
this.web3 = new Web3(window.ethereum)
if (this.$cookies.isKey('address')) {
this.clientAddr = this.$cookies.get('address')
} else {
console.log("Use default address")
this.clientAddr = import.meta.env.VITE_DEFAULT_SHOP
}
this.web3.eth.defaultAccount = this.clientAddr
this.token = new this.web3.eth.Contract(SBT, this.SBTAddress)
this.bank = new this.web3.eth.Contract(Bank, this.BankAddress)
var result = await fetch(import.meta.env.VITE_BACKEND_PREFIX + "/shop/" + this.clientAddr + "/products")
var data = await result.json()
for (let product in data['products']) {
this.products[product] = data['products'][product]
}
},
computed: {
amount() {
if (!this.web3) {
return 0
}
var ans = 0
for (let product of this.productCar) {
ans += Number(product['price']) * product['count']
}
return this.web3.utils.fromWei(ans.toString())
}
},
methods: {
onScanSuccess(decodedText, decodedResult) {
// handle the scanned code as you like, for example:
console.log(`Code matched = ${decodedText}`, decodedResult);
var product = this.products[decodedText]
this.productCar.push({
'name': product['name'],
'id': product['id'],
'price': product['price'],
'count': 1
})
this.scanner.clear()
},
scan() {
this.scanner = new Html5QrcodeScanner(
"reader",
{ fps: 10, qrbox: { width: 400, height: 400 } },
/* verbose= */ false);
this.scanner.render(this.onScanSuccess);
},
async send() {
this.isWaiting = true
var data = {}
data['from'] = this.clientAddr
data['products'] = []
for (let product of this.productCar) {
data['products'].push({
'product_id': product['id'],
'count': product['count']
})
}
try {
var result = await fetch(import.meta.env.VITE_BACKEND_PREFIX+"/order", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
this.orderId = await result.json()
this.msg = '完成'
this.successModalStatus = true
} catch (error) {
this.msg = '錯誤'
this.warningModalStatus = true
}
this.productCar = []
this.isWaiting = false
}
}
}
</script>
<template>
<section class="blog-posts">
<div class="container">
<div class="columns">
<div class="column is-2">
<ClientNav path="shoppay"></ClientNav>
<ShopNav path="shoppay"></ShopNav>
</div>
<div class="column">
<div class="container">
<div class="block">
<PageTitle title="店家收款" subtitle="掃描商品條碼以加入收款單"></PageTitle>
</div>
<div class="block">
<button class="button is-success is-outlined is-large" @click="scan">掃描商品條碼</button>
<div id="reader" width="300px"></div>
</div>
<div class="block">
<table class="table is-fullwidth">
<tbody>
<tr>
<th colspan="4" style="text-align: center;">商品清單</th>
</tr>
<template v-for="product in productCar">
<tr>
<td>{{ product['name'] }}</td>
<td>{{ this.web3.utils.fromWei(product['price']) }} ETH</td>
<td><input class="input is-info" type="number" v-model="product['count']"></td>
</tr>
</template>
<tr>
<td colspan="3">共計 {{ amount }} ETH</td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<template v-if="!isWaiting">
<button class="button is-info is-outlined is-large" @click="send">確認以上訂單</button>
</template>
<template v-else>
<button class="button is-info is-outlined is-large is-loading"></button>
</template>
</div>
</div>
</div>
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false"
:link="`/shop/pay/${this.orderId}`" btnName="繼續"></SuccessModal>
</template>

View File

@ -7,7 +7,7 @@ import { useClientStore } from '../stores/Client.js'
export default {
components: { PageTitle, WarningModal, SuccessModal },
data () {
data() {
return {
warningModalStatus: false,
successModalStatus: false,
@ -16,7 +16,7 @@ export default {
}
},
methods: {
async detect () {
async detect() {
const provider = await detectEthereumProvider()
if (provider) {
const chainId = await window.ethereum.request({ method: 'eth_chainId' })
@ -72,6 +72,7 @@ export default {
</div>
</div>
</section>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus=false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus=false" link="/signup/linksbt" btnName="繼續"></SuccessModal>
<WarningModal :active="warningModalStatus" :errorMsg="msg" @closeModal="warningModalStatus = false"></WarningModal>
<SuccessModal :active="successModalStatus" :successMsg="msg" @closeModal="successModalStatus = false"
link="/signup/linksbt" btnName="繼續"></SuccessModal>
</template>