From 5638574339196714c36c3c6ef6dd86780dc887c2 Mon Sep 17 00:00:00 2001 From: zty Date: Tue, 4 Mar 2025 09:12:51 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=80=83?= =?UTF-8?q?=E8=AF=95=E5=92=8C=20=E6=96=B0=E5=A2=9E=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_server/cms/models.py | 2 + test_server/crm/serializers.py | 16 +++- test_server/examtest/models.py | 2 +- test_server/examtest/services.py | 122 ++++++++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/test_server/cms/models.py b/test_server/cms/models.py index 6391436..770a97f 100644 --- a/test_server/cms/models.py +++ b/test_server/cms/models.py @@ -69,6 +69,8 @@ class CompanyInfo(CommonModel): edu_points = models.FloatField('教育积分', null=True, blank=True) co_sponsor_points = models.FloatField('协办大赛积分', null=True, blank=True) sum_points = models.FloatField('总积分', null=True, blank=True) + photo = models.ImageField('公司图片', upload_to='photo', null=True, blank=True) + remark = models.TextField('备注', null=True, blank=True, max_length=500) def __str__(self): return self.company_name diff --git a/test_server/crm/serializers.py b/test_server/crm/serializers.py index 7e83bd5..5ad426c 100644 --- a/test_server/crm/serializers.py +++ b/test_server/crm/serializers.py @@ -4,7 +4,8 @@ from .models import Candidate, Company, Consumer, PaySubject, ConsumerPerm, Cons from question.models import Question, Questioncat from .permission import get_consumerperm_list from rbac.serializers.user_serializer import UserListSerializer - +from examtest.services import make_img +from server import settings class ConsumerPermSerializer(serializers.ModelSerializer): class Meta: @@ -43,7 +44,7 @@ class ConsumerSerializer(serializers.ModelSerializer): create_admin_name = serializers.StringRelatedField(source='create_admin', read_only=True) class Meta: model = Consumer - exclude = ('avatar','collects', 'process') + exclude = ('collects', 'process') @staticmethod def setup_eager_loading(queryset): @@ -83,6 +84,7 @@ class ConsumerDetailSerializer(serializers.ModelSerializer): class CandidateSerializer(serializers.ModelSerializer): create_admin_username = serializers.StringRelatedField(source='create_admin', read_only=True) + cert_template = serializers.StringRelatedField(source='examtest.exam.cert_template', read_only=True) class Meta: model = Candidate fields = '__all__' @@ -96,6 +98,16 @@ class CandidateCreateSerializer(serializers.ModelSerializer): validated_data['is_manual'] = True validated_data['create_admin'] = self.context['request'].user return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data['is_manual'] = True + validated_data['create_admin'] = self.context['request'].user + issue_date = validated_data.get('issue_date', None) + exp_date = validated_data.get('end_date', None) + header_photo = settings.BASE_DIR + validated_data.get('photo', None) + path = make_img(validated_data["consumer_name"], str(issue_date.year), str(issue_date.month), str(exp_date.year), str(exp_date.month), str(exp_date.day), validated_data["number"], header_photo) + validated_data['path'] = path + return super().update(instance,validated_data) class MsgSerializer(serializers.ModelSerializer): class Meta: diff --git a/test_server/examtest/models.py b/test_server/examtest/models.py index 958bb38..31a6a15 100644 --- a/test_server/examtest/models.py +++ b/test_server/examtest/models.py @@ -27,7 +27,7 @@ class Exam(CommonModel): qdimgs = JSONField('签到图片', default=list) xcimgs = JSONField('现场图片', default=list) only_vip = models.BooleanField('仅允许培训班学员', default=False) - cert_template = models.PositiveSmallIntegerField('证书模板', null=True, blank=True, help_text='1,2') + cert_template = models.PositiveSmallIntegerField('证书模板', null=True, blank=True, help_text='1,2,3') auto_issue = models.BooleanField('是否自动发证', default=False) def __str__(self): diff --git a/test_server/examtest/services.py b/test_server/examtest/services.py index 3080dde..1656840 100644 --- a/test_server/examtest/services.py +++ b/test_server/examtest/services.py @@ -4,6 +4,10 @@ from django.utils import timezone from datetime import timedelta from rest_framework.exceptions import ParseError from rbac.models import UserProfile +from PIL import Image, ImageDraw, ImageFont +import settings, datetime +from server import settings + def issue(obj: ExamTest, create_admin: UserProfile = None): if create_admin is None: create_admin = obj.exam.create_admin @@ -35,8 +39,124 @@ def issue(obj: ExamTest, create_admin: UserProfile = None): if cert_template == 2: count = Candidate.objects.filter(create_admin=create_admin, issue_date__year=now.year, issue_date__month=now.month).count() candidate.number='HNHK'+ str(now.year) + str(now.month).zfill(2) + str(count+1).zfill(4) + elif cert_template == 3: + cer_path = make_img(candidate.consumer_name, str(candidate.start_date.year), str(candidate.start_date.month), str(candidate.end_date.year), str(candidate.end_date.month), str(candidate.end_date.day), candidate.number, header_path=candidate.photo, mode=True) + candidate.path = cer_path + candidate.number='HNHK'+ str(now.year) + str(now.month).zfill(2) + str(count+1).zfill(4) + elif cert_template == 4: + cer_path = make_img(candidate.consumer_name, str(candidate.start_date.year), str(candidate.start_date.month), str(candidate.end_date.year), str(candidate.end_date.month), str(candidate.end_date.day), candidate.number, header_path=candidate.photo, mode=False) + candidate.path = cer_path + candidate.number='HNHK'+ str(now.year) + str(now.month).zfill(2) + str(count+1).zfill(4) else: candidate.number='SL'+ str(now.year)[-2:] + str(candidate.pk).zfill(6) + candidate.create_admin = create_admin candidate.save() - return {"id":candidate.pk, "number":candidate.number, "path":None} \ No newline at end of file + return {"id":candidate.pk, "number":candidate.number, "path":None} + + +def make_img(name:str, year:str, month:str, expiry_year:str, expiry_month:str, expiry_day:str, certificate_number:str, header_path=None, mode=None): + now = datetime.datetime.now() + font_path = settings.BASE_DIR + "/media/cert/template/STKAITI.TTF" + if mode: + demo_image_path = settings.BASE_DIR + "/media/cert/template/assess.png" + # demo_image_path = r"C:\code\cma_search\server\media\test.png" + img = Image.open(demo_image_path) + if img.mode == 'RGBA': + img = img.convert('RGB') + # Initialize ImageDraw + draw = ImageDraw.Draw(img) + + # Set font paths and sizes + # font_path = r"C:/code/cma_search/server/media/font/SIMLI.TTF" # Update this to the correct font path + font_name = ImageFont.truetype(font_path, 20) # Font size for the name + font_text = ImageFont.truetype(font_path, 20) # Font size for the other text + font_number = ImageFont.truetype(font_path, 15) # Font size for the other text + + # uoload header image + # header_path = "C:\code\cma_search\server\media\OIP.jpg" + if header_path: + header_img = Image.open(header_path) + # 指定粘贴的位置 + header_position = (210, 235) # 粘贴图像的左上角位置 + header_img = header_img.resize((140, 150)) + img.paste(header_img, header_position) + if len(name) <= 3: + position_name = (105, 395) + else: + position_name = (100, 395) + position_year = (220, 395) + position_month = (280, 395) + + position_exp_day = (310, 490) + position_exp_month = (270, 490) + position_exp_year = (210, 490) + position_cert_number = (180, 530) + + # Define colors + color = (0, 0, 0) # Black color + + # Draw the text on the image + draw.text(position_name, name, fill=color, font=font_name) + draw.text(position_year, year, fill=color, font=font_text) + draw.text(position_month, month, fill=color, font=font_text) + + draw.text(position_exp_year, expiry_year, fill=color, font=font_text) + draw.text(position_exp_month, expiry_month, fill=color, font=font_text) + draw.text(position_exp_day, expiry_day, fill=color, font=font_text) + + draw.text(position_cert_number, certificate_number, fill=color, font=font_number) + # Save the edited image + output_path = f"/media/cert/template/certificate/{now}.jpg"# Update this to the desired output path + img.save(settings.BASE_DIR + output_path, format='JPEG') + else: + demo_image_path = settings.BASE_DIR + "/media/cert/template/inspect.jpg" + # font_path = r"C:/code/cma_search/server/media/font/SIMLI.TTF" # Update this to the correct font path + img = Image.open(demo_image_path) + if img.mode == 'RGBA': + img = img.convert('RGB') + # Initialize ImageDraw + draw = ImageDraw.Draw(img) + # Set font paths and sizes + font_name = ImageFont.truetype(font_path, 20) # Font size for the name + font_text = ImageFont.truetype(font_path, 20) # Font size for the other text + font_number = ImageFont.truetype(font_path, 15) # Font size for the other text + + # uoload header image + if header_path: + header_img = Image.open(header_path) + # 指定粘贴的位置 + header_position = (240, 250) # 粘贴图像的左上角位置 + header_img = header_img.resize((120, 150)) + img.paste(header_img, header_position) + + # Define text positions (X, Y) based on your image + if len(name) <= 3: + position_name = (130, 430) + else: + position_name = (140, 430) + position_year = (245, 430) + position_month = (308, 430) + + position_exp_day = (430, 525) + position_exp_month = (390, 525) + position_exp_year = (330, 525) + position_cert_number = (200, 565) + + # Define colors + color = (0, 0, 0) # Black color + + # Draw the text on the image + draw.text(position_name, name, fill=color, font=font_name) + draw.text(position_year, year, fill=color, font=font_text) + draw.text(position_month, month, fill=color, font=font_text) + + draw.text(position_exp_year, expiry_year, fill=color, font=font_text) + draw.text(position_exp_month, expiry_month, fill=color, font=font_text) + draw.text(position_exp_day, expiry_day, fill=color, font=font_text) + + draw.text(position_cert_number, certificate_number, fill=color, font=font_number) + # Save the edited image + output_path = f"/media/cert/template/certificate/{now}.jpg"# Update this to the desired output path # Update this to the desired output path + img.save(settings.BASE_DIR + output_path, format='JPEG') + return output_path \ No newline at end of file From 343f82e859ffd2aa1bcfe402e6736ac0a995eed1 Mon Sep 17 00:00:00 2001 From: zty Date: Wed, 5 Mar 2025 13:56:45 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=81=8C?= =?UTF-8?q?=E4=B8=9A=E7=AD=89=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_client/src/views/exam/issue.vue | 22 +++++--- test_client/src/views/material/material.vue | 7 +-- test_client/src/views/youpei/index.vue | 54 ++++++++++++++++--- .../cms/migrations/0019_auto_20250304_1552.py | 23 ++++++++ test_server/cms/models.py | 2 +- test_server/cms/serializers.py | 21 -------- test_server/cms/views.py | 1 + .../crm/migrations/0048_auto_20250304_1410.py | 22 ++++++++ test_server/crm/models.py | 10 +++- test_server/crm/views.py | 3 +- test_server/examtest/services.py | 2 +- 11 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 test_server/cms/migrations/0019_auto_20250304_1552.py create mode 100644 test_server/crm/migrations/0048_auto_20250304_1410.py diff --git a/test_client/src/views/exam/issue.vue b/test_client/src/views/exam/issue.vue index 8aa4d24..c6784db 100644 --- a/test_client/src/views/exam/issue.vue +++ b/test_client/src/views/exam/issue.vue @@ -48,7 +48,7 @@ - + @@ -259,7 +259,7 @@ import Pagination from '@/components/Pagination' const listQuery = { page: 1, limit: 20, - search: '' + ordering: 'opllevel' } export default { components: { Pagination }, @@ -281,13 +281,12 @@ export default { number: [{ required: true, message: '请输入编号', trigger: 'blur' }] }, typeOption:[ - { name: '初级工', value: '初级工' }, - { name: '中级工', value: '中级工'}, - { name: '高级工', value: '高级工' }, - { name: '技师', value: '技师'}, - { name: '高级技师', value: '高级技师' } + { name: '初级工', value: 2 }, + { name: '中级工', value: 1}, + { name: '高级工', value: 0}, + { name: '技师', value: 4}, + { name: '高级技师', value: 3 } ], - } }, computed: {}, @@ -299,6 +298,13 @@ export default { handleAvatarSuccess(res, file) { this.candidate.photo = res.data.path }, + getOpllevelName(value) { + console.log(value, 'value') + const numValue = Number(value) + const item = this.typeOption.find((item) => item.value === numValue); + console.log(item, 'item-name') + return item ? item.name : ''; + }, beforeAvatarUpload(file) { const isLt2M = file.size / 1024 / 1024 < 2 if (!isLt2M) { diff --git a/test_client/src/views/material/material.vue b/test_client/src/views/material/material.vue index 281ee6d..1c43c14 100644 --- a/test_client/src/views/material/material.vue +++ b/test_client/src/views/material/material.vue @@ -1,6 +1,6 @@