QR code
This commit is contained in:
parent
ab007d5971
commit
bed03c9234
|
@ -19,7 +19,7 @@ from django.urls import path, include, re_path
|
|||
from django.views.generic.base import TemplateView
|
||||
from django.views.static import serve
|
||||
|
||||
from utils.views import UploadFileAPI
|
||||
from utils.views import UploadFileAPI, QRCodeAPI, DownloadAllQRCodeByRoomAPI
|
||||
|
||||
urlpatterns = [
|
||||
path('', TemplateView.as_view(template_name='index.html')),
|
||||
|
@ -28,7 +28,9 @@ urlpatterns = [
|
|||
path('admin/', admin.site.urls),
|
||||
path('api/user/', include('user.urls', namespace='user')),
|
||||
path('api/booking/', include('booking.urls', namespace='booking')),
|
||||
path('api/file/', UploadFileAPI.as_view())
|
||||
path('api/file/', UploadFileAPI.as_view()),
|
||||
path('api/qrcode/', QRCodeAPI.as_view()),
|
||||
path('api/qrcode/all/', DownloadAllQRCodeByRoomAPI.as_view())
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
1
Pipfile
1
Pipfile
|
@ -15,6 +15,7 @@ celery = "*"
|
|||
eventlet = "*"
|
||||
psycopg2 = "*"
|
||||
python-memcached = "*"
|
||||
qrcode = {extras = ["pil"],version = "*"}
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f5e0c38bf5a4c12520573fccc9fd57bd36112da57038e5456845fc259c1fef91"
|
||||
"sha256": "1e97ca750e0514dd32253773df5c99acd2df1c3ed92d5b615c79a581ca6b9329"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -25,10 +25,10 @@
|
|||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
"sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab",
|
||||
"sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
"version": "==2.7.0"
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
|
@ -44,6 +44,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||
],
|
||||
"markers": "platform_system == 'Windows'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:6fcc3cbd55b16f9a01f37de8bcbe286e0ea22e87096557f1511051780338eaea",
|
||||
|
@ -128,6 +136,37 @@
|
|||
],
|
||||
"version": "==1.5"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55",
|
||||
"sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479",
|
||||
"sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a",
|
||||
"sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d",
|
||||
"sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb",
|
||||
"sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb",
|
||||
"sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8",
|
||||
"sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72",
|
||||
"sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754",
|
||||
"sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f",
|
||||
"sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce",
|
||||
"sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601",
|
||||
"sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5",
|
||||
"sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734",
|
||||
"sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b",
|
||||
"sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b",
|
||||
"sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1",
|
||||
"sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91",
|
||||
"sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8",
|
||||
"sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239",
|
||||
"sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af",
|
||||
"sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8",
|
||||
"sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232",
|
||||
"sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a",
|
||||
"sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3",
|
||||
"sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062"
|
||||
],
|
||||
"version": "==6.0.0"
|
||||
},
|
||||
"psycopg2": {
|
||||
"hashes": [
|
||||
"sha256:00cfecb3f3db6eb76dcc763e71777da56d12b6d61db6a2c6ccbbb0bff5421f8f",
|
||||
|
@ -160,6 +199,17 @@
|
|||
],
|
||||
"version": "==2019.1"
|
||||
},
|
||||
"qrcode": {
|
||||
"extras": [
|
||||
"pil"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5",
|
||||
"sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
|
|
|
@ -32,27 +32,8 @@ export function getCookie (name) {
|
|||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export default function fetchAPI (url, method, data = null, params = null) {
|
||||
let body = null
|
||||
// csrf
|
||||
let headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(method.toUpperCase())) {
|
||||
headers['X-CSRFToken'] = getCookie('csrftoken')
|
||||
}
|
||||
if (data) {
|
||||
body = JSON.stringify(data)
|
||||
}
|
||||
if (params) {
|
||||
url += '?' + (new URLSearchParams(params)).toString()
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(BASE_URL + url, {
|
||||
credentials: 'include',
|
||||
headers: headers,
|
||||
method: method,
|
||||
body: body
|
||||
})
|
||||
fetchAPIBase(url, method, data, params)
|
||||
.then(res => {
|
||||
if (res.status === 204) {
|
||||
return res.text()
|
||||
|
@ -75,4 +56,27 @@ export default function fetchAPI (url, method, data = null, params = null) {
|
|||
notification.error({ message: '错误', description: error, key: 'ERROR' })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchAPIBase (url, method, data = null, params = null) {
|
||||
let body = null
|
||||
// csrf
|
||||
let headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(method.toUpperCase())) {
|
||||
headers['X-CSRFToken'] = getCookie('csrftoken')
|
||||
}
|
||||
if (data) {
|
||||
body = JSON.stringify(data)
|
||||
}
|
||||
if (params) {
|
||||
url += '?' + (new URLSearchParams(params)).toString()
|
||||
}
|
||||
return fetch(BASE_URL + url, {
|
||||
credentials: 'include',
|
||||
headers: headers,
|
||||
method: method,
|
||||
body: body
|
||||
})
|
||||
}
|
|
@ -33,3 +33,17 @@ export function removeLoadingAnimate (id = '', timeout = 1500) {
|
|||
document.body.removeChild(document.getElementById(id))
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param blob
|
||||
* @param fileName
|
||||
*/
|
||||
export function downloadFile (blob, fileName) {
|
||||
let aLink = document.createElement('a')
|
||||
let evt = document.createEvent('HTMLEvents')
|
||||
evt.initEvent('click', true, true) //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
|
||||
aLink.download = fileName
|
||||
aLink.href = URL.createObjectURL(blob)
|
||||
aLink.click()
|
||||
}
|
|
@ -30,7 +30,7 @@
|
|||
编辑
|
||||
</a-button>
|
||||
<a-button @click="$router.push({name: 'seat', params: {id: record.id.toString()}})">座位管理</a-button>
|
||||
<a-button @click="handleDownload">二维码</a-button>
|
||||
<a-button @click="handleDownload(record)">二维码</a-button>
|
||||
</a-button-group>
|
||||
</template>
|
||||
</a-table>
|
||||
|
@ -69,8 +69,11 @@
|
|||
width: '30%'
|
||||
}
|
||||
]
|
||||
|
||||
import api from '../../api/room'
|
||||
import PageLayout from '../../components/page/PageLayout'
|
||||
import { fetchAPIBase } from '../../utils/fetch'
|
||||
import { downloadFile } from '../../utils/util'
|
||||
|
||||
export default {
|
||||
name: 'Room',
|
||||
|
@ -101,8 +104,22 @@
|
|||
this.state.loading = false
|
||||
})
|
||||
},
|
||||
handleDownload () {
|
||||
|
||||
handleDownload (record) {
|
||||
const qrcode = {
|
||||
type: 'room',
|
||||
name: record.name,
|
||||
id: record.id
|
||||
}
|
||||
fetchAPIBase('qrcode/', 'get', null, qrcode)
|
||||
.then(res => {
|
||||
return res.blob()
|
||||
})
|
||||
.then(data => {
|
||||
downloadFile(data, `房间-${record.name}.png`)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$notification({ message: '下载失败', description: error, key: 'ERROR' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
icon="cloud-download"
|
||||
style="width: 100%;"
|
||||
@click="handleDownloadAll">
|
||||
下载全部
|
||||
全部二维码
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -59,7 +59,7 @@
|
|||
:pagination="false">
|
||||
<template slot="operation" slot-scope="text, record, index">
|
||||
<a-button-group>
|
||||
<a-button @click="handleDownload">二维码</a-button>
|
||||
<a-button @click="handleDownload(record)">二维码</a-button>
|
||||
</a-button-group>
|
||||
</template>
|
||||
</a-table>
|
||||
|
@ -95,6 +95,8 @@
|
|||
]
|
||||
import PageLayout from '../../components/page/PageLayout'
|
||||
import api from '../../api/room'
|
||||
import { fetchAPIBase } from '../../utils/fetch'
|
||||
import { downloadFile } from '../../utils/util'
|
||||
|
||||
export default {
|
||||
name: 'Seat',
|
||||
|
@ -164,11 +166,31 @@
|
|||
this.$notification.error({ message: '错误', description: '暂无座位', key: 'ERROR' })
|
||||
}
|
||||
},
|
||||
handleDownload () {
|
||||
alert('handleDownload')
|
||||
handleDownload (record) {
|
||||
const qrcode = {
|
||||
type: 'seat',
|
||||
name: record.name,
|
||||
id: record.id
|
||||
}
|
||||
fetchAPIBase('qrcode/', 'get', null, qrcode)
|
||||
.then(res => {
|
||||
return res.blob()
|
||||
})
|
||||
.then(data => {
|
||||
downloadFile(data, `座位-${record.name}.png`)
|
||||
})
|
||||
},
|
||||
handleDownloadAll () {
|
||||
alert('handleDownloadAll')
|
||||
fetchAPIBase('qrcode/all/', 'get', null, { room: this.room.id })
|
||||
.then(res => {
|
||||
return res.blob()
|
||||
})
|
||||
.then(data => {
|
||||
downloadFile(data, `房间-${this.room.name}.zip`)
|
||||
})
|
||||
.catch(error => {
|
||||
this.$notification({ message: '下载失败', description: error, key: 'ERROR' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
amqp==2.4.2
|
||||
anyjson==0.3.3
|
||||
Babel==2.6.0
|
||||
Babel==2.7.0
|
||||
billiard==3.6.0.0
|
||||
celery==4.3.0
|
||||
colorama==0.4.1
|
||||
Django==2.2.1
|
||||
django-debug-toolbar==1.11
|
||||
django-filter==2.1.0
|
||||
|
@ -13,9 +14,11 @@ flower==0.9.3
|
|||
greenlet==0.4.15
|
||||
kombu==4.5.0
|
||||
monotonic==1.5
|
||||
Pillow==6.0.0
|
||||
psycopg2==2.8.2
|
||||
python-memcached==1.59
|
||||
pytz==2019.1
|
||||
qrcode==6.1
|
||||
six==1.12.0
|
||||
sqlparse==0.3.0
|
||||
tornado==5.1.1
|
||||
|
|
Binary file not shown.
|
@ -1,8 +1,18 @@
|
|||
import os
|
||||
import time
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
import qrcode
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from django.conf import settings
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import permissions
|
||||
from rest_framework import views
|
||||
from rest_framework.response import Response
|
||||
|
||||
from booking.models import Room
|
||||
from utils.models import UploadFile
|
||||
|
||||
|
||||
|
@ -21,3 +31,64 @@ class UploadFileAPI(views.APIView):
|
|||
'id': f.id,
|
||||
'url': f.get_url()
|
||||
})
|
||||
|
||||
|
||||
class QRCodeAPI(views.APIView):
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request):
|
||||
data = {}
|
||||
for key in request.query_params:
|
||||
data[key] = request.query_params[key]
|
||||
stream = create_qrcode(data)
|
||||
return HttpResponse(stream.getvalue(), content_type="image/png")
|
||||
|
||||
|
||||
class DownloadAllQRCodeByRoomAPI(views.APIView):
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request):
|
||||
room_id = request.query_params.get('room')
|
||||
room = get_object_or_404(Room, id=room_id)
|
||||
seat_qrcode_stream_list = []
|
||||
seat_name_list = []
|
||||
for seat in room.seat_set.all():
|
||||
data = {
|
||||
'type': 'seat',
|
||||
'name': seat.name,
|
||||
'id': seat.id
|
||||
}
|
||||
seat_qrcode_stream_list.append(create_qrcode(data))
|
||||
seat_name_list.append(seat.name)
|
||||
|
||||
zip = BytesIO()
|
||||
with zipfile.ZipFile(zip, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for index, file in enumerate(seat_qrcode_stream_list):
|
||||
zf.writestr(f'座位-{seat_name_list[index]}.png', file.getvalue())
|
||||
|
||||
return HttpResponse(zip.getvalue(), content_type="application/zip")
|
||||
|
||||
|
||||
def _create_qrcode(data):
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_H,
|
||||
box_size=10,
|
||||
border=4,
|
||||
)
|
||||
qr.add_data(data)
|
||||
qr.make(fit=True)
|
||||
qrcode_img = qr.make_image()
|
||||
return qrcode_img
|
||||
|
||||
|
||||
def create_qrcode(data):
|
||||
img = _create_qrcode(data).convert('RGBA')
|
||||
tagged_img = Image.new(img.mode, (img.size[0], img.size[1] + 40), (255, 255, 255))
|
||||
tagged_img.paste(img, (0, 0))
|
||||
draw = ImageDraw.Draw(tagged_img)
|
||||
font = ImageFont.truetype(os.path.join(settings.BASE_DIR, 'utils', 'font', 'SourceHanSansCN.otf'), 40)
|
||||
draw.text((40, img.size[1] - 20), f'{data["type"].upper()} - {data["name"]}', font=font, fill=(0, 0, 0))
|
||||
stream = BytesIO()
|
||||
tagged_img.save(stream, 'png')
|
||||
return stream
|
||||
|
|
Loading…
Reference in New Issue