From bba8d60dd0d94aee280c922c96aeda2f5f47d067 Mon Sep 17 00:00:00 2001 From: allen_xiong Date: Tue, 24 Mar 2026 14:43:42 +0800 Subject: [PATCH 001/111] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E8=BF=81=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...3_remove_pricetemplate_creator_and_more.py | 57 +++++++++++++++++++ ..._quotationmaster_validity_days_and_more.py | 23 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py create mode 100644 backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py b/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py new file mode 100644 index 0000000..c0c2d63 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 4.2.14 on 2026-03-24 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0002_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='pricetemplate', + name='creator', + ), + migrations.AddField( + model_name='costestimatetemplatebody', + name='version', + field=models.IntegerField(db_column='Version', default=1, verbose_name='版本号'), + ), + migrations.AddField( + model_name='costestimatetemplatehead', + name='status', + field=models.IntegerField(choices=[(0, '未确认'), (1, '已确认'), (2, '作废')], db_column='Status', default=0, verbose_name='状态'), + ), + migrations.AddField( + model_name='costestimatetemplatehead', + name='version', + field=models.IntegerField(db_column='Version', default=1, verbose_name='版本号'), + ), + migrations.AlterField( + model_name='costestimatetemplatebody', + name='template_no', + field=models.CharField(db_column='TemplateNo', max_length=20, verbose_name='模板编号'), + ), + migrations.AlterField( + model_name='costestimatetemplatehead', + name='template_no', + field=models.CharField(db_column='TemplateNo', db_index=True, max_length=20, verbose_name='模板编号'), + ), + migrations.AddIndex( + model_name='costestimatetemplatebody', + index=models.Index(fields=['template_no', 'version'], name='idx_cost_template_body_no_ver'), + ), + migrations.AddIndex( + model_name='costestimatetemplatehead', + index=models.Index(fields=['template_no', 'version'], name='idx_cost_template_head_no_ver'), + ), + migrations.AddConstraint( + model_name='costestimatetemplatehead', + constraint=models.UniqueConstraint(fields=('template_no', 'version'), name='uq_cost_template_head_no_ver'), + ), + migrations.DeleteModel( + name='PriceTemplate', + ), + ] diff --git a/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py b/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py new file mode 100644 index 0000000..e06e690 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.14 on 2026-03-24 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaster', + name='validity_days', + field=models.IntegerField(blank=True, null=True, verbose_name='有效天数'), + ), + migrations.AlterField( + model_name='quotationprocess', + name='unit', + field=models.CharField(blank=True, db_column='Unit', max_length=20, null=True, verbose_name='单位'), + ), + ] From 93d53bd9087d48b60a716c76005863ede5bc97aa Mon Sep 17 00:00:00 2001 From: Developer01 Date: Wed, 25 Mar 2026 14:40:07 +0800 Subject: [PATCH 002/111] =?UTF-8?q?1.=E5=8D=95=E6=8D=AE=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E8=A1=A8=E8=BF=81=E7=A7=BB=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../basicinfo/migrations/0003_systemnorule.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py diff --git a/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py b/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py new file mode 100644 index 0000000..96e1edc --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.14 on 2026-03-25 11:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('basicinfo', '0002_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SystemNoRule', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(help_text='交易厂区(公司)', max_length=20, verbose_name='交易厂区')), + ('rule_code', models.CharField(choices=[('miscQTS', '杂采报价单'), ('miscRFS', '杂采询价单')], help_text='标识一组单号规则', max_length=50, verbose_name='生成单据标识号')), + ('reset_cycle', models.CharField(choices=[('yy', 'YY'), ('yyyy', 'YYYY'), ('yymm', 'YYMM'), ('yyyymm', 'YYYYMM'), ('yymmdd', 'YYMMDD'), ('yyyymmdd', 'YYYYMMDD')], help_text='重置粒度', max_length=16, verbose_name='流水码重置类别')), + ('prefix', models.CharField(blank=True, help_text='单据开头固定字符', max_length=20, null=True, verbose_name='单据头')), + ('factory_code', models.CharField(blank=True, help_text='厂区区分码', max_length=20, null=True, verbose_name='厂区区分码')), + ('seq_length', models.IntegerField(default=4, help_text='流水码长度', verbose_name='流水码长度')), + ('createuser', models.CharField(blank=True, help_text='单据创建人', max_length=50, null=True, verbose_name='单据创建人')), + ('updateuser', models.CharField(blank=True, help_text='单据修改人', max_length=50, null=True, verbose_name='单据修改人')), + ('sequence_date', models.CharField(blank=True, help_text='与 reset_cycle 对应的日期段(如 yymmdd→260324);新建/改重置类别时由服务端写入', max_length=12, null=True, verbose_name='流水日期')), + ('prev_sequence', models.IntegerField(default=0, help_text='上一笔已成功发放的流水号;未发过为 0,与「下一流水码」相差 1', verbose_name='上一流水码')), + ('current_sequence', models.IntegerField(default=1, help_text='下一笔待发流水号;新建/重置周期后为 1,每次取号成功后 +1', verbose_name='下一流水码')), + ('last_generate_user', models.CharField(blank=True, help_text='最后产生人', max_length=50, null=True, verbose_name='单据最后产生人')), + ('last_generate_time', models.DateTimeField(blank=True, help_text='最后产生时间', null=True, verbose_name='单据最后产生时间')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '系统单号规则', + 'verbose_name_plural': '系统单号规则', + 'db_table': 'pis_system_no_rules', + 'ordering': ('-create_datetime',), + 'unique_together': {('company_code', 'rule_code')}, + }, + ), + ] From 81b313d790acdd99c89c246a70b5016d019e7c4e Mon Sep 17 00:00:00 2001 From: Developer01 Date: Wed, 25 Mar 2026 17:41:41 +0800 Subject: [PATCH 003/111] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../basicinfo/migrations/0004_emailnotice.py | 49 +++++++++++++++++++ backend/conf/env.py | 19 +++++++ 2 files changed, 68 insertions(+) create mode 100644 backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py diff --git a/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py b/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py new file mode 100644 index 0000000..9f4d718 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.14 on 2026-03-25 17:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('basicinfo', '0003_systemnorule'), + ] + + operations = [ + migrations.CreateModel( + name='EmailNotice', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('subject', models.CharField(max_length=256, verbose_name='邮件主题')), + ('body', models.TextField(blank=True, null=True, verbose_name='邮件正文')), + ('to_emails', models.JSONField(default=list, verbose_name='收件人')), + ('cc_emails', models.JSONField(default=list, verbose_name='抄送')), + ('bcc_emails', models.JSONField(default=list, verbose_name='密送')), + ('attachments', models.JSONField(default=list, verbose_name='附件')), + ('biz_type', models.CharField(blank=True, max_length=64, null=True, verbose_name='业务类型')), + ('biz_id', models.CharField(blank=True, max_length=128, null=True, verbose_name='业务标识')), + ('status', models.CharField(choices=[('pending', 'pending'), ('sending', 'sending'), ('success', 'success'), ('failed', 'failed')], db_index=True, default='pending', max_length=16, verbose_name='发送状态')), + ('sent_at', models.DateTimeField(blank=True, null=True, verbose_name='发送时间')), + ('message_id', models.CharField(blank=True, max_length=128, null=True, verbose_name='消息ID')), + ('last_error', models.TextField(blank=True, null=True, verbose_name='失败原因')), + ('payload', models.JSONField(default=dict, verbose_name='发送参数')), + ('response', models.JSONField(default=dict, verbose_name='服务端响应')), + ('retry_count', models.IntegerField(default=0, verbose_name='重试次数')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '邮件通知', + 'verbose_name_plural': '邮件通知', + 'db_table': 'pis_proc_email_notice', + 'ordering': ('-update_datetime', '-id'), + }, + ), + ] diff --git a/backend/conf/env.py b/backend/conf/env.py index 3be8fbe..055b4ed 100644 --- a/backend/conf/env.py +++ b/backend/conf/env.py @@ -48,3 +48,22 @@ LOGIN_NO_CAPTCHA_AUTH = True ALLOWED_HOSTS = ["*"] # 列权限中排除App应用 COLUMN_EXCLUDE_APPS = [] + + +# ================================================= # +# ****************** 邮件 配置 ******************* # +# ================================================= # +# 发送邮箱地址(From 显示) +EMAIL_FROM = "eip@avc.co" +# SMTP 登录账号(服务器不支持认证,留空) +EMAIL_HOST_USER = "" +# SMTP 登录密码/授权码(服务器不支持认证,留空) +EMAIL_HOST_PASSWORD = "" +# SMTP 服务器地址 +EMAIL_HOST = "mailflow.avc.co" +# SMTP 端口 +EMAIL_PORT = 25 +# 是否使用 SSL(如果走 STARTTLS,将 EMAIL_USE_SSL 设为 False,EMAIL_USE_TLS=True) +# 服务器不支持STARTTLS,使用纯明文连接 +EMAIL_USE_SSL = False +EMAIL_USE_TLS = False \ No newline at end of file From 23fd346151c39f8d33a97034c9c989d6286545b4 Mon Sep 17 00:00:00 2001 From: Developer01 Date: Thu, 26 Mar 2026 11:48:37 +0800 Subject: [PATCH 004/111] =?UTF-8?q?=E8=AF=A2=E6=8A=A5=E4=BB=B7=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=87=8D=E9=87=8F=E6=A0=8F=E4=BD=8D=E5=92=8C=E8=AF=A2?= =?UTF-8?q?=E4=BB=B7=E5=8D=95=E5=A2=9E=E5=8A=A0=E6=A8=A1=E6=9D=BF=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...late_version_inquirymaterialcost_weight.py | 24 ++++++++++++++ .../0003_quotationmaterial_weight_and_more.py | 33 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py create mode 100644 backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py b/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py new file mode 100644 index 0000000..e330044 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.14 on 2026-03-26 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0003_remove_pricetemplate_creator_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='inquiry', + name='template_version', + field=models.IntegerField(db_column='TemplateVersion', default=1, help_text='与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。', verbose_name='模板版本号'), + preserve_default=False, + ), + migrations.AddField( + model_name='inquirymaterialcost', + name='weight', + field=models.DecimalField(blank=True, db_column='Weight', decimal_places=4, max_digits=12, null=True, verbose_name='重量'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py b/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py new file mode 100644 index 0000000..35ce847 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.14 on 2026-03-26 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0002_quotationmaster_validity_days_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaterial', + name='weight', + field=models.DecimalField(blank=True, db_column='Weight', decimal_places=4, max_digits=12, null=True, verbose_name='重量'), + ), + migrations.AlterField( + model_name='quotationattachment', + name='file_name', + field=models.CharField(db_column='file_name', max_length=100, verbose_name='文件名称'), + ), + migrations.AlterField( + model_name='quotationattachment', + name='file_path', + field=models.CharField(blank=True, db_column='file_path', max_length=200, null=True, verbose_name='文件路径'), + ), + migrations.AlterField( + model_name='quotationmaster', + name='status', + field=models.IntegerField(blank=True, choices=[(1, '待报价'), (2, '报价中'), (3, '已报价'), (4, '已过期')], default=1, help_text='1 待报价 2 已报价 3 已过期;置为已报价请走供应商端 POST quotation_master/{id}/submit/', null=True, verbose_name='状态'), + ), + ] From b87d549f41380e6f7a9ce350a467d30ed5f2206a Mon Sep 17 00:00:00 2001 From: Developer01 Date: Thu, 26 Mar 2026 15:56:19 +0800 Subject: [PATCH 005/111] =?UTF-8?q?=E8=AF=A2=E4=BB=B7=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=A2=9E=E5=8A=A0=E5=88=9D=E5=A7=8B=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0005_alter_inquiry_template_version.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py b/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py new file mode 100644 index 0000000..bebdc0c --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.14 on 2026-03-26 15:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0004_inquiry_template_version_inquirymaterialcost_weight'), + ] + + operations = [ + migrations.AlterField( + model_name='inquiry', + name='template_version', + field=models.IntegerField(db_column='TemplateVersion', default=1, help_text='与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。', verbose_name='模板版本号'), + ), + ] From 5075ff91c274495e74e7f3b25d9c9e676dfffac9 Mon Sep 17 00:00:00 2001 From: Developer01 Date: Fri, 27 Mar 2026 16:09:16 +0800 Subject: [PATCH 006/111] =?UTF-8?q?=E8=AF=A2=E6=8A=A5=E4=BB=B7=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=87=87=E8=B4=AD=E6=96=B9=E5=BC=8F=E3=80=81=E6=8B=9B?= =?UTF-8?q?=E6=A0=87=E5=BC=80=E5=A7=8B=E6=97=B6=E9=97=B4=E3=80=81=E6=8B=9B?= =?UTF-8?q?=E6=A0=87=E7=BB=93=E6=9D=9F=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...nd_time_inquiry_bid_start_time_and_more.py | 140 ++++++++++++++++++ ...4_quotationmaster_bid_end_time_and_more.py | 28 ++++ 2 files changed, 168 insertions(+) create mode 100644 backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py create mode 100644 backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py b/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py new file mode 100644 index 0000000..f9b0b50 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py @@ -0,0 +1,140 @@ +# Generated by Django 4.2.14 on 2026-03-27 16:02 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('miscprocurement', '0005_alter_inquiry_template_version'), + ] + + operations = [ + migrations.AddField( + model_name='inquiry', + name='bid_end_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标截止时间'), + ), + migrations.AddField( + model_name='inquiry', + name='bid_start_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标开始时间'), + ), + migrations.AddField( + model_name='inquiry', + name='buying_method', + field=models.IntegerField(blank=True, choices=[(1, '询价'), (2, '招标')], null=True, verbose_name='采购方式(寻源方式)'), + ), + migrations.CreateModel( + name='RFQOperationLogs', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('operation_type', models.CharField(choices=[(1, '询价单创建'), (2, '询价单确认'), (3, '询价单发布'), (4, '询价单还原'), (5, '询价发送通知'), (6, '报价'), (7, '比议价'), (8, '议价审核提交'), (9, '议价审核完成'), (10, '议价审核驳回')], db_column='OperationType', max_length=20, verbose_name='操作类型')), + ('operation_user', models.CharField(blank=True, db_column='OperationUser', max_length=20, null=True, verbose_name='操作人')), + ('operation_time', models.DateTimeField(blank=True, db_column='OperationTime', null=True, verbose_name='操作时间')), + ('operation_desc', models.TextField(blank=True, db_column='OperationDesc', max_length=200, null=True, verbose_name='操作描述')), + ('inquiry_no', models.CharField(db_column='InquiryNo', db_index=True, max_length=20, verbose_name='询价单号')), + ('quotation_no', models.CharField(db_column='QuotationNo', db_index=True, max_length=20, verbose_name='报价单号')), + ('per_status', models.CharField(blank=True, db_column='PerStatus', max_length=50, null=True, verbose_name='作业前状态')), + ('cur_status', models.TextField(blank=True, db_column='CurStatus', max_length=100, null=True, verbose_name='作业后状态')), + ('is_show_user', models.IntegerField(blank=True, db_column='IsShowUser', help_text='履历显示否(1:是 0:否)', null=True, verbose_name='履历显示否')), + ('purchase_type', models.IntegerField(choices=[(1, '策采'), (2, '杂采')], db_column='PurchaseType', help_text='采购类别(1:策采 2:杂采)', verbose_name='采购类别')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '询价单操作日志', + 'verbose_name_plural': '询价单操作日志', + 'db_table': 'pis_rfq_operation_logs', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'quotation_no'], name='pis_rfq_ope_Inquiry_57e005_idx')], + }, + ), + migrations.CreateModel( + name='MiscNegotiationRecords', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('supplier_code', models.CharField(blank=True, db_column='SupplierCode', max_length=50, null=True, verbose_name='供应商代码')), + ('bargaining_price', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价后价格')), + ('bargaining_time', models.DateTimeField(blank=True, null=True, verbose_name='议价时间')), + ('bargaining_user', models.CharField(blank=True, db_column='BargainingUser', max_length=20, null=True, verbose_name='议价人')), + ('is_awarded', models.IntegerField(blank=True, default=0, help_text='是否中标(1:是 0:否)', null=True, verbose_name='是否中标')), + ('quotation_no', models.CharField(blank=True, db_column='QuotationNo', max_length=20, null=True, verbose_name='报价单号')), + ('total_price_excl_tax', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价前不含税总价')), + ('total_price_incl_tax', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价前含税总价')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采议价记录表', + 'verbose_name_plural': '杂采议价记录表', + 'db_table': 'pis_misc_negotiation_records', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_ne_inquiry_5d842e_idx'), models.Index(fields=['inquiry_no', 'part_id', 'quotation_no'], name='pis_misc_ne_inq_part_qtn_idx')], + }, + ), + migrations.CreateModel( + name='MiscLowPriceHeader', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, help_text='业务主键列之一;逻辑主键与 id 并存', max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('souce_no', models.CharField(blank=True, db_column='SouceNo', max_length=20, null=True, verbose_name='来源单号(报价单&询价单)')), + ('cost_type', models.CharField(choices=[('1', '材料'), ('2', '加工'), ('3', '包装费'), ('4', '运输费'), ('5', '管销研费用'), ('6', '利润率')], db_column='CostType', max_length=50, verbose_name='成本类别')), + ('item_no', models.CharField(db_column='ItemNo', help_text='如:铝等项次名称', max_length=100, verbose_name='项次名')), + ('min_price', models.CharField(db_column='MinPrice', help_text='表结构为 varchar,若需参与运算可在业务层转换', max_length=10, verbose_name='最低价格')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '比价-制程最低价记录主表', + 'verbose_name_plural': '比价-制程最低价记录主表', + 'db_table': 'pis_misc_low_price_header', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_lo_inquiry_51eb98_idx')], + }, + ), + migrations.CreateModel( + name='MiscLowPriceDetail', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('cost_type', models.CharField(db_column='CostType', help_text='仅支持1-材料', max_length=50, verbose_name='成本类别')), + ('material_spec', models.CharField(db_column='materialspec', max_length=50, verbose_name='材料规格')), + ('item_no', models.CharField(choices=[(1, '重量'), (2, '单价')], db_column='ItemNo', help_text='策采:重量、损耗、单价;杂采:重量、单价', max_length=100, verbose_name='项次名')), + ('value', models.CharField(db_column='Value', max_length=10, verbose_name='最小值')), + ('souce_no', models.CharField(db_column='SouceNo', max_length=20, verbose_name='来源单号(报价单&询价单)')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '比价-制程最低价记录次表', + 'verbose_name_plural': '比价-制程最低价记录次表', + 'db_table': 'pis_misc_low_price_detail', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_lo_inquiry_005d6d_idx')], + }, + ), + ] diff --git a/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py b/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py new file mode 100644 index 0000000..0795e18 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.14 on 2026-03-27 16:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0003_quotationmaterial_weight_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaster', + name='bid_end_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标截止时间'), + ), + migrations.AddField( + model_name='quotationmaster', + name='bid_start_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标开始时间'), + ), + migrations.AddField( + model_name='quotationmaster', + name='buying_method', + field=models.IntegerField(blank=True, choices=[(1, '询价'), (2, '招标')], null=True, verbose_name='采购方式(寻源方式)'), + ), + ] From 8211811dd03e20aeba1d723be258764b2be75c4e Mon Sep 17 00:00:00 2001 From: Developer01 Date: Sun, 29 Mar 2026 19:50:25 +0800 Subject: [PATCH 007/111] =?UTF-8?q?=E6=9B=B4=E6=96=B00007=5Falter=5Fmisclo?= =?UTF-8?q?wpriceheader=5Fitem=5Fno.py=E5=92=8C0005=5Falter=5Fquotationmas?= =?UTF-8?q?ter=5Fquote=5Fdeadline=5Fand=5Fmore.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0007_alter_misclowpriceheader_item_no.py | 18 +++++++++++++++ ...quotationmaster_quote_deadline_and_more.py | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py create mode 100644 backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py b/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py new file mode 100644 index 0000000..de4eac2 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.14 on 2026-03-29 19:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='misclowpriceheader', + name='item_no', + field=models.CharField(db_column='ItemNo', help_text='根据成本类别获取询价单对应子表数据行,材料类别 - 材料成本的产品料号 part_id, 加工类别 - 加工工站 process_station', max_length=100, verbose_name='项次名'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py b/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py new file mode 100644 index 0000000..2a9354c --- /dev/null +++ b/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.14 on 2026-03-29 19:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0004_quotationmaster_bid_end_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='quotationmaster', + name='quote_deadline', + field=models.DateTimeField(blank=True, null=True, verbose_name='报价截止时间'), + ), + migrations.AlterField( + model_name='quotationmaster', + name='status', + field=models.IntegerField(blank=True, choices=[(1, '待报价'), (2, '报价中'), (3, '已报价'), (4, '已过期')], default=1, help_text='1 待报价 2 报价中 3 已报价 4 已过期;置为已报价请走 POST quotation_master/{id}/submit/;超时批量置过期及询价单收口见 sync_expired / Inquiry.sync_to_quote_closed_when_no_open_quotations;名单内供应商均已提交见 submit 与 Inquiry.sync_to_quote_closed_when_all_suppliers_quoted。', null=True, verbose_name='状态'), + ), + ] From 8004cb4b5003ea742f4cb2284427e116e7588619 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:44:31 +0800 Subject: [PATCH 008/111] docs: add Jenkins CI/CD design spec --- .../specs/2026-03-31-jenkins-cicd-design.md | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md diff --git a/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md b/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md new file mode 100644 index 0000000..78e5b74 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md @@ -0,0 +1,335 @@ +# Jenkins CI/CD Pipeline 设计 + +## 概述 + +为 PIS 采购询报价管理系统配置 Jenkins 持续集成/持续部署,实现前后端独立构建、镜像推送至 Harbor、私有服务器 Docker Compose 部署。 + +## 技术栈 + +| 组件 | 技术 | +|------|------| +| 后端 | Django 4.2 + DRF + uWSGI | +| 前端 | Node.js + Vite + TypeScript | +| 数据库 | MySQL 8.0 | +| 队列 | Redis 6.2 + Celery | +| 镜像仓库 | Harbor (私有) | +| 部署方式 | Docker Compose | + +## 架构设计 + +``` +GitLab (develop branch) + │ + ▼ +┌─────────────────────────────────────┐ +│ Jenkins Master │ +├─────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Backend Jenkinsfile │ │Web Jenkinsfile │ │ +│ └───────┬───────┘ └───────┬──────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ │ +│ │ Build & │ │ Build & │ │ +│ │ Push to │ │ Push to │ │ +│ │ Harbor │ │ Harbor │ │ +│ └────┬─────┘ └────┬─────┘ │ +│ └────────┬────────┘ │ +│ ▼ │ +│ Docker Compose │ +│ 远程服务器 │ +└─────────────────────────────────────┘ +``` + +## Pipeline 设计 + +### 1. Backend Pipeline (`backend/Jenkinsfile`) + +#### 环境变量 + +| 变量 | 说明 | 示例值 | +|------|------|--------| +| HARBOR_URL | Harbor 仓库地址 | harbor.example.com | +| HARBOR_PROJECT | Harbor 项目名 | pis | +| IMAGE_NAME | 镜像名 | dvadmin3-django | +| SERVER_HOST | 部署服务器IP | 192.168.1.100 | +| COMPOSE_PATH | docker-compose 路径 | /mnt/data/pis | + +#### 构建阶段 + +| Stage | 步骤 | 命令 | +|-------|------|------| +| Checkout | 拉取后端代码 | `git clone` | +| Build | 构建 Django 镜像 | `docker build -f docker_env/django/Dockerfile -t ${IMAGE_TAG}` | +| Push | 推送镜像到 Harbor | `docker push ${IMAGE_TAG}` | +| Deploy | SSH 远程部署 | `ssh ${SERVER_HOST} "cd ${COMPOSE_PATH} && docker-compose pull && docker-compose up -d --no-deps dvadmin3-django"` | + +#### Jenkinsfile 模板 + +```groovy +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-django' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/django/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} """ + cd ${COMPOSE_PATH} && \\ + docker-compose pull dvadmin3-django && \\ + docker-compose up -d --no-deps dvadmin3-django + """ + """ + } + } + } + } + } + + post { + failure { + echo "Pipeline failed! Check Jenkins logs." + } + } +} +``` + +### 2. Frontend Pipeline (`web/Jenkinsfile`) + +#### 构建阶段 + +| Stage | 步骤 | 命令 | +|-------|------|------| +| Checkout | 拉取前端代码 | `git clone` | +| Install | 安装依赖 | `npm install` | +| Build | 构建前端产物 | `npm run build` | +| Build Image | 构建 Nginx 镜像 | `docker build -f docker_env/web/Dockerfile -t ${IMAGE_TAG}` | +| Push | 推送镜像到 Harbor | `docker push ${IMAGE_TAG}` | +| Deploy | SSH 远程部署 | `ssh ${SERVER_HOST} "docker-compose up -d --no-deps dvadmin3-web"` | + +#### Jenkinsfile 模板 + +```groovy +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-web' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Install & Build') { + steps { + sh """ + npm install + npm run build + """ + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/web/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} """ + cd ${COMPOSE_PATH} && \\ + docker-compose pull dvadmin3-web && \\ + docker-compose up -d --no-deps dvadmin3-web + """ + """ + } + } + } + } + } + + post { + failure { + echo "Pipeline failed! Check Jenkins logs." + } + } +} +``` + +## Jenkins 配置要求 + +### 1. 凭证配置 + +#### Harbor 凭证 +- 类型:Username with Password +- ID:`harbor-credentials` +- 用户名/密码:Harbor 仓库账号 + +#### SSH 凭证 +- 类型:SSH Username with Private Key +- ID:`server-ssh-key` +- 用户名:服务器 SSH 用户 +- 私钥:服务器 SSH 私钥 + +### 2. Jenkins Job 配置 + +#### Backend Job +- 类型:Pipeline +- 源码管理:Git +- Repository URL:项目 Git 地址 +- Branch:*/develop +- Script Path:backend/Jenkinsfile + +#### Frontend Job +- 类型:Pipeline +- 源码管理:Git +- Repository URL:项目 Git 地址 +- Branch:*/develop +- Script Path:web/Jenkinsfile + +### 3. Docker 配置 + +Jenkins Agent 需要: +- Docker CLI 已安装 +- Docker daemon 正常运行 +- 可访问 Harbor 仓库 + +## Web Dockerfile 要求 + +需要在 `web/docker_env/web/Dockerfile` 创建 Nginx 镜像: + +```dockerfile +FROM nginx:alpine +COPY dist/ /usr/share/nginx/html/ +COPY docker_env/nginx/conf.d /etc/nginx/conf.d/ +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +## GitLab CI 禁用 + +在 `.gitlab-ci.yml` 开头添加: + +```yaml +# Jenkins 接管 CI/CD,禁用 GitLab CI +workflow: + rules: + - when: never +``` + +或者直接删除 `.gitlab-ci.yml` 文件。 + +## 部署流程 + +1. 开发者 push 代码到 `develop` 分支 +2. Jenkins 自动触发 Pipeline +3. 前后端独立构建镜像 +4. 镜像推送到 Harbor +5. SSH 登录部署服务器 +6. 执行 `docker-compose pull` 拉取新镜像 +7. 执行 `docker-compose up -d` 重启服务 +8. Jenkins Job 显示成功/失败状态 + +## 未来扩展 + +- [ ] 添加单元测试阶段 +- [ ] 添加 SonarQube 代码审查 +- [ ] 添加钉钉/企业微信通知 +- [ ] 添加镜像版本回滚功能 +- [ ] 配置 Blue-Green Deployment From 1ec7253d36ef83e160d8b301570b257cd289fcb9 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:46:47 +0800 Subject: [PATCH 009/111] docs: add Jenkins CI/CD implementation plan --- .../2026-03-31-jenkins-cicd-implementation.md | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md diff --git a/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md b/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md new file mode 100644 index 0000000..c2c275c --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md @@ -0,0 +1,323 @@ +# Jenkins CI/CD Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 为 PIS 项目创建 Jenkins Pipeline,实现前后端独立构建、镜像推送至 Harbor、Docker Compose 部署。 + +**Architecture:** 前后端分离 Pipeline 设计。Backend 和 Frontend 各自独立构建镜像、推送 Harbor、SSH 部署。两个 Pipeline 可独立触发、互不影响。 + +**Tech Stack:** Jenkins Pipeline (Groovy), Docker, Harbor, Docker Compose, SSH + +--- + +## File Structure + +``` +├── backend/ +│ └── Jenkinsfile # 后端构建+部署Pipeline +├── web/ +│ └── Jenkinsfile # 前端构建+部署Pipeline +└── .gitlab-ci.yml # 禁用GitLab CI +``` + +--- + +## Task 1: Create Backend Jenkinsfile + +**Files:** +- Create: `backend/Jenkinsfile` + +- [ ] **Step 1: Create backend/Jenkinsfile** + +```groovy +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-django' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/django/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + cd ${COMPOSE_PATH} + docker-compose pull dvadmin3-django + docker-compose up -d --no-deps dvadmin3-django + EOF + """ + } + } + } + } + } + + post { + failure { + echo "Backend Pipeline failed! Check Jenkins logs." + } + success { + echo "Backend deployed successfully!" + } + } +} +``` + +- [ ] **Step 2: Commit backend/Jenkinsfile** + +```bash +git add backend/Jenkinsfile +git commit -m "feat(jenkins): add backend CI/CD pipeline" +``` + +--- + +## Task 2: Create Frontend Jenkinsfile + +**Files:** +- Create: `web/Jenkinsfile` + +- [ ] **Step 1: Create web/Jenkinsfile** + +```groovy +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-web' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Install & Build') { + steps { + sh """ + cd web + yarn install --registry=https://registry.npmmirror.com + yarn build + """ + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/web/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + cd ${COMPOSE_PATH} + docker-compose pull dvadmin3-web + docker-compose up -d --no-deps dvadmin3-web + EOF + """ + } + } + } + } + } + + post { + failure { + echo "Frontend Pipeline failed! Check Jenkins logs." + } + success { + echo "Frontend deployed successfully!" + } + } +} +``` + +- [ ] **Step 2: Commit web/Jenkinsfile** + +```bash +git add web/Jenkinsfile +git commit -m "feat(jenkins): add frontend CI/CD pipeline" +``` + +--- + +## Task 3: Disable GitLab CI + +**Files:** +- Modify: `.gitlab-ci.yml` + +- [ ] **Step 1: Modify .gitlab-ci.yml to disable GitLab CI** + +```yaml +# Jenkins 接管 CI/CD,禁用 GitLab CI +workflow: + rules: + - when: never +``` + +- [ ] **Step 2: Commit .gitlab-ci.yml change** + +```bash +git add .gitlab-ci.yml +git commit -m "chore: disable GitLab CI, use Jenkins instead" +``` + +--- + +## Task 4: Final Verification + +- [ ] **Step 1: Verify all files created** + +```bash +git status +``` + +Expected output: +``` +new file: backend/Jenkinsfile +new file: web/Jenkinsfile +modified: .gitlab-ci.yml +``` + +--- + +## Jenkins Configuration Summary (Manual Steps Required) + +These steps must be done manually in Jenkins UI: + +### 1. Add Credentials + +**Harbor Credentials:** +- Type: Username with Password +- ID: `harbor-credentials` +- Username: (your Harbor username) +- Password: (your Harbor password) + +**SSH Credentials:** +- Type: SSH Username with Private Key +- ID: `server-ssh-key` +- Username: (server SSH user) +- Private Key: (server SSH private key) + +### 2. Create Backend Job +- New Item → Pipeline +- Name: `pis-backend` +- Branch: `*/develop` +- Script Path: `backend/Jenkinsfile` + +### 3. Create Frontend Job +- New Item → Pipeline +- Name: `pis-web` +- Branch: `*/develop` +- Script Path: `web/Jenkinsfile` + +### 4. Update Pipeline Variables + +In each Jenkinsfile, update these placeholder values: +```groovy +HARBOR_URL = 'harbor.example.com' // 替换为实际Harbor地址 +HARBOR_PROJECT = 'pis' // 替换为实际项目名 +SERVER_HOST = 'your-server-ip' // 替换为实际服务器IP +``` + +--- + +## Spec Coverage Checklist + +- [x] Backend Jenkinsfile (build, push, deploy) +- [x] Frontend Jenkinsfile (build, push, deploy) +- [x] GitLab CI disabled +- [x] Harbor push integration +- [x] SSH deployment +- [x] Manual Jenkins configuration guide From a034a4dc27e6969144f43820a3e6a94f7195afa2 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:48:07 +0800 Subject: [PATCH 010/111] feat(jenkins): add backend CI/CD pipeline --- backend/Jenkinsfile | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 backend/Jenkinsfile diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile new file mode 100644 index 0000000..a3a5542 --- /dev/null +++ b/backend/Jenkinsfile @@ -0,0 +1,81 @@ +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-django' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/django/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} || true + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + cd ${COMPOSE_PATH} + docker-compose pull dvadmin3-django + docker-compose up -d --no-deps dvadmin3-django + EOF + """ + } + } + } + } + } + + post { + failure { + echo "Backend Pipeline failed! Check Jenkins logs." + } + success { + echo "Backend deployed successfully!" + } + } +} From e7f5e6e8fae0dc1689416fbd380360ad6f1287f4 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:50:40 +0800 Subject: [PATCH 011/111] feat(jenkins): add frontend CI/CD pipeline --- web/Jenkinsfile | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 web/Jenkinsfile diff --git a/web/Jenkinsfile b/web/Jenkinsfile new file mode 100644 index 0000000..bcd85e2 --- /dev/null +++ b/web/Jenkinsfile @@ -0,0 +1,91 @@ +pipeline { + agent any + + environment { + HARBOR_URL = 'harbor.example.com' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'dvadmin3-web' + SERVER_HOST = 'your-server-ip' + COMPOSE_PATH = '/mnt/data/pis' + CREDENTIALS_ID = 'harbor-credentials' + SSH_CREDENTIALS_ID = 'server-ssh-key' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Install & Build') { + steps { + sh """ + cd web + yarn install --registry=https://registry.npmmirror.com + yarn build + """ + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f docker_env/web/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} || true + docker logout ${HARBOR_URL} + """ + } + } + } + } + + stage('Deploy') { + steps { + script { + withCredentials([sshUserPrivateKey( + credentialsId: SSH_CREDENTIALS_ID, + usernameVariable: 'SSH_USER', + privateKeyVariable: 'SSH_KEY' + )]) { + sh """ + ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + cd ${COMPOSE_PATH} + docker-compose pull dvadmin3-web + docker-compose up -d --no-deps dvadmin3-web + EOF + """ + } + } + } + } + } + + post { + failure { + echo "Frontend Pipeline failed! Check Jenkins logs." + } + success { + echo "Frontend deployed successfully!" + } + } +} From 853223aa8b88682350bb5d39a2011b3aeaf67958 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:52:19 +0800 Subject: [PATCH 012/111] chore: disable GitLab CI, use Jenkins instead --- .gitlab-ci.yml | 41 +++-------------------------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 876ba83..57feb91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,39 +1,4 @@ -stages: - - deploy - -deploy-to-server: - stage: deploy - tags: - - pis-deploy-code # 使用指定标签的 runner - image: alpine:latest # 轻量级基础镜像 - before_script: - # 更换 Alpine 镜像源为阿里云(国内加速) - - sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories - # 安装 rsync - - apk add --no-cache rsync - # 确保目标目录存在(若不存在则创建,用于后续判断和备份逻辑的基础) - - mkdir -p /mnt/data/pis/python3/DV3Admin - script: - # --- 新增:备份逻辑 --- - # 定义变量 - - TARGET_DIR="/mnt/data/pis/python3/DV3Admin" - - BACKUP_DATE=$(date +%Y%m%d) - - BACKUP_DIR="${TARGET_DIR}_bak_${BACKUP_DATE}" - - # 检查目标目录是否为空或是否存在有效文件,若存在则进行备份 - # 使用 ls -A 检查目录下是否有非隐藏文件或所有文件(包括隐藏),如果有内容则备份 - - if [ -d "$TARGET_DIR" ] && [ "$(ls -A $TARGET_DIR)" ]; then - echo "备份文件至路径 $BACKUP_DIR"; - cp -a "$TARGET_DIR" "$BACKUP_DIR"; - mkdir -p "$TARGET_DIR"; - else - echo "没有文件需要部署或文件夹为空"; - fi - - # --- 新增:代码同步逻辑(排除特定文件) --- - # 修复:将多行合并为一行,避免反斜杠被当作文件名处理 - - rsync -avz --delete --exclude='.git/' --exclude='.gitignore' --exclude='.gitattributes' --exclude='.gitlab-ci.yml' ./ "$TARGET_DIR/" - - - echo "代码部署成功" +# Jenkins 接管 CI/CD,禁用 GitLab CI +workflow: rules: - - when: manual # 手动触发流水线 \ No newline at end of file + - when: never From 530fde4a28d9fb14ec090efd3ddc6473eb624062 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 16:52:19 +0800 Subject: [PATCH 013/111] chore: disable GitLab CI, use Jenkins instead --- .gitlab-ci.yml | 41 +-- .../2026-03-31-jenkins-cicd-implementation.md | 323 ----------------- .../specs/2026-03-31-jenkins-cicd-design.md | 335 ------------------ 3 files changed, 3 insertions(+), 696 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md delete mode 100644 docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 876ba83..57feb91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,39 +1,4 @@ -stages: - - deploy - -deploy-to-server: - stage: deploy - tags: - - pis-deploy-code # 使用指定标签的 runner - image: alpine:latest # 轻量级基础镜像 - before_script: - # 更换 Alpine 镜像源为阿里云(国内加速) - - sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories - # 安装 rsync - - apk add --no-cache rsync - # 确保目标目录存在(若不存在则创建,用于后续判断和备份逻辑的基础) - - mkdir -p /mnt/data/pis/python3/DV3Admin - script: - # --- 新增:备份逻辑 --- - # 定义变量 - - TARGET_DIR="/mnt/data/pis/python3/DV3Admin" - - BACKUP_DATE=$(date +%Y%m%d) - - BACKUP_DIR="${TARGET_DIR}_bak_${BACKUP_DATE}" - - # 检查目标目录是否为空或是否存在有效文件,若存在则进行备份 - # 使用 ls -A 检查目录下是否有非隐藏文件或所有文件(包括隐藏),如果有内容则备份 - - if [ -d "$TARGET_DIR" ] && [ "$(ls -A $TARGET_DIR)" ]; then - echo "备份文件至路径 $BACKUP_DIR"; - cp -a "$TARGET_DIR" "$BACKUP_DIR"; - mkdir -p "$TARGET_DIR"; - else - echo "没有文件需要部署或文件夹为空"; - fi - - # --- 新增:代码同步逻辑(排除特定文件) --- - # 修复:将多行合并为一行,避免反斜杠被当作文件名处理 - - rsync -avz --delete --exclude='.git/' --exclude='.gitignore' --exclude='.gitattributes' --exclude='.gitlab-ci.yml' ./ "$TARGET_DIR/" - - - echo "代码部署成功" +# Jenkins 接管 CI/CD,禁用 GitLab CI +workflow: rules: - - when: manual # 手动触发流水线 \ No newline at end of file + - when: never diff --git a/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md b/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md deleted file mode 100644 index c2c275c..0000000 --- a/docs/superpowers/plans/2026-03-31-jenkins-cicd-implementation.md +++ /dev/null @@ -1,323 +0,0 @@ -# Jenkins CI/CD Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** 为 PIS 项目创建 Jenkins Pipeline,实现前后端独立构建、镜像推送至 Harbor、Docker Compose 部署。 - -**Architecture:** 前后端分离 Pipeline 设计。Backend 和 Frontend 各自独立构建镜像、推送 Harbor、SSH 部署。两个 Pipeline 可独立触发、互不影响。 - -**Tech Stack:** Jenkins Pipeline (Groovy), Docker, Harbor, Docker Compose, SSH - ---- - -## File Structure - -``` -├── backend/ -│ └── Jenkinsfile # 后端构建+部署Pipeline -├── web/ -│ └── Jenkinsfile # 前端构建+部署Pipeline -└── .gitlab-ci.yml # 禁用GitLab CI -``` - ---- - -## Task 1: Create Backend Jenkinsfile - -**Files:** -- Create: `backend/Jenkinsfile` - -- [ ] **Step 1: Create backend/Jenkinsfile** - -```groovy -pipeline { - agent any - - environment { - HARBOR_URL = 'harbor.example.com' - HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-django' - SERVER_HOST = 'your-server-ip' - COMPOSE_PATH = '/mnt/data/pis' - CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' - } - - stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Build Image') { - steps { - script { - def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" - env.IMAGE_TAG = imageTag - sh """ - docker build -f docker_env/django/Dockerfile \ - -t ${imageTag} . - """ - } - } - } - - stage('Push to Harbor') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: CREDENTIALS_ID, - usernameVariable: 'HARBOR_USER', - passwordVariable: 'HARBOR_PASS' - )]) { - sh """ - echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin - docker push ${IMAGE_TAG} - docker logout ${HARBOR_URL} - """ - } - } - } - } - - stage('Deploy') { - steps { - script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, - usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' - )]) { - sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' - cd ${COMPOSE_PATH} - docker-compose pull dvadmin3-django - docker-compose up -d --no-deps dvadmin3-django - EOF - """ - } - } - } - } - } - - post { - failure { - echo "Backend Pipeline failed! Check Jenkins logs." - } - success { - echo "Backend deployed successfully!" - } - } -} -``` - -- [ ] **Step 2: Commit backend/Jenkinsfile** - -```bash -git add backend/Jenkinsfile -git commit -m "feat(jenkins): add backend CI/CD pipeline" -``` - ---- - -## Task 2: Create Frontend Jenkinsfile - -**Files:** -- Create: `web/Jenkinsfile` - -- [ ] **Step 1: Create web/Jenkinsfile** - -```groovy -pipeline { - agent any - - environment { - HARBOR_URL = 'harbor.example.com' - HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-web' - SERVER_HOST = 'your-server-ip' - COMPOSE_PATH = '/mnt/data/pis' - CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' - } - - stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Install & Build') { - steps { - sh """ - cd web - yarn install --registry=https://registry.npmmirror.com - yarn build - """ - } - } - - stage('Build Image') { - steps { - script { - def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" - env.IMAGE_TAG = imageTag - sh """ - docker build -f docker_env/web/Dockerfile \ - -t ${imageTag} . - """ - } - } - } - - stage('Push to Harbor') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: CREDENTIALS_ID, - usernameVariable: 'HARBOR_USER', - passwordVariable: 'HARBOR_PASS' - )]) { - sh """ - echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin - docker push ${IMAGE_TAG} - docker logout ${HARBOR_URL} - """ - } - } - } - } - - stage('Deploy') { - steps { - script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, - usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' - )]) { - sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' - cd ${COMPOSE_PATH} - docker-compose pull dvadmin3-web - docker-compose up -d --no-deps dvadmin3-web - EOF - """ - } - } - } - } - } - - post { - failure { - echo "Frontend Pipeline failed! Check Jenkins logs." - } - success { - echo "Frontend deployed successfully!" - } - } -} -``` - -- [ ] **Step 2: Commit web/Jenkinsfile** - -```bash -git add web/Jenkinsfile -git commit -m "feat(jenkins): add frontend CI/CD pipeline" -``` - ---- - -## Task 3: Disable GitLab CI - -**Files:** -- Modify: `.gitlab-ci.yml` - -- [ ] **Step 1: Modify .gitlab-ci.yml to disable GitLab CI** - -```yaml -# Jenkins 接管 CI/CD,禁用 GitLab CI -workflow: - rules: - - when: never -``` - -- [ ] **Step 2: Commit .gitlab-ci.yml change** - -```bash -git add .gitlab-ci.yml -git commit -m "chore: disable GitLab CI, use Jenkins instead" -``` - ---- - -## Task 4: Final Verification - -- [ ] **Step 1: Verify all files created** - -```bash -git status -``` - -Expected output: -``` -new file: backend/Jenkinsfile -new file: web/Jenkinsfile -modified: .gitlab-ci.yml -``` - ---- - -## Jenkins Configuration Summary (Manual Steps Required) - -These steps must be done manually in Jenkins UI: - -### 1. Add Credentials - -**Harbor Credentials:** -- Type: Username with Password -- ID: `harbor-credentials` -- Username: (your Harbor username) -- Password: (your Harbor password) - -**SSH Credentials:** -- Type: SSH Username with Private Key -- ID: `server-ssh-key` -- Username: (server SSH user) -- Private Key: (server SSH private key) - -### 2. Create Backend Job -- New Item → Pipeline -- Name: `pis-backend` -- Branch: `*/develop` -- Script Path: `backend/Jenkinsfile` - -### 3. Create Frontend Job -- New Item → Pipeline -- Name: `pis-web` -- Branch: `*/develop` -- Script Path: `web/Jenkinsfile` - -### 4. Update Pipeline Variables - -In each Jenkinsfile, update these placeholder values: -```groovy -HARBOR_URL = 'harbor.example.com' // 替换为实际Harbor地址 -HARBOR_PROJECT = 'pis' // 替换为实际项目名 -SERVER_HOST = 'your-server-ip' // 替换为实际服务器IP -``` - ---- - -## Spec Coverage Checklist - -- [x] Backend Jenkinsfile (build, push, deploy) -- [x] Frontend Jenkinsfile (build, push, deploy) -- [x] GitLab CI disabled -- [x] Harbor push integration -- [x] SSH deployment -- [x] Manual Jenkins configuration guide diff --git a/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md b/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md deleted file mode 100644 index 78e5b74..0000000 --- a/docs/superpowers/specs/2026-03-31-jenkins-cicd-design.md +++ /dev/null @@ -1,335 +0,0 @@ -# Jenkins CI/CD Pipeline 设计 - -## 概述 - -为 PIS 采购询报价管理系统配置 Jenkins 持续集成/持续部署,实现前后端独立构建、镜像推送至 Harbor、私有服务器 Docker Compose 部署。 - -## 技术栈 - -| 组件 | 技术 | -|------|------| -| 后端 | Django 4.2 + DRF + uWSGI | -| 前端 | Node.js + Vite + TypeScript | -| 数据库 | MySQL 8.0 | -| 队列 | Redis 6.2 + Celery | -| 镜像仓库 | Harbor (私有) | -| 部署方式 | Docker Compose | - -## 架构设计 - -``` -GitLab (develop branch) - │ - ▼ -┌─────────────────────────────────────┐ -│ Jenkins Master │ -├─────────────────────────────────────┤ -│ ┌──────────────┐ ┌──────────────┐ │ -│ │ Backend Jenkinsfile │ │Web Jenkinsfile │ │ -│ └───────┬───────┘ └───────┬──────┘ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌──────────┐ ┌──────────┐ │ -│ │ Build & │ │ Build & │ │ -│ │ Push to │ │ Push to │ │ -│ │ Harbor │ │ Harbor │ │ -│ └────┬─────┘ └────┬─────┘ │ -│ └────────┬────────┘ │ -│ ▼ │ -│ Docker Compose │ -│ 远程服务器 │ -└─────────────────────────────────────┘ -``` - -## Pipeline 设计 - -### 1. Backend Pipeline (`backend/Jenkinsfile`) - -#### 环境变量 - -| 变量 | 说明 | 示例值 | -|------|------|--------| -| HARBOR_URL | Harbor 仓库地址 | harbor.example.com | -| HARBOR_PROJECT | Harbor 项目名 | pis | -| IMAGE_NAME | 镜像名 | dvadmin3-django | -| SERVER_HOST | 部署服务器IP | 192.168.1.100 | -| COMPOSE_PATH | docker-compose 路径 | /mnt/data/pis | - -#### 构建阶段 - -| Stage | 步骤 | 命令 | -|-------|------|------| -| Checkout | 拉取后端代码 | `git clone` | -| Build | 构建 Django 镜像 | `docker build -f docker_env/django/Dockerfile -t ${IMAGE_TAG}` | -| Push | 推送镜像到 Harbor | `docker push ${IMAGE_TAG}` | -| Deploy | SSH 远程部署 | `ssh ${SERVER_HOST} "cd ${COMPOSE_PATH} && docker-compose pull && docker-compose up -d --no-deps dvadmin3-django"` | - -#### Jenkinsfile 模板 - -```groovy -pipeline { - agent any - - environment { - HARBOR_URL = 'harbor.example.com' - HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-django' - SERVER_HOST = 'your-server-ip' - COMPOSE_PATH = '/mnt/data/pis' - CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' - } - - stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Build Image') { - steps { - script { - def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" - env.IMAGE_TAG = imageTag - sh """ - docker build -f docker_env/django/Dockerfile \ - -t ${imageTag} . - """ - } - } - } - - stage('Push to Harbor') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: CREDENTIALS_ID, - usernameVariable: 'HARBOR_USER', - passwordVariable: 'HARBOR_PASS' - )]) { - sh """ - echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin - docker push ${IMAGE_TAG} - docker logout ${HARBOR_URL} - """ - } - } - } - } - - stage('Deploy') { - steps { - script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, - usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' - )]) { - sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} """ - cd ${COMPOSE_PATH} && \\ - docker-compose pull dvadmin3-django && \\ - docker-compose up -d --no-deps dvadmin3-django - """ - """ - } - } - } - } - } - - post { - failure { - echo "Pipeline failed! Check Jenkins logs." - } - } -} -``` - -### 2. Frontend Pipeline (`web/Jenkinsfile`) - -#### 构建阶段 - -| Stage | 步骤 | 命令 | -|-------|------|------| -| Checkout | 拉取前端代码 | `git clone` | -| Install | 安装依赖 | `npm install` | -| Build | 构建前端产物 | `npm run build` | -| Build Image | 构建 Nginx 镜像 | `docker build -f docker_env/web/Dockerfile -t ${IMAGE_TAG}` | -| Push | 推送镜像到 Harbor | `docker push ${IMAGE_TAG}` | -| Deploy | SSH 远程部署 | `ssh ${SERVER_HOST} "docker-compose up -d --no-deps dvadmin3-web"` | - -#### Jenkinsfile 模板 - -```groovy -pipeline { - agent any - - environment { - HARBOR_URL = 'harbor.example.com' - HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-web' - SERVER_HOST = 'your-server-ip' - COMPOSE_PATH = '/mnt/data/pis' - CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' - } - - stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Install & Build') { - steps { - sh """ - npm install - npm run build - """ - } - } - - stage('Build Image') { - steps { - script { - def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" - env.IMAGE_TAG = imageTag - sh """ - docker build -f docker_env/web/Dockerfile \ - -t ${imageTag} . - """ - } - } - } - - stage('Push to Harbor') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: CREDENTIALS_ID, - usernameVariable: 'HARBOR_USER', - passwordVariable: 'HARBOR_PASS' - )]) { - sh """ - echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin - docker push ${IMAGE_TAG} - docker logout ${HARBOR_URL} - """ - } - } - } - } - - stage('Deploy') { - steps { - script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, - usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' - )]) { - sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} """ - cd ${COMPOSE_PATH} && \\ - docker-compose pull dvadmin3-web && \\ - docker-compose up -d --no-deps dvadmin3-web - """ - """ - } - } - } - } - } - - post { - failure { - echo "Pipeline failed! Check Jenkins logs." - } - } -} -``` - -## Jenkins 配置要求 - -### 1. 凭证配置 - -#### Harbor 凭证 -- 类型:Username with Password -- ID:`harbor-credentials` -- 用户名/密码:Harbor 仓库账号 - -#### SSH 凭证 -- 类型:SSH Username with Private Key -- ID:`server-ssh-key` -- 用户名:服务器 SSH 用户 -- 私钥:服务器 SSH 私钥 - -### 2. Jenkins Job 配置 - -#### Backend Job -- 类型:Pipeline -- 源码管理:Git -- Repository URL:项目 Git 地址 -- Branch:*/develop -- Script Path:backend/Jenkinsfile - -#### Frontend Job -- 类型:Pipeline -- 源码管理:Git -- Repository URL:项目 Git 地址 -- Branch:*/develop -- Script Path:web/Jenkinsfile - -### 3. Docker 配置 - -Jenkins Agent 需要: -- Docker CLI 已安装 -- Docker daemon 正常运行 -- 可访问 Harbor 仓库 - -## Web Dockerfile 要求 - -需要在 `web/docker_env/web/Dockerfile` 创建 Nginx 镜像: - -```dockerfile -FROM nginx:alpine -COPY dist/ /usr/share/nginx/html/ -COPY docker_env/nginx/conf.d /etc/nginx/conf.d/ -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -``` - -## GitLab CI 禁用 - -在 `.gitlab-ci.yml` 开头添加: - -```yaml -# Jenkins 接管 CI/CD,禁用 GitLab CI -workflow: - rules: - - when: never -``` - -或者直接删除 `.gitlab-ci.yml` 文件。 - -## 部署流程 - -1. 开发者 push 代码到 `develop` 分支 -2. Jenkins 自动触发 Pipeline -3. 前后端独立构建镜像 -4. 镜像推送到 Harbor -5. SSH 登录部署服务器 -6. 执行 `docker-compose pull` 拉取新镜像 -7. 执行 `docker-compose up -d` 重启服务 -8. Jenkins Job 显示成功/失败状态 - -## 未来扩展 - -- [ ] 添加单元测试阶段 -- [ ] 添加 SonarQube 代码审查 -- [ ] 添加钉钉/企业微信通知 -- [ ] 添加镜像版本回滚功能 -- [ ] 配置 Blue-Green Deployment From c228095c8fe11102a8d9b9e59d644ee36b5ae74a Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:25:18 +0800 Subject: [PATCH 014/111] fix(jenkins): install Node.js and yarn if not present --- web/Jenkinsfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/Jenkinsfile b/web/Jenkinsfile index bcd85e2..e8579f1 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -22,6 +22,12 @@ pipeline { steps { sh """ cd web + # 安装 Node.js 和 yarn (如果不存在) + if ! command -v yarn &> /dev/null; then + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + npm install -g yarn + fi yarn install --registry=https://registry.npmmirror.com yarn build """ From 4837d799928b45229f352c29265f537fc6397d73 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:30:58 +0800 Subject: [PATCH 015/111] perf: use docker.1ms.run mirror to speed up builds --- docker_env/django/Dockerfile | 24 ++++++++++++++++-------- docker_env/web/Dockerfile | 12 +++++++++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docker_env/django/Dockerfile b/docker_env/django/Dockerfile index e11bdf0..90ea534 100644 --- a/docker_env/django/Dockerfile +++ b/docker_env/django/Dockerfile @@ -1,15 +1,15 @@ -# 使用非Alpine源,兼容SQL Server安装 -FROM python:3.10-slim +# 第一阶段:构建依赖 +FROM docker.1ms.run/python:3.10-slim as builder -# 3. 安装系统依赖 (替换原来的 apk 命令) -# 注意:这里安装了构建依赖和微软驱动所需的依赖 +WORKDIR /backend + +# 安装构建依赖 RUN apt-get update && apt-get install -y \ bash \ git \ curl \ gnupg \ ca-certificates \ - # 以下为原 apk 中的开发库对应的 apt 包名 (可能需微调) libjpeg-dev \ libmariadb-dev-compat \ default-libmysqlclient-dev \ @@ -26,18 +26,26 @@ RUN apt-get update && apt-get install -y \ unixodbc-dev \ && rm -rf /var/lib/apt/lists/* -# 4. 添加微软软件源和密钥 (解决 msodbcsql17 安装问题) +# 添加微软源 RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update \ && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \ && rm -rf /var/lib/apt/lists/* -WORKDIR /backend COPY ./backend/ . RUN ls ./conf/ RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' RUN sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py -RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt +RUN pip install --no-cache-dir -i https://docker.1ms.run/simple/ -r requirements.txt + +# 第二阶段:运行时镜像 +FROM docker.1ms.run/python:3.10-slim + +COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /backend /backend + +WORKDIR /backend CMD ["sh","docker_start.sh"] diff --git a/docker_env/web/Dockerfile b/docker_env/web/Dockerfile index a10f353..cdb646d 100644 --- a/docker_env/web/Dockerfile +++ b/docker_env/web/Dockerfile @@ -1,9 +1,15 @@ -FROM node:20-alpine +# 第一阶段:构建 +FROM docker.1ms.run/node:20-alpine as builder + WORKDIR /web/ COPY web/. . + +# 使用 npmmirror 加速 yarn install RUN yarn install --registry=https://registry.npmmirror.com RUN yarn build -FROM nginx:alpine +# 第二阶段:Nginx 运行时 +FROM docker.1ms.run/nginx:alpine + COPY ./docker_env/nginx/my.conf /etc/nginx/conf.d/my.conf -COPY --from=0 /web/dist /usr/share/nginx/html +COPY --from=builder /web/dist /usr/share/nginx/html From e918235b4f794ff86b4e28980dbb8cc4ad4bfb13 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:31:38 +0800 Subject: [PATCH 016/111] fix: use correct pip mirror (tsinghua) instead of docker mirror --- docker_env/django/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_env/django/Dockerfile b/docker_env/django/Dockerfile index 90ea534..ac3a91e 100644 --- a/docker_env/django/Dockerfile +++ b/docker_env/django/Dockerfile @@ -38,7 +38,7 @@ RUN ls ./conf/ RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' RUN sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py -RUN pip install --no-cache-dir -i https://docker.1ms.run/simple/ -r requirements.txt +RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim From 10563ef779c1207e2edce6969cc6265a21bb1636 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:34:29 +0800 Subject: [PATCH 017/111] perf: optimize docker build with layer consolidation and caching --- docker_env/django/Dockerfile | 51 +++++++++++++++--------------------- docker_env/web/Dockerfile | 13 ++++++--- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/docker_env/django/Dockerfile b/docker_env/django/Dockerfile index ac3a91e..dfa74e7 100644 --- a/docker_env/django/Dockerfile +++ b/docker_env/django/Dockerfile @@ -1,45 +1,36 @@ -# 第一阶段:构建依赖 +# 第一阶段:构建 FROM docker.1ms.run/python:3.10-slim as builder WORKDIR /backend -# 安装构建依赖 +# 安装系统依赖(合并为一个RUN减少层数) RUN apt-get update && apt-get install -y \ - bash \ - git \ - curl \ - gnupg \ - ca-certificates \ - libjpeg-dev \ - libmariadb-dev-compat \ - default-libmysqlclient-dev \ - build-essential \ - libffi-dev \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libpcre2-dev \ - libncurses5-dev \ - libreadline-dev \ - tk-dev \ - libpq-dev \ - unixodbc-dev \ - && rm -rf /var/lib/apt/lists/* - -# 添加微软源 -RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ + bash git curl gnupg ca-certificates \ + libjpeg-dev libmariadb-dev-compat default-libmysqlclient-dev \ + build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev \ + libpcre2-dev libncurses5-dev libreadline-dev tk-dev libpq-dev unixodbc-dev \ + && rm -rf /var/lib/apt/lists/* \ + && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update \ && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \ && rm -rf /var/lib/apt/lists/* -COPY ./backend/ . -RUN ls ./conf/ -RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' -RUN sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py -RUN sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py +# 先复制依赖文件,利用缓存 +COPY ./backend/requirements.txt . + +# 安装 Python 依赖(带缓存加速) RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt +# 复制剩余代码 +COPY ./backend/ . + +# 配置 +RUN ls ./conf/ \ + && awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' \ + && sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py \ + && sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py + # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim diff --git a/docker_env/web/Dockerfile b/docker_env/web/Dockerfile index cdb646d..16a6dcc 100644 --- a/docker_env/web/Dockerfile +++ b/docker_env/web/Dockerfile @@ -1,11 +1,18 @@ # 第一阶段:构建 FROM docker.1ms.run/node:20-alpine as builder -WORKDIR /web/ -COPY web/. . +WORKDIR /web -# 使用 npmmirror 加速 yarn install +# 先复制 package.json 和 yarn.lock +COPY web/package.json web/yarn.lock* ./ + +# 安装依赖(利用缓存) RUN yarn install --registry=https://registry.npmmirror.com + +# 复制源代码 +COPY web/ . + +# 构建 RUN yarn build # 第二阶段:Nginx 运行时 From 6a17de2231c752bd65777ddb5df4fcd43c40c589 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:36:33 +0800 Subject: [PATCH 018/111] chore: update HARBOR_URL to actual Harbor address --- backend/Jenkinsfile | 2 +- web/Jenkinsfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index a3a5542..6731d0e 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any environment { - HARBOR_URL = 'harbor.example.com' + HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'dvadmin3-django' SERVER_HOST = 'your-server-ip' diff --git a/web/Jenkinsfile b/web/Jenkinsfile index e8579f1..5418035 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any environment { - HARBOR_URL = 'harbor.example.com' + HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'dvadmin3-web' SERVER_HOST = 'your-server-ip' From 1b986905679b0caf406fbafce49fd903ea95fb25 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:37:12 +0800 Subject: [PATCH 019/111] chore: update SERVER_HOST to actual deployment server IP --- backend/Jenkinsfile | 2 +- web/Jenkinsfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index 6731d0e..a79cb3d 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'dvadmin3-django' - SERVER_HOST = 'your-server-ip' + SERVER_HOST = '192.168.80.90' COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' SSH_CREDENTIALS_ID = 'server-ssh-key' diff --git a/web/Jenkinsfile b/web/Jenkinsfile index 5418035..7adcd7e 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'dvadmin3-web' - SERVER_HOST = 'your-server-ip' + SERVER_HOST = '192.168.80.90' COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' SSH_CREDENTIALS_ID = 'server-ssh-key' From 05d6932cbe49bc1c8e97c57bb05e22647c2921fa Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 17:38:01 +0800 Subject: [PATCH 020/111] chore: rename image names to pis-backend and pis-web --- backend/Jenkinsfile | 2 +- web/Jenkinsfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index a79cb3d..d062ee7 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { environment { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-django' + IMAGE_NAME = 'pis-backend' SERVER_HOST = '192.168.80.90' COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' diff --git a/web/Jenkinsfile b/web/Jenkinsfile index 7adcd7e..dbc4f10 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { environment { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' - IMAGE_NAME = 'dvadmin3-web' + IMAGE_NAME = 'pis-web' SERVER_HOST = '192.168.80.90' COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' From 66c7721ced4304388a9616f7ea89a84f9d74f732 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 19:22:14 +0800 Subject: [PATCH 021/111] fix: use usernamePassword instead of sshUserPrivateKey for deploy --- backend/Jenkinsfile | 12 ++++++------ web/Jenkinsfile | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index d062ee7..95581e8 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -52,16 +52,16 @@ pipeline { stage('Deploy') { steps { script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, + withCredentials([usernamePassword( + credentialsId: 'server-ssh-creds', usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' + passwordVariable: 'SSH_PASS' )]) { sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + sshpass -p "${SSH_PASS}" ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' cd ${COMPOSE_PATH} - docker-compose pull dvadmin3-django - docker-compose up -d --no-deps dvadmin3-django + docker-compose pull pis-backend + docker-compose up -d --no-deps pis-backend EOF """ } diff --git a/web/Jenkinsfile b/web/Jenkinsfile index dbc4f10..c0acde6 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -68,16 +68,16 @@ pipeline { stage('Deploy') { steps { script { - withCredentials([sshUserPrivateKey( - credentialsId: SSH_CREDENTIALS_ID, + withCredentials([usernamePassword( + credentialsId: 'server-ssh-creds', usernameVariable: 'SSH_USER', - privateKeyVariable: 'SSH_KEY' + passwordVariable: 'SSH_PASS' )]) { sh """ - ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' + sshpass -p "${SSH_PASS}" ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' cd ${COMPOSE_PATH} - docker-compose pull dvadmin3-web - docker-compose up -d --no-deps dvadmin3-web + docker-compose pull pis-web + docker-compose up -d --no-deps pis-web EOF """ } From 37027bf0682c968ee8d7ed2e42fa8dc78c1e2a64 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 19:23:42 +0800 Subject: [PATCH 022/111] chore: remove deploy stage, only build and push to Harbor --- backend/Jenkinsfile | 25 +------------------------ web/Jenkinsfile | 25 +------------------------ 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index 95581e8..8e59d25 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -5,10 +5,7 @@ pipeline { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'pis-backend' - SERVER_HOST = '192.168.80.90' - COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' } stages { @@ -48,26 +45,6 @@ pipeline { } } } - - stage('Deploy') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: 'server-ssh-creds', - usernameVariable: 'SSH_USER', - passwordVariable: 'SSH_PASS' - )]) { - sh """ - sshpass -p "${SSH_PASS}" ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' - cd ${COMPOSE_PATH} - docker-compose pull pis-backend - docker-compose up -d --no-deps pis-backend - EOF - """ - } - } - } - } } post { @@ -75,7 +52,7 @@ pipeline { echo "Backend Pipeline failed! Check Jenkins logs." } success { - echo "Backend deployed successfully!" + echo "Backend image pushed to Harbor: ${IMAGE_TAG}" } } } diff --git a/web/Jenkinsfile b/web/Jenkinsfile index c0acde6..de32cca 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -5,10 +5,7 @@ pipeline { HARBOR_URL = '192.168.80.90:8090' HARBOR_PROJECT = 'pis' IMAGE_NAME = 'pis-web' - SERVER_HOST = '192.168.80.90' - COMPOSE_PATH = '/mnt/data/pis' CREDENTIALS_ID = 'harbor-credentials' - SSH_CREDENTIALS_ID = 'server-ssh-key' } stages { @@ -64,26 +61,6 @@ pipeline { } } } - - stage('Deploy') { - steps { - script { - withCredentials([usernamePassword( - credentialsId: 'server-ssh-creds', - usernameVariable: 'SSH_USER', - passwordVariable: 'SSH_PASS' - )]) { - sh """ - sshpass -p "${SSH_PASS}" ssh -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER_HOST} << 'EOF' - cd ${COMPOSE_PATH} - docker-compose pull pis-web - docker-compose up -d --no-deps pis-web - EOF - """ - } - } - } - } } post { @@ -91,7 +68,7 @@ pipeline { echo "Frontend Pipeline failed! Check Jenkins logs." } success { - echo "Frontend deployed successfully!" + echo "Frontend image pushed to Harbor: ${IMAGE_TAG}" } } } From a5e7230fd7c696733cde227187b63e0a208a7347 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Tue, 31 Mar 2026 19:35:25 +0800 Subject: [PATCH 023/111] docs: add CLAUDE.md for Claude Code guidance Add project documentation for future Claude Code sessions covering backend/frontend commands, architecture, and configuration. --- CLAUDE.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..978c5f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,105 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is **django-vue3-admin** based project with custom procurement/supplier management modules. + +- **Backend**: Django 4.2.14 + Django REST Framework +- **Frontend**: Vue 3 + TypeScript + Vite + Element Plus + FastCRUD +- **Database**: MySQL 8.0 (configured via `backend/conf/env.py`) +- **Cache/Queue**: Redis + Celery +- **Real-time**: Django Channels (WebSocket) +- **Auth**: JWT via SimpleJWT + +## Directory Structure + +``` +pis/ +├── backend/ +│ ├── application/ # Django project settings +│ │ ├── settings.py # Main settings (imports from conf/env.py) +│ │ ├── urls.py # Root URL routing +│ │ ├── asgi.py # ASGI config (WebSocket support) +│ │ ├── wsgi.py # WSGI config +│ │ └── celery.py # Celery configuration +│ ├── apps/ +│ │ ├── pisadmin/ # Custom procurement app +│ │ │ ├── basicinfo/ # Company, currency, supplier, unit, system no +│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections +│ │ └── pissupplier/ # Supplier quotation module +│ ├── dvadmin/ # Core RBAC system +│ │ └── system/ # Users, roles, menus, departments, logs +│ └── conf/ +│ └── env.py # Environment configuration (DB, Redis credentials) +├── web/ # Vue 3 frontend +│ └── src/ +│ ├── views/pisadmin/ # Custom procurement pages +│ ├── views/pissupplier/# Supplier pages +│ └── views/system/ # Admin system pages +└── docker-compose.yml # Docker deployment +``` + +## Common Commands + +### Backend +```bash +cd backend + +# Make migrations for custom apps +python manage.py makemigrations apps.pisadmin.apps.pisadmin +python manage.py makemigrations apps.pissupplier + +# Apply migrations +python manage.py migrate + +# Create superuser +python manage.py init + +# Initialize area data +python manage.py init_area + +# Run development server +python manage.py runserver 0.0.0.0:8000 + +# Run with uvicorn (production-like) +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 +``` + +### Frontend +```bash +cd web +yarn install +yarn dev # Development server (port 8080) +yarn build # Production build +yarn build:dev # Development build +yarn lint-fix # Lint and fix +``` + +### Docker +```bash +docker-compose up -d +docker exec dvadmin3-django python manage.py makemigrations +docker exec dvadmin3-django python manage.py migrate +docker exec dvadmin3-django python manage.py init +``` + +## Configuration + +Backend settings are split: +- `application/settings.py` - Django defaults +- `conf/env.py` - Environment-specific config (database, Redis, secrets) + +## Key Dependencies + +**Backend**: Django 4.2.14, djangorestframework 3.15.2, channels 4.1.0, celery, SimpleJWT + +**Frontend**: Vue 3.4, Element Plus 2.8, FastCRUD 1.21, Vite 5.4, Pinia, Vue Router 4.4 + +## Notes + +- Custom apps (`pisadmin`, `pissupplier`) extend the base `dvadmin` RBAC system +- The `miscprocurement` app handles询价 (RFQ) and cost template building +- Supplier quotations are managed in `pissupplier` app +- Default demo credentials: `superadmin` / `admin123456` From ea172148030b63f0f50295f87ca1ba69da6130ab Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 09:14:46 +0800 Subject: [PATCH 024/111] chore: remove docker-compose files Docker compose is no longer used for deployment. --- docker-compose-副本.yml | 117 ---------------------------------------- docker-compose.yml | 114 --------------------------------------- 2 files changed, 231 deletions(-) delete mode 100644 docker-compose-副本.yml delete mode 100644 docker-compose.yml diff --git a/docker-compose-副本.yml b/docker-compose-副本.yml deleted file mode 100644 index 65d7a52..0000000 --- a/docker-compose-副本.yml +++ /dev/null @@ -1,117 +0,0 @@ -version: "3" -services: - dvadmin3-web: - container_name: dvadmin3-web - ports: - - "8080:8080" - build: - context: ./ - dockerfile: ./docker_env/web/Dockerfile - environment: - TZ: Asia/Shanghai - volumes: - - ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf - - ./backend/media:/backend/media - expose: - - "8080" - restart: always - networks: - network: - ipv4_address: 177.10.0.11 - - dvadmin3-django: - build: - context: . - dockerfile: ./docker_env/django/Dockerfile - container_name: dvadmin3-django - working_dir: /backend - depends_on: - - dvadmin3-mysql - environment: - PYTHONUNBUFFERED: 1 - DATABASE_HOST: dvadmin3-mysql - TZ: Asia/Shanghai - volumes: - - ./backend:/backend - - ./logs/log:/var/log - ports: - - "8000:8000" - expose: - - "8000" - restart: always - networks: - network: - ipv4_address: 177.10.0.12 - - dvadmin3-mysql: - image: mysql:8.0 - container_name: dvadmin3-mysql - privileged: true - restart: always - ports: - - "3306:3306" - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} - MYSQL_DATABASE: "django-vue3-admin" - TZ: Asia/Shanghai - command: - --wait_timeout=31536000 - --interactive_timeout=31536000 - --max_connections=1000 - --default-authentication-plugin=mysql_native_password - volumes: - - "./docker_env/mysql/data:/var/lib/mysql" - - "./docker_env/mysql/conf.d:/etc/mysql/conf.d" - - "./docker_env/mysql/logs:/logs" - networks: - network: - ipv4_address: 177.10.0.13 - - - dvadmin3-celery: - build: - context: . - dockerfile: ./docker_env/celery/Dockerfile - container_name: dvadmin3-celery - working_dir: /backend - depends_on: - - dvadmin3-mysql - environment: - PYTHONUNBUFFERED: 1 - DATABASE_HOST: dvadmin3-mysql - TZ: Asia/Shanghai - volumes: - - ./backend:/backend - - ./logs/log:/var/log - restart: always - networks: - network: - ipv4_address: 177.10.0.14 - - - dvadmin3-redis: - image: redis:6.2.6-alpine # 指定服务镜像,最好是与之前下载的redis配置文件保持一致 - container_name: dvadmin3-redis # 容器名称 - restart: always - environment: - - TZ=Asia/Shanghai # 设置时区 - volumes: # 配置数据卷 - - ./docker_env/redis/data:/data - - ./docker_env/redis/redis.conf:/etc/redis/redis.conf - ports: # 映射端口 - - "6379:6379" - sysctls: # 设置容器中的内核参数 - - net.core.somaxconn=1024 - command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes --requirepass ${REDIS_PASSWORD}" # 指定配置文件并开启持久化 - privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限 - networks: - network: - ipv4_address: 177.10.0.15 - - -networks: - network: - ipam: - driver: default - config: - - subnet: '177.10.0.0/16' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e06b461..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,114 +0,0 @@ -version: "3" -services: - # dvadmin3-web: - # container_name: dvadmin3-web - # ports: - # - "8080:8080" - # build: - # context: ./ - # dockerfile: ./docker_env/web/Dockerfile - # environment: - # TZ: Asia/Shanghai - # volumes: - # - ./docker_env/nginx/my.conf:/etc/nginx/conf.d/my.conf - # - ./backend/media:/backend/media - # expose: - # - "8080" - # restart: always - # networks: - # network: - # ipv4_address: 177.10.0.11 - - dvadmin3-django: - build: - context: . - dockerfile: ./docker_env/django/Dockerfile - container_name: dvadmin3-django - working_dir: /backend - environment: - PYTHONUNBUFFERED: 1 - TZ: Asia/Shanghai - volumes: - - ./backend:/backend - - ./logs/log:/var/log - ports: - - "8000:8000" - expose: - - "8000" - restart: always - networks: - network: - ipv4_address: 177.10.0.12 - - dvadmin3-mysql: - image: mysql:8.0 - container_name: dvadmin3-mysql - privileged: true - restart: always - ports: - - "3306:3306" - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} - MYSQL_DATABASE: "django-vue3-admin" - TZ: Asia/Shanghai - command: - --wait_timeout=31536000 - --interactive_timeout=31536000 - --max_connections=1000 - --default-authentication-plugin=mysql_native_password - volumes: - - "./docker_env/mysql/data:/var/lib/mysql" - - "./docker_env/mysql/conf.d:/etc/mysql/conf.d" - - "./docker_env/mysql/logs:/logs" - networks: - network: - ipv4_address: 177.10.0.13 - - - dvadmin3-celery: - build: - context: . - dockerfile: ./docker_env/celery/Dockerfile - container_name: dvadmin3-celery - working_dir: /backend - depends_on: - - dvadmin3-mysql - environment: - PYTHONUNBUFFERED: 1 - DATABASE_HOST: dvadmin3-mysql - TZ: Asia/Shanghai - volumes: - - ./backend:/backend - - ./logs/log:/var/log - restart: always - networks: - network: - ipv4_address: 177.10.0.14 - - - dvadmin3-redis: - image: redis:6.2.6-alpine # 指定服务镜像,最好是与之前下载的redis配置文件保持一致 - container_name: dvadmin3-redis # 容器名称 - restart: always - environment: - - TZ=Asia/Shanghai # 设置时区 - volumes: # 配置数据卷 - - ./docker_env/redis/data:/data - - ./docker_env/redis/redis.conf:/etc/redis/redis.conf - ports: # 映射端口 - - "6379:6379" - sysctls: # 设置容器中的内核参数 - - net.core.somaxconn=1024 - command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes --requirepass ${REDIS_PASSWORD}" # 指定配置文件并开启持久化 - privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限 - networks: - network: - ipv4_address: 177.10.0.15 - - -networks: - network: - ipam: - driver: default - config: - - subnet: '177.10.0.0/16' \ No newline at end of file From dfe5284ba146fd8339237c5b18796d5b38273e40 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:27:42 +0800 Subject: [PATCH 025/111] fix: push latest tag after build --- backend/Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index 8e59d25..561ffaa 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -39,6 +39,8 @@ pipeline { sh """ echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin docker push ${IMAGE_TAG} || true + docker tag ${IMAGE_TAG} ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest + docker push ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest || true docker logout ${HARBOR_URL} """ } From f50c55408dd0fa93a84c64b6730f3ad605ee60d1 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:28:10 +0800 Subject: [PATCH 026/111] fix: push latest tag after build --- web/Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/Jenkinsfile b/web/Jenkinsfile index de32cca..a0e212e 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -55,6 +55,8 @@ pipeline { sh """ echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin docker push ${IMAGE_TAG} || true + docker tag ${IMAGE_TAG} ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest + docker push ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest || true docker logout ${HARBOR_URL} """ } From 9ee8a047146e696bd8a9bd9c053210293a642f3a Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:35:43 +0800 Subject: [PATCH 027/111] chore: remove docker_env directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部署配置已迁移到独立仓库 --- docker_env/README.md | 65 ------------------------------- docker_env/celery/Dockerfile | 7 ---- docker_env/django/Dockerfile | 42 -------------------- docker_env/django/DockerfileBuild | 6 --- docker_env/mysql/conf.d/my.cnf | 33 ---------------- docker_env/mysql/launch.sh | 5 --- docker_env/nginx/my-80.conf | 33 ---------------- docker_env/nginx/my.conf | 54 ------------------------- docker_env/redis/redis.conf | 24 ------------ docker_env/web/Dockerfile | 22 ----------- docker_env/web/DockerfileBuild | 4 -- 11 files changed, 295 deletions(-) delete mode 100644 docker_env/README.md delete mode 100644 docker_env/celery/Dockerfile delete mode 100644 docker_env/django/Dockerfile delete mode 100644 docker_env/django/DockerfileBuild delete mode 100644 docker_env/mysql/conf.d/my.cnf delete mode 100644 docker_env/mysql/launch.sh delete mode 100644 docker_env/nginx/my-80.conf delete mode 100644 docker_env/nginx/my.conf delete mode 100644 docker_env/redis/redis.conf delete mode 100644 docker_env/web/Dockerfile delete mode 100644 docker_env/web/DockerfileBuild diff --git a/docker_env/README.md b/docker_env/README.md deleted file mode 100644 index f281ab5..0000000 --- a/docker_env/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# docker 镜像打包 - -### 打包web基础Build包 - -~~~sh -# 编译打包到本地 -docker build -f ./docker_env/web/DockerfileBuild -t registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine . -# 上传到阿里云仓库 -docker push registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-web:16.19-alpine - -~~~ - -### 打包Backend基础Build包 - -~~~sh -# 编译打包到本地 -docker build -f ./docker_env/django/DockerfileBuild -t registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest . -# 上传到阿里云仓库 -docker push registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest -~~~ - -### 运行前端 - -~~~ -docker build -f ./docker_env/web/Dockerfile -t dvadmin-pro-web . -~~~ - -### 运行后端 - -~~~ -docker build -f ./docker_env/django/Dockerfile -t dvadmin-pro-django . -~~~ - -### 运行celery - -~~~ -docker build -f ./docker_env/celery/Dockerfile -t dvadmin-pro-celery . -~~~ - -## docker-compose 运行 - -~~~ -# 先安装docker-compose (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释 -docker-compose up -d -# 初始化后端数据(第一次执行即可) -docker exec -ti dvadmin-django bash -python manage.py makemigrations -python manage.py migrate -python manage.py init -y -exit - -前端地址:http://127.0.0.1:8080 -后端地址:http://127.0.0.1:8000 -# 在服务器上请把127.0.0.1 换成自己公网ip -账号:superadmin 密码:admin123456 - -# docker-compose 停止 -docker-compose down -# docker-compose 重启 -docker-compose restart -# docker-compose 启动时重新进行 build -docker-compose up -d --build - -~~~ - diff --git a/docker_env/celery/Dockerfile b/docker_env/celery/Dockerfile deleted file mode 100644 index 7d57c7a..0000000 --- a/docker_env/celery/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/dvadmin3-base-backend:latest -WORKDIR /backend -COPY ./backend/ . -RUN awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' -RUN python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt -CMD ["celery", "-A", "application", "worker", "-B", "--loglevel=info"] - diff --git a/docker_env/django/Dockerfile b/docker_env/django/Dockerfile deleted file mode 100644 index dfa74e7..0000000 --- a/docker_env/django/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# 第一阶段:构建 -FROM docker.1ms.run/python:3.10-slim as builder - -WORKDIR /backend - -# 安装系统依赖(合并为一个RUN减少层数) -RUN apt-get update && apt-get install -y \ - bash git curl gnupg ca-certificates \ - libjpeg-dev libmariadb-dev-compat default-libmysqlclient-dev \ - build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev \ - libpcre2-dev libncurses5-dev libreadline-dev tk-dev libpq-dev unixodbc-dev \ - && rm -rf /var/lib/apt/lists/* \ - && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ - && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ - && apt-get update \ - && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \ - && rm -rf /var/lib/apt/lists/* - -# 先复制依赖文件,利用缓存 -COPY ./backend/requirements.txt . - -# 安装 Python 依赖(带缓存加速) -RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt - -# 复制剩余代码 -COPY ./backend/ . - -# 配置 -RUN ls ./conf/ \ - && awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' \ - && sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py \ - && sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py - -# 第二阶段:运行时镜像 -FROM docker.1ms.run/python:3.10-slim - -COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages -COPY --from=builder /usr/local/bin /usr/local/bin -COPY --from=builder /backend /backend - -WORKDIR /backend -CMD ["sh","docker_start.sh"] diff --git a/docker_env/django/DockerfileBuild b/docker_env/django/DockerfileBuild deleted file mode 100644 index 53e5049..0000000 --- a/docker_env/django/DockerfileBuild +++ /dev/null @@ -1,6 +0,0 @@ -FROM python:3.10-alpine -RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories -RUN apk update && apk add bash bash-doc bash-completion git freetds-dev jpeg-dev linux-headers mysql-client mariadb-dev build-base libffi-dev openssl-dev zlib-dev bzip2-dev pcre-dev ncurses-dev readline-dev tk-dev postgresql-dev -WORKDIR /backend -COPY ./backend/requirements.txt . -RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt diff --git a/docker_env/mysql/conf.d/my.cnf b/docker_env/mysql/conf.d/my.cnf deleted file mode 100644 index 469af8b..0000000 --- a/docker_env/mysql/conf.d/my.cnf +++ /dev/null @@ -1,33 +0,0 @@ -[mysql] -#设置mysql客户端默认字符集 -default-character-set=utf8 -socket=/var/lib/mysql/mysql.sock - - -[mysqld] -# skip-grant-tables -#mysql5.7以后的不兼容问题处理 -sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES -datadir=/var/lib/mysql -socket=/var/lib/mysql/mysql.sock - -# Disabling symbolic-links is recommended to prevent assorted security risks -symbolic-links=0 -# Settings user and group are ignored when systemd is used. -# If you need to run mysqld under a different user or group, -# customize your systemd unit file for mariadb according to the -# instructions in http://fedoraproject.org/wiki/Systemdd -#允许最大连接数 -max_connections=1000 -#服务端使用的字符集默认为8比特编码的latin1字符集 -character-set-server=utf8 -#创建新表时将使用的默认存储引擎 -default-storage-engine=INNODB -lower_case_table_names=1 -max_allowed_packet=16M -#设置时区 -default-time_zone='+8:00' - -[mysqld_safe] -log-error=/var/log/mariadb/mariadb.log -pid-file=/var/lib/mariadb/mariadb.pid diff --git a/docker_env/mysql/launch.sh b/docker_env/mysql/launch.sh deleted file mode 100644 index 31b3687..0000000 --- a/docker_env/mysql/launch.sh +++ /dev/null @@ -1,5 +0,0 @@ -cur_path=`pwd` -docker rm mysql -# docker pull mysql:5.7 -# docker pull mysql:5.5 -docker run -p 3306:3306 --privileged=true --name mysql -v $cur_path/logs:/logs -v $cur_path/data:/var/lib/mysql -v $cur_path/conf.d/my.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf -v $cur_path/run/:/var/run/mysql -e MYSQL_ROOT_PASSWORD=q1w2e3r4 -d mysql:5.7 diff --git a/docker_env/nginx/my-80.conf b/docker_env/nginx/my-80.conf deleted file mode 100644 index b69923a..0000000 --- a/docker_env/nginx/my-80.conf +++ /dev/null @@ -1,33 +0,0 @@ -server { - listen 80; - server_name localhost; - client_max_body_size 100M; - # 配置前端静态文件目录 - location / { - index index.html index.htm; - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html; - # 禁止缓存html文件,避免前端页面不及时更新,需要用户手动刷新的情况 - if ($request_uri ~* "^/$|^/index.html|^/index.htm") { - add_header Cache-Control "no-store"; - } - } - - location ~ ^/api/ { - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Nginx-Proxy true; - set_real_ip_from 0.0.0.0/0; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_connect_timeout 600s; - proxy_read_timeout 600s; - proxy_send_timeout 600s; - real_ip_header X-Forwarded-For; - # 保留 /api/ 前缀,让后端 application.urls.py 的 path("api/...") 正常命中 - proxy_pass http://177.10.0.12:8000; # 设置代理服务器的协议和地址 - } - } diff --git a/docker_env/nginx/my.conf b/docker_env/nginx/my.conf deleted file mode 100644 index 9b34d68..0000000 --- a/docker_env/nginx/my.conf +++ /dev/null @@ -1,54 +0,0 @@ -server { - listen 8080; - server_name localhost; - client_max_body_size 100M; - location / { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - set_real_ip_from 0.0.0.0/0; - real_ip_header X-Forwarded-For; - root /usr/share/nginx/html; - index index.html index.php index.htm; - # 禁止缓存html文件,避免前端页面不及时更新,需要用户手动刷新的情况 - if ($request_uri ~* "^/$|^/index.html|^/index.htm") { - add_header Cache-Control "no-store"; - } - } - - location /api/ { - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Nginx-Proxy true; - set_real_ip_from 0.0.0.0/0; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_connect_timeout 600s; - proxy_read_timeout 600s; - proxy_send_timeout 600s; - real_ip_header X-Forwarded-For; - proxy_pass http://dvadmin3-django:8000; # 反向代理到后端(保留 /api/ 前缀) - } - location /media { - alias /backend/media; - expires 7d; - gzip on; - gzip_min_length 1k; - gzip_comp_level 2; - gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - gzip_vary on; - } - location /api/media { - alias /backend/media; - expires 7d; - gzip on; - gzip_min_length 1k; - gzip_comp_level 2; - gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - gzip_vary on; - } - } diff --git a/docker_env/redis/redis.conf b/docker_env/redis/redis.conf deleted file mode 100644 index 7995fd4..0000000 --- a/docker_env/redis/redis.conf +++ /dev/null @@ -1,24 +0,0 @@ -### 指定redis绑定的主机地址,注释掉这部分,使redis可以外部访问 -# bind 127.0.0.1 -::1 -### 指定访问redis服务端的端口 -port 6379 -### 指定客户端连接redis服务器时,当闲置的时间为多少(如300)秒时关闭连接(0表示禁用) -timeout 0 -### 默认情况下,Redis不作为守护进程运行。如果需要,请使用“yes” -daemonize no -### 给redis设置密码,不需要密码的话则注释 -# requirepass dvadmin -### 开启redis持久化,默认为no -appendonly yes -### 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300 -tcp-keepalive 300 -### 指定redis数据库的日志级别,常用的日志级别有debug、verbose、notice、warning,不进行修改的情况下默认的是notice -loglevel notice -### 指定redis数据库多长时间内(s)有多少次(c)更新操作时就把缓存中的数据同步到本地库,比如:save 600 2,指的是10分钟内有2次更新操作,就同步到本地库 -save 600 2 -### 指定redis的最大内存。由于Redis 在启动时会把数据加载到内存中,当数据达到最大内存时,redis会自动把已经到期和即将到期的key值。所以可以根据需求调整自己的所需的最大内存 -maxmemory 500mb -### 设置了maxmemory的选项,redis内存使用达到上限。可以通过设置LRU算法来删除部分key,释放空间。默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory -maxmemory-policy volatile-lru -### 设置外部网络连接redis服务,开启需配置bind ip或者设置访问密码,关闭此时外部网络可以直接访问 -# protected-mode yes diff --git a/docker_env/web/Dockerfile b/docker_env/web/Dockerfile deleted file mode 100644 index 16a6dcc..0000000 --- a/docker_env/web/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# 第一阶段:构建 -FROM docker.1ms.run/node:20-alpine as builder - -WORKDIR /web - -# 先复制 package.json 和 yarn.lock -COPY web/package.json web/yarn.lock* ./ - -# 安装依赖(利用缓存) -RUN yarn install --registry=https://registry.npmmirror.com - -# 复制源代码 -COPY web/ . - -# 构建 -RUN yarn build - -# 第二阶段:Nginx 运行时 -FROM docker.1ms.run/nginx:alpine - -COPY ./docker_env/nginx/my.conf /etc/nginx/conf.d/my.conf -COPY --from=builder /web/dist /usr/share/nginx/html diff --git a/docker_env/web/DockerfileBuild b/docker_env/web/DockerfileBuild deleted file mode 100644 index 1f725ca..0000000 --- a/docker_env/web/DockerfileBuild +++ /dev/null @@ -1,4 +0,0 @@ -FROM node:16.19-alpine -WORKDIR / -COPY ./web/package.json . -RUN yarn install --registry=https://registry.npmmirror.com From 56aca2107b837096a4c989dd5ef2b3f031c7ea9b Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:39:57 +0800 Subject: [PATCH 028/111] chore: add pnpm-lock.yaml for faster builds --- web/pnpm-lock.yaml | 8577 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 8577 insertions(+) create mode 100644 web/pnpm-lock.yaml diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..9b82115 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,8577 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.1 + version: 2.3.2(vue@3.5.31(typescript@4.9.5)) + '@fast-crud/fast-crud': + specifier: ^1.21.2 + version: 1.27.10(vue@3.5.31(typescript@4.9.5)) + '@fast-crud/fast-extends': + specifier: ^1.21.2 + version: 1.27.10(aws-crt@1.30.0)(vue@3.5.31(typescript@4.9.5)) + '@fast-crud/ui-element': + specifier: ^1.21.2 + version: 1.27.10 + '@fast-crud/ui-interface': + specifier: ^1.21.2 + version: 1.27.10 + '@great-dream/dvadmin3-celery-web': + specifier: ^3.1.3 + version: 3.1.3 + '@iconify/vue': + specifier: ^4.1.2 + version: 4.3.0(vue@3.5.31(typescript@4.9.5)) + '@meetjs/vant4-kit': + specifier: ^1.0.1 + version: 1.0.1(vue@3.5.31(typescript@4.9.5)) + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.24 + '@vitejs/plugin-vue-jsx': + specifier: ^4.0.1 + version: 4.2.0(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1))(vue@3.5.31(typescript@4.9.5)) + '@wangeditor/editor': + specifier: ^5.1.23 + version: 5.1.23 + '@wangeditor/editor-for-vue': + specifier: ^5.1.12 + version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.31(typescript@4.9.5)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.27(postcss@8.5.8) + axios: + specifier: ^1.7.4 + version: 1.14.0 + countup.js: + specifier: ^2.8.0 + version: 2.10.0 + cropperjs: + specifier: ^1.6.2 + version: 1.6.2 + date-holidays: + specifier: ^3.24.1 + version: 3.26.12 + e-icon-picker: + specifier: 2.1.1 + version: 2.1.1(vue@3.5.31(typescript@4.9.5)) + echarts: + specifier: ^5.5.1 + version: 5.6.0 + echarts-gl: + specifier: ^2.0.9 + version: 2.0.9(echarts@5.6.0) + echarts-wordcloud: + specifier: ^2.1.0 + version: 2.1.0(echarts@5.6.0) + element-plus: + specifier: ^2.8.0 + version: 2.13.6(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)) + element-tree-line: + specifier: ^0.2.1 + version: 0.2.1(vue@3.5.31(typescript@4.9.5)) + font-awesome: + specifier: ^4.7.0 + version: 4.7.0 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 + js-table2excel: + specifier: ^1.1.2 + version: 1.1.2 + jsplumb: + specifier: ^2.15.6 + version: 2.15.6 + less: + specifier: ^4.3.0 + version: 4.6.4 + lodash-es: + specifier: ^4.17.21 + version: 4.17.23 + lunar-javascript: + specifier: ^1.7.1 + version: 1.7.7 + mitt: + specifier: ^3.0.1 + version: 3.0.1 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + pinia: + specifier: ^2.0.28 + version: 2.3.1(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)) + pinia-plugin-persist: + specifier: ^1.0.0 + version: 1.0.0(pinia@2.3.1(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)))(vue@3.5.31(typescript@4.9.5)) + postcss: + specifier: ^8.4.21 + version: 8.5.8 + print-js: + specifier: ^1.6.0 + version: 1.6.0 + qrcodejs2-fixes: + specifier: ^0.0.2 + version: 0.0.2 + qs: + specifier: ^6.11.0 + version: 6.15.0 + rollup-plugin-postcss: + specifier: ^4.0.2 + version: 4.0.2(postcss@8.5.8) + rollup-plugin-terser: + specifier: ^7.0.2 + version: 7.0.2(rollup@4.60.1) + screenfull: + specifier: ^6.0.2 + version: 6.0.2 + sortablejs: + specifier: ^1.15.0 + version: 1.15.7 + splitpanes: + specifier: ^3.1.5 + version: 3.2.0(vue@3.5.31(typescript@4.9.5)) + tailwindcss: + specifier: ^3.2.7 + version: 3.4.19 + ts-md5: + specifier: ^1.3.1 + version: 1.3.1 + upgrade: + specifier: ^1.1.0 + version: 1.1.0 + vant: + specifier: ^4.9.19 + version: 4.9.24(vue@3.5.31(typescript@4.9.5)) + vant4-kit: + specifier: ^1.0.3 + version: 1.0.6(vue@3.5.31(typescript@4.9.5)) + vue: + specifier: ^3.4.38 + version: 3.5.31(typescript@4.9.5) + vue-clipboard3: + specifier: ^2.0.0 + version: 2.0.0 + vue-cropper: + specifier: ^1.0.8 + version: 1.1.4 + vue-draggable-plus: + specifier: ^0.6.0 + version: 0.6.1(@types/sortablejs@1.15.9) + vue-grid-layout: + specifier: ^3.0.0-beta1 + version: 3.0.0-beta1(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@4.9.5) + vue-i18n: + specifier: ^9.14.0 + version: 9.14.5(vue@3.5.31(typescript@4.9.5)) + vue-qr: + specifier: ^4.0.9 + version: 4.0.9 + vue-router: + specifier: ^4.4.3 + version: 4.6.4(vue@3.5.31(typescript@4.9.5)) + vxe-table: + specifier: ^4.6.18 + version: 4.18.10(vue@3.5.31(typescript@4.9.5)) + xe-utils: + specifier: ^3.5.30 + version: 3.9.1 + yarn: + specifier: ^1.22.22 + version: 1.22.22 + devDependencies: + '@types/node': + specifier: ^18.19.42 + version: 18.19.130 + '@types/nprogress': + specifier: ^0.2.3 + version: 0.2.3 + '@types/sortablejs': + specifier: ^1.15.8 + version: 1.15.9 + '@typescript-eslint/eslint-plugin': + specifier: ^5.46.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.46.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@vitejs/plugin-vue': + specifier: ^5.1.2 + version: 5.2.4(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1))(vue@3.5.31(typescript@4.9.5)) + '@vue/compiler-sfc': + specifier: ^3.4.38 + version: 3.5.31 + baseline-browser-mapping: + specifier: ^2.9.19 + version: 2.10.13 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-vue: + specifier: ^9.27.0 + version: 9.33.0(eslint@8.57.1) + prettier: + specifier: ^2.8.1 + version: 2.8.8 + sass: + specifier: ^1.56.2 + version: 1.98.0 + typescript: + specifier: ^4.9.4 + version: 4.9.5 + vite: + specifier: ^5.4.1 + version: 5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1) + vite-plugin-vue-setup-extend: + specifier: ^0.4.0 + version: 0.4.0(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1)) + vue-eslint-parser: + specifier: ^9.4.3 + version: 9.4.3(eslint@8.57.1) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.1021.0': + resolution: {integrity: sha512-BCfggq8gYSjlKOZlMSVApix3cgKAQIWGeoJFX/AU5HMvqz1BZBEw83jJFL9LYrqTPCocH8NGl++1Xr70ro+jcg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.26': + resolution: {integrity: sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.5': + resolution: {integrity: sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.24': + resolution: {integrity: sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.26': + resolution: {integrity: sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.28': + resolution: {integrity: sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.28': + resolution: {integrity: sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.29': + resolution: {integrity: sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.24': + resolution: {integrity: sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.28': + resolution: {integrity: sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.28': + resolution: {integrity: sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crt-loader@3.972.28': + resolution: {integrity: sha512-9kUljr6w63yjnvNPqy1YJs5v2h7sIiFHFBY5iv1vIA102VxRZmI8MjD1F102ZnCEpDO1oxqulsiJpMAbs46MZg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + resolution: {integrity: sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.8': + resolution: {integrity: sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.6': + resolution: {integrity: sha512-YckB8k1ejbyCg/g36gUMFLNzE4W5cERIa4MtsdO+wpTmJEP0+TB7okWIt7d8TDOvnb7SwvxJ21E4TGOBxFpSWQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.8': + resolution: {integrity: sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.9': + resolution: {integrity: sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.27': + resolution: {integrity: sha512-gomO6DZwx+1D/9mbCpcqO5tPBqYBK7DtdgjTIjZ4yvfh/S7ETwAPS0XbJgP2JD8Ycr5CwVrEkV1sFtu3ShXeOw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.8': + resolution: {integrity: sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.28': + resolution: {integrity: sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.18': + resolution: {integrity: sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.10': + resolution: {integrity: sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1021.0': + resolution: {integrity: sha512-kkIzsIAc7wnG7vVRkZFIwJ3noOyF3S6ozOQ9t2KxzPde1LsmpmPwYbmiB91DzdfuGySdk4Hpb0JmHh4KhGECXQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-crt@3.1021.0': + resolution: {integrity: sha512-LtRM0atnl4qKxl60vqGmJKUY8e0Gnp2VKWRi22d4BFBiQpO12Z8gpoaDx/pgoWcBmTvGYQZ427dL2eEj/qYNuw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.15': + resolution: {integrity: sha512-Ukw2RpqvaL96CjfH/FgfBmy/ZosHBqoHBCFsN61qGg99F33vpntIVii8aNeh65XuOja73arSduskoa4OJea9RQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1021.0': + resolution: {integrity: sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-format-url@3.972.8': + resolution: {integrity: sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + + '@aws-sdk/util-user-agent-node@3.973.14': + resolution: {integrity: sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/util-utf8-browser@3.259.0': + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + + '@aws-sdk/xml-builder@3.972.16': + resolution: {integrity: sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime-corejs2@7.29.2': + resolution: {integrity: sha512-+FqVkbqWaDleqS9fgzFypApKoPvmGFgk5X2lGXbL9wgz6tf88qt2HEUuEn9E3yBeLt7p8pIgODbJ5icVRALKhQ==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@fast-crud/fast-crud@1.27.10': + resolution: {integrity: sha512-msOCxmv0m9uOwqRsBlxrHW88KyHocskRl99ebqLsNSFD0e80GCQErLnUoqF5hOIq6a3uPWoXrfalQzWWyNSfEg==} + + '@fast-crud/fast-extends@1.27.10': + resolution: {integrity: sha512-Ha1h7ciVqOEPYJr0a1rMdihwa57aMwJkU0CEiWGRxdR+UP7Xjg1juQ402YULLs+ALMNOPyoDxvbjIhkRHuWQiQ==} + + '@fast-crud/ui-element@1.27.10': + resolution: {integrity: sha512-w9Gtxo6SaSbBkLvawE60Xpt5LWcTk4RTLdRWmdnnpXTMsTDbycCHvnORucGJqMCHvs9RPHUuxmL8b+EHwoFMLw==} + + '@fast-crud/ui-interface@1.27.10': + resolution: {integrity: sha512-Kz3qMvTFjRDu+dqSQh7LMteHvFzLThm+l8xLYgwgheUzHq+RR2i4z+hoMHF2UUOIhkU+gdqfNXPxkivFHgxNvQ==} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@great-dream/dvadmin3-celery-web@3.1.3': + resolution: {integrity: sha512-zf1UO6le8rhizN1FjIfHzb1qNoTF23YXczEJhJm0NLoWAbziA+FmobYq/LePvsAEpiVqhsMzrRvbpYj/nWzTQw==} + + '@httptoolkit/websocket-stream@6.0.1': + resolution: {integrity: sha512-A0NOZI+Glp3Xgcz6Na7i7o09+/+xm2m0UCU8gdtM2nIv6/cjLmhMZMqehSpTlgbx9omtLmV8LVqOskPEyWnmZQ==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/vue@4.3.0': + resolution: {integrity: sha512-Xq0h6zMrHBbrW8jXJ9fISi+x8oDQllg5hTDkDuxnWiskJ63rpJu9CvJshj8VniHVTbsxCg9fVoPAaNp3RQI5OQ==} + peerDependencies: + vue: '>=3' + + '@interactjs/actions@1.10.27': + resolution: {integrity: sha512-FCRg5KwB+stkPcAMx/Cn0fgGP6p4LyMX9S/Upcn/W+hpYme31bPi54PCqmOebzz6myTthN6zFf9jMyLOqtI/gg==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/auto-scroll@1.10.27': + resolution: {integrity: sha512-zPg5TnVsZv+9Hnt4qnbxLvBMf+rIWHkoJVoSETEbLNaj90C8hIyr0pVwukSUySSgDhCgQ7np0f3pg4INLq9beQ==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/auto-start@1.10.27': + resolution: {integrity: sha512-ECLBO/nxmaF1knncJKIE5F7la3KKRgEkn0Cu2JTPOYj9xy/LpfYElo3wkRHsodgOqF651nR70GK2/IzPR2lO9A==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/core@1.10.27': + resolution: {integrity: sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/dev-tools@1.10.27': + resolution: {integrity: sha512-YolmBwRaKH1gWbvyLeV3m5QSwtD38lOZnCBA87PCAlcd9PQAC2gb03fEPeEyD336bE20oLB8f0WZt4Wre+afiw==} + peerDependencies: + '@interactjs/modifiers': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/inertia@1.10.27': + resolution: {integrity: sha512-S/SVj/M0D+wWWPVXHcXN/YUWOK51LFJsEA+CTgVnFhlSU04+1FUvNLwilCZcHgECu1RJxZNKDwZysDATg+r8jQ==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/modifiers': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/interact@1.10.27': + resolution: {integrity: sha512-XdH3A2UUzjEFGGJgFuJlhiz99tE8jB8xNh/DmnoMuL6uOQPxNA+sWRnzEVjG0+zY2P3/dbhEpi4Cn3FLPzydwA==} + + '@interactjs/interactjs@1.10.27': + resolution: {integrity: sha512-UwhfUZMZVXUY72efPABuKSBz1sUY+r+49v8t6Ku9o5Jq76AKg9mwmdGszIlOn3ppnFDDjvtzK/8TL+Sbd0EQEA==} + + '@interactjs/modifiers@1.10.27': + resolution: {integrity: sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/offset@1.10.27': + resolution: {integrity: sha512-AezsLiuK+Qv4jXdYuRa65HJ2pMFMZPlqiAep6ZRLwhP9HE7O75c0EAm+gfx+dpPrHNHs6J9LaiKSZl+B+A2qAw==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/pointer-events@1.10.27': + resolution: {integrity: sha512-Yo5SS6PhWfC93gHNxnwwW0wvebo5hSYJKGaSnAHO4f9Lh25yibecMnmPBmiEfWVcdMboK/kXrme43mHQaRegVg==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/reflow@1.10.27': + resolution: {integrity: sha512-Msm0QdYFr40oSsPFxyCR3dHN/pQx34k7QSkdN1uIsUn/drrm+YSFvrvVOu99DFOwr7gTThr5vNe06Sz4vubTSA==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/snappers@1.10.27': + resolution: {integrity: sha512-HZLZ0XSi6HI08OmTv/HKG6AltQoaKAALLQ+KDW92utj3XSgw7oren0KsWUKPhaPg3Av7R1jFQd08s+uafqIlLw==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/utils@1.10.27': + resolution: {integrity: sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw==} + + '@intlify/core-base@9.14.5': + resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==} + engines: {node: '>= 16'} + + '@intlify/message-compiler@9.14.5': + resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==} + engines: {node: '>= 16'} + + '@intlify/shared@9.14.5': + resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==} + engines: {node: '>= 16'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@meetjs/vant4-kit@1.0.1': + resolution: {integrity: sha512-94/j0+pjhT4L5B0+cASmhHGeJtLNmmy0GV2o2MpOL9P2eIL9ndUxOWr8S3MQVvgIMXtGZgBoFpGMYD945NESlw==} + peerDependencies: + vue: ^3.4.19 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.13': + resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.13': + resolution: {integrity: sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.12': + resolution: {integrity: sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.12': + resolution: {integrity: sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.12': + resolution: {integrity: sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.12': + resolution: {integrity: sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.12': + resolution: {integrity: sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.13': + resolution: {integrity: sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.12': + resolution: {integrity: sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.12': + resolution: {integrity: sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.28': + resolution: {integrity: sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.46': + resolution: {integrity: sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.16': + resolution: {integrity: sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.1': + resolution: {integrity: sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.8': + resolution: {integrity: sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.44': + resolution: {integrity: sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.48': + resolution: {integrity: sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.13': + resolution: {integrity: sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.21': + resolution: {integrity: sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.14': + resolution: {integrity: sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + + '@soerenmartius/vue3-clipboard@0.1.2': + resolution: {integrity: sha512-a5er6cFGcFIQ/3qg6kWvU0yFqIz9iWoQGEVs9azjevyRyMAKvj1w3JuLH9y6+u0WhMNby3kn7nuR8nktSnDesg==} + + '@sphinxxxx/color-conversion@2.2.2': + resolution: {integrity: sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==} + + '@sxzz/popperjs-es@2.11.8': + resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + + '@transloadit/prettier-bytes@0.0.7': + resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/event-emitter@0.3.5': + resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/sortablejs@1.15.9': + resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==} + + '@types/web-bluetooth@0.0.16': + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@typescript-eslint/eslint-plugin@5.62.0': + resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@5.62.0': + resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/type-utils@5.62.0': + resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@uppy/companion-client@2.2.2': + resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==} + + '@uppy/core@2.3.4': + resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==} + + '@uppy/store-default@2.1.1': + resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==} + + '@uppy/utils@4.1.3': + resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==} + + '@uppy/xhr-upload@2.1.3': + resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==} + peerDependencies: + '@uppy/core': ^2.3.3 + + '@vant/popperjs@1.3.0': + resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==} + + '@vant/use@1.6.0': + resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==} + peerDependencies: + vue: ^3.0.0 + + '@vitejs/plugin-vue-jsx@4.2.0': + resolution: {integrity: sha512-DSTrmrdLp+0LDNF77fqrKfx7X0ErRbOcUAgJL/HbSesqQwoUvUQ4uYQqaex+rovqgGcoPqVk+AwUh3v9CuiYIw==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.31': + resolution: {integrity: sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==} + + '@vue/compiler-dom@3.5.31': + resolution: {integrity: sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==} + + '@vue/compiler-sfc@3.5.31': + resolution: {integrity: sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==} + + '@vue/compiler-ssr@3.5.31': + resolution: {integrity: sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.31': + resolution: {integrity: sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==} + + '@vue/runtime-core@3.5.31': + resolution: {integrity: sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==} + + '@vue/runtime-dom@3.5.31': + resolution: {integrity: sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==} + + '@vue/server-renderer@3.5.31': + resolution: {integrity: sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==} + peerDependencies: + vue: 3.5.31 + + '@vue/shared@3.5.31': + resolution: {integrity: sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==} + + '@vueuse/core@12.0.0': + resolution: {integrity: sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==} + + '@vueuse/core@9.13.0': + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + + '@vueuse/metadata@12.0.0': + resolution: {integrity: sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==} + + '@vueuse/metadata@9.13.0': + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + + '@vueuse/shared@12.0.0': + resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==} + + '@vueuse/shared@9.13.0': + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + + '@vxe-ui/core@4.4.3': + resolution: {integrity: sha512-IIwi5+h7Yopq3Ps+JtlSmggUQZg3wvzuad2xuWGR9sNzR/r0qlWKfax7qimrM2UNpVhSF1jI+3Z9+3yg+HPYTw==} + peerDependencies: + vue: ^3.2.0 + + '@wangeditor/basic-modules@1.1.7': + resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/code-highlight@1.0.3': + resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/core@1.1.19': + resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==} + peerDependencies: + '@uppy/core': ^2.1.1 + '@uppy/xhr-upload': ^2.0.3 + dom7: ^3.0.0 + is-hotkey: ^0.2.0 + lodash.camelcase: ^4.3.0 + lodash.clonedeep: ^4.5.0 + lodash.debounce: ^4.0.8 + lodash.foreach: ^4.5.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + lodash.toarray: ^4.4.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/editor-for-vue@5.1.12': + resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==} + peerDependencies: + '@wangeditor/editor': '>=5.1.0' + vue: ^3.0.5 + + '@wangeditor/editor@5.1.23': + resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==} + + '@wangeditor/list-module@1.0.5': + resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/table-module@1.1.4': + resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/upload-image-module@1.0.2': + resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==} + peerDependencies: + '@uppy/core': ^2.0.3 + '@uppy/xhr-upload': ^2.0.3 + '@wangeditor/basic-modules': 1.x + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.foreach: ^4.5.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/video-module@1.1.4': + resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==} + peerDependencies: + '@uppy/core': ^2.1.4 + '@uppy/xhr-upload': ^2.0.7 + '@wangeditor/core': 1.x + dom7: ^3.0.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + ace-builds@1.43.6: + resolution: {integrity: sha512-L1ddibQ7F3vyXR2k2fg+I8TQTPWVA6CKeDQr/h2+8CeyTp3W6EQL8xNFZRTztuP8xNOAqL3IYPqdzs31GCjDvg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + + adler-32@1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + + agentkeepalive@3.5.3: + resolution: {integrity: sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==} + engines: {node: '>= 4.0.0'} + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ali-oss@6.23.0: + resolution: {integrity: sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + astronomia@4.2.0: + resolution: {integrity: sha512-mTvpBGyXB80aSsDhAAiuwza5VqAyqmj5yzhjBrFhRy17DcWDzJrb8Vdl4Sm+g276S+mY7bk/5hi6akZ5RQFeHg==} + engines: {node: '>=12.0.0'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + aws-crt@1.30.0: + resolution: {integrity: sha512-PSbNf2HdLT5orB91SaAR9cARBRTaYznPJIVWDeNlsr//xlcdqYywPAOJQ6L5nBwZnTDArvk7awt6RpL4SIqWsQ==} + + axios@1.14.0: + resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.13: + resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==} + engines: {node: '>=6.0.0'} + hasBin: true + + batch-processor@1.0.0: + resolution: {integrity: sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + bowser@1.9.4: + resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + brace-expansion@1.1.13: + resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + + caldate@2.0.5: + resolution: {integrity: sha512-JndhrUuDuE975KUhFqJaVR1OQkCHZqpOrJur/CFXEIEhWhBMjxO85cRSK8q4FW+B+yyPq6GYua2u4KvNzTcq0w==} + engines: {node: '>=12.0.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001782: + resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} + + cfb@1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + claygl@1.3.0: + resolution: {integrity: sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ==} + + clipboard@2.0.11: + resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + codepage@1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commist@1.1.0: + resolution: {integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + concat-with-sourcemaps@1.1.0: + resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + copy-to@2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + + core-js@2.6.12: + resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cos-js-sdk-v5@1.10.1: + resolution: {integrity: sha512-a4SRfCY5g6Z35C7OWe9te/S1zk77rVQzfpvZ33gmTdJQzKxbNbEG7Aw/v453XwVMsQB352FIf7KRMm5Ya/wlZQ==} + + countup.js@2.10.0: + resolution: {integrity: sha512-QQpZx7oYxsR+OeITlZe46fY/OQjV11oBqjY8wgIXzLU2jIz8GzOrbMhqKLysGY8bWI3T1ZNrYkwGzKb4JNgyzg==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + cron-chinese@1.2.0: + resolution: {integrity: sha512-kx8F42h4hNXjo1zvpkNWnnErpu2Crcba7f3sVeA5RB83CVSHIu+dwLXkRoZkPDU3IbHXsXqZV9QmSUHoIXwx/Q==} + + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + + cropperjs@1.6.2: + resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + css-declaration-sorter@6.4.1: + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-default@5.2.14: + resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano-utils@3.1.0: + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano@5.1.15: + resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + date-bengali-revised@2.0.2: + resolution: {integrity: sha512-q9iDru4+TSA9k4zfm0CFHJj6nBsxP7AYgWC/qodK/i7oOIlj5K2z5IcQDtESfs/Qwqt/xJYaP86tkazd/vRptg==} + engines: {node: '>=12.0.0'} + + date-chinese@2.1.4: + resolution: {integrity: sha512-WY+6+Qw92ZGWFvGtStmNQHEYpNa87b8IAQ5T8VKt4wqrn24lBXyyBnWI5jAIyy7h/KVwJZ06bD8l/b7yss82Ww==} + engines: {node: '>=12.0.0'} + + date-easter@1.0.3: + resolution: {integrity: sha512-aOViyIgpM4W0OWUiLqivznwTtuMlD/rdUWhc5IatYnplhPiWrLv75cnifaKYhmQwUBLAMWLNG4/9mlLIbXoGBQ==} + engines: {node: '>=12.0.0'} + + date-holidays-parser@3.4.7: + resolution: {integrity: sha512-h09ZEtM6u5cYM6m1bX+1Ny9f+nLO9KVZUKNPEnH7lhbXYTfqZogaGTnhONswGeIJFF91UImIftS3CdM9HLW5oQ==} + engines: {node: '>=12.0.0'} + + date-holidays@3.26.12: + resolution: {integrity: sha512-2OXPk5Nbt4iyKLIaoqiCkiQFt+zxGA3SZ3CWMldy8YXq6Zisv+CguVM8TqrKaGEoiZY2gzGH6qCDCrbFoZFyBA==} + engines: {node: '>=12.0.0'} + hasBin: true + + dateformat@2.2.0: + resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-equal@1.1.2: + resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} + engines: {node: '>= 0.4'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-user-agent@1.0.0: + resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==} + engines: {node: '>= 0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegate@3.2.0: + resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + digest-header@1.1.0: + resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==} + engines: {node: '>= 8.0.0'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-zindex@1.0.6: + resolution: {integrity: sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA==} + + dom7@3.0.0: + resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + + e-icon-picker@2.1.1: + resolution: {integrity: sha512-oj+gDuqDEQErbZctBX+DW8IcB0AYvHUJYo7/TcMPpiToITwOPcDRZYbVILb+M+j446YqG71Kkon4y1Y4BkGDww==} + peerDependencies: + vue: ^3.2.0 + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + echarts-gl@2.0.9: + resolution: {integrity: sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==} + peerDependencies: + echarts: ^5.1.2 + + echarts-wordcloud@2.1.0: + resolution: {integrity: sha512-Kt1JmbcROgb+3IMI48KZECK2AP5lG6bSsOEs+AsuwaWJxQom31RTNd6NFYI01E/YaI1PFZeueaupjlmzSQasjQ==} + peerDependencies: + echarts: ^5.0.1 + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.330: + resolution: {integrity: sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA==} + + element-plus@2.13.6: + resolution: {integrity: sha512-XHgwXr8Fjz6i+6BaqFhAbae/dJbG7bBAAlHrY3pWL7dpj+JcqcOyKYt4Oy5KP86FQwS1k4uIZDjCx2FyUR5lDg==} + peerDependencies: + vue: ^3.3.0 + + element-resize-detector@1.2.4: + resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} + + element-tree-line@0.2.1: + resolution: {integrity: sha512-YWHftDSOFiBuw49drxJV7Yi7pCqPpwBBBeQmySjchcxU3uUOAu72FDLK5RyTGJ5qt4Ci+yoUg7zJqsFP5u7N5A==} + peerDependencies: + vue: ^2.6.11 || ^3.2.0 + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + end-or-error@1.0.1: + resolution: {integrity: sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==} + engines: {node: '>= 0.11.14'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-vue@9.33.0: + resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + eventemitter3@2.0.3: + resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.1.2: + resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@4.5.0: + resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==} + hasBin: true + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + hasBin: true + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-saver@2.0.5: + resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + font-awesome@4.7.0: + resolution: {integrity: sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==} + engines: {node: '>=0.10.3'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formstream@1.5.2: + resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==} + + frac@1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generic-names@4.0.0: + resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-ready@1.0.0: + resolution: {integrity: sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + good-listener@1.2.2: + resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + help-me@3.0.0: + resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==} + + html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + + humanize-duration@3.33.2: + resolution: {integrity: sha512-K7Ny/ULO1hDm2nnhvAY+SJV1skxFb61fd073SG1IWJl+D44ULrruCuTyjHKjBVVcSuTlnY99DKtgEG39CM5QOQ==} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + i18next@20.6.1: + resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + icss-replace-symbols@1.1.0: + resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + + import-cwd@3.0.0: + resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} + engines: {node: '>=8'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-from@3.0.0: + resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} + engines: {node: '>=8'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-class-hotfix@0.0.6: + resolution: {integrity: sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hotkey@0.2.0: + resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-type-of@1.4.0: + resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isomorphic-ws@4.0.1: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jalaali-js@1.2.8: + resolution: {integrity: sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==} + + javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + + jest-worker@26.6.2: + resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} + engines: {node: '>= 10.13.0'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jmespath@0.16.0: + resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} + engines: {node: '>= 0.6.0'} + + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + + js-binary-schema-parser@2.0.3: + resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-sdsl@4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + + js-table2excel@1.1.2: + resolution: {integrity: sha512-E5QcPLjlF7qcqqF+DgaPxj9b+lQsItMqkwcXKpODtZ8zo5f07huuvem3Ek1WYzOcSOalU7RwEAG/66WQ5PVfWA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-editor-vue3@1.1.1: + resolution: {integrity: sha512-3qslVNZAY+roVfL10ukuaAw1ia9YpWdLO9PnBL+4SuM5a6R3oWCSQU6dLTTPd/dVtqhIefNn4QmthAioYOBHeA==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-source-map@0.6.1: + resolution: {integrity: sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsoneditor@9.10.5: + resolution: {integrity: sha512-fVZ0NMt+zm4rqTKBv2x7zPdLeaRyKo1EjJkaR1QjK4gEM1rMwICILYSW1OPxSc1qqyAoDaA/eeNrluKoxOocCA==} + + jsonrepair@3.1.0: + resolution: {integrity: sha512-idqReg23J0PVRAADmZMc5xQM3xeOX5bTB6OTyMnzq33IXJXmn9iJuWIEvGmrN80rQf4d7uLTMEDwpzujNcI0Rg==} + hasBin: true + + jsplumb@2.15.6: + resolution: {integrity: sha512-sIpbpz5eMVM+vV+MQzFCidlaa1RsknrQs6LOTKYDjYUDdTAi2AN2bFi94TxB33TifcIsRNV1jebcaxg0tCoPzg==} + + jstoxml@2.2.9: + resolution: {integrity: sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + less@4.6.4: + resolution: {integrity: sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==} + engines: {node: '>=18'} + hasBin: true + + leven@2.1.0: + resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} + engines: {node: '>=0.10.0'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-utils@3.3.1: + resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} + engines: {node: '>= 12.13.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.toarray@4.4.0: + resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.18.0: + resolution: {integrity: sha512-l1mfj2atMqndAHI3ls7XqPxEjV2J9ZkcNyHpoZA3r2T1LLwDB69jgkMWh71YKwhBbK0G2f4WSn05ahmQXVxupA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lunar-javascript@1.7.7: + resolution: {integrity: sha512-u/KYiwPIBo/0bT+WWfU7qO1d+aqeB90Tuy4ErXenr2Gam0QcWeezUvtiOIyXR7HbVnW2I1DKfU0NBvzMZhbVQw==} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-match@1.0.2: + resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@2.1.0: + resolution: {integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mobius1-selectr@2.4.13: + resolution: {integrity: sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==} + + moment-timezone@0.5.48: + resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + mqtt-packet@6.10.0: + resolution: {integrity: sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==} + + mqtt@4.3.8: + resolution: {integrity: sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw==} + engines: {node: '>=10.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + namespace-emitter@2.0.1: + resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + needle@3.5.0: + resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==} + engines: {node: '>= 4.4.x'} + hasBin: true + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-hex@1.0.1: + resolution: {integrity: sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==} + engines: {node: '>=8.0.0'} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + number-allocator@1.0.14: + resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-name@1.0.3: + resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==} + engines: {node: '>=0.10.0'} + hasBin: true + + osx-release@1.1.0: + resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==} + engines: {node: '>=0.10.0'} + hasBin: true + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parchment@1.1.4: + resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parenthesis@3.1.8: + resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-expression-matcher@1.2.0: + resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + engines: {node: '>=14.0.0'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + picomodal@3.0.0: + resolution: {integrity: sha512-FoR3TDfuLlqUvcEeK5ifpKSVVns6B4BQvc8SDF6THVMuadya6LLtji0QgUDSStw0ZR2J7I6UGi5V2V23rnPWTw==} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + pinia-plugin-persist@1.0.0: + resolution: {integrity: sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==} + peerDependencies: + '@vue/composition-api': ^1.0.0 + pinia: ^2.0.0 + vue: ^2.0.0 || >=3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + pinia@2.3.1: + resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + + postcss-calc@8.2.4: + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + + postcss-colormin@5.3.1: + resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-convert-values@5.1.3: + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-comments@5.1.2: + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-duplicates@5.1.0: + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-empty@5.1.1: + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-overridden@5.1.0: + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-merge-longhand@5.1.7: + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-merge-rules@5.1.4: + resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-font-values@5.1.0: + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-gradients@5.1.1: + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-params@5.1.4: + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-selectors@5.2.1: + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules@4.3.1: + resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} + peerDependencies: + postcss: ^8.0.0 + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-normalize-charset@5.1.0: + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-display-values@5.1.0: + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-positions@5.1.1: + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-repeat-style@5.1.1: + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-string@5.1.0: + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-timing-functions@5.1.0: + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-unicode@5.1.1: + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-url@5.1.0: + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-whitespace@5.1.1: + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-ordered-values@5.1.3: + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-initial@5.1.2: + resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-transforms@5.1.0: + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-svgo@5.1.0: + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-unique-selectors@5.1.1: + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.0: + resolution: {integrity: sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prepin@1.0.3: + resolution: {integrity: sha512-0XL2hreherEEvUy0fiaGEfN/ioXFV+JpImqIzQjxk6iBg4jQ2ARKqvC4+BmRD8w/pnpD+lbxvh0Ub+z7yBEjvA==} + hasBin: true + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + print-js@1.6.0: + resolution: {integrity: sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + promise.series@0.2.0: + resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} + engines: {node: '>=0.12'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qiniu-js@3.4.1: + resolution: {integrity: sha512-8vxrLqDPlJUk3fUAaTozh3TAT3ww9B5KqGogmGuTiFHnewXDoMxTCSY5z8Ab5UNdrCo6ZxDM07G/o++CICRUFw==} + + qrcodejs2-fixes@0.0.2: + resolution: {integrity: sha512-wMUXYMOixAEJlLnjk5MbLiFaz0gQObWYm/TIFWB5+j7sTY5gPyr09Cx1EpcLYbsgfFdN3wHjrKAhZofTuCBGhg==} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + + querystring@0.2.1: + resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} + engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quill-delta@3.6.3: + resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==} + engines: {node: '>=0.10'} + + quill@1.3.7: + resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + reinterval@1.1.0: + resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup-plugin-postcss@4.0.2: + resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} + engines: {node: '>=10'} + peerDependencies: + postcss: 8.x + + rollup-plugin-terser@7.0.2: + resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser + peerDependencies: + rollup: ^2.0.0 + + rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-identifier@0.4.2: + resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.98.0: + resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + screenfull@6.0.2: + resolution: {integrity: sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==} + engines: {node: ^14.13.1 || >=16.0.0} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + sdk-base@2.0.1: + resolution: {integrity: sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==} + + select@1.1.2: + resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@4.0.0: + resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slate-history@0.66.0: + resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==} + peerDependencies: + slate: '>=0.65.3' + + slate@0.72.8: + resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==} + + snabbdom@3.6.3: + resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==} + engines: {node: '>=12.17.0'} + + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + + sortablejs@1.15.7: + resolution: {integrity: sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + spark-md5@3.0.2: + resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==} + + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + + splitpanes@3.2.0: + resolution: {integrity: sha512-K+WKxWdqtKShV33gPjQl769wHxB3glypTOReCvYu/AJd38J+abHlpiF8rK6uBNPMrgw5thHZCI5JkEwsAqa9XA==} + peerDependencies: + vue: ^3.2.0 + + ssf@0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + + ssr-window@3.0.0: + resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + stream-http@2.8.2: + resolution: {integrity: sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + stream-wormhole@1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + + string-hash@1.1.3: + resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} + + string-split-by@1.0.0: + resolution: {integrity: sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@1.1.2: + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + + strnum@2.2.2: + resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==} + + style-inject@0.3.0: + resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} + + stylehacks@5.1.1: + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svgo@2.8.2: + resolution: {integrity: sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==} + engines: {node: '>=10.13.0'} + hasBin: true + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + terser@5.46.1: + resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + engines: {node: '>=10'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-arraybuffer@1.0.1: + resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-md5@1.3.1: + resolution: {integrity: sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==} + engines: {node: '>=12'} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + upgrade@1.1.0: + resolution: {integrity: sha512-NtkVvqVCqsJo5U3mYRum2Tw6uCltOxfIJ/AfTZeTmw6U39IB5X23xF+kRZ9aiPaORqeiQQ7Q209/ibhOvxzwHA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urllib@2.44.0: + resolution: {integrity: sha512-zRCJqdfYllRDA9bXUtx+vccyRqtJPKsw85f44zH7zPD28PIvjMqIgw9VwoTLV7xTBWZsbebUFVHU5ghQcWku2A==} + engines: {node: '>= 0.10.0'} + peerDependencies: + proxy-agent: ^5.0.0 + peerDependenciesMeta: + proxy-agent: + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utility@1.18.0: + resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} + engines: {node: '>= 0.12.0'} + + vanilla-picker@2.12.3: + resolution: {integrity: sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==} + + vant4-kit@1.0.6: + resolution: {integrity: sha512-xdVFkNTTjvnVY572fpZWrgXVOGkGYWLxvMw4A+K2pnV2ZbETR0n375F0k5eIrwES7oHMSdyM8D/XtSSIZH14Lg==} + peerDependencies: + vue: ^3.4.19 + + vant@4.9.24: + resolution: {integrity: sha512-tP1A7Vjzv1/B1ljb95Jhv9Q9w6acaaZDJvy6wcKrwGgY0gQZlg+FXLZH/AIKZBE3xvYGDUsv/M7AuGcr/Pqd6A==} + peerDependencies: + vue: ^3.0.0 + + vite-plugin-vue-setup-extend@0.4.0: + resolution: {integrity: sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==} + peerDependencies: + vite: '>=2.0.0' + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vue-clipboard3@2.0.0: + resolution: {integrity: sha512-Q9S7dzWGax7LN5iiSPcu/K1GGm2gcBBlYwmMsUc5/16N6w90cbKow3FnPmPs95sungns4yvd9/+JhbAznECS2A==} + + vue-component-type-helpers@3.2.6: + resolution: {integrity: sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ==} + + vue-cropper@1.1.4: + resolution: {integrity: sha512-5m98vBsCEI9rbS4JxELxXidtAui3qNyTHLHg67Qbn7g8cg+E6LcnC+hh3SM/p94x6mFh6KRxT1ttnta+wCYqWA==} + + vue-cropperjs@5.0.0: + resolution: {integrity: sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==} + peerDependencies: + vue: '>=3.0.0' + + vue-demi@0.12.5: + resolution: {integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-draggable-plus@0.6.1: + resolution: {integrity: sha512-FbtQ/fuoixiOfTZzG3yoPl4JAo9HJXRHmBQZFB9x2NYCh6pq0TomHf7g5MUmpaDYv+LU2n6BPq2YN9sBO+FbIg==} + peerDependencies: + '@types/sortablejs': ^1.15.0 + '@vue/composition-api': '*' + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-grid-layout@3.0.0-beta1: + resolution: {integrity: sha512-MsW0yfYNtnAO/uDhfZvkP6effxSJxvhAFbIL37x6Rn3vW9xf0WHVefKaSbQMLpSq3mXnR6ut0pg2Cd5lqIIZzg==} + + vue-i18n@9.14.5: + resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==} + engines: {node: '>= 16'} + deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html + peerDependencies: + vue: ^3.0.0 + + vue-qr@4.0.9: + resolution: {integrity: sha512-pAISV94T0MNEYA3NGjykUpsXRE2QfaNxlu9ZhEL6CERgqNc21hJYuP3hRVzAWfBQlgO18DPmZTbrFerJC3+Ikw==} + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue@3.5.31: + resolution: {integrity: sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable-es@4.1.1: + resolution: {integrity: sha512-F35pjSwC8HS/lnaOd+B59nYR4FZmwuhWAzccK9xftRuWds8SU1TZh5myKVM86j5dFOI7S26O64Kwe7LUHnXjlA==} + peerDependencies: + vue: ^3.2.31 + + vxe-pc-ui@4.13.20: + resolution: {integrity: sha512-AZqFcE5M+KPPIOdPxvQ2Ny+Zf152seKY/o6zqSjHWWzX4EGHYd7dpw3Qrnlp1VT1gVd1Nq599TsSq2MAOR7MaQ==} + + vxe-table@4.18.10: + resolution: {integrity: sha512-8V9WL83pB4PrvgBS6DoDU7dSRxOlJw9ZkVcxDnKBFQkTTnLVvC8HYOJ75uwfRNyo9cALNIpJnSkow2uqaMKbNQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wildcard@1.1.2: + resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + + win-release@1.1.1: + resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==} + engines: {node: '>=0.10.0'} + + wmf@1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + word@0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xe-utils@3.9.1: + resolution: {integrity: sha512-Ujk5UmoH6Iaqhgz3oGwfCXVcMdUJKlXnfvLABdnMyseMG0eHsX2mcCvLd/8sGlIXtfwsprI9bW7vgcVognLmqQ==} + + xe-utils@4.0.4: + resolution: {integrity: sha512-1IUuTZRHa5w88Y3kEf0O4Y/99O0hIdh5o8EXqmgqyhY3MaT1xWZgS5iuD7pys6v+9JrJOzECb07+6gxRahDsyw==} + + xlsx@0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yarn@1.22.22: + resolution: {integrity: sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==} + engines: {node: '>=4.0.0'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1021.0(aws-crt@1.30.0)': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/credential-provider-node': 3.972.29(aws-crt@1.30.0) + '@aws-sdk/middleware-bucket-endpoint': 3.972.8 + '@aws-sdk/middleware-expect-continue': 3.972.8 + '@aws-sdk/middleware-flexible-checksums': 3.974.6 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-location-constraint': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.9 + '@aws-sdk/middleware-sdk-s3': 3.972.27 + '@aws-sdk/middleware-ssec': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.28 + '@aws-sdk/region-config-resolver': 3.972.10 + '@aws-sdk/signature-v4-multi-region': 3.996.15 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.14(aws-crt@1.30.0) + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.13 + '@smithy/eventstream-serde-browser': 4.2.12 + '@smithy/eventstream-serde-config-resolver': 4.3.12 + '@smithy/eventstream-serde-node': 4.2.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-blob-browser': 4.2.13 + '@smithy/hash-node': 4.2.12 + '@smithy/hash-stream-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/md5-js': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/middleware-retry': 4.4.46 + '@smithy/middleware-serde': 4.2.16 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.1 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.44 + '@smithy/util-defaults-mode-node': 4.2.48 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.13 + '@smithy/util-stream': 4.5.21 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.14 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.26': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.16 + '@smithy/core': 3.23.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.5': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.24': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.26': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/types': 3.973.6 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.1 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.21 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.28(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/credential-provider-env': 3.972.24 + '@aws-sdk/credential-provider-http': 3.972.26 + '@aws-sdk/credential-provider-login': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/credential-provider-process': 3.972.24 + '@aws-sdk/credential-provider-sso': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/credential-provider-web-identity': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/nested-clients': 3.996.18(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.28(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.29(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.24 + '@aws-sdk/credential-provider-http': 3.972.26 + '@aws-sdk/credential-provider-ini': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/credential-provider-process': 3.972.24 + '@aws-sdk/credential-provider-sso': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/credential-provider-web-identity': 3.972.28(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.24': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.28(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18(aws-crt@1.30.0) + '@aws-sdk/token-providers': 3.1021.0(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.28(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/crt-loader@3.972.28': + dependencies: + '@aws-sdk/util-user-agent-node': 3.973.14(aws-crt@1.30.0) + aws-crt: 1.30.0 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@aws-sdk/middleware-bucket-endpoint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.6': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/crc64-nvme': 3.972.5 + '@aws-sdk/types': 3.973.6 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.21 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.27': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.21 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.28': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.13 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.13 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.18(aws-crt@1.30.0)': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.26 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.28 + '@aws-sdk/region-config-resolver': 3.972.10 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.14(aws-crt@1.30.0) + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.13 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/middleware-retry': 4.4.46 + '@smithy/middleware-serde': 4.2.16 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.1 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.44 + '@smithy/util-defaults-mode-node': 4.2.48 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.13 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1021.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.996.15 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-format-url': 3.972.8 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-crt@3.1021.0': + dependencies: + '@aws-sdk/crt-loader': 3.972.28 + '@aws-sdk/signature-v4-multi-region': 3.996.15 + '@aws-sdk/types': 3.973.6 + '@smithy/querystring-parser': 4.2.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@aws-sdk/signature-v4-multi-region@3.996.15': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.27 + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1021.0(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/core': 3.973.26 + '@aws-sdk/nested-clients': 3.996.18(aws-crt@1.30.0) + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.972.3': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.14(aws-crt@1.30.0)': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.28 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + optionalDependencies: + aws-crt: 1.30.0 + + '@aws-sdk/util-utf8-browser@3.259.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.16': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime-corejs2@7.29.2': + dependencies: + core-js: 2.6.12 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@ctrl/tinycolor@4.2.0': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.31(typescript@4.9.5))': + dependencies: + vue: 3.5.31(typescript@4.9.5) + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@fast-crud/fast-crud@1.27.10(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@iconify/types': 2.0.0 + file-saver: 2.0.5 + lru-cache: 10.4.3 + vuedraggable-es: 4.1.1(vue@3.5.31(typescript@4.9.5)) + xlsx: 0.18.5 + transitivePeerDependencies: + - vue + + '@fast-crud/fast-extends@1.27.10(aws-crt@1.30.0)(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@aws-sdk/client-s3': 3.1021.0(aws-crt@1.30.0) + '@aws-sdk/s3-request-presigner': 3.1021.0 + '@aws-sdk/signature-v4-crt': 3.1021.0 + '@soerenmartius/vue3-clipboard': 0.1.2 + '@wangeditor/editor': 5.1.23 + '@wangeditor/editor-for-vue': 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.31(typescript@4.9.5)) + ali-oss: 6.23.0 + cos-js-sdk-v5: 1.10.1 + cropperjs: 1.6.2 + dayjs: 1.11.20 + glob: 10.4.5 + humanize-duration: 3.33.2 + js-yaml: 4.1.1 + jsoneditor: 9.10.5 + lodash-es: 4.17.23 + object-assign: 4.1.1 + qiniu-js: 3.4.1 + quill: 1.3.7 + vue-cropperjs: 5.0.0(vue@3.5.31(typescript@4.9.5)) + transitivePeerDependencies: + - aws-crt + - bufferutil + - debug + - proxy-agent + - supports-color + - utf-8-validate + - vue + + '@fast-crud/ui-element@1.27.10': {} + + '@fast-crud/ui-interface@1.27.10': + dependencies: + lodash-es: 4.17.23 + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + + '@great-dream/dvadmin3-celery-web@3.1.3': + dependencies: + cron-chinese: 1.2.0 + cron-parser: 4.9.0 + json-editor-vue3: 1.1.1 + + '@httptoolkit/websocket-stream@6.0.1': + dependencies: + '@types/ws': 8.18.1 + duplexify: 3.7.1 + inherits: 2.0.4 + isomorphic-ws: 4.0.1(ws@8.20.0) + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + ws: 8.20.0 + xtend: 4.0.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@iconify/types@2.0.0': {} + + '@iconify/vue@4.3.0(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@iconify/types': 2.0.0 + vue: 3.5.31(typescript@4.9.5) + + '@interactjs/actions@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/auto-scroll@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/auto-start@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/core@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/dev-tools@1.10.27(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@4.9.5)': + dependencies: + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + vue: 3.5.31(typescript@4.9.5) + transitivePeerDependencies: + - typescript + + '@interactjs/inertia@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/offset': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/interact@1.10.27': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + + '@interactjs/interactjs@1.10.27(typescript@4.9.5)': + dependencies: + '@interactjs/actions': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/auto-scroll': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/auto-start': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/dev-tools': 1.10.27(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@4.9.5) + '@interactjs/inertia': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/interact': 1.10.27 + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/offset': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/pointer-events': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/reflow': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + transitivePeerDependencies: + - typescript + + '@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/snappers': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/offset@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/pointer-events@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/reflow@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/snappers@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/utils@1.10.27': {} + + '@intlify/core-base@9.14.5': + dependencies: + '@intlify/message-compiler': 9.14.5 + '@intlify/shared': 9.14.5 + + '@intlify/message-compiler@9.14.5': + dependencies: + '@intlify/shared': 9.14.5 + source-map-js: 1.2.1 + + '@intlify/shared@9.14.5': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@meetjs/vant4-kit@1.0.1(vue@3.5.31(typescript@4.9.5))': + dependencies: + vue: 3.5.31(typescript@4.9.5) + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@popperjs/core@2.11.8': {} + + '@rolldown/pluginutils@1.0.0-rc.12': {} + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/core@3.23.13': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.21 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.12': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.12': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.12': + dependencies: + '@smithy/eventstream-codec': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.13': + dependencies: + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.28': + dependencies: + '@smithy/core': 3.23.13 + '@smithy/middleware-serde': 4.2.16 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.46': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.13 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.16': + dependencies: + '@smithy/core': 3.23.13 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.5.1': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/smithy-client@4.12.8': + dependencies: + '@smithy/core': 3.23.13 + '@smithy/middleware-endpoint': 4.4.28 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.21 + tslib: 2.8.1 + + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.44': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.48': + dependencies: + '@smithy/config-resolver': 4.4.13 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.8 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.13': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.21': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.1 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.14': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + + '@soerenmartius/vue3-clipboard@0.1.2': + dependencies: + clipboard: 2.0.11 + + '@sphinxxxx/color-conversion@2.2.2': {} + + '@sxzz/popperjs-es@2.11.8': {} + + '@transloadit/prettier-bytes@0.0.7': {} + + '@types/estree@1.0.8': {} + + '@types/event-emitter@0.3.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + + '@types/nprogress@0.2.3': {} + + '@types/semver@7.7.1': {} + + '@types/sortablejs@1.15.9': {} + + '@types/web-bluetooth@0.0.16': {} + + '@types/web-bluetooth@0.0.20': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 18.19.130 + + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare-lite: 1.4.0 + semver: 7.7.4 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) + debug: 4.4.3 + eslint: 8.57.1 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.4 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@uppy/companion-client@2.2.2': + dependencies: + '@uppy/utils': 4.1.3 + namespace-emitter: 2.0.1 + + '@uppy/core@2.3.4': + dependencies: + '@transloadit/prettier-bytes': 0.0.7 + '@uppy/store-default': 2.1.1 + '@uppy/utils': 4.1.3 + lodash.throttle: 4.1.1 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 3.3.11 + preact: 10.29.0 + + '@uppy/store-default@2.1.1': {} + + '@uppy/utils@4.1.3': + dependencies: + lodash.throttle: 4.1.1 + + '@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)': + dependencies: + '@uppy/companion-client': 2.2.2 + '@uppy/core': 2.3.4 + '@uppy/utils': 4.1.3 + nanoid: 3.3.11 + + '@vant/popperjs@1.3.0': {} + + '@vant/use@1.6.0(vue@3.5.31(typescript@4.9.5))': + dependencies: + vue: 3.5.31(typescript@4.9.5) + + '@vitejs/plugin-vue-jsx@4.2.0(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1))(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.12 + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0) + vite: 5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1) + vue: 3.5.31(typescript@4.9.5) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1))(vue@3.5.31(typescript@4.9.5))': + dependencies: + vite: 5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1) + vue: 3.5.31(typescript@4.9.5) + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.0)': + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0) + '@vue/shared': 3.5.31 + optionalDependencies: + '@babel/core': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.0)': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.29.2 + '@vue/compiler-sfc': 3.5.31 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.31': + dependencies: + '@babel/parser': 7.29.2 + '@vue/shared': 3.5.31 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.31': + dependencies: + '@vue/compiler-core': 3.5.31 + '@vue/shared': 3.5.31 + + '@vue/compiler-sfc@3.5.31': + dependencies: + '@babel/parser': 7.29.2 + '@vue/compiler-core': 3.5.31 + '@vue/compiler-dom': 3.5.31 + '@vue/compiler-ssr': 3.5.31 + '@vue/shared': 3.5.31 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.8 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.31': + dependencies: + '@vue/compiler-dom': 3.5.31 + '@vue/shared': 3.5.31 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.31': + dependencies: + '@vue/shared': 3.5.31 + + '@vue/runtime-core@3.5.31': + dependencies: + '@vue/reactivity': 3.5.31 + '@vue/shared': 3.5.31 + + '@vue/runtime-dom@3.5.31': + dependencies: + '@vue/reactivity': 3.5.31 + '@vue/runtime-core': 3.5.31 + '@vue/shared': 3.5.31 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.31(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@vue/compiler-ssr': 3.5.31 + '@vue/shared': 3.5.31 + vue: 3.5.31(typescript@4.9.5) + + '@vue/shared@3.5.31': {} + + '@vueuse/core@12.0.0(typescript@4.9.5)': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 12.0.0 + '@vueuse/shared': 12.0.0(typescript@4.9.5) + vue: 3.5.31(typescript@4.9.5) + transitivePeerDependencies: + - typescript + + '@vueuse/core@9.13.0(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.5.31(typescript@4.9.5)) + vue-demi: 0.14.10(vue@3.5.31(typescript@4.9.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@12.0.0': {} + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/shared@12.0.0(typescript@4.9.5)': + dependencies: + vue: 3.5.31(typescript@4.9.5) + transitivePeerDependencies: + - typescript + + '@vueuse/shared@9.13.0(vue@3.5.31(typescript@4.9.5))': + dependencies: + vue-demi: 0.14.10(vue@3.5.31(typescript@4.9.5)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vxe-ui/core@4.4.3(vue@3.5.31(typescript@4.9.5))': + dependencies: + dom-zindex: 1.0.6 + vue: 3.5.31(typescript@4.9.5) + xe-utils: 4.0.4 + + '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-url: 1.2.4 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + prismjs: 1.30.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@types/event-emitter': 0.3.5 + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + dom7: 3.0.0 + event-emitter: 0.3.5 + html-void-elements: 2.0.1 + i18next: 20.6.1 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + scroll-into-view-if-needed: 2.2.31 + slate: 0.72.8 + slate-history: 0.66.0(slate@0.72.8) + snabbdom: 3.6.3 + + '@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.31(typescript@4.9.5))': + dependencies: + '@wangeditor/editor': 5.1.23 + vue: 3.5.31(typescript@4.9.5) + + '@wangeditor/editor@5.1.23': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.foreach: 4.5.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + ace-builds@1.43.6: {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + address@1.2.2: {} + + adler-32@1.3.1: {} + + agentkeepalive@3.5.3: + dependencies: + humanize-ms: 1.2.1 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ali-oss@6.23.0: + dependencies: + address: 1.2.2 + agentkeepalive: 3.5.3 + bowser: 1.9.4 + copy-to: 2.0.1 + dateformat: 2.2.0 + debug: 4.4.3 + destroy: 1.2.0 + end-or-error: 1.0.1 + get-ready: 1.0.0 + humanize-ms: 1.2.1 + is-type-of: 1.4.0 + js-base64: 2.6.4 + jstoxml: 2.2.9 + lodash: 4.17.21 + merge-descriptors: 1.0.3 + mime: 2.6.0 + platform: 1.3.6 + pump: 3.0.4 + qs: 6.15.0 + sdk-base: 2.0.1 + stream-http: 2.8.2 + stream-wormhole: 1.1.0 + urllib: 2.44.0 + utility: 1.18.0 + xml2js: 0.6.2 + transitivePeerDependencies: + - proxy-agent + - supports-color + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + astronomia@4.2.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.27(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001782 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + aws-crt@1.30.0: + dependencies: + '@aws-sdk/util-utf8-browser': 3.259.0 + '@httptoolkit/websocket-stream': 6.0.1 + axios: 1.14.0 + buffer: 6.0.3 + crypto-js: 4.2.0 + mqtt: 4.3.8 + process: 0.11.10 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + axios@1.14.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.13: {} + + batch-processor@1.0.0: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + boolbase@1.0.0: {} + + bowser@1.9.4: {} + + bowser@2.14.1: {} + + brace-expansion@1.1.13: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.13 + caniuse-lite: 1.0.30001782 + electron-to-chromium: 1.5.330 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builtin-status-codes@3.0.0: {} + + caldate@2.0.5: + dependencies: + moment-timezone: 0.5.48 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001782 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001782: {} + + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + claygl@1.3.0: {} + + clipboard@2.0.11: + dependencies: + good-listener: 1.2.2 + select: 1.1.2 + tiny-emitter: 2.1.0 + + clone@2.1.2: {} + + codepage@1.15.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colord@2.9.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commist@1.1.0: + dependencies: + leven: 2.1.0 + minimist: 1.2.8 + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + concat-with-sourcemaps@1.1.0: + dependencies: + source-map: 0.6.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + copy-to@2.0.1: {} + + core-js@2.6.12: {} + + core-util-is@1.0.3: {} + + cos-js-sdk-v5@1.10.1: + dependencies: + fast-xml-parser: 4.5.0 + + countup.js@2.10.0: {} + + crc-32@1.2.2: {} + + cron-chinese@1.2.0: {} + + cron-parser@4.9.0: + dependencies: + luxon: 3.7.2 + + cropperjs@1.6.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + css-declaration-sorter@6.4.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@5.2.14(postcss@8.5.8): + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.5.8) + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-calc: 8.2.4(postcss@8.5.8) + postcss-colormin: 5.3.1(postcss@8.5.8) + postcss-convert-values: 5.1.3(postcss@8.5.8) + postcss-discard-comments: 5.1.2(postcss@8.5.8) + postcss-discard-duplicates: 5.1.0(postcss@8.5.8) + postcss-discard-empty: 5.1.1(postcss@8.5.8) + postcss-discard-overridden: 5.1.0(postcss@8.5.8) + postcss-merge-longhand: 5.1.7(postcss@8.5.8) + postcss-merge-rules: 5.1.4(postcss@8.5.8) + postcss-minify-font-values: 5.1.0(postcss@8.5.8) + postcss-minify-gradients: 5.1.1(postcss@8.5.8) + postcss-minify-params: 5.1.4(postcss@8.5.8) + postcss-minify-selectors: 5.2.1(postcss@8.5.8) + postcss-normalize-charset: 5.1.0(postcss@8.5.8) + postcss-normalize-display-values: 5.1.0(postcss@8.5.8) + postcss-normalize-positions: 5.1.1(postcss@8.5.8) + postcss-normalize-repeat-style: 5.1.1(postcss@8.5.8) + postcss-normalize-string: 5.1.0(postcss@8.5.8) + postcss-normalize-timing-functions: 5.1.0(postcss@8.5.8) + postcss-normalize-unicode: 5.1.1(postcss@8.5.8) + postcss-normalize-url: 5.1.0(postcss@8.5.8) + postcss-normalize-whitespace: 5.1.1(postcss@8.5.8) + postcss-ordered-values: 5.1.3(postcss@8.5.8) + postcss-reduce-initial: 5.1.2(postcss@8.5.8) + postcss-reduce-transforms: 5.1.0(postcss@8.5.8) + postcss-svgo: 5.1.0(postcss@8.5.8) + postcss-unique-selectors: 5.1.1(postcss@8.5.8) + + cssnano-utils@3.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + cssnano@5.1.15(postcss@8.5.8): + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.5.8) + lilconfig: 2.1.0 + postcss: 8.5.8 + yaml: 1.10.3 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csstype@3.2.3: {} + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + date-bengali-revised@2.0.2: {} + + date-chinese@2.1.4: + dependencies: + astronomia: 4.2.0 + + date-easter@1.0.3: {} + + date-holidays-parser@3.4.7: + dependencies: + astronomia: 4.2.0 + caldate: 2.0.5 + date-bengali-revised: 2.0.2 + date-chinese: 2.1.4 + date-easter: 1.0.3 + deepmerge: 4.3.1 + jalaali-js: 1.2.8 + moment-timezone: 0.5.48 + + date-holidays@3.26.12: + dependencies: + date-holidays-parser: 3.4.7 + js-yaml: 4.1.1 + lodash: 4.18.0 + prepin: 1.0.3 + + dateformat@2.2.0: {} + + dayjs@1.11.20: {} + + debounce@1.2.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-equal@1.1.2: + dependencies: + is-arguments: 1.2.0 + is-date-object: 1.1.0 + is-regex: 1.2.1 + object-is: 1.1.6 + object-keys: 1.1.1 + regexp.prototype.flags: 1.5.4 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-user-agent@1.0.0: + dependencies: + os-name: 1.0.3 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + delegate@3.2.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: + optional: true + + didyoumean@1.2.2: {} + + digest-header@1.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-zindex@1.0.6: {} + + dom7@3.0.0: + dependencies: + ssr-window: 3.0.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexify@3.7.1: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 + + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + + e-icon-picker@2.1.1(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@popperjs/core': 2.11.8 + '@vue/shared': 3.5.31 + '@vueuse/core': 9.13.0(vue@3.5.31(typescript@4.9.5)) + debounce: 1.2.1 + vue: 3.5.31(typescript@4.9.5) + transitivePeerDependencies: + - '@vue/composition-api' + + eastasianwidth@0.2.0: {} + + echarts-gl@2.0.9(echarts@5.6.0): + dependencies: + claygl: 1.3.0 + echarts: 5.6.0 + zrender: 5.6.1 + + echarts-wordcloud@2.1.0(echarts@5.6.0): + dependencies: + echarts: 5.6.0 + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.330: {} + + element-plus@2.13.6(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@ctrl/tinycolor': 4.2.0 + '@element-plus/icons-vue': 2.3.2(vue@3.5.31(typescript@4.9.5)) + '@floating-ui/dom': 1.7.6 + '@popperjs/core': '@sxzz/popperjs-es@2.11.8' + '@types/lodash': 4.17.24 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 12.0.0(typescript@4.9.5) + async-validator: 4.2.5 + dayjs: 1.11.20 + lodash: 4.18.0 + lodash-es: 4.17.23 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.23)(lodash@4.18.0) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.31(typescript@4.9.5) + vue-component-type-helpers: 3.2.6 + transitivePeerDependencies: + - typescript + + element-resize-detector@1.2.4: + dependencies: + batch-processor: 1.0.0 + + element-tree-line@0.2.1(vue@3.5.31(typescript@4.9.5)): + dependencies: + vue: 3.5.31(typescript@4.9.5) + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + end-or-error@1.0.1: {} + + entities@2.2.0: {} + + entities@7.0.1: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-vue@9.33.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + eslint: 8.57.1 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.4 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@9.6.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-walker@0.6.1: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + eventemitter3@2.0.3: {} + + eventemitter3@4.0.7: {} + + ext@1.7.0: + dependencies: + type: 2.7.3 + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.1.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.0 + + fast-xml-parser@4.5.0: + dependencies: + strnum: 1.1.2 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.0 + strnum: 2.2.2 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-saver@2.0.5: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.2: {} + + follow-redirects@1.15.11: {} + + font-awesome@4.7.0: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formstream@1.5.2: + dependencies: + destroy: 1.2.0 + mime: 2.6.0 + node-hex: 1.0.1 + pause-stream: 0.0.11 + + frac@1.1.2: {} + + fraction.js@5.3.4: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + functions-have-names@1.2.3: {} + + generic-names@4.0.0: + dependencies: + loader-utils: 3.3.1 + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-ready@1.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + good-listener@1.2.2: + dependencies: + delegate: 3.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: + optional: true + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + help-me@3.0.0: + dependencies: + glob: 7.2.3 + readable-stream: 3.6.2 + + html-void-elements@2.0.1: {} + + humanize-duration@3.33.2: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + i18next@20.6.1: + dependencies: + '@babel/runtime': 7.29.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + icss-replace-symbols@1.1.0: {} + + icss-utils@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + image-size@0.5.5: + optional: true + + immer@9.0.21: {} + + immutable@5.1.5: {} + + import-cwd@3.0.0: + dependencies: + import-from: 3.0.0 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from@3.0.0: + dependencies: + resolve-from: 5.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-class-hotfix@0.0.6: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extendable@0.1.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.2.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-object@5.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-type-of@1.4.0: + dependencies: + core-util-is: 1.0.3 + is-class-hotfix: 0.0.6 + isstream: 0.1.2 + + is-url@1.2.4: {} + + is-what@4.1.16: {} + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + isomorphic-ws@4.0.1(ws@8.20.0): + dependencies: + ws: 8.20.0 + + isstream@0.1.2: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jalaali-js@1.2.8: {} + + javascript-natural-sort@0.7.1: {} + + jest-worker@26.6.2: + dependencies: + '@types/node': 18.19.130 + merge-stream: 2.0.0 + supports-color: 7.2.0 + + jiti@1.21.7: {} + + jmespath@0.16.0: {} + + js-base64@2.6.4: {} + + js-binary-schema-parser@2.0.3: {} + + js-cookie@3.0.5: {} + + js-sdsl@4.3.0: {} + + js-table2excel@1.1.2: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-editor-vue3@1.1.1: + dependencies: + jsoneditor: 9.10.5 + + json-schema-traverse@0.4.1: {} + + json-source-map@0.6.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsoneditor@9.10.5: + dependencies: + ace-builds: 1.43.6 + ajv: 6.14.0 + javascript-natural-sort: 0.7.1 + jmespath: 0.16.0 + json-source-map: 0.6.1 + jsonrepair: 3.1.0 + mobius1-selectr: 2.4.13 + picomodal: 3.0.0 + vanilla-picker: 2.12.3 + + jsonrepair@3.1.0: {} + + jsplumb@2.15.6: {} + + jstoxml@2.2.9: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + less@4.6.4: + dependencies: + copy-anything: 3.0.5 + parse-node-version: 1.0.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.5.0 + source-map: 0.6.1 + + leven@2.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + loader-utils@3.3.1: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.23: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.23)(lodash@4.18.0): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.18.0 + lodash-es: 4.17.23 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.foreach@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + + lodash.toarray@4.4.0: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.21: {} + + lodash@4.18.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lunar-javascript@1.7.7: {} + + luxon@3.7.2: {} + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + + math-intrinsics@1.1.0: {} + + mdn-data@2.0.14: {} + + memoize-one@6.0.0: {} + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: + optional: true + + mime@2.6.0: {} + + mimic-response@3.1.0: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.13 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.3 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.3 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mitt@2.1.0: {} + + mitt@3.0.1: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mobius1-selectr@2.4.13: {} + + moment-timezone@0.5.48: + dependencies: + moment: 2.30.1 + + moment@2.30.1: {} + + mqtt-packet@6.10.0: + dependencies: + bl: 4.1.0 + debug: 4.4.3 + process-nextick-args: 2.0.1 + transitivePeerDependencies: + - supports-color + + mqtt@4.3.8: + dependencies: + commist: 1.1.0 + concat-stream: 2.0.0 + debug: 4.4.3 + duplexify: 4.1.3 + help-me: 3.0.0 + inherits: 2.0.4 + lru-cache: 6.0.0 + minimist: 1.2.8 + mqtt-packet: 6.10.0 + number-allocator: 1.0.14 + pump: 3.0.4 + readable-stream: 3.6.2 + reinterval: 1.1.0 + rfdc: 1.4.1 + split2: 3.2.2 + ws: 7.5.10 + xtend: 4.0.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + namespace-emitter@2.0.1: {} + + nanoid@3.3.11: {} + + natural-compare-lite@1.4.0: {} + + natural-compare@1.4.0: {} + + needle@3.5.0: + dependencies: + iconv-lite: 0.6.3 + sax: 1.6.0 + optional: true + + next-tick@1.1.0: {} + + node-addon-api@7.1.1: + optional: true + + node-hex@1.0.1: {} + + node-releases@2.0.36: {} + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + normalize-wheel-es@1.2.0: {} + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + number-allocator@1.0.14: + dependencies: + debug: 4.4.3 + js-sdsl: 4.3.0 + transitivePeerDependencies: + - supports-color + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-name@1.0.3: + dependencies: + osx-release: 1.1.0 + win-release: 1.1.1 + + osx-release@1.1.0: + dependencies: + minimist: 1.2.8 + + p-finally@1.0.0: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + package-json-from-dist@1.0.1: {} + + parchment@1.1.4: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parenthesis@3.1.8: {} + + parse-node-version@1.0.1: {} + + path-exists@4.0.0: {} + + path-expression-matcher@1.2.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-type@4.0.0: {} + + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + picomodal@3.0.0: {} + + pify@2.3.0: {} + + pify@4.0.1: + optional: true + + pify@5.0.0: {} + + pinia-plugin-persist@1.0.0(pinia@2.3.1(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)))(vue@3.5.31(typescript@4.9.5)): + dependencies: + pinia: 2.3.1(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)) + vue: 3.5.31(typescript@4.9.5) + vue-demi: 0.12.5(vue@3.5.31(typescript@4.9.5)) + + pinia@2.3.1(typescript@4.9.5)(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.31(typescript@4.9.5) + vue-demi: 0.14.10(vue@3.5.31(typescript@4.9.5)) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - '@vue/composition-api' + + pirates@4.0.7: {} + + platform@1.3.6: {} + + postcss-calc@8.2.4(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-colormin@5.3.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-convert-values@5.1.3(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-discard-comments@5.1.2(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-duplicates@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-empty@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-overridden@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-import@15.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.8): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.8 + + postcss-load-config@3.1.4(postcss@8.5.8): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.3 + optionalDependencies: + postcss: 8.5.8 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.8 + + postcss-merge-longhand@5.1.7(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.5.8) + + postcss-merge-rules@5.1.4(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-minify-font-values@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@5.1.1(postcss@8.5.8): + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-params@5.1.4(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@5.2.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-modules-extract-imports@3.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + + postcss-modules-values@4.0.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + + postcss-modules@4.3.1(postcss@8.5.8): + dependencies: + generic-names: 4.0.0 + icss-replace-symbols: 1.1.0 + lodash.camelcase: 4.3.0 + postcss: 8.5.8 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) + postcss-modules-scope: 3.2.1(postcss@8.5.8) + postcss-modules-values: 4.0.0(postcss@8.5.8) + string-hash: 1.1.3 + + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-normalize-charset@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-normalize-display-values@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@5.1.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@5.1.0(postcss@8.5.8): + dependencies: + normalize-url: 6.1.0 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@5.1.3(postcss@8.5.8): + dependencies: + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@5.1.2(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + caniuse-api: 3.0.0 + postcss: 8.5.8 + + postcss-reduce-transforms@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-svgo@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + svgo: 2.8.2 + + postcss-unique-selectors@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.0: {} + + prelude-ls@1.2.1: {} + + prepin@1.0.3: {} + + prettier@2.8.8: {} + + print-js@1.6.0: {} + + prismjs@1.30.0: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + promise.series@0.2.0: {} + + proxy-from-env@2.1.0: {} + + prr@1.0.1: + optional: true + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + qiniu-js@3.4.1: + dependencies: + '@babel/runtime-corejs2': 7.29.2 + querystring: 0.2.1 + spark-md5: 3.0.2 + + qrcodejs2-fixes@0.0.2: {} + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + querystring@0.2.1: {} + + queue-microtask@1.2.3: {} + + quill-delta@3.6.3: + dependencies: + deep-equal: 1.1.2 + extend: 3.0.2 + fast-diff: 1.1.2 + + quill@1.3.7: + dependencies: + clone: 2.1.2 + deep-equal: 1.1.2 + eventemitter3: 2.0.3 + extend: 3.0.2 + parchment: 1.1.4 + quill-delta: 3.6.3 + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + readdirp@4.1.2: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + reinterval@1.1.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup-plugin-postcss@4.0.2(postcss@8.5.8): + dependencies: + chalk: 4.1.2 + concat-with-sourcemaps: 1.1.0 + cssnano: 5.1.15(postcss@8.5.8) + import-cwd: 3.0.0 + p-queue: 6.6.2 + pify: 5.0.0 + postcss: 8.5.8 + postcss-load-config: 3.1.4(postcss@8.5.8) + postcss-modules: 4.3.1(postcss@8.5.8) + promise.series: 0.2.0 + resolve: 1.22.11 + rollup-pluginutils: 2.8.2 + safe-identifier: 0.4.2 + style-inject: 0.3.0 + transitivePeerDependencies: + - ts-node + + rollup-plugin-terser@7.0.2(rollup@4.60.1): + dependencies: + '@babel/code-frame': 7.29.0 + jest-worker: 26.6.2 + rollup: 4.60.1 + serialize-javascript: 4.0.0 + terser: 5.46.1 + + rollup-pluginutils@2.8.2: + dependencies: + estree-walker: 0.6.1 + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-identifier@0.4.2: {} + + safer-buffer@2.1.2: {} + + sass@1.98.0: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.5 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + + sax@1.6.0: {} + + screenfull@6.0.2: {} + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + sdk-base@2.0.1: + dependencies: + get-ready: 1.0.0 + + select@1.1.2: {} + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + serialize-javascript@4.0.0: + dependencies: + randombytes: 2.1.0 + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + slash@3.0.0: {} + + slate-history@0.66.0(slate@0.72.8): + dependencies: + is-plain-object: 5.0.0 + slate: 0.72.8 + + slate@0.72.8: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + snabbdom@3.6.3: {} + + sortablejs@1.14.0: {} + + sortablejs@1.15.7: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sourcemap-codec@1.4.8: {} + + spark-md5@3.0.2: {} + + split2@3.2.2: + dependencies: + readable-stream: 3.6.2 + + splitpanes@3.2.0(vue@3.5.31(typescript@4.9.5)): + dependencies: + vue: 3.5.31(typescript@4.9.5) + + ssf@0.11.2: + dependencies: + frac: 1.1.2 + + ssr-window@3.0.0: {} + + stable@0.1.8: {} + + statuses@1.5.0: {} + + stream-http@2.8.2: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.8 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + + stream-shift@1.0.3: {} + + stream-wormhole@1.1.0: {} + + string-hash@1.1.3: {} + + string-split-by@1.0.0: + dependencies: + parenthesis: 3.1.8 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@3.1.1: {} + + strnum@1.1.2: {} + + strnum@2.2.2: {} + + style-inject@0.3.0: {} + + stylehacks@5.1.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svgo@2.8.2: + dependencies: + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + sax: 1.6.0 + stable: 0.1.8 + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-import: 15.1.0(postcss@8.5.8) + postcss-js: 4.1.0(postcss@8.5.8) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8) + postcss-nested: 6.2.0(postcss@8.5.8) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + terser@5.46.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through@2.3.8: {} + + tiny-emitter@2.1.0: {} + + tiny-warning@1.0.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-arraybuffer@1.0.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + ts-md5@1.3.1: {} + + tslib@1.14.1: {} + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + tsutils@3.21.0(typescript@4.9.5): + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type@2.7.3: {} + + typedarray@0.0.6: {} + + typescript@4.9.5: {} + + undici-types@5.26.5: {} + + unescape@1.0.1: + dependencies: + extend-shallow: 2.0.1 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + upgrade@1.1.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urllib@2.44.0: + dependencies: + any-promise: 1.3.0 + content-type: 1.0.5 + default-user-agent: 1.0.0 + digest-header: 1.1.0 + ee-first: 1.1.1 + formstream: 1.5.2 + humanize-ms: 1.2.1 + iconv-lite: 0.6.3 + pump: 3.0.4 + qs: 6.15.0 + statuses: 1.5.0 + utility: 1.18.0 + + util-deprecate@1.0.2: {} + + utility@1.18.0: + dependencies: + copy-to: 2.0.1 + escape-html: 1.0.3 + mkdirp: 0.5.6 + mz: 2.7.0 + unescape: 1.0.1 + + vanilla-picker@2.12.3: + dependencies: + '@sphinxxxx/color-conversion': 2.2.2 + + vant4-kit@1.0.6(vue@3.5.31(typescript@4.9.5)): + dependencies: + vue: 3.5.31(typescript@4.9.5) + + vant@4.9.24(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@vant/popperjs': 1.3.0 + '@vant/use': 1.6.0(vue@3.5.31(typescript@4.9.5)) + '@vue/shared': 3.5.31 + vue: 3.5.31(typescript@4.9.5) + + vite-plugin-vue-setup-extend@0.4.0(vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1)): + dependencies: + '@vue/compiler-sfc': 3.5.31 + magic-string: 0.25.9 + vite: 5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1) + + vite@5.4.21(@types/node@18.19.130)(less@4.6.4)(sass@1.98.0)(terser@5.46.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.8 + rollup: 4.60.1 + optionalDependencies: + '@types/node': 18.19.130 + fsevents: 2.3.3 + less: 4.6.4 + sass: 1.98.0 + terser: 5.46.1 + + vue-clipboard3@2.0.0: + dependencies: + clipboard: 2.0.11 + + vue-component-type-helpers@3.2.6: {} + + vue-cropper@1.1.4: {} + + vue-cropperjs@5.0.0(vue@3.5.31(typescript@4.9.5)): + dependencies: + cropperjs: 1.6.2 + vue: 3.5.31(typescript@4.9.5) + + vue-demi@0.12.5(vue@3.5.31(typescript@4.9.5)): + dependencies: + vue: 3.5.31(typescript@4.9.5) + + vue-demi@0.14.10(vue@3.5.31(typescript@4.9.5)): + dependencies: + vue: 3.5.31(typescript@4.9.5) + + vue-draggable-plus@0.6.1(@types/sortablejs@1.15.9): + dependencies: + '@types/sortablejs': 1.15.9 + + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.4.3 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + lodash: 4.17.21 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + vue-grid-layout@3.0.0-beta1(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@4.9.5): + dependencies: + '@interactjs/actions': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/auto-start': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/dev-tools': 1.10.27(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@4.9.5) + '@interactjs/interactjs': 1.10.27(typescript@4.9.5) + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + element-resize-detector: 1.2.4 + mitt: 2.1.0 + transitivePeerDependencies: + - '@interactjs/core' + - '@interactjs/utils' + - typescript + + vue-i18n@9.14.5(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@intlify/core-base': 9.14.5 + '@intlify/shared': 9.14.5 + '@vue/devtools-api': 6.6.4 + vue: 3.5.31(typescript@4.9.5) + + vue-qr@4.0.9: + dependencies: + glob: 8.1.0 + js-binary-schema-parser: 2.0.3 + simple-get: 4.0.1 + string-split-by: 1.0.0 + + vue-router@4.6.4(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.31(typescript@4.9.5) + + vue@3.5.31(typescript@4.9.5): + dependencies: + '@vue/compiler-dom': 3.5.31 + '@vue/compiler-sfc': 3.5.31 + '@vue/runtime-dom': 3.5.31 + '@vue/server-renderer': 3.5.31(vue@3.5.31(typescript@4.9.5)) + '@vue/shared': 3.5.31 + optionalDependencies: + typescript: 4.9.5 + + vuedraggable-es@4.1.1(vue@3.5.31(typescript@4.9.5)): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.31(typescript@4.9.5) + + vxe-pc-ui@4.13.20(vue@3.5.31(typescript@4.9.5)): + dependencies: + '@vxe-ui/core': 4.4.3(vue@3.5.31(typescript@4.9.5)) + transitivePeerDependencies: + - vue + + vxe-table@4.18.10(vue@3.5.31(typescript@4.9.5)): + dependencies: + vxe-pc-ui: 4.13.20(vue@3.5.31(typescript@4.9.5)) + transitivePeerDependencies: + - vue + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wildcard@1.1.2: {} + + win-release@1.1.1: + dependencies: + semver: 5.7.2 + + wmf@1.0.2: {} + + word-wrap@1.2.5: {} + + word@0.3.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + ws@8.20.0: {} + + xe-utils@3.9.1: {} + + xe-utils@4.0.4: {} + + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + + xml-name-validator@4.0.0: {} + + xml2js@0.6.2: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xtend@4.0.2: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@1.10.3: {} + + yarn@1.22.22: {} + + yocto-queue@0.1.0: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 From e0545af483ce5c27bc579573dd14f0cae67785a6 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:50:06 +0800 Subject: [PATCH 029/111] chore: restore Dockerfiles and update Jenkinsfile paths - Add web/Dockerfile for frontend image build - Add backend/Dockerfile for backend image build - Update Jenkinsfile paths from docker_env/ to web/ and backend/ Note: docker_env directory was removed; Dockerfiles now placed in respective app directories. --- backend/Dockerfile | 42 ++++++++++++++++++++++++++++++++++++++++++ backend/Jenkinsfile | 2 +- web/Dockerfile | 22 ++++++++++++++++++++++ web/Jenkinsfile | 2 +- 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 web/Dockerfile diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..dfa74e7 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,42 @@ +# 第一阶段:构建 +FROM docker.1ms.run/python:3.10-slim as builder + +WORKDIR /backend + +# 安装系统依赖(合并为一个RUN减少层数) +RUN apt-get update && apt-get install -y \ + bash git curl gnupg ca-certificates \ + libjpeg-dev libmariadb-dev-compat default-libmysqlclient-dev \ + build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev \ + libpcre2-dev libncurses5-dev libreadline-dev tk-dev libpq-dev unixodbc-dev \ + && rm -rf /var/lib/apt/lists/* \ + && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ + && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ + && apt-get update \ + && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \ + && rm -rf /var/lib/apt/lists/* + +# 先复制依赖文件,利用缓存 +COPY ./backend/requirements.txt . + +# 安装 Python 依赖(带缓存加速) +RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt + +# 复制剩余代码 +COPY ./backend/ . + +# 配置 +RUN ls ./conf/ \ + && awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' \ + && sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py \ + && sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py + +# 第二阶段:运行时镜像 +FROM docker.1ms.run/python:3.10-slim + +COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /backend /backend + +WORKDIR /backend +CMD ["sh","docker_start.sh"] diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index 561ffaa..1ebab16 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -21,7 +21,7 @@ pipeline { def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" env.IMAGE_TAG = imageTag sh """ - docker build -f docker_env/django/Dockerfile \ + docker build -f backend/Dockerfile \ -t ${imageTag} . """ } diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..788847a --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,22 @@ +# 第一阶段:构建 +FROM node:20-alpine as builder + +WORKDIR /web + +# 先复制 package.json 和 yarn.lock +COPY package.json yarn.lock* ./ + +# 安装依赖(利用缓存) +RUN yarn install --registry=https://registry.npmmirror.com + +# 复制源代码 +COPY . . + +# 构建 +RUN yarn build + +# 第二阶段:Nginx 运行时 +FROM docker.1ms.run/nginx:alpine + +# nginx.conf 由 docker-compose volumes 映射,无需复制 +COPY --from=builder /web/dist /usr/share/nginx/html diff --git a/web/Jenkinsfile b/web/Jenkinsfile index a0e212e..565b769 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -37,7 +37,7 @@ pipeline { def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" env.IMAGE_TAG = imageTag sh """ - docker build -f docker_env/web/Dockerfile \ + docker build -f web/Dockerfile \ -t ${imageTag} . """ } From cf22b8cfe5b5d77da83094d8145adfcb871be876 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:51:25 +0800 Subject: [PATCH 030/111] chore: use pnpm instead of yarn in web Dockerfile --- web/Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/Dockerfile b/web/Dockerfile index 788847a..19e5755 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -3,17 +3,20 @@ FROM node:20-alpine as builder WORKDIR /web -# 先复制 package.json 和 yarn.lock -COPY package.json yarn.lock* ./ +# 安装 pnpm +RUN npm install -g pnpm --registry=https://registry.npmmirror.com + +# 先复制 package.json 和 pnpm-lock.yaml +COPY package.json pnpm-lock.yaml* ./ # 安装依赖(利用缓存) -RUN yarn install --registry=https://registry.npmmirror.com +RUN pnpm install --registry=https://registry.npmmirror.com --frozen-lockfile # 复制源代码 COPY . . # 构建 -RUN yarn build +RUN pnpm build # 第二阶段:Nginx 运行时 FROM docker.1ms.run/nginx:alpine From 30ba17c91eb7d0ba001b27b56706faab46b0e0ad Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:53:43 +0800 Subject: [PATCH 031/111] chore: use pnpm instead of yarn in Jenkinsfile --- web/Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/Jenkinsfile b/web/Jenkinsfile index 565b769..2230533 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -19,14 +19,14 @@ pipeline { steps { sh """ cd web - # 安装 Node.js 和 yarn (如果不存在) - if ! command -v yarn &> /dev/null; then + # 安装 Node.js 和 pnpm (如果不存在) + if ! command -v pnpm &> /dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs - npm install -g yarn + npm install -g pnpm --registry=https://registry.npmmirror.com fi - yarn install --registry=https://registry.npmmirror.com - yarn build + pnpm install --registry=https://registry.npmmirror.com + pnpm build """ } } From a44a0d847e717c6ba0bed631ecf3442091a12670 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:54:26 +0800 Subject: [PATCH 032/111] fix: correct COPY paths in web Dockerfile (build context is project root) --- web/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/Dockerfile b/web/Dockerfile index 19e5755..6055713 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -6,14 +6,14 @@ WORKDIR /web # 安装 pnpm RUN npm install -g pnpm --registry=https://registry.npmmirror.com -# 先复制 package.json 和 pnpm-lock.yaml -COPY package.json pnpm-lock.yaml* ./ +# 先复制 package.json 和 pnpm-lock.yaml(build context 是项目根目录) +COPY web/package.json web/pnpm-lock.yaml* ./ # 安装依赖(利用缓存) RUN pnpm install --registry=https://registry.npmmirror.com --frozen-lockfile # 复制源代码 -COPY . . +COPY web/ . # 构建 RUN pnpm build From 0a541826d414f66ed78042becedfe209a6f37cae Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 10:58:51 +0800 Subject: [PATCH 033/111] chore: remove redundant Install & Build stage (Docker handles build) --- web/Jenkinsfile | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/web/Jenkinsfile b/web/Jenkinsfile index 2230533..adb7969 100644 --- a/web/Jenkinsfile +++ b/web/Jenkinsfile @@ -15,22 +15,6 @@ pipeline { } } - stage('Install & Build') { - steps { - sh """ - cd web - # 安装 Node.js 和 pnpm (如果不存在) - if ! command -v pnpm &> /dev/null; then - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y nodejs - npm install -g pnpm --registry=https://registry.npmmirror.com - fi - pnpm install --registry=https://registry.npmmirror.com - pnpm build - """ - } - } - stage('Build Image') { steps { script { From 08e3767d6b9bbc01f90c74960766526d76395c7e Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 11:47:40 +0800 Subject: [PATCH 034/111] chore: use /api proxy in web .env.production Switch VITE_API_URL from direct backend URL to /api proxy, allowing nginx to handle routing to the backend container. --- web/.env.production | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/web/.env.production b/web/.env.production index 2764313..f60a9ca 100644 --- a/web/.env.production +++ b/web/.env.production @@ -2,10 +2,5 @@ ENV = 'production' # 线上环境接口地址 -# 说明:项目内接口多以 /api/... 作为绝对路径(如 /api/captcha/、/api/init/settings/)。 -# 因此 baseURL 不应再额外带 /api,避免拼接成 /api/api/... -# VITE_API_URL = '/' -VITE_API_URL = 'http://192.168.80.90:8000' - -# 是否启用按钮权限 -VITE_PM_ENABLED = true +# 使用 /api 代理,由 nginx 转发请求到后端容器 +VITE_API_URL = '/api' From 38d7da3762525068021dfc272905904d5c32b04b Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 14:40:44 +0800 Subject: [PATCH 035/111] fix: copy ODBC driver libraries to runtime image in backend Dockerfile msodbcsql17 and its dependencies (libodbc.so.2, libodbcinst.so.2, libmsodbcsql-17.so, libiconv.so.2, libcharset.so.1) were only installed in the builder stage but not copied to the runtime image, causing "libodbc.so.2: cannot open shared object file" at startup. --- backend/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index dfa74e7..0c5472d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -36,6 +36,11 @@ FROM docker.1ms.run/python:3.10-slim COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/lib/x86_64-linux-gnu/libmsodbcsql-17.so /usr/lib/x86_64-linux-gnu/libmsodbcsql-17.so +COPY --from=builder /usr/lib/x86_64-linux-gnu/libiconv.so.2 /usr/lib/x86_64-linux-gnu/libiconv.so.2 +COPY --from=builder /usr/lib/x86_64-linux-gnu/libcharset.so.1 /usr/lib/x86_64-linux-gnu/libcharset.so.1 +COPY --from=builder /usr/lib/x86_64-linux-gnu/libodbc.so.2 /usr/lib/x86_64-linux-gnu/libodbc.so.2 +COPY --from=builder /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 COPY --from=builder /backend /backend WORKDIR /backend From 0a6e44d3de4e1db984a90fd436b27ccef96a3d49 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 14:42:39 +0800 Subject: [PATCH 036/111] chore: update dockerfile no cache --- backend/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index 1ebab16..d48387a 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -21,7 +21,7 @@ pipeline { def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" env.IMAGE_TAG = imageTag sh """ - docker build -f backend/Dockerfile \ + docker build --no-cache -f backend/Dockerfile \ -t ${imageTag} . """ } From 83fa2ea31374759c7b5ed7179ca4d6ba3c0569bc Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 14:45:48 +0800 Subject: [PATCH 037/111] chore: remove Jenkins build image stage use --no-cache --- backend/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile index d48387a..1ebab16 100644 --- a/backend/Jenkinsfile +++ b/backend/Jenkinsfile @@ -21,7 +21,7 @@ pipeline { def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" env.IMAGE_TAG = imageTag sh """ - docker build --no-cache -f backend/Dockerfile \ + docker build -f backend/Dockerfile \ -t ${imageTag} . """ } From 8c7761ed26ae34368625b49ddcf77e5e418d2c87 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:02:06 +0800 Subject: [PATCH 038/111] fix: correct msodbcsql17 library path in Dockerfile The msodbcsql17 package installs to /opt/microsoft/msodbcsql17/, not /usr/lib/x86_64-linux-gnu/. Copy from the correct path in runtime image. --- backend/Dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 0c5472d..0a93550 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -36,11 +36,7 @@ FROM docker.1ms.run/python:3.10-slim COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin -COPY --from=builder /usr/lib/x86_64-linux-gnu/libmsodbcsql-17.so /usr/lib/x86_64-linux-gnu/libmsodbcsql-17.so -COPY --from=builder /usr/lib/x86_64-linux-gnu/libiconv.so.2 /usr/lib/x86_64-linux-gnu/libiconv.so.2 -COPY --from=builder /usr/lib/x86_64-linux-gnu/libcharset.so.1 /usr/lib/x86_64-linux-gnu/libcharset.so.1 -COPY --from=builder /usr/lib/x86_64-linux-gnu/libodbc.so.2 /usr/lib/x86_64-linux-gnu/libodbc.so.2 -COPY --from=builder /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 +COPY --from=builder /opt/microsoft/msodbcsql17 /opt/microsoft/msodbcsql17 COPY --from=builder /backend /backend WORKDIR /backend From 065278a15f8ea97cf1d714b64f6b4981669a1462 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:06:03 +0800 Subject: [PATCH 039/111] fix: add unixodbc runtime library to Dockerfile The runtime image needs libodbc.so.2 for pyodbc to work with SQL Server. --- backend/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 0a93550..e403617 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,6 +34,8 @@ RUN ls ./conf/ \ # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim +RUN apt-get update && apt-get install -y unixodbc && rm -rf /var/lib/apt/lists/* + COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /opt/microsoft/msodbcsql17 /opt/microsoft/msodbcsql17 From 5f37a9391b00ed642360bdf446bc02fc5b71ad83 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:10:59 +0800 Subject: [PATCH 040/111] fix: use ODBC Driver 18 for SQL Server instead of Driver 17 The settings.py is configured to use "ODBC Driver 18 for SQL Server", so we need to install msodbcsql18 package. --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index e403617..fed7794 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y \ && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ && apt-get update \ - && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \ + && ACCEPT_EULA=Y apt-get install -y msodbcsql18 \ && rm -rf /var/lib/apt/lists/* # 先复制依赖文件,利用缓存 @@ -38,7 +38,7 @@ RUN apt-get update && apt-get install -y unixodbc && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin -COPY --from=builder /opt/microsoft/msodbcsql17 /opt/microsoft/msodbcsql17 +COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend WORKDIR /backend From 5f4d27952ada62d1a874bfdd5a41a19528e9496d Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:30:46 +0800 Subject: [PATCH 041/111] fix: register ODBC Driver 18 in runtime image The msodbcsql18 driver needs to be registered with odbcinst so that unixODBC can find it when pyodbc connects to SQL Server. --- backend/Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index fed7794..56e16b0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,12 +34,15 @@ RUN ls ./conf/ \ # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim -RUN apt-get update && apt-get install -y unixodbc && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y unixodbc libodbc1 && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend +# 注册 ODBC Driver 18 +RUN odbcinst -i -d -f /opt/microsoft/msodbcsql18/etc/odbcinst.ini + WORKDIR /backend CMD ["sh","docker_start.sh"] From e65bc8b8b1c5942584845867c1edb3511c25f850 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:44:59 +0800 Subject: [PATCH 042/111] fix: use libodbc2 instead of libodbc1 for Debian Trixie --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 56e16b0..e4bc0b4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,7 +34,7 @@ RUN ls ./conf/ \ # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim -RUN apt-get update && apt-get install -y unixodbc libodbc1 && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y unixodbc libodbc2 && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin From 8e62124695e9721935c6fc3c1a648d0209c3025c Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:48:25 +0800 Subject: [PATCH 043/111] fix: copy odbcinst binary from builder to runtime image The odbcinst binary is installed in /usr/bin/ by unixodbc, not /usr/local/bin/. Copy it explicitly to the runtime image. --- backend/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index e4bc0b4..9d57137 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -38,11 +38,13 @@ RUN apt-get update && apt-get install -y unixodbc libodbc2 && rm -rf /var/lib/ap COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/bin/odbcinst /usr/bin/odbcinst +COPY --from=builder /usr/bin/odbc_config /usr/bin/odbc_config COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend # 注册 ODBC Driver 18 -RUN odbcinst -i -d -f /opt/microsoft/msodbcsql18/etc/odbcinst.ini +RUN /usr/bin/odbcinst -i -d -f /opt/microsoft/msodbcsql18/etc/odbcinst.ini WORKDIR /backend CMD ["sh","docker_start.sh"] From f3d367db02185709cdd082ccd520d89503c35803 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:50:54 +0800 Subject: [PATCH 044/111] fix: remove unnecessary odbcinst copy, use system-installed version The odbcinst binary is provided by unixodbc installed in the runtime image, not copied from builder. --- backend/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 9d57137..3b83ecf 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -38,12 +38,10 @@ RUN apt-get update && apt-get install -y unixodbc libodbc2 && rm -rf /var/lib/ap COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin -COPY --from=builder /usr/bin/odbcinst /usr/bin/odbcinst -COPY --from=builder /usr/bin/odbc_config /usr/bin/odbc_config COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend -# 注册 ODBC Driver 18 +# 注册 ODBC Driver 18(odbcinst 在 /usr/bin 由 unixodbc 安装提供) RUN /usr/bin/odbcinst -i -d -f /opt/microsoft/msodbcsql18/etc/odbcinst.ini WORKDIR /backend From c14640901269a80da5a348a1484eaea4d1d026cd Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 15:56:25 +0800 Subject: [PATCH 045/111] fix: manually register ODBC Driver 18 by appending to odbcinst.ini Instead of using odbcinst command (location uncertain), directly append driver config to /etc/odbcinst.ini --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 3b83ecf..677eee0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -41,8 +41,8 @@ COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend -# 注册 ODBC Driver 18(odbcinst 在 /usr/bin 由 unixodbc 安装提供) -RUN /usr/bin/odbcinst -i -d -f /opt/microsoft/msodbcsql18/etc/odbcinst.ini +# 手动注册 ODBC Driver 18(写入 odbcinst.ini) +RUN cat /opt/microsoft/msodbcsql18/etc/odbcinst.ini >> /etc/odbcinst.ini || true WORKDIR /backend CMD ["sh","docker_start.sh"] From 0dc5a3d730764aa6455d8470d263798522fa8d69 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 16:07:06 +0800 Subject: [PATCH 046/111] fix: add OpenSSL and Kerberos dependencies for msodbcsql18 driver The ODBC Driver 18 requires libssl, libcrypto, and Kerberos libraries to function. Also set LD_LIBRARY_PATH to include the driver lib64 directory. --- backend/Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 677eee0..06cf4bd 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,14 +34,18 @@ RUN ls ./conf/ \ # 第二阶段:运行时镜像 FROM docker.1ms.run/python:3.10-slim -RUN apt-get update && apt-get install -y unixodbc libodbc2 && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y \ + unixodbc libodbc2 \ + libssl3 libcrypto3 libkrb5-3 libgssapi-krb5-2 \ + && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 COPY --from=builder /backend /backend -# 手动注册 ODBC Driver 18(写入 odbcinst.ini) +# 设置库路径并注册 ODBC Driver 18 +ENV LD_LIBRARY_PATH=/opt/microsoft/msodbcsql18/lib64:${LD_LIBRARY_PATH} RUN cat /opt/microsoft/msodbcsql18/etc/odbcinst.ini >> /etc/odbcinst.ini || true WORKDIR /backend From d16ae56f9ade5a3e9d6e64ea874aded9e016730b Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 16:12:51 +0800 Subject: [PATCH 047/111] fix: fix OpenSSL and Kerberos package names for Debian Trixie libcrypto3 doesn't exist as separate package on Trixie (part of libssl3). Use correct package names: libssl3, libkrb5-3, krb5-user. --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 06cf4bd..d8b5393 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -36,7 +36,7 @@ FROM docker.1ms.run/python:3.10-slim RUN apt-get update && apt-get install -y \ unixodbc libodbc2 \ - libssl3 libcrypto3 libkrb5-3 libgssapi-krb5-2 \ + libssl3 libkrb5-3 krb5-user \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages From 1c664060aba68713f451855640b61abef02e31e0 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 18:48:30 +0800 Subject: [PATCH 048/111] docs: add .planning/codebase/ with 7 structured documents Mapped codebase with 4 parallel agents covering: - TECH: STACK.md, INTEGRATIONS.md - ARCH: ARCHITECTURE.md, STRUCTURE.md - QUALITY: CONVENTIONS.md, TESTING.md - CONCERNS: CONCERNS.md --- .planning/codebase/ARCHITECTURE.md | 208 +++++++++++++++++++ .planning/codebase/CONCERNS.md | 248 ++++++++++++++++++++++ .planning/codebase/CONVENTIONS.md | 316 +++++++++++++++++++++++++++++ .planning/codebase/INTEGRATIONS.md | 295 +++++++++++++++++++++++++++ .planning/codebase/STACK.md | 227 +++++++++++++++++++++ .planning/codebase/STRUCTURE.md | 247 ++++++++++++++++++++++ .planning/codebase/TESTING.md | 238 ++++++++++++++++++++++ 7 files changed, 1779 insertions(+) create mode 100644 .planning/codebase/ARCHITECTURE.md create mode 100644 .planning/codebase/CONCERNS.md create mode 100644 .planning/codebase/CONVENTIONS.md create mode 100644 .planning/codebase/INTEGRATIONS.md create mode 100644 .planning/codebase/STACK.md create mode 100644 .planning/codebase/STRUCTURE.md create mode 100644 .planning/codebase/TESTING.md diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..52e8bf6 --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,208 @@ +# Architecture + +**Analysis Date:** 2026-04-01 + +## Pattern Overview + +**Overall:** Django REST Framework backend with Vue 3 SPA frontend, built on django-vue3-admin base + +**Key Characteristics:** +- Backend uses Django 4.2 with DRF for API-first design +- Frontend is Vue 3 + TypeScript + Vite SPA +- JWT authentication via SimpleJWT +- Real-time support via Django Channels (WebSocket) +- Backend-controlled routing (menu/permissions served via API) +- Custom apps extend dvadmin base RBAC system + +## Layers + +**Django Application Layer:** +- Purpose: Main Django project configuration and URL routing +- Location: `backend/application/` +- Contains: `settings.py` (imports config from `conf/env.py`), `urls.py`, `asgi.py`, `wsgi.py`, `celery.py` +- Depends on: All Django apps +- Used by: uvicorn/ASGI server + +**Custom Business Apps Layer (pisadmin, pissupplier):** +- Purpose: Procurement and supplier quotation business logic +- Location: `backend/apps/pisadmin/` and `backend/apps/pissupplier/` +- Contains: Models, Views (ViewSets), Serializers, URLs for procurement RFQ, cost templates, suppliers +- Depends on: dvadmin utils, Django REST Framework +- Used by: Frontend SPA via REST API + +**dvadmin Core Layer:** +- Purpose: Base RBAC system - users, roles, menus, departments, permissions +- Location: `backend/dvadmin/system/` +- Contains: Auth models (Users, Roles, Menus), views, serializers +- Depends on: Django contrib apps +- Used by: Custom apps, frontend + +**dvadmin Utils Layer:** +- Purpose: Reusable utilities - CustomModelViewSet, filters, permissions, pagination, exception handling +- Location: `backend/dvadmin/utils/` +- Contains: `viewset.py` (CustomModelViewSet base), `filters.py`, `permission.py`, `pagination.py`, `serializers.py` +- Depends on: Django REST Framework +- Used by: All ViewSets across apps + +**Frontend SPA Layer:** +- Purpose: Vue 3 single-page application for UI +- Location: `web/src/` +- Contains: Components, views, stores (Pinia), router, API clients +- Depends on: Vue 3, Element Plus, FastCRUD, Pinia, Vue Router + +## Data Flow + +**Frontend-to-Backend API Flow:** + +``` +Vue Component (Page) + -> Pinia Store (State Management) + -> API Client (src/api/*/index.ts) + -> HTTP Request (Axios wrapper) + -> Django REST Framework ViewSet + -> Django Serializer (Validation) + -> Django Model (Database) + -> Database Response + -> Serializer Response + -> JSON API Response + -> Pinia Store Update + -> Vue Component Re-render +``` + +**Authentication Flow:** + +``` +Login Page -> POST /api/login/ -> JWT Token Response + -> Token stored in Session storage + -> All subsequent requests include JWT header + -> Backend validates JWT, attaches user to request + -> Permission classes check role/menu permissions +``` + +**Menu/Routing Flow (Backend-Controlled):** + +``` +User Login -> GET /api/system/get_menu/ + -> Returns user-specific menu tree from Menu model + -> Frontend builds routes dynamically + -> Cached in frontendMenu store +``` + +**WebSocket Flow:** + +``` +Frontend connects to /ws// + -> Django Channels AuthMiddlewareStack validates JWT + -> URLRouter routes to MegCenter consumer + -> Consumer handles real-time messaging +``` + +## Key Design Patterns + +**CustomModelViewSet Pattern:** +All custom ViewSets inherit from `dvadmin.utils.viewset.CustomModelViewSet` which extends DRF's `ModelViewSet`. This provides: +- Standardized JSON responses (`SuccessResponse`, `DetailResponse`, `ErrorResponse`) +- Automatic filtering with `DataLevelPermissionMargeFilter` (data scope permissions) +- Field-level permissions via `FieldPermission` model +- Import/export mixins +- Custom serializer selection per action (`create_serializer_class`, `update_serializer_class`) + +**CoreModel Pattern:** +All models inherit from `dvadmin.utils.models.CoreModel` which provides: +- Audit fields: `creator`, `modifier`, `dept_belong_id`, `create_datetime`, `update_datetime` +- Auto-setting creator/modifier on create/update +- `SoftDeleteModel` support for soft deletes + +**Status Workflow Pattern (Inquiry/RFQ):** +The `Inquiry` model uses a status field with workflow transitions: +``` +1(开立) -> 2(确认) -> 3(发布) -> 4(报价中) -> 5(报价结束) + -> 6(比议价中) -> 7(价格审核) -> 8(核价通过) +9(落标结束) or 0(作废) +``` +Each transition is handled by a dedicated `@action` method on the ViewSet. + +**Template-Generated Quotation Pattern:** +- `CostEstimateTemplateHead` + `CostEstimateTemplateBody` define a cost structure template +- When Inquiry is published, `_create_supplier_quotations` iterates the template to generate `QuotationMaster` + child tables for each supplier +- Template fields with `is_computed=1` or `supplier_required in (1,2)` are prefilled in quotations + +## Module Responsibilities + +**pisadmin.miscprocurement:** +- `Inquiry`: RFQ (Request for Quotation) master with workflow states +- `InquirySupplier`: Supplier invite list per RFQ +- `InquiryMaterialCost`, `InquiryProcessCost`, `InquiryOtherCost`, `InquiryProfitCost`: Cost breakdown per RFQ line item +- `InquiryRfqItem`: Upper-level material items for the RFQ +- `CostEstimateTemplateHead/Body`: Cost template structure definition +- `MiscLowPriceHeader/Detail`: Comparison price records (lowest price tracking) +- `MiscNegotiationRecords`: Bargaining records +- `RFQOperationLogs`: Audit trail for RFQ operations + +**pisadmin.basicinfo:** +- `Company`: Company/factory information +- `Currency`: Currency and tax rate configuration +- `Supplier`: Supplier master data +- `SupplierUser`: Supplier user accounts +- `Unit`: Measurement units +- `SystemNoRule`: Document numbering rules (e.g., RFQ numbers, quotation numbers) +- `EmailNotice`: Email notification queue + +**pissupplier:** +- `QuotationMaster`: Supplier quotation header +- `QuotationMaterial`, `QuotationProcess`, `QuotationOther`, `QuotationProfit`: Quotation cost lines +- `QuotationItem`: Upper-level product items in quotation +- `QuotationAttachment`: Quotation attachments + +**dvadmin.system:** +- `Users`: User accounts with role/dept associations +- `Role`: Role definitions with permission keys +- `Menu`: Menu tree for UI and permission routing +- `MenuButton`: Button-level permissions on menus +- `MenuField`: Field-level permissions per model +- `Dept`: Department hierarchy +- `FieldPermission`: Role-field permission matrix +- `RoleMenuPermission`, `RoleMenuButtonPermission`: Role-menu assignments +- `OperationLog`, `LoginLog`: Audit logging +- `SystemConfig`: System configuration key-value store +- `Dictionary`: Dropdown value definitions +- `FileList`: File upload management +- `ApiWhiteList`: Public API endpoints bypassing auth + +## Error Handling + +**Backend Exception Handling:** +- Custom exception handler: `dvadmin.utils.exception.CustomExceptionHandler` +- DRF exceptions caught and formatted to standard JSON response +- Validation errors return 400 with field-specific messages + +**Frontend Error Handling:** +- Axios interceptor catches HTTP errors +- 401 redirects to login +- 403 shows permission denied +- 500 shows generic error message +- API errors display returned `msg` field + +## Cross-Cutting Concerns + +**Authentication:** SimpleJWT with 24-hour access tokens, 1-day refresh tokens. `CustomBackend` allows username OR email login. + +**Authorization:** +- Menu-level via `Menu` model and role assignments +- Button-level via `MenuButton` and `RoleMenuButtonPermission` +- Field-level via `MenuField` and `FieldPermission` +- Data scope via `DataLevelPermissionMargeFilter` (dept-based filtering) + +**Logging:** +- API access logged via `ApiLoggingMiddleware` +- Operation logs stored in `OperationLog` model +- RFQ operations logged to `RFQOperationLogs` + +**Validation:** +- Serializer-level validation in DRF +- Custom validators in `dvadmin.utils.validator` +- Business logic validation in ViewSet methods (e.g., workflow state checks) + +--- + +*Architecture analysis: 2026-04-01* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..f81ab46 --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -0,0 +1,248 @@ +# Codebase Concerns + +**Analysis Date:** 2026-04-01 + +## Security Concerns + +### Hardcoded Django SECRET_KEY + +**Severity:** Critical + +- **File:** `backend/application/settings.py:31` +- **Issue:** Django SECRET_KEY is hardcoded in source code: `"django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm"` +- **Impact:** If this code is committed to version control or accessible externally, attackers can forge session cookies, CSRF tokens, and other signed data. +- **Recommendation:** Move SECRET_KEY to `conf/env.py` and load from environment variable. Generate a new key for production. + +### DEBUG Mode Enabled in Production Configuration + +**Severity:** High + +- **File:** `backend/conf/env.py:39` +- **Issue:** `DEBUG = True` is set in the actual environment configuration file +- **Impact:** This enables Swagger UI, detailed error pages, and sets `permission_classes = [permissions.AllowAny]` on schema views (`backend/application/urls.py:45`). It also bypasses authentication requirements for API documentation. +- **Recommendation:** Set `DEBUG = False` in `conf/env.py` for production deployments. The code already has conditional logic to handle this. + +### ALLOWED_HOSTS = ["*"] + +**Severity:** High + +- **Files:** `backend/conf/env.py:48`, `backend/conf/env.example.py:48`, `backend/application/settings.py:44` +- **Issue:** Backend accepts requests from any host +- **Impact:** Allows host header attacks, including cache poisoning and CSS injection. +- **Recommendation:** Specify explicit hosts in `ALLOWED_HOSTS` for production. + +### CORS Allows All Origins + +**Severity:** High + +- **File:** `backend/application/settings.py:179-181` +- **Issue:** + ```python + CORS_ORIGIN_ALLOW_ALL = True + CORS_ALLOW_CREDENTIALS = True + ``` +- **Impact:** Combined with `AllowAny` permission classes, any website can make authenticated requests to the API, potentially leaking user data. +- **Recommendation:** Configure `CORS_ALLOWED_ORIGINS` with explicit allowed origins. + +### Weak Password Hashing (MD5 Fallback) + +**Severity:** Critical + +- **Files:** + - `backend/dvadmin/utils/backends.py:30-33` + - `backend/dvadmin/system/models.py:91` + - `backend/dvadmin/system/views/login.py:275` + +- **Issue:** Authentication backend falls back to MD5 hashing when PBKDF2 verification fails: + ```python + verify_password = check_password(password, user.password) + if not verify_password: + password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest() + verify_password = check_password(password, user.password) + ``` + The `set_password` method also uses MD5: + ```python + def set_password(self, raw_password): + if raw_password: + super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest()) + ``` + +- **Impact:** MD5 is cryptographically broken and provides no protection against rainbow table attacks. User passwords in the database are effectively stored in plaintext. +- **Recommendation:** Remove MD5 fallback entirely. Migrate existing MD5-stored passwords to proper bcrypt/PBKDF2 hashing on next login. + +### API Endpoint Allows Unauthenticated Access in DEBUG + +**Severity:** Medium + +- **File:** `backend/application/urls.py:45` +- **Issue:** Swagger schema view permission depends on DEBUG flag: + ```python + permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ] + ``` +- **Impact:** Since DEBUG is True, API documentation is publicly accessible, potentially revealing endpoint structure. +- **Recommendation:** Disable DEBUG or ensure permission classes are properly set regardless of DEBUG. + +### Exception Handler Exposes Stack Traces + +**Severity:** Medium + +- **File:** `backend/dvadmin/utils/exception.py:113` +- **Issue:** + ```python + elif isinstance(ex, Exception): + logger.exception(traceback.format_exc()) + msg = str(ex) + ``` +- **Impact:** In DEBUG mode, raw exception messages can reveal internal implementation details. +- **Recommendation:** Return generic error messages in production. Current implementation logs but still exposes `str(ex)`. + +## Code Quality Concerns + +### Debug Print Statements in Production Code + +**Severity:** Low + +- **Files:** Multiple files contain print statements: + - `backend/application/dispatch.py:87, 110` - "请先进行数据库迁移!" + - `backend/util/currency.py:41, 57, 73` - Raw SQL execution with no logging + - `backend/apps/pisadmin/miscprocurement/cost_template_builder.py:317-345` - Extensive debug output with Unicode symbols + - `backend/dvadmin/utils/core_initialize.py:36, 56, 60, 86` - Initialization status prints + - `backend/dvadmin/system/tests.py:21, 44, 51, 53` - Test debugging output + +- **Impact:** Print statements can interfere with JSON API responses if any are output during request handling. They also expose internal logic in logs. +- **Recommendation:** Replace print statements with proper logging calls using the configured logger. + +### TODO Comments Found in Code + +**Severity:** Low + +- **Files:** + - `backend/dvadmin/utils/filters.py:127` - "TODO Rename this here and in `filter_queryset`" + - `backend/dvadmin/utils/filters.py:322, 329` - "TODO: remove assertion in 2.1" + +- **Impact:** Incomplete refactoring work can lead to confusion or breaking changes if assumptions change. +- **Recommendation:** Address TODOs before major version releases or refactoring phases. + +### Bare Except Clauses + +**Severity:** Medium + +- **File:** `backend/application/websocketConfig.py:90` +- **Issue:** + ```python + except Exception: + pass + ``` +- **Impact:** Silently ignores all exceptions, making debugging impossible and masking failures. +- **Recommendation:** Log exceptions or handle them with specific exception types. + +## Performance & Scalability Concerns + +### In-Memory Channel Layer for WebSocket + +**Severity:** High + +- **File:** `backend/application/settings.py:187-190` +- **Issue:** + ```python + CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels.layers.InMemoryChannelLayer" + } + } + ``` +- **Impact:** WebSocket connections work only within a single worker process. With multiple uvicorn workers (configured as 8 in CLAUDE.md), WebSocket functionality will fail unpredictably across workers. +- **Recommendation:** Use Redis channel layer for production: + ```python + CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + "hosts": [('127.0.0.1', 6379)], + }, + }, + } + ``` + Note: The commented-out configuration exists at lines 192-199 but is not active. + +### Celery Running as Root + +**Severity:** Medium + +- **File:** `backend/application/celery.py:22` +- **Issue:** `platforms.C_FORCE_ROOT = True` +- **Impact:** Security risk running Celery worker processes as root user. +- **Recommendation:** Configure proper user/group for Celery workers in deployment. + +## Dependency Risks + +### Potentially Outdated Dependencies + +**Severity:** Medium + +- **File:** `backend/requirements.txt` +- **Observations:** + - Django 4.2.14 (current latest is 4.2.x) - Acceptable but check for security patches + - SimpleJWT 5.4.0 (5.5.0 available) - Minor version behind + - DRF 3.15.2 (3.15.4 available) - Minor version behind + - Channels 4.1.0 (4.2.x available) + - PyInstaller 6.9.0 - Major build tool dependency + +- **Recommendation:** Run `pip list --outdated` to identify packages needing updates, particularly security patches. + +## Operational Concerns + +### Hardcoded Infrastructure IPs + +**Severity:** High + +- **File:** `backend/conf/env.py` +- **Issue:** + ```python + DATABASE_HOST = '192.168.80.29' + REDIS_HOST = '177.10.0.15' + REDIS_PASSWORD = 'redis_5pGXn2' + ``` + These are actual production IPs and credentials in the codebase. +- **Impact:** If committed to version control, infrastructure credentials are exposed. Also prevents portability across environments. +- **Recommendation:** Use environment variables for all infrastructure configuration. Never commit real credentials. + +### No Docker Compose for Infrastructure + +**Severity:** Medium + +- **Observation:** No `docker-compose.yml` found in project root (only `backend/Dockerfile` and `web/Dockerfile`) +- **Impact:** No documented way to spin up the full stack (Django, Redis, database) locally. Makes local development and testing harder. +- **Recommendation:** Create `docker-compose.yml` that orchestrates backend, Redis, and database containers. + +### Log Directory Runtime Creation + +**Severity:** Low + +- **File:** `backend/application/settings.py:209-210` +- **Issue:** + ```python + if not os.path.exists(os.path.join(BASE_DIR, "logs")): + os.makedirs(os.path.join(BASE_DIR, "logs")) + ``` +- **Impact:** If the logs directory cannot be created (permissions issue), the application will crash on startup. +- **Recommendation:** Ensure logs directory exists before deployment via CI/CD or container entrypoint. + +## Known Issues Summary + +| Area | Severity | Issue | Files | +|------|----------|-------|-------| +| Secrets | Critical | Hardcoded SECRET_KEY | `application/settings.py:31` | +| Auth | Critical | MD5 password hashing | `backends.py`, `models.py`, `login.py` | +| Config | High | DEBUG=True in prod env | `conf/env.py:39` | +| Config | High | ALLOWED_HOSTS=* | `conf/env.py:48` | +| CORS | High | Allow all origins | `settings.py:179-181` | +| WebSocket | High | In-memory channel layer | `settings.py:189` | +| Secrets | High | Hardcoded IPs/passwords | `conf/env.py` | +| Code Quality | Medium | Bare except clauses | `websocketConfig.py:90` | +| Code Quality | Low | Debug print statements | Multiple files | +| Dependencies | Medium | Outdated packages | `requirements.txt` | + +--- + +*Concerns audit: 2026-04-01* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..667c2e6 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,316 @@ +# Coding Conventions + +**Analysis Date:** 2026-04-01 + +## Naming Patterns + +### Backend (Python/Django) + +**Files:** +- Python source files: snake_case (e.g., `email_utils.py`, `system_no_rule.py`) +- URL routing: `urls.py` +- Serializers: `serializers.py` +- Models: `models.py` +- Views: either single `views.py` or organized in `views/` directory with one file per resource + +**Classes:** +- Models: PascalCase (e.g., `Company`, `Supplier`, `SystemNoRule`) +- ViewSets: PascalCase ending with `ViewSet` (e.g., `CompanyViewSet`, `InquiryViewSet`) +- Serializers: PascalCase ending with `Serializer` (e.g., `CompanySerializer`, `InquirySerializer`) +- Custom serializers for create/update: `ModelNameCreateUpdateSerializer` + +**Functions/Methods:** +- snake_case (e.g., `allocate_system_number`, `get_request_username`) +- Private methods: prefixed with underscore (e.g., `_allocate_next_code_for_locked_rule`) + +**Database Fields:** +- snake_case (e.g., `company_code`, `supplier_name`, `create_datetime`) +- Primary key: `id` +- Foreign keys: `model_name_id` or just `field_name` (e.g., `quotation_no`) +- Timestamps: `create_datetime`, `update_datetime` +- User tracking: `createuser`, `updateuser` (char fields, not FK) + +**URL Patterns:** +- Router.register uses snake_case plural (e.g., `r'units'`, `r'companies'`, `r'inquiry'`) +- Full endpoint example: `/api/pisadmin/basicinfo/companies/` + +### Frontend (Vue 3/TypeScript) + +**Files:** +- Vue components: kebab-case in directory (e.g., `company/index.vue`) +- API modules: `api.ts` in same directory (e.g., `company/api.ts`) +- CRUD configuration: `.tsx` extension (e.g., `company/crud.tsx`) +- Stores: `userInfo.ts`, `dictionary.ts` +- Utilities: `service.ts`, `storage.ts`, `message.ts` + +**Components:** +- Vue components use ` + + +``` + +**FastCRUD Configuration Pattern:** +```typescript +export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { + return { + crudOptions: { + request: { + pageRequest: async (query) => api.GetList(query), + addRequest: async ({ form }) => api.AddObj(form), + editRequest: async ({ form, row }) => api.UpdateObj({ ...form, id: row.id }), + delRequest: async ({ row }) => api.DelObj(row.id) + }, + columns: { + company_code: { + title: '公司代码', + type: 'input', + search: { show: true }, + form: { rules: [{ required: true, message: '请输入公司代码' }] } + } + } + } + } +} +``` + +**API Module Pattern:** +```typescript +// /@/utils/service exports `request` +import { request } from '/@/utils/service' + +const apiPrefix = '/api/pisadmin/basicinfo/companies/' + +export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) +export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) +export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) +export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) +``` + +**Pinia Store Pattern:** +```typescript +export const useUserInfo = defineStore('userInfo', { + state: (): UserInfosStates => ({ + userInfos: { /* initial state */ } + }), + actions: { + async setUserInfos() { /* action */ } + } +}) +``` + +## Linting and Formatting Configuration + +### Frontend (ESLint) + +**Config File:** `web/.eslintrc.js` + +**Parser:** `vue-eslint-parser` with `@typescript-eslint/parser` + +**Key Rules:** +- `@typescript-eslint/no-unused-vars`: `'off'` (disabled) +- `@typescript-eslint/no-explicit-any`: `'off'` (disabled) +- `no-console`: `'error'` (console.log is error, use ElMessage instead) +- Vue component rules mostly `'off'` for flexibility + +**Run Lint Fix:** +```bash +yarn lint-fix +``` + +### Frontend (Prettier) + +**Config File:** `web/.prettierrc.js` + +**Key Settings:** +```javascript +{ + printWidth: 150, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + arrowParens: 'always', + endOfLine: 'lf' +} +``` + +### Backend (Python) + +No explicit Python linting configuration detected. Python code follows PEP 8 implicitly. + +## Error Handling + +### Backend + +- Use `try/except` blocks with specific exception handling +- Use `serializers.ValidationError` for API validation errors +- Use `ErrorResponse` from dvadmin for consistent error responses +- Log errors with `logger = logging.getLogger(__name__)` + +### Frontend + +- Use `ElMessage.error()` for user-facing error messages +- Use try/catch with async/await +- API errors handled in `service.ts` interceptors + +## Logging + +### Backend +```python +import logging +logger = logging.getLogger(__name__) + +logger.warning("message with %s", value) +logger.exception("error message") # includes traceback +``` + +### Frontend +- `console.log` allowed for debugging (stripped in production build) +- Production uses `ElMessage` for user feedback +- No structured logging library detected + +## Comments and Documentation + +### Backend +- Chinese docstrings for business logic (e.g., `"""公司信息-序列化器"""`) +- Complex methods have inline comments explaining logic +- Use `# pragma: no cover` for untested branches + +### Frontend +- Component `name` attribute for debugging +- Chinese comments for complex business logic +- JSDoc style not strictly enforced + +--- + +*Convention analysis: 2026-04-01* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 0000000..76532b2 --- /dev/null +++ b/.planning/codebase/INTEGRATIONS.md @@ -0,0 +1,295 @@ +# External Integrations + +**Analysis Date:** 2026-04-01 + +## Database Connection + +**Primary Database:** +- **Engine:** Microsoft SQL Server (via mssql-django 1.7) +- **Connection Driver:** ODBC Driver 18 for SQL Server +- **Database Name:** `pisdb` +- **Host:** `192.168.80.29` +- **Port:** `1433` +- **Credentials:** Configured in `backend/conf/env.py` +- **Configuration File:** `backend/conf/env.py` lines 14-24 + +**Connection Settings:** +```python +DATABASE_ENGINE = "mssql" +DATABASE_NAME = 'pisdb' +DATABASE_HOST = '192.168.80.29' +DATABASE_PORT = 1433 +DATABASE_USER = "test" +DATABASE_PASSWORD = '123456' +OPTIONS = { + "driver": "ODBC Driver 18 for SQL Server", + "extra_params": "Encrypt=yes;TrustServerCertificate=yes" +} +``` + +**Alternative Database Support:** +- MySQL 8.0 (via mysqlclient 2.2.0) +- PostgreSQL (via psycopg2 2.9.9) + +**Table Prefix:** `pis_` (Procurement Inquiry System) + +**Django Settings:** `backend/application/settings.py` lines 105-119 + +## Redis Configuration + +**Connection:** +- **Host:** `177.10.0.15` +- **Port:** `6379` +- **Password:** `redis_5pGXn2` +- **URL Format:** `redis://:redis_5pGXn2@177.10.0.15:6379` + +**Usage in Application:** +- Redis DB 1: General caching (`REDIS_DB`) +- Redis DB 3: Celery broker (`CELERY_BROKER_DB`) + +**Configuration File:** `backend/conf/env.py` lines 31-35 + +**Cache Backend Configuration:** +```python +REDIS_DB = 1 +CELERY_BROKER_DB = 3 +REDIS_PASSWORD = 'redis_5pGXn2' +REDIS_HOST = '177.10.0.15' +REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' +``` + +## Celery Task Queue + +**Broker:** Redis (same server as cache) + +**Configuration Source:** `backend/application/celery.py` + +**Celery Setup:** +- Auto-discovers tasks from all installed apps +- Uses Django settings namespace for configuration (`CELERY_*`) +- Supports tenant-aware mode if `django_tenants` is installed +- Runs with `C_FORCE_ROOT = True` for root execution + +**Retry Strategy:** +- Default retry delay: 180 seconds +- Maximum retries: 3 +- Decorator: `@retry_base_task_error()` + +**Task Result Storage:** Django Celery Results (`django_celery_results`) + +**Celery Beat Configuration:** +- Timezone: `Asia/Shanghai` +- Result backend stores task results in database + +**Django Settings Import:** `from django.conf:settings, namespace='CELERY'` + +**Installed Apps Integration:** +```python +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) +``` + +## WebSocket / Django Channels + +**ASGI Application:** `backend/application/asgi.py` + +**Protocol Support:** +- HTTP (via Django) +- WebSocket (via Channels) + +**WebSocket URL Pattern:** +- Route: `ws//` +- Consumer: `MegCenter` (from `application.websocketConfig`) +- Routing: `backend/application/ws_routing.py` + +**Channel Layers:** +- Default (development): `InMemoryChannelLayer` +- Production: `channels_redis.core.RedisChannelLayer` (commented out) + +**Authentication:** +- WebSocket connections validated via JWT token in `service_uid` parameter +- Token decoded using Django `SECRET_KEY` +- User ID extracted from decoded token +- Channel group format: `user_{user_id}` + +**WebSocket Consumer Classes:** +- `DvadminWebSocket` - Base WebSocket consumer with JWT auth +- `MegCenter` - Message center consumer extending base class + +**Message Flow:** +1. Client connects to `ws://host/ws//` +2. Token validated, user_id extracted +3. User added to channel group `user_{user_id}` +4. Messages pushed to user's channel group +5. `websocket_push()` function sends messages to specific users + +**File:** `backend/application/websocketConfig.py` lines 57-114 + +## Authentication & Authorization + +**JWT Authentication:** +- **Package:** djangorestframework-simplejwt 5.4.0 +- **Header Type:** `JWT` +- **Access Token Lifetime:** 1440 minutes (24 hours) +- **Refresh Token Lifetime:** 1 day +- **Rotate Refresh Tokens:** Enabled + +**JWT Configuration (settings.py lines 321-329):** +```python +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "AUTH_HEADER_TYPES": ("JWT",), + "ROTATE_REFRESH_TOKENS": True, +} +``` + +**Authentication Backends:** +- `rest_framework_simplejwt.authentication.JWTAuthentication` +- `rest_framework.authentication.SessionAuthentication` + +**Custom Backend:** `dvadmin.utils.backends.CustomBackend` + +**REST Framework Default Authentication (settings.py lines 304-307):** +```python +"DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + "rest_framework.authentication.SessionAuthentication", +), +``` + +**Permission:** `rest_framework.permissions.IsAuthenticated` + +## Email Configuration + +**SMTP Configuration (backend/conf/env.py lines 53-68):** +```python +EMAIL_FROM = "eip@avc.co" +EMAIL_HOST_USER = "" +EMAIL_HOST_PASSWORD = "" +EMAIL_HOST = "mailflow.avc.co" +EMAIL_PORT = 25 +EMAIL_USE_SSL = False +EMAIL_USE_TLS = False +``` + +**Email Features:** +- Plain text connection (no SSL/TLS) +- System emails sent from `eip@avc.co` + +## Cloud Storage Integrations + +**Alibaba Cloud OSS:** +- Package: `oss2 2.19.1` +- Used for file storage + +**Tencent Cloud COS:** +- Package: `cos-python-sdk-v5 1.9.37` +- Used for file storage + +## API Documentation + +**Swagger/OpenAPI:** +- Package: `drf-yasg 1.21.7` +- Login URL: `/apiLogin/` +- Logout URL: `/rest_framework:logout/` +- Auto schema generation with custom schema class +- JSON editor enabled in Swagger UI + +**Configuration (settings.py lines 334-354):** +```python +SWAGGER_SETTINGS = { + "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}}, + "LOGIN_URL": "apiLogin/", + "LOGOUT_URL": "rest_framework:logout", + "JSON_EDITOR": True, + "DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema", +} +``` + +## CORS Configuration + +**CORS Settings (settings.py lines 177-181):** +```python +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = True +``` + +**CORS Middleware:** `corsheaders.middleware.CorsMiddleware` + +## Static Files & Media + +**Static Files:** +- URL: `/static/` +- Storage: Whitenoise with compression +- Storage Class: `whitenoise.storage.CompressedStaticFilesStorage` + +**Media Files:** +- URL: `/media/` +- Root: `media` directory in project + +**Configuration (settings.py lines 156-169):** +```python +STATIC_URL = "/static/" +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] +MEDIA_ROOT = "media" +MEDIA_URL = "/media/" +STATICFILES_FINDERS = ( + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder" +) +``` + +## Proxy Configuration (Development) + +**Vite Dev Server Proxy (vite.config.ts lines 37-44):** +```typescript +proxy: { + '/gitee': { + target: 'https://gitee.com', + ws: true, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/gitee/, ''), + }, +} +``` + +## Environment Variables + +**Configuration Source:** `backend/conf/env.py` + +**Key Environment Variables:** +| Variable | Purpose | +|----------|---------| +| `DATABASE_ENGINE` | Database backend type | +| `DATABASE_HOST` | Database server host | +| `DATABASE_PORT` | Database server port | +| `DATABASE_USER` | Database username | +| `DATABASE_PASSWORD` | Database password | +| `REDIS_HOST` | Redis server host | +| `REDIS_PASSWORD` | Redis password | +| `DEBUG` | Debug mode flag | +| `EMAIL_HOST` | SMTP server | +| `EMAIL_PORT` | SMTP port | + +**Note:** Actual credentials are stored in `backend/conf/env.py` (not committed to version control per best practices). + +## Key Dependencies Between Systems + +``` +Frontend (Vue 3) + | + | HTTP/REST + WebSocket + v +Backend (Django + DRF) + | + +---> Database (SQL Server/MySQL/PostgreSQL) + +---> Cache (Redis) + +---> Task Queue (Celery + Redis) + +---> WebSocket (Channels + Redis) + +---> Cloud Storage (OSS/COS) + +---> Email (SMTP) +``` + +--- + +*Integration audit: 2026-04-01* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md new file mode 100644 index 0000000..60a2721 --- /dev/null +++ b/.planning/codebase/STACK.md @@ -0,0 +1,227 @@ +# Technology Stack + +**Analysis Date:** 2026-04-01 + +## Languages + +**Primary:** +- Python 3.x - Backend development +- TypeScript 4.9.4 - Frontend development +- JavaScript (ESNext) - Frontend runtime + +**Secondary:** +- Vue 3.4.38 - Frontend UI framework +- HTML/CSS - Frontend markup and styling + +## Backend Framework + +**Django Core:** +- Django 4.2.14 - Web framework +- Django REST Framework 3.15.2 - REST API development +- channels 4.1.0 - WebSocket support +- Celery 5.x (via dvadmin3-celery 3.1.6) - Async task queue + +**Authentication:** +- djangorestframework-simplejwt 5.4.0 - JWT token authentication +- django-simple-captcha 0.6.0 - CAPTCHA verification + +**Database:** +- mssql-django 1.7 - SQL Server driver (configured for pisdb) +- mysqlclient 2.2.0 - MySQL driver (also available) +- psycopg2 2.9.9 - PostgreSQL driver + +**Key Django Plugins:** +- django-cors-headers 4.4.0 - Cross-origin resource sharing +- django-filter 24.2 - Database query filtering +- django-ranged-response 0.2.0 - HTTP range response support +- django-restql 0.15.4 - GraphQL-like query language for REST +- drf-yasg 1.21.7 - Swagger/OpenAPI documentation +- django-timezone-field 7.0 - Timezone support +- django-comment-migrate 0.1.7 - Comment migration utility +- Pillow 10.4.0 - Image processing +- openpyxl 3.1.5 - Excel file handling +- requests 2.32.4 - HTTP client +- pypinyin 0.51.0 - Chinese pinyin conversion +- ua-parser 0.18.0 - User agent parsing +- user-agents 2.2.0 - User agent detection +- six 1.16.0 - Python 2/3 compatibility +- whitenoise 6.7.0 - Static file serving +- pyparsing 3.1.2 - Parsing expression grammar +- typing-extensions 4.12.2 - Type hint extensions +- tzlocal 5.2 - Timezone localization + +**Server:** +- uvicorn 0.30.3 - ASGI server +- gunicorn 23.0.0 - WSGI server +- gevent 24.2.1 - Async networking library + +**Cloud Storage:** +- oss2 2.19.1 - Alibaba Cloud OSS integration +- cos-python-sdk-v5 1.9.37 - Tencent Cloud COS integration + +## Frontend Framework + +**Core:** +- Vue 3.4.38 - Progressive JavaScript framework +- TypeScript 4.9.4 - Typed superset of JavaScript +- Vite 5.4.1 - Next-generation frontend build tool +- Pinia 2.0.28 - State management +- Vue Router 4.4.3 - SPA routing +- Vue I18n 9.14.0 - Internationalization + +**UI Component Libraries:** +- Element Plus 2.8.0 - UI component library +- @element-plus/icons-vue 2.3.1 - Element Plus icons +- Vant 4.9.19 - Mobile UI component library +- vant4-kit 1.0.3 - Vant utility kit + +**FastCRUD:** +- @fast-crud/fast-crud 1.21.2 - Quick CRUD development +- @fast-crud/fast-extends 1.21.2 - FastCRUD extensions +- @fast-crud/ui-element 1.21.2 - Element Plus UI for FastCRUD +- @fast-crud/ui-interface 1.21.2 - FastCRUD UI interface + +**Data Visualization:** +- echarts 5.5.1 - Interactive charting library +- echarts-gl 2.0.9 - ECharts 3D visualization +- echarts-wordcloud 2.1.0 - Word cloud for ECharts +- vxe-table 4.6.18 - Grid/table component +- xe-utils 3.5.30 - Utility functions for vxe-table + +**Build & Development:** +- @vitejs/plugin-vue 5.1.2 - Vue plugin for Vite +- @vitejs/plugin-vue-jsx 4.0.1 - Vue JSX support +- vite-plugin-vue-setup-extend 0.4.0 - Vue setup syntax extension +- sass 1.56.2 - CSS preprocessor +- less 4.3.0 - CSS preprocessor +- autoprefixer 10.4.20 - CSS vendor prefixing +- postcss 8.4.21 - CSS transformation tool +- tailwindcss 3.2.7 - Utility-first CSS framework +- rollup 4.60.1 - Module bundler + +**Utilities:** +- axios 1.7.4 - HTTP client +- qs 6.11.0 - Query string parsing +- lodash-es 4.17.21 - Utility library +- js-cookie 3.0.5 - Cookie handling +- mitt 3.0.1 - Tiny event emitter +- nprogress 0.2.0 - Progress bar +- sortablejs 1.15.0 - Drag and drop sorting +- vue-draggable-plus 0.6.0 - Vue draggable component +- screenfull 6.0.2 - Fullscreen API wrapper +- print-js 1.6.0 - Print functionality +- qrcodejs2-fixes 0.0.2 - QR code generation +- cropperjs 1.6.2 - Image cropping +- vue-cropper 1.0.8 - Vue image cropper +- countup.js 2.8.0 - Animated number counting +- e-icon-picker 2.1.1 - Icon picker +- element-tree-line 0.2.1 - Tree line for Element +- font-awesome 4.7.0 - Icon library +- @iconify/vue 4.1.2 - Icon library +- vue-clipboard3 2.0.0 - Clipboard API wrapper +- ts-md5 1.3.1 - MD5 hashing +- vue-grid-layout 3.0.0-beta1 - Grid layout +- vue-qr 4.0.9 - QR code for Vue +- jsplumb 2.15.6 - Visual connectivity +- @wangeditor/editor 5.1.23 - Rich text editor +- @wangeditor/editor-for-vue 5.1.12 - Vue wrapper for WangEditor +- js-table2excel 1.1.2 - Table to Excel export +- date-holidays 3.24.1 - Holiday data +- lunar-javascript 1.7.1 - Chinese lunar calendar +- upgrade 1.1.0 - Upgrade utility +- @great-dream/dvadmin3-celery-web 3.1.3 - Celery web UI + +**Development Tools:** +- eslint 8.57.1 - Linting +- eslint-plugin-vue 9.27.0 - Vue ESLint plugin +- @typescript-eslint/parser 5.46.0 - TypeScript ESLint parser +- @typescript-eslint/eslint-plugin 5.46.0 - TypeScript ESLint plugin +- prettier 2.8.1 - Code formatting +- vue-eslint-parser 9.4.3 - Vue template parser +- @types/node 18.19.42 - Node.js type definitions +- @types/nprogress 0.2.3 - NProgress type definitions +- @types/sortablejs 1.15.8 - SortableJS type definitions +- @types/lodash 4.17.7 - Lodash type definitions +- baseline-browser-mapping 2.9.19 - Browser capability mapping + +## Infrastructure + +**Database:** +- MySQL 8.0 - Primary relational database (configured) +- SQL Server (via ODBC Driver 18) - Alternative database option + +**Cache & Queue:** +- Redis - Caching, session storage, Celery broker +- Celery - Async task processing + +**Container:** +- Docker - Containerization +- Docker Compose - Multi-container orchestration + +## Configuration Files + +**Backend:** +- `backend/conf/env.py` - Environment configuration (DB, Redis, Email) +- `backend/application/settings.py` - Django settings (imports from conf/env.py) +- `backend/application/celery.py` - Celery configuration +- `backend/application/asgi.py` - ASGI configuration (WebSocket support) +- `backend/application/ws_routing.py` - WebSocket URL routing +- `backend/application/websocketConfig.py` - WebSocket consumer implementation + +**Frontend:** +- `web/package.json` - Node.js dependencies and scripts +- `web/vite.config.ts` - Vite build configuration +- `web/tsconfig.json` - TypeScript compiler options + +## Project Structure + +``` +pis/ +├── backend/ +│ ├── application/ # Django project configuration +│ │ ├── settings.py # Main Django settings +│ │ ├── urls.py # Root URL configuration +│ │ ├── asgi.py # ASGI application (HTTP + WebSocket) +│ │ ├── celery.py # Celery task configuration +│ │ ├── ws_routing.py # WebSocket routing +│ │ └── websocketConfig.py # WebSocket consumers +│ ├── apps/ +│ │ ├── pisadmin/ # Custom procurement app +│ │ │ ├── basicinfo/ # Company, currency, supplier, unit +│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections +│ │ └── pissupplier/ # Supplier quotation module +│ ├── dvadmin/ # Core RBAC system (dvadmin3) +│ │ └── system/ # Users, roles, menus, departments +│ └── conf/ +│ └── env.py # Environment configuration +├── web/ # Vue 3 frontend +│ ├── src/ +│ │ ├── views/ # Page components +│ │ ├── api/ # API clients +│ │ ├── stores/ # Pinia stores +│ │ └── router/ # Vue Router config +│ ├── vite.config.ts # Vite configuration +│ └── tsconfig.json # TypeScript config +└── docker-compose.yml # Docker deployment +``` + +## Build Commands + +**Backend:** +```bash +python manage.py makemigrations # Create migrations +python manage.py migrate # Apply migrations +python manage.py runserver # Development server +uvicorn application.asgi:application --workers 8 # Production +``` + +**Frontend:** +```bash +yarn dev # Development server (port 8080) +yarn build # Production build +yarn lint-fix # Lint and auto-fix +``` + +--- + +*Stack analysis: 2026-04-01* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md new file mode 100644 index 0000000..8072c21 --- /dev/null +++ b/.planning/codebase/STRUCTURE.md @@ -0,0 +1,247 @@ +# Codebase Structure + +**Analysis Date:** 2026-04-01 + +## Directory Layout + +``` +pis/ +├── backend/ +│ ├── application/ # Django project config (settings, urls, asgi) +│ ├── apps/ +│ │ ├── pisadmin/ # Custom procurement app +│ │ │ ├── basicinfo/ # Company, currency, supplier, unit, system no +│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections +│ │ └── pissupplier/ # Supplier quotation module +│ ├── conf/ +│ │ └── env.py # Environment config (DB, Redis, Email) +│ ├── dvadmin/ # Base RBAC system +│ │ ├── system/ # Users, roles, menus, departments +│ │ └── utils/ # CoreModel, CustomViewSet, filters, etc. +│ ├── plugins/ # Plugin modules (celery, etc.) +│ ├── static/ # Static files +│ ├── templates/ # Django templates +│ └── util/ # Utility scripts +├── web/ # Vue 3 frontend +│ └── src/ +│ ├── api/ # API client modules +│ ├── components/ # Reusable Vue components +│ ├── layout/ # App layout (header, sidebar, nav) +│ ├── router/ # Vue Router config +│ ├── stores/ # Pinia state stores +│ ├── utils/ # Helper functions +│ └── views/ # Page components +├── docker-compose.yml # Container deployment (not in repo root) +├── Makefile # Build scripts +└── scripts/ # Utility scripts +``` + +## Backend Directory Purposes + +**`backend/application/`:** +- Purpose: Django project configuration entry point +- Key files: + - `settings.py`: Main Django settings (imports from `conf/env.py`) + - `urls.py`: Root URL routing (includes all app URLs) + - `asgi.py`: ASGI config for HTTP + WebSocket + - `ws_routing.py`: WebSocket URL patterns + - `celery.py`: Celery task configuration + +**`backend/conf/`:** +- Purpose: Environment-specific configuration +- Key file: `env.py` - Contains DATABASE_*, REDIS_*, EMAIL_* settings +- This file contains credentials - DO NOT commit to version control + +**`backend/apps/pisadmin/basicinfo/`:** +- Purpose: Master data for procurement (companies, currencies, suppliers, units) +- Structure: + - `models.py`: Company, Currency, Supplier, SupplierUser, Unit, SystemNoRule, EmailNotice + - `views/`: company.py, currency.py, supplier.py, unit.py, system_no_rule.py, email_template.py, email_utils.py + - `urls.py`: Routes to views + - `admin.py`: Django admin registration + +**`backend/apps/pisadmin/miscprocurement/`:** +- Purpose: RFQ (Request for Quotation) and cost template management +- Structure: + - `models.py`: Inquiry, InquirySupplier, InquiryAttachment, InquiryMaterialCost, InquiryProcessCost, InquiryOtherCost, InquiryProfitCost, InquiryRfqItem, CostEstimateTemplateHead, CostEstimateTemplateBody, MiscLowPriceHeader, MiscLowPriceDetail, MiscNegotiationRecords, RFQOperationLogs + - `views.py`: ViewSets for all models (1500+ lines with complex workflow logic) + - `serializers.py`: DRF serializers + - `urls.py`: Routes to views + - `cost_template_builder.py`: Template construction utility + +**`backend/apps/pissupplier/`:** +- Purpose: Supplier quotation management (separate from pisadmin for supplier-facing access) +- Structure: + - `models.py`: QuotationMaster, QuotationMaterial, QuotationProcess, QuotationOther, QuotationProfit, QuotationItem, QuotationAttachment + - `views.py`: Quotation ViewSets + - `serializers.py`: DRF serializers + - `urls.py`: Routes to views + +**`backend/dvadmin/system/`:** +- Purpose: Base RBAC system (users, roles, menus, departments, logs) +- Structure: + - `models.py`: Users, Role, Menu, MenuButton, MenuField, FieldPermission, Dept, Post, OperationLog, LoginLog, SystemConfig, Dictionary, FileList, Area, ApiWhiteList, MessageCenter + - `views/`: ViewSets for each model + - `urls.py`: Routes to views + - `fixtures/`: Initial data fixtures + - `management/commands/`: Django commands (init, init_area, check_dept_users) + +**`backend/dvadmin/utils/`:** +- Purpose: Reusable utilities for all apps +- Key files: + - `viewset.py`: CustomModelViewSet base class with standardized responses + - `models.py`: CoreModel, SoftDeleteModel base classes + - `filters.py`: DataLevelPermissionMargeFilter, CoreModelFilterBankend + - `permission.py`: CustomPermission class + - `pagination.py`: CustomPagination class + - `serializers.py`: Common serializer utilities + - `exception.py`: Custom exception handler + - `json_response.py`: SuccessResponse, DetailResponse, ErrorResponse wrappers + +## Frontend Directory Purposes + +**`web/src/api/`:** +- Purpose: API client modules +- Structure: + - `login/index.ts`: Login/auth API + - `menu/index.ts`: Menu/permission API + - `miscprocurement/priceTemplate.ts`: Cost template API + - `procurement/priceTemplate.ts`: (duplicate or alternative) + +**`web/src/components/`:** +- Purpose: Reusable Vue components +- Key components: + - `table/index.vue`: Custom table component with FastCRUD + - `auth/auth*.vue`: Permission directive components + - `fileSelector/`: File upload component + - `foreignKey/`: Foreign key selector + - `importExcel/`: Excel import component + +**`web/src/layout/`:** +- Purpose: Application layout structure +- Subdirectories: + - `component/`: aside.vue, header.vue, main.vue + - `navBars/`: breadcrumb, tagsView, user menu + - `navMenu/`: vertical and horizontal menu components + - `main/`: classic, columns, defaults, transverse layout variants + - `routerView/`: iframes, link, parent route views + +**`web/src/router/`:** +- Purpose: Vue Router configuration +- Key files: + - `index.ts`: Router creation, beforeEach guards, token validation + - `route.ts`: Static and dynamic route definitions + - `frontEnd.ts`: Frontend-controlled routing logic + - `backEnd.ts`: Backend-controlled routing logic + +**`web/src/stores/`:** +- Purpose: Pinia state management +- Key stores: + - `userInfo.ts`: User authentication state + - `frontendMenu.ts`: Menu tree cache + - `themeConfig.ts`: UI theme settings + - `tagsViewRoutes.ts`: Open tab tracking + - `routesList.ts`: Available routes + - `permission.ts`: Permission state + +**`web/src/views/`:** +- Purpose: Page-level Vue components +- Structure mirrors backend apps: + - `pisadmin/basicinfo/`: company, currency, supplier, unit, systemno, emailnotice + - `pisadmin/miscprocurement/`: cost_template, misc_materials, misc_parts, misc_stations + - `system/`: admin pages (dept, menu, role, user, dictionary, config, etc.) + - `pissupplier/` (if exists): Supplier-facing pages + +## Key Configuration File Locations + +**Backend Configuration:** +- `backend/application/settings.py` - Main Django settings +- `backend/conf/env.py` - Environment variables (DB, Redis, Email) +- `backend/application/urls.py` - URL routing +- `backend/application/asgi.py` - ASGI/WebSocket config + +**Frontend Configuration:** +- `web/src/settings.ts` - Frontend settings (API base URLs, app info) +- `web/src/main.ts` - Vue app initialization +- `web/src/App.vue` - Root Vue component +- `web/vite.config.ts` - Vite build configuration + +**Environment/Secrets (DO NOT COMMIT):** +- `backend/conf/env.py` - Database, Redis, Email credentials +- `.env` files (if present) + +## Custom Apps vs Base dvadmin Structure + +**Base dvadmin (`dvadmin/`):** +- Provided by django-vue3-admin template +- Contains generic functionality: user management, role management, menu management, RBAC +- Should NOT be modified for custom business logic +- Extended by custom apps for domain-specific features + +**Custom Apps (`apps/pisadmin/`, `apps/pissupplier/`):** +- `pisadmin`: Internal procurement admin (RFQ, cost templates, supplier master, etc.) +- `pissupplier`: Supplier portal (quotations) +- Custom apps import and extend dvadmin's `CustomModelViewSet`, `CoreModel`, `CustomPermission` +- Custom apps define their own models, serializers, views, urls + +**Extension Pattern:** +```python +from dvadmin.utils.viewset import CustomModelViewSet + +class InquiryViewSet(CustomModelViewSet): + queryset = Inquiry.objects.all() + serializer_class = InquirySerializer + # Inherits: create, list, retrieve, update, destroy, multiple_delete, get_by_ids + # Custom actions added via @action decorator +``` + +## Docker/Container Structure + +The project uses Docker for deployment (docker-compose not present in current branch): + +**Backend Container:** +- Based on Python 3.x +- Runs Django via uvicorn (ASGI) +- Port: 8000 +- Depends on: MySQL, Redis + +**Frontend Container:** +- Based on Node.js for build +- nginx for serving built SPA +- Port: 8080 (dev), 80 (prod) + +**External Dependencies:** +- MySQL 8.0: Database (configured via `conf/env.py`) +- Redis: Caching and Celery broker +- SMTP: Email notifications + +## Where to Add New Code + +**New Model (Backend):** +1. Add to appropriate app's `models.py` +2. Inherit from `CoreModel` +3. Create serializer in `serializers.py` +4. Create ViewSet extending `CustomModelViewSet` +5. Add URL route in app's `urls.py` +6. Run migrations: `python manage.py makemigrations apps..` +7. Apply: `python manage.py migrate` + +**New API Endpoint (Backend):** +1. If CRUD: Add ViewSet method or use default actions +2. If custom: Add `@action` method to ViewSet +3. Register route in app `urls.py` with `router.register()` + +**New Page (Frontend):** +1. Create Vue component in `web/src/views///` +2. Add API client in `web/src/api//` +3. Menu added via backend `Menu` model (not frontend route config) +4. Component uses FastCRUD pattern with custom table and form components + +**New Store (Frontend):** +1. Create in `web/src/stores/modules/` or root stores +2. Use Pinia `defineStore` +3. Persist sensitive data to Session storage + +--- + +*Structure analysis: 2026-04-01* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md new file mode 100644 index 0000000..1300ff4 --- /dev/null +++ b/.planning/codebase/TESTING.md @@ -0,0 +1,238 @@ +# Testing Patterns + +**Analysis Date:** 2026-04-01 + +## Test Framework + +### Backend (Python/Django) + +**No dedicated test framework detected.** + +- No `tests.py` files in Django apps +- No `pytest` configuration +- No `unittest` test suites +- Migrations are generated via `python manage.py makemigrations` + +### Frontend (Vue 3/TypeScript) + +**No test framework detected.** + +- No Jest, Vitest, or other test runner configuration +- No `.test.ts`, `.spec.ts` files in `web/src/` +- Build and lint commands only, no test commands in `package.json` + +## Test File Organization + +### Backend Structure + +``` +backend/ +├── application/ # Django settings +├── apps/ +│ ├── pisadmin/ # Custom procurement app +│ │ ├── basicinfo/ +│ │ │ ├── models.py +│ │ │ ├── views/ +│ │ │ ├── urls.py +│ │ │ └── migrations/ +│ │ └── miscprocurement/ +│ │ ├── models.py +│ │ ├── serializers.py +│ │ ├── views.py +│ │ └── migrations/ +│ └── pissupplier/ +└── dvadmin/ # Core RBAC system +``` + +**No `tests.py` directories or test files in application code.** + +### Frontend Structure + +``` +web/ +├── src/ +│ ├── api/ +│ ├── assets/ +│ ├── components/ +│ ├── directive/ +│ ├── layout/ +│ ├── plugin/ +│ ├── router/ +│ ├── stores/ +│ ├── theme/ +│ ├── types/ +│ ├── utils/ +│ ├── views/ # Page components +│ │ ├── pisadmin/ +│ │ │ ├── basicinfo/ +│ │ │ │ ├── company/ +│ │ │ │ │ ├── index.vue +│ │ │ │ ├── api.ts +│ │ │ │ └── crud.tsx +│ │ │ └── miscprocurement/ +│ │ │ ├── rfqmiscellaneous/ +│ │ │ └── cost_template/ +│ │ ├── pissupplier/ +│ │ └── system/ +│ ├── App.vue +│ └── main.ts +└── package.json +``` + +**No test files (`*.test.ts`, `*.spec.ts`) in `web/src/` directory.** + +## Available Scripts + +### Frontend (`web/package.json`) + +```json +{ + "scripts": { + "dev": "vite --force", + "build:dev": "vite build --mode development", + "build": "vite build", + "build:local": "vite build --mode local_prod", + "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/", + "build:flowH5": "vite build --config flowH5.config.ts" + } +} +``` + +**No test-related scripts.** + +### Backend + +```bash +# Migrations +python manage.py makemigrations apps.pisadmin.apps.pisadmin +python manage.py makemigrations apps.pissupplier +python manage.py migrate + +# Development server +python manage.py runserver 0.0.0.0:8000 + +# Production-like +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 +``` + +**No test commands.** + +## CI/CD Pipeline + +**No CI/CD configuration detected.** + +- No GitHub Actions workflows (`.github/workflows/`) +- No `.gitlab-ci.yml` +- No Jenkinsfile +- Docker Compose present but no automated testing in Dockerfile + +## Coverage + +**No coverage tools configured.** + +- No `coverage.py` configuration +- No `pytest-cov` or similar +- No Istanbul/nyc for frontend + +## Manual Testing Approach + +Based on project structure, manual testing likely involves: + +### Backend API Testing + +1. **Django Admin Interface** - Models registered in `admin.py` provide basic CRUD +2. **DRF Browsable API** - Available at `/api/` endpoints for interactive testing +3. **Direct HTTP requests** - Using curl or Postman + +### Frontend Testing + +1. **Browser testing** - Manual UI verification via `yarn dev` +2. **FastCRUD configuration** - CRUD options in `crud.tsx` files control behavior +3. **Vue DevTools** - For state inspection + +## What Could Be Tested + +### Backend + +- **ViewSet endpoints**: Each ViewSet handles CRUD + custom actions +- **Serializer validation**: Input/output transformation +- **Model methods**: Business logic like `SystemNoRule.allocate_system_number()` +- **Transaction integrity**: Database operations wrapped in `transaction.atomic()` +- **Status workflow**: Inquiry state machine (open -> confirmed -> published -> quoting -> quote_ended -> bargaining -> negotiated -> approved -> awarded) + +### Frontend + +- **FastCRUD options**: Configuration in `createCrudOptions()` +- **API integration**: Request/response handling +- **Store actions**: Pinia store mutations +- **Router guards**: Authentication and authorization checks + +## Recommendations for Testing Setup + +### Backend + +Add `pytest` with `pytest-django`: + +``` +# requirements.txt additions +pytest==7.4.0 +pytest-django==4.5.0 +pytest-cov==4.1.0 +``` + +Create test files: +``` +backend/apps/pisadmin/basicinfo/tests.py +backend/apps/pisadmin/miscprocurement/tests.py +``` + +### Frontend + +Add Vitest for testing: + +```bash +yarn add -D vitest @vue/test-utils jsdom +``` + +Add test script: +```json +{ + "scripts": { + "test": "vitest", + "coverage": "vitest run --coverage" + } +} +``` + +Create test files: +``` +web/src/views/pisadmin/basicinfo/company/__tests__/Company.spec.ts +``` + +## Build and Deployment + +### Build Commands + +**Frontend:** +```bash +cd web +yarn install +yarn build # Production build +yarn build:dev # Development build +``` + +**Backend:** +```bash +cd backend +pip install -r requirements.txt +python manage.py migrate +python manage.py init # Create superuser +``` + +### Docker + +The project uses Docker for deployment but no test containers. + +--- + +*Testing analysis: 2026-04-01* From b29f4bf71ab2ad3a27ec2f88df89b1dd80a26b7a Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:00:29 +0800 Subject: [PATCH 049/111] docs: add dashboard redesign spec Design document for buyer/supplier dashboard with role-based data filtering, unified API, and frontend components. --- .../2026-04-01-dashboard-redesign-design.md | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md diff --git a/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md b/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md new file mode 100644 index 0000000..ac500ef --- /dev/null +++ b/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md @@ -0,0 +1,267 @@ +# 控制台看板重构设计文档 + +## 1. 概述 + +### 1.1 背景 +当前系统首页使用硬编码模拟数据,需要重构为真实业务数据看板。 + +### 1.2 目标 +- 区分采购方看板和供应商看板两种视图 +- 基于用户角色动态返回对应的看板数据 +- 后端提供统一 API,前端按角色渲染 + +### 1.3 设计图 +- 采购方看板:`D:\AVC-PROJECT\docs\采购方看板.png` +- 供应商看板:`D:\AVC-PROJECT\docs\供应商看板.png` + +--- + +## 2. API 设计 + +### 2.1 接口信息 + +| 项目 | 值 | +|------|-----| +| 方法 | GET | +| 路径 | `/api/dashboard/` | +| 认证 | JWT Token | +| 响应格式 | JSON | + +### 2.2 响应结构 + +```json +{ + "buyer": { + "kpi": { + "total_inquiries": 0, + "pending_inquiries": 0, + "completed_quotes": 0, + "total_suppliers": 0 + }, + "tasks": [ + { + "id": 0, + "title": "string", + "inquiry_no": "string", + "status": "string", + "created_at": "datetime" + } + ], + "messages": [ + { + "id": 0, + "title": "string", + "content": "string", + "is_read": false, + "created_at": "datetime" + } + ], + "trend": [ + {"month": "2026-01", "count": 0} + ] + }, + "supplier": { + "kpi": { + "total_quotes": 0, + "pending_quotes": 0, + "won_quotes": 0, + "conversion_rate": 0.0 + }, + "pending_quotes": [ + { + "id": 0, + "inquiry_no": "string", + "item_name": "string", + "quantity": 0, + "unit": "string", + "deadline": "datetime" + } + ], + "messages": [], + "trend": [ + {"month": "2026-01", "quotes": 0, "won": 0} + ] + } +} +``` + +### 2.3 业务规则 +- 用户同时有采购方和供应商角色:两个模块都返回 +- 用户只有采购方角色:只返回 `buyer` 模块,`supplier` 为 `null` +- 用户只有供应商角色:只返回 `supplier` 模块,`buyer` 为 `null` + +--- + +## 3. 后端实现 + +### 3.1 应用位置 +新建 `backend/apps/pisadmin/dashboard/` 应用 + +### 3.2 数据来源 + +**采购方 KPI 计算:** +| 指标 | 数据来源 | +|------|----------| +| total_inquiries | Inquiry 表中当前用户创建的记录数 | +| pending_inquiries | Inquiry 状态为"待报价"的记录数 | +| completed_quotes | QuotationMaster 中已报价的记录数 | +| total_suppliers | Supplier 表总数 | + +**供应商 KPI 计算:** +| 指标 | 数据来源 | +|------|----------| +| total_quotes | QuotationMaster 中属于当前供应商的记录数 | +| pending_quotes | QuotationMaster 状态为"待报价"的记录数 | +| won_quotes | QuotationMaster 状态为"已中标"的记录数 | +| conversion_rate | won_quotes / total_quotes * 100 | + +**待办任务(采购方):** +- 查询条件:Inquiry 状态为"待处理",分配给当前用户 + +**待报价清单(供应商):** +- 查询条件:QuotationMaster 状态为"待报价",属于当前供应商 + +**消息通知:** +- 使用现有通知表,按时间倒序取最新 5 条 + +**趋势图数据:** +- 近 6 个月的记录创建数,按月聚合 + +### 3.3 序列化器 + +```python +# dashboard/serializers.py +class BuyerKPISerializer(serializers.Serializer): ... +class SupplierKPISerializer(serializers.Serializer): ... +class BuyerTaskSerializer(serializers.ModelSerializer): ... +class SupplierQuoteSerializer(serializers.ModelSerializer): ... +class MessageSerializer(serializers.ModelSerializer): ... +class TrendSerializer(serializers.Serializer): ... +class DashboardResponseSerializer(serializers.Serializer): ... +``` + +### 3.4 视图 + +```python +# dashboard/views.py +class DashboardView(APIView): + def get(self, request): + # 根据用户角色过滤返回数据 + ... +``` + +### 3.5 URL 配置 + +```python +# pisadmin/urls.py +path('dashboard/', include('apps.pisadmin.dashboard.urls')) +``` + +--- + +## 4. 前端实现 + +### 4.1 路由设计 + +| 路径 | 组件 | 说明 | +|------|------|------| +| `/dashboard` | `DashboardIndex.vue` | 根路由,根据角色重定向 | +| `/dashboard/buyer` | `BuyerDashboard.vue` | 采购方看板 | +| `/dashboard/supplier` | `SupplierDashboard.vue` | 供应商看板 | + +### 4.2 页面布局 + +``` +┌─────────────────────────────────────────────────┐ +│ Header: 欢迎语 + 当前日期 │ +├─────────────────────────────────────────────────┤ +│ KPI Cards (4个指标卡片横向排列) │ +├─────────────────────────────────────────────────┤ +│ Main Content (左右分栏) │ +│ ┌────────────────┐ ┌─────────────────────┐ │ +│ │ 待办/待报价清单 │ │ 趋势图 │ │ +│ │ (列表组件) │ │ (ECharts 折线图) │ │ +│ └────────────────┘ └─────────────────────┘ │ +├─────────────────────────────────────────────────┤ +│ 消息通知 (横向卡片列表) │ +└─────────────────────────────────────────────────┘ +``` + +### 4.3 组件结构 + +``` +web/src/ +├── views/ +│ └── pisadmin/ +│ └── dashboard/ +│ ├── index.vue # 重定向组件 +│ ├── BuyerDashboard.vue # 采购方看板 +│ └── SupplierDashboard.vue # 供应商看板 +├── components/ +│ └── dashboard/ +│ ├── KpiCard.vue # KPI 卡片 +│ ├── TrendChart.vue # 趋势图 +│ ├── MessageList.vue # 消息列表 +│ ├── BuyerTaskList.vue # 采购方待办 +│ └── SupplierQuoteList.vue # 供应商待报价 +└── stores/ + └── dashboard.ts # Pinia Store +``` + +### 4.4 Store 设计 + +```typescript +// stores/dashboard.ts +export const useDashboardStore = defineStore('dashboard', { + state: () => ({ + buyer: { + kpi: null, + tasks: [], + messages: [], + trend: [] + }, + supplier: { + kpi: null, + pendingQuotes: [], + messages: [], + trend: [] + }, + loading: false + }), + actions: { + async fetchDashboard() { ... } + } +}) +``` + +### 4.5 API 服务 + +```typescript +// api/dashboard.ts +export function getDashboard() { + return request.get('/api/dashboard/') +} +``` + +--- + +## 5. 权限控制 + +### 5.1 后端 +- 根据 `request.user` 的角色过滤返回数据 +- 无角色用户返回空数据 + +### 5.2 前端 +- 根据 `user.roles` 判断显示哪个看板菜单 +- 未授权用户访问看板路由时跳转至首页 + +--- + +## 6. 实现顺序 + +1. 后端 API 开发(dashboard 应用、序列化器、视图、URL) +2. 前端 API 服务和 Store +3. 通用组件(KpiCard、TrendChart、MessageList) +4. 采购方看板页面 +5. 供应商看板页面 +6. 路由配置和权限控制 From a7e5bf8ddfbc463e510280b719930ed11e92d882 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:04:56 +0800 Subject: [PATCH 050/111] docs: add dashboard redesign implementation plan Comprehensive plan for buyer/supplier dashboard with: - Role-based unified API - Backend serializers and views - Frontend Pinia store and Vue components - ECharts trend visualization --- .../2026-04-01-dashboard-redesign-plan.md | 1085 +++++++++++++++++ 1 file changed, 1085 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md diff --git a/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md b/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md new file mode 100644 index 0000000..ded7b06 --- /dev/null +++ b/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md @@ -0,0 +1,1085 @@ +# Dashboard Redesign Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement role-based buyer/supplier dashboard with unified API, real business data, and Vue 3 frontend components. + +**Architecture:** Role-based data filtering via unified `/api/dashboard/` endpoint. Backend computes KPIs from Inquiry/QuotationMaster/Supplier tables. Frontend uses Pinia store with separate buyer/supplier state branches. + +**Tech Stack:** Django REST Framework, Vue 3 + TypeScript + Pinia + ECharts + +--- + +## Backend Implementation + +### File Structure + +**Create:** +- `backend/apps/pisadmin/dashboard/__init__.py` +- `backend/apps/pisadmin/dashboard/apps.py` +- `backend/apps/pisadmin/dashboard/urls.py` +- `backend/apps/pisadmin/dashboard/serializers.py` +- `backend/apps/pisadmin/dashboard/views.py` + +**Modify:** +- `backend/application/urls.py` - add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` + +--- + +### Task 1: Create Dashboard App Skeleton + +- [ ] **Step 1: Create `backend/apps/pisadmin/dashboard/__init__.py`** +```python +``` + +- [ ] **Step 2: Create `backend/apps/pisadmin/dashboard/apps.py`** +```python +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.pisadmin.dashboard' +``` + +- [ ] **Step 3: Create `backend/apps/pisadmin/dashboard/urls.py`** +```python +from django.urls import path +from .views import DashboardView + +urlpatterns = [ + path('', DashboardView.as_view(), name='dashboard'), +] +``` + +- [ ] **Step 4: Register app in `backend/application/settings.py`** +Add `'apps.pisadmin.dashboard'` to `INSTALLED_APPS` list. + +- [ ] **Step 5: Add URL to `backend/application/urls.py`** +Add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` to urlpatterns. + +- [ ] **Step 6: Commit** +```bash +git add backend/apps/pisadmin/dashboard/ backend/application/urls.py backend/application/settings.py +git commit -m "feat(dashboard): create dashboard app skeleton with URL routing" +``` + +--- + +### Task 2: Implement Dashboard Serializers + +- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/serializers.py`** + +```python +from rest_framework import serializers + + +class BuyerKPISerializer(serializers.Serializer): + total_inquiries = serializers.IntegerField() + pending_inquiries = serializers.IntegerField() + completed_quotes = serializers.IntegerField() + total_suppliers = serializers.IntegerField() + + +class BuyerTaskSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + inquiry_no = serializers.CharField() + status = serializers.CharField() + created_at = serializers.DateTimeField() + + +class SupplierKPISerializer(serializers.Serializer): + total_quotes = serializers.IntegerField() + pending_quotes = serializers.IntegerField() + won_quotes = serializers.IntegerField() + conversion_rate = serializers.FloatField() + + +class SupplierQuoteSerializer(serializers.Serializer): + id = serializers.IntegerField() + inquiry_no = serializers.CharField() + item_name = serializers.CharField() + quantity = serializers.IntegerField() + unit = serializers.CharField() + deadline = serializers.DateTimeField() + + +class MessageSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + content = serializers.CharField() + is_read = serializers.BooleanField() + created_at = serializers.DateTimeField() + + +class TrendSerializer(serializers.Serializer): + month = serializers.CharField() + count = serializers.IntegerField(required=False) + quotes = serializers.IntegerField(required=False) + won = serializers.IntegerField(required=False) + + +class BuyerDashboardSerializer(serializers.Serializer): + kpi = BuyerKPISerializer() + tasks = BuyerTaskSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class SupplierDashboardSerializer(serializers.Serializer): + kpi = SupplierKPISerializer() + pending_quotes = SupplierQuoteSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class DashboardResponseSerializer(serializers.Serializer): + buyer = BuyerDashboardSerializer(allow_null=True) + supplier = SupplierDashboardSerializer(allow_null=True) +``` + +- [ ] **Step 2: Commit** +```bash +git add backend/apps/pisadmin/dashboard/serializers.py +git commit -m "feat(dashboard): add dashboard serializers" +``` + +--- + +### Task 3: Implement Dashboard View + +- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/views.py`** + +```python +from datetime import timedelta + +from django.utils import timezone +from django.db.models import Count +from django.db.models.functions import TruncMonth +from rest_framework.views import APIView +from rest_framework.response import Response + +from apps.pisadmin.miscprocurement.models import Inquiry +from apps.pissupplier.models import QuotationMaster +from apps.pisadmin.basicinfo.models import Supplier + + +def get_buyer_kpi(user): + """采购方 KPI 计算""" + total_inquiries = Inquiry.objects.filter(create_user=user.username).count() + pending_inquiries = Inquiry.objects.filter(create_user=user.username, status__in=[1, 2, 3]).count() + completed_quotes = QuotationMaster.objects.filter( + inquiry_no__in=Inquiry.objects.filter(create_user=user.username).values('inquiry_no') + ).count() + total_suppliers = Supplier.objects.count() + return { + 'total_inquiries': total_inquiries, + 'pending_inquiries': pending_inquiries, + 'completed_quotes': completed_quotes, + 'total_suppliers': total_suppliers, + } + + +def get_buyer_tasks(user): + """采购方待办任务""" + six_months_ago = timezone.now() - timedelta(days=180) + inquiries = Inquiry.objects.filter( + create_user=user.username, + status__in=[1, 2, 3] + ).order_by('-create_time')[:10] + return [ + { + 'id': i.id, + 'title': i.title, + 'inquiry_no': i.inquiry_no, + 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), + 'created_at': i.create_time, + } + for i in inquiries + ] + + +def get_supplier_kpi(user): + """供应商 KPI 计算""" + total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() + pending_quotes = QuotationMaster.objects.filter(supplier_code=user.username, status__in=[1, 2]).count() + won_quotes = QuotationMaster.objects.filter(supplier_code=user.username, is_awarded=1).count() + conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 + return { + 'total_quotes': total_quotes, + 'pending_quotes': pending_quotes, + 'won_quotes': won_quotes, + 'conversion_rate': round(conversion_rate, 2), + } + + +def get_supplier_pending_quotes(user): + """供应商待报价清单""" + quotes = QuotationMaster.objects.filter( + supplier_code=user.username, + status__in=[1, 2] + ).order_by('-creattime')[:10] + result = [] + for q in quotes: + inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() + result.append({ + 'id': q.autoid, + 'inquiry_no': q.inquiry_no, + 'item_name': inquiry.title if inquiry else '', + 'quantity': 0, + 'unit': '', + 'deadline': q.quote_deadline, + }) + return result + + +def get_trend_data(user, is_buyer=True): + """近6个月趋势数据""" + six_months_ago = timezone.now() - timedelta(days=180) + if is_buyer: + data = ( + Inquiry.objects.filter( + create_user=user.username, + create_time__gte=six_months_ago + ) + .annotate(month=TruncMonth('create_time')) + .values('month') + .annotate(count=Count('id')) + .order_by('month') + ) + else: + data = ( + QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__gte=six_months_ago + ) + .annotate(month=TruncMonth('creattime')) + .values('month') + .annotate(quotes=Count('id')) + .order_by('month') + ) + # Add won counts + result = [] + for d in data: + won = QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__month=d['month'].month, + creattime__year=d['month'].year, + is_awarded=1 + ).count() + result.append({ + 'month': d['month'].strftime('%Y-%m'), + 'quotes': d['quotes'], + 'won': won, + }) + return result + return [{'month': d['month'].strftime('%Y-%m'), 'count': d['count']} for d in data] + + +def get_messages(user): + """消息通知 - 占位实现,后续接入通知系统""" + return [] + + +class DashboardView(APIView): + """看板数据视图""" + + def get(self, request): + user = request.user + if not user or not user.is_authenticated: + return Response({'buyer': None, 'supplier': None}) + + # 判断用户角色 + # 这里需要根据实际角色系统判断,暂用用户名特征区分 + # 供应商用户:有 supplier_code 在 QuotationMaster 中 + has_supplier_role = QuotationMaster.objects.filter(supplier_code=user.username).exists() + # 采购方用户:有 create_user 在 Inquiry 中 + has_buyer_role = Inquiry.objects.filter(create_user=user.username).exists() + + response_data = {'buyer': None, 'supplier': None} + + if has_buyer_role: + response_data['buyer'] = { + 'kpi': get_buyer_kpi(user), + 'tasks': get_buyer_tasks(user), + 'messages': get_messages(user), + 'trend': get_trend_data(user, is_buyer=True), + } + + if has_supplier_role: + response_data['supplier'] = { + 'kpi': get_supplier_kpi(user), + 'pending_quotes': get_supplier_pending_quotes(user), + 'messages': get_messages(user), + 'trend': get_trend_data(user, is_buyer=False), + } + + return Response(response_data) +``` + +- [ ] **Step 2: Commit** +```bash +git add backend/apps/pisadmin/dashboard/views.py +git commit -m "feat(dashboard): implement dashboard view with role-based data filtering" +``` + +--- + +## Frontend Implementation + +### File Structure + +**Create:** +- `web/src/api/pisadmin/dashboard.ts` +- `web/src/stores/modules/dashboard.ts` +- `web/src/views/pisadmin/dashboard/index.vue` +- `web/src/views/pisadmin/dashboard/BuyerDashboard.vue` +- `web/src/views/pisadmin/dashboard/SupplierDashboard.vue` +- `web/src/components/pisadmin/dashboard/KpiCard.vue` +- `web/src/components/pisadmin/dashboard/TrendChart.vue` +- `web/src/components/pisadmin/dashboard/MessageList.vue` +- `web/src/components/pisadmin/dashboard/BuyerTaskList.vue` +- `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue` + +**Modify:** +- `web/src/router/route.ts` - add dashboard routes + +--- + +### Task 4: Create Frontend API and Store + +- [ ] **Step 1: Create `web/src/api/pisadmin/dashboard.ts`** + +```typescript +import request from '/@/utils/request'; + +export function getDashboard() { + return request({ + url: '/api/pisadmin/dashboard/', + method: 'get', + }); +} +``` + +- [ ] **Step 2: Create `web/src/stores/modules/dashboard.ts`** + +```typescript +import { defineStore } from 'pinia'; +import { getDashboard } from '/@/api/pisadmin/dashboard'; + +interface BuyerDashboard { + kpi: { + total_inquiries: number; + pending_inquiries: number; + completed_quotes: number; + total_suppliers: number; + } | null; + tasks: Array<{ + id: number; + title: string; + inquiry_no: string; + status: string; + created_at: string; + }>; + messages: Array<{ + id: number; + title: string; + content: string; + is_read: boolean; + created_at: string; + }>; + trend: Array<{ month: string; count: number }>; +} + +interface SupplierDashboard { + kpi: { + total_quotes: number; + pending_quotes: number; + won_quotes: number; + conversion_rate: number; + } | null; + pending_quotes: Array<{ + id: number; + inquiry_no: string; + item_name: string; + quantity: number; + unit: string; + deadline: string; + }>; + messages: Array<{ + id: number; + title: string; + content: string; + is_read: boolean; + created_at: string; + }>; + trend: Array<{ month: string; quotes: number; won: number }>; +} + +export const useDashboardStore = defineStore('dashboard', { + state: () => ({ + buyer: null as BuyerDashboard | null, + supplier: null as SupplierDashboard | null, + loading: false, + }), + actions: { + async fetchDashboard() { + this.loading = true; + try { + const res: any = await getDashboard(); + this.buyer = res.data?.buyer || null; + this.supplier = res.data?.supplier || null; + } finally { + this.loading = false; + } + }, + }, +}); +``` + +- [ ] **Step 3: Commit** +```bash +git add web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts +git commit -m "feat(dashboard): add dashboard API and Pinia store" +``` + +--- + +### Task 5: Create Dashboard Components + +- [ ] **Step 1: Create `web/src/components/pisadmin/dashboard/KpiCard.vue`** + +```vue + + + + + +``` + +- [ ] **Step 2: Create `web/src/components/pisadmin/dashboard/TrendChart.vue`** + +```vue + + + + + +``` + +- [ ] **Step 3: Create `web/src/components/pisadmin/dashboard/MessageList.vue`** + +```vue + + + + + +``` + +- [ ] **Step 4: Create `web/src/components/pisadmin/dashboard/BuyerTaskList.vue`** + +```vue + + + +``` + +- [ ] **Step 5: Create `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue`** + +```vue + + + +``` + +- [ ] **Step 6: Commit** +```bash +git add web/src/components/pisadmin/dashboard/ web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts +git commit -m "feat(dashboard): add dashboard Vue components" +``` + +--- + +### Task 6: Create Dashboard Pages + +- [ ] **Step 1: Create `web/src/views/pisadmin/dashboard/index.vue`** + +```vue + + + + + +``` + +- [ ] **Step 2: Create `web/src/views/pisadmin/dashboard/BuyerDashboard.vue`** + +```vue + + + + + +``` + +- [ ] **Step 3: Create `web/src/views/pisadmin/dashboard/SupplierDashboard.vue`** + +```vue + + + + + +``` + +- [ ] **Step 4: Commit** +```bash +git add web/src/views/pisadmin/dashboard/ +git commit -m "feat(dashboard): add buyer/supplier dashboard pages" +``` + +--- + +### Task 7: Add Dashboard Routes + +- [ ] **Step 1: Modify `web/src/router/route.ts`** + +Add to `dynamicRoutes` array: +```typescript +{ + path: '/dashboard', + name: 'Dashboard', + component: () => import('/@/views/pisadmin/dashboard/index.vue'), + meta: { + title: 'message.router.dashboard', + isLink: '', + isHide: false, + isKeepAlive: true, + isAffix: false, + isIframe: false, + icon: 'iconfont icon-dashboard', + }, +}, +``` + +- [ ] **Step 2: Commit** +```bash +git add web/src/router/route.ts +git commit -m "feat(dashboard): add dashboard route" +``` + +--- + +## Verification Checklist + +- [ ] Backend: `GET /api/pisadmin/dashboard/` returns correct data structure +- [ ] Backend: Role-based filtering works (buyer only, supplier only, both) +- [ ] Frontend: Dashboard page loads without errors +- [ ] Frontend: KPI cards display correct data +- [ ] Frontend: Trend charts render correctly +- [ ] Frontend: Task/Quote lists display correctly +- [ ] Frontend: Role switching works for users with both roles From c3fb11c481914948e9818407cdcd404159199512 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:06:17 +0800 Subject: [PATCH 051/111] feat(dashboard): create dashboard app skeleton with URL routing --- backend/application/settings.py | 1 + backend/application/urls.py | 1 + backend/apps/pisadmin/dashboard/__init__.py | 0 backend/apps/pisadmin/dashboard/apps.py | 6 ++++++ backend/apps/pisadmin/dashboard/urls.py | 13 +++++++++++++ 5 files changed, 21 insertions(+) create mode 100644 backend/apps/pisadmin/dashboard/__init__.py create mode 100644 backend/apps/pisadmin/dashboard/apps.py create mode 100644 backend/apps/pisadmin/dashboard/urls.py diff --git a/backend/application/settings.py b/backend/application/settings.py index 9167cf7..004452a 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -62,6 +62,7 @@ INSTALLED_APPS = [ "dvadmin.system", "apps.pisadmin.miscprocurement", "apps.pisadmin.basicinfo", + "apps.pisadmin.dashboard", "apps.pissupplier", ] diff --git a/backend/application/urls.py b/backend/application/urls.py index fbf71af..9673a41 100644 --- a/backend/application/urls.py +++ b/backend/application/urls.py @@ -103,6 +103,7 @@ urlpatterns = ( ), path("api/pisadmin/miscprocurement/", include("apps.pisadmin.miscprocurement.urls")), path("api/pisadmin/basicinfo/", include("apps.pisadmin.basicinfo.urls")), + path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls")), path("api/pissupplier/", include("apps.pissupplier.urls")), path("api/system/", include("dvadmin.system.urls")), path("api/login/", LoginView.as_view(), name="token_obtain_pair"), diff --git a/backend/apps/pisadmin/dashboard/__init__.py b/backend/apps/pisadmin/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/pisadmin/dashboard/apps.py b/backend/apps/pisadmin/dashboard/apps.py new file mode 100644 index 0000000..3569efc --- /dev/null +++ b/backend/apps/pisadmin/dashboard/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.pisadmin.dashboard' diff --git a/backend/apps/pisadmin/dashboard/urls.py b/backend/apps/pisadmin/dashboard/urls.py new file mode 100644 index 0000000..fc297cf --- /dev/null +++ b/backend/apps/pisadmin/dashboard/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from rest_framework.decorators import api_view +from rest_framework.response import Response + + +@api_view(['GET']) +def dashboard_view(request): + return Response({"status": "ok"}) + + +urlpatterns = [ + path('', dashboard_view, name='dashboard'), +] From 54976e0335bc8f75ef91b134141ad66ed673876c Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:07:00 +0800 Subject: [PATCH 052/111] feat(dashboard): add dashboard serializers --- .../apps/pisadmin/dashboard/serializers.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 backend/apps/pisadmin/dashboard/serializers.py diff --git a/backend/apps/pisadmin/dashboard/serializers.py b/backend/apps/pisadmin/dashboard/serializers.py new file mode 100644 index 0000000..4691603 --- /dev/null +++ b/backend/apps/pisadmin/dashboard/serializers.py @@ -0,0 +1,66 @@ +from rest_framework import serializers + + +class BuyerKPISerializer(serializers.Serializer): + total_inquiries = serializers.IntegerField() + pending_inquiries = serializers.IntegerField() + completed_quotes = serializers.IntegerField() + total_suppliers = serializers.IntegerField() + + +class BuyerTaskSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + inquiry_no = serializers.CharField() + status = serializers.CharField() + created_at = serializers.DateTimeField() + + +class SupplierKPISerializer(serializers.Serializer): + total_quotes = serializers.IntegerField() + pending_quotes = serializers.IntegerField() + won_quotes = serializers.IntegerField() + conversion_rate = serializers.FloatField() + + +class SupplierQuoteSerializer(serializers.Serializer): + id = serializers.IntegerField() + inquiry_no = serializers.CharField() + item_name = serializers.CharField() + quantity = serializers.IntegerField() + unit = serializers.CharField() + deadline = serializers.DateTimeField() + + +class MessageSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + content = serializers.CharField() + is_read = serializers.BooleanField() + created_at = serializers.DateTimeField() + + +class TrendSerializer(serializers.Serializer): + month = serializers.CharField() + count = serializers.IntegerField(required=False) + quotes = serializers.IntegerField(required=False) + won = serializers.IntegerField(required=False) + + +class BuyerDashboardSerializer(serializers.Serializer): + kpi = BuyerKPISerializer() + tasks = BuyerTaskSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class SupplierDashboardSerializer(serializers.Serializer): + kpi = SupplierKPISerializer() + pending_quotes = SupplierQuoteSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class DashboardResponseSerializer(serializers.Serializer): + buyer = BuyerDashboardSerializer(allow_null=True) + supplier = SupplierDashboardSerializer(allow_null=True) From 0bd82e517bce3632f906bba7d2020f33fa1c3713 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:07:47 +0800 Subject: [PATCH 053/111] feat(dashboard): implement dashboard view with role-based data filtering --- backend/apps/pisadmin/dashboard/views.py | 162 +++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 backend/apps/pisadmin/dashboard/views.py diff --git a/backend/apps/pisadmin/dashboard/views.py b/backend/apps/pisadmin/dashboard/views.py new file mode 100644 index 0000000..f79432b --- /dev/null +++ b/backend/apps/pisadmin/dashboard/views.py @@ -0,0 +1,162 @@ +from datetime import timedelta + +from django.utils import timezone +from django.db.models import Count +from django.db.models.functions import TruncMonth +from rest_framework.views import APIView +from rest_framework.response import Response + +from apps.pisadmin.miscprocurement.models import Inquiry +from apps.pissupplier.models import QuotationMaster +from apps.pisadmin.basicinfo.models import Supplier + + +def get_buyer_kpi(user): + """采购方 KPI 计算""" + total_inquiries = Inquiry.objects.filter(create_user=user.username).count() + pending_inquiries = Inquiry.objects.filter(create_user=user.username, status__in=[1, 2, 3]).count() + completed_quotes = QuotationMaster.objects.filter( + inquiry_no__in=Inquiry.objects.filter(create_user=user.username).values('inquiry_no') + ).count() + total_suppliers = Supplier.objects.count() + return { + 'total_inquiries': total_inquiries, + 'pending_inquiries': pending_inquiries, + 'completed_quotes': completed_quotes, + 'total_suppliers': total_suppliers, + } + + +def get_buyer_tasks(user): + """采购方待办任务""" + inquiries = Inquiry.objects.filter( + create_user=user.username, + status__in=[1, 2, 3] + ).order_by('-create_time')[:10] + return [ + { + 'id': i.id, + 'title': i.title, + 'inquiry_no': i.inquiry_no, + 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), + 'created_at': i.create_time, + } + for i in inquiries + ] + + +def get_supplier_kpi(user): + """供应商 KPI 计算""" + total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() + pending_quotes = QuotationMaster.objects.filter(supplier_code=user.username, status__in=[1, 2]).count() + won_quotes = QuotationMaster.objects.filter(supplier_code=user.username, is_awarded=1).count() + conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 + return { + 'total_quotes': total_quotes, + 'pending_quotes': pending_quotes, + 'won_quotes': won_quotes, + 'conversion_rate': round(conversion_rate, 2), + } + + +def get_supplier_pending_quotes(user): + """供应商待报价清单""" + quotes = QuotationMaster.objects.filter( + supplier_code=user.username, + status__in=[1, 2] + ).order_by('-creattime')[:10] + result = [] + for q in quotes: + inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() + result.append({ + 'id': q.autoid, + 'inquiry_no': q.inquiry_no, + 'item_name': inquiry.title if inquiry else '', + 'quantity': 0, + 'unit': '', + 'deadline': q.quote_deadline, + }) + return result + + +def get_trend_data(user, is_buyer=True): + """近6个月趋势数据""" + six_months_ago = timezone.now() - timedelta(days=180) + if is_buyer: + data = ( + Inquiry.objects.filter( + create_user=user.username, + create_time__gte=six_months_ago + ) + .annotate(month=TruncMonth('create_time')) + .values('month') + .annotate(count=Count('id')) + .order_by('month') + ) + else: + data = ( + QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__gte=six_months_ago + ) + .annotate(month=TruncMonth('creattime')) + .values('month') + .annotate(quotes=Count('id')) + .order_by('month') + ) + # Add won counts + result = [] + for d in data: + won = QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__month=d['month'].month, + creattime__year=d['month'].year, + is_awarded=1 + ).count() + result.append({ + 'month': d['month'].strftime('%Y-%m'), + 'quotes': d['quotes'], + 'won': won, + }) + return result + return [{'month': d['month'].strftime('%Y-%m'), 'count': d['count']} for d in data] + + +def get_messages(user): + """消息通知 - 占位实现,后续接入通知系统""" + return [] + + +class DashboardView(APIView): + """看板数据视图""" + + def get(self, request): + user = request.user + if not user or not user.is_authenticated: + return Response({'buyer': None, 'supplier': None}) + + # 判断用户角色 + # 供应商用户:有 supplier_code 在 QuotationMaster 中 + has_supplier_role = QuotationMaster.objects.filter(supplier_code=user.username).exists() + # 采购方用户:有 create_user 在 Inquiry 中 + has_buyer_role = Inquiry.objects.filter(create_user=user.username).exists() + + response_data = {'buyer': None, 'supplier': None} + + if has_buyer_role: + response_data['buyer'] = { + 'kpi': get_buyer_kpi(user), + 'tasks': get_buyer_tasks(user), + 'messages': get_messages(user), + 'trend': get_trend_data(user, is_buyer=True), + } + + if has_supplier_role: + response_data['supplier'] = { + 'kpi': get_supplier_kpi(user), + 'pending_quotes': get_supplier_pending_quotes(user), + 'messages': get_messages(user), + 'trend': get_trend_data(user, is_buyer=False), + } + + return Response(response_data) From c4a754753e3de41d267ea7e59db8ae96f90f100d Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:08:27 +0800 Subject: [PATCH 054/111] feat(dashboard): add dashboard API and Pinia store --- web/src/api/pisadmin/dashboard.ts | 8 ++++ web/src/stores/modules/dashboard.ts | 71 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 web/src/api/pisadmin/dashboard.ts create mode 100644 web/src/stores/modules/dashboard.ts diff --git a/web/src/api/pisadmin/dashboard.ts b/web/src/api/pisadmin/dashboard.ts new file mode 100644 index 0000000..6e02c8d --- /dev/null +++ b/web/src/api/pisadmin/dashboard.ts @@ -0,0 +1,8 @@ +import request from '/@/utils/request'; + +export function getDashboard() { + return request({ + url: '/api/pisadmin/dashboard/', + method: 'get', + }); +} diff --git a/web/src/stores/modules/dashboard.ts b/web/src/stores/modules/dashboard.ts new file mode 100644 index 0000000..0be5a99 --- /dev/null +++ b/web/src/stores/modules/dashboard.ts @@ -0,0 +1,71 @@ +import { defineStore } from 'pinia'; +import { getDashboard } from '/@/api/pisadmin/dashboard'; + +interface BuyerDashboard { + kpi: { + total_inquiries: number; + pending_inquiries: number; + completed_quotes: number; + total_suppliers: number; + } | null; + tasks: Array<{ + id: number; + title: string; + inquiry_no: string; + status: string; + created_at: string; + }>; + messages: Array<{ + id: number; + title: string; + content: string; + is_read: boolean; + created_at: string; + }>; + trend: Array<{ month: string; count: number }>; +} + +interface SupplierDashboard { + kpi: { + total_quotes: number; + pending_quotes: number; + won_quotes: number; + conversion_rate: number; + } | null; + pending_quotes: Array<{ + id: number; + inquiry_no: string; + item_name: string; + quantity: number; + unit: string; + deadline: string; + }>; + messages: Array<{ + id: number; + title: string; + content: string; + is_read: boolean; + created_at: string; + }>; + trend: Array<{ month: string; quotes: number; won: number }>; +} + +export const useDashboardStore = defineStore('dashboard', { + state: () => ({ + buyer: null as BuyerDashboard | null, + supplier: null as SupplierDashboard | null, + loading: false, + }), + actions: { + async fetchDashboard() { + this.loading = true; + try { + const res: any = await getDashboard(); + this.buyer = res.data?.buyer || null; + this.supplier = res.data?.supplier || null; + } finally { + this.loading = false; + } + }, + }, +}); From 15d06266b7c4546ced76146c34ee414e6bfff166 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:09:24 +0800 Subject: [PATCH 055/111] feat(dashboard): add dashboard Vue components --- .../pisadmin/dashboard/BuyerTaskList.vue | 43 +++++++++++++ .../components/pisadmin/dashboard/KpiCard.vue | 57 +++++++++++++++++ .../pisadmin/dashboard/MessageList.vue | 51 +++++++++++++++ .../pisadmin/dashboard/SupplierQuoteList.vue | 38 ++++++++++++ .../pisadmin/dashboard/TrendChart.vue | 62 +++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 web/src/components/pisadmin/dashboard/BuyerTaskList.vue create mode 100644 web/src/components/pisadmin/dashboard/KpiCard.vue create mode 100644 web/src/components/pisadmin/dashboard/MessageList.vue create mode 100644 web/src/components/pisadmin/dashboard/SupplierQuoteList.vue create mode 100644 web/src/components/pisadmin/dashboard/TrendChart.vue diff --git a/web/src/components/pisadmin/dashboard/BuyerTaskList.vue b/web/src/components/pisadmin/dashboard/BuyerTaskList.vue new file mode 100644 index 0000000..5839c7e --- /dev/null +++ b/web/src/components/pisadmin/dashboard/BuyerTaskList.vue @@ -0,0 +1,43 @@ + + + diff --git a/web/src/components/pisadmin/dashboard/KpiCard.vue b/web/src/components/pisadmin/dashboard/KpiCard.vue new file mode 100644 index 0000000..ff42d95 --- /dev/null +++ b/web/src/components/pisadmin/dashboard/KpiCard.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/web/src/components/pisadmin/dashboard/MessageList.vue b/web/src/components/pisadmin/dashboard/MessageList.vue new file mode 100644 index 0000000..b903cfc --- /dev/null +++ b/web/src/components/pisadmin/dashboard/MessageList.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/web/src/components/pisadmin/dashboard/SupplierQuoteList.vue b/web/src/components/pisadmin/dashboard/SupplierQuoteList.vue new file mode 100644 index 0000000..598b89c --- /dev/null +++ b/web/src/components/pisadmin/dashboard/SupplierQuoteList.vue @@ -0,0 +1,38 @@ + + + diff --git a/web/src/components/pisadmin/dashboard/TrendChart.vue b/web/src/components/pisadmin/dashboard/TrendChart.vue new file mode 100644 index 0000000..956500a --- /dev/null +++ b/web/src/components/pisadmin/dashboard/TrendChart.vue @@ -0,0 +1,62 @@ + + + + + From 5106cfcdaae5c43b50f19b447ca8c53957f667d2 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:10:31 +0800 Subject: [PATCH 056/111] feat(dashboard): add buyer/supplier dashboard pages --- .../pisadmin/dashboard/BuyerDashboard.vue | 121 ++++++++++++++++++ .../pisadmin/dashboard/SupplierDashboard.vue | 121 ++++++++++++++++++ web/src/views/pisadmin/dashboard/index.vue | 45 +++++++ 3 files changed, 287 insertions(+) create mode 100644 web/src/views/pisadmin/dashboard/BuyerDashboard.vue create mode 100644 web/src/views/pisadmin/dashboard/SupplierDashboard.vue create mode 100644 web/src/views/pisadmin/dashboard/index.vue diff --git a/web/src/views/pisadmin/dashboard/BuyerDashboard.vue b/web/src/views/pisadmin/dashboard/BuyerDashboard.vue new file mode 100644 index 0000000..4888d69 --- /dev/null +++ b/web/src/views/pisadmin/dashboard/BuyerDashboard.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/web/src/views/pisadmin/dashboard/SupplierDashboard.vue b/web/src/views/pisadmin/dashboard/SupplierDashboard.vue new file mode 100644 index 0000000..abbc0fd --- /dev/null +++ b/web/src/views/pisadmin/dashboard/SupplierDashboard.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/web/src/views/pisadmin/dashboard/index.vue b/web/src/views/pisadmin/dashboard/index.vue new file mode 100644 index 0000000..f9f183e --- /dev/null +++ b/web/src/views/pisadmin/dashboard/index.vue @@ -0,0 +1,45 @@ + + + + + From c55263f742b90019bbb6981b05a7f6b9768812f5 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:11:03 +0800 Subject: [PATCH 057/111] feat(dashboard): add dashboard route --- web/src/router/route.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/src/router/route.ts b/web/src/router/route.ts index 35df9f6..24b20a2 100644 --- a/web/src/router/route.ts +++ b/web/src/router/route.ts @@ -45,6 +45,20 @@ export const dynamicRoutes: Array = [ isIframe: false, icon: 'iconfont icon-gerenzhongxin', }, + }, + { + path: '/dashboard', + name: 'Dashboard', + component: () => import('/@/views/pisadmin/dashboard/index.vue'), + meta: { + title: 'message.router.dashboard', + isLink: '', + isHide: false, + isKeepAlive: true, + isAffix: false, + isIframe: false, + icon: 'iconfont icon-dashboard', + }, } ]; From f6313a93cec880a37e14a590d6be0c5b018ad34e Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:11:45 +0800 Subject: [PATCH 058/111] feat: add cross-platform service management scripts Add comprehensive scripts for managing PIS project services: - start-backend.ps1/.sh: Start Django backend with uvicorn/gunicorn - start-frontend.ps1/.sh: Start Vue frontend dev server - start-celery.ps1/.sh: Start Celery worker and beat - stop-all.ps1/.sh: Stop all services gracefully - stop-celery.ps1/.sh: Stop Celery services only - view-logs.ps1/.sh: View and tail log files - check-status.ps1/.sh: Check service status and ports Cross-platform support: - Windows PowerShell scripts with English output (UTF-8 safe) - Unix/Linux/macOS Bash scripts with Chinese output - WMI queries for PowerShell 5.1 compatibility - Unified Makefile and make.ps1 orchestrators Also add scripts-documentation.md with comprehensive usage guide. --- Makefile | 206 +++++++++++++++++++++++++++ docs/scripts-documentation.md | 257 ++++++++++++++++++++++++++++++++++ make.ps1 | 202 ++++++++++++++++++++++++++ scripts/check-status.ps1 | 103 ++++++++++++++ scripts/check-status.sh | 60 ++++++++ scripts/start-backend.ps1 | 78 +++++++++++ scripts/start-backend.sh | 89 ++++++++++++ scripts/start-celery.ps1 | 92 ++++++++++++ scripts/start-celery.sh | 99 +++++++++++++ scripts/start-frontend.ps1 | 98 +++++++++++++ scripts/start-frontend.sh | 72 ++++++++++ scripts/stop-all.ps1 | 76 ++++++++++ scripts/stop-all.sh | 60 ++++++++ scripts/stop-celery.ps1 | 45 ++++++ scripts/stop-celery.sh | 36 +++++ scripts/view-logs.ps1 | 85 +++++++++++ scripts/view-logs.sh | 61 ++++++++ 17 files changed, 1719 insertions(+) create mode 100644 Makefile create mode 100644 docs/scripts-documentation.md create mode 100644 make.ps1 create mode 100644 scripts/check-status.ps1 create mode 100644 scripts/check-status.sh create mode 100644 scripts/start-backend.ps1 create mode 100644 scripts/start-backend.sh create mode 100644 scripts/start-celery.ps1 create mode 100644 scripts/start-celery.sh create mode 100644 scripts/start-frontend.ps1 create mode 100644 scripts/start-frontend.sh create mode 100644 scripts/stop-all.ps1 create mode 100644 scripts/stop-all.sh create mode 100644 scripts/stop-celery.ps1 create mode 100644 scripts/stop-celery.sh create mode 100644 scripts/view-logs.ps1 create mode 100644 scripts/view-logs.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b506438 --- /dev/null +++ b/Makefile @@ -0,0 +1,206 @@ +# ============================================================ +# PIS Project Startup Script - Makefile +# PIS 项目启动脚本 +# ============================================================ +# 使用方法: +# make help 查看所有可用命令 +# make dev 启动开发环境(前端 + 后端 + Celery) +# make dev-backend 仅启动后端 +# make dev-frontend 仅启动前端 +# make dev-celery 仅启动 Celery +# make stop 停止所有服务 +# make status 查看服务状态 +# make logs 查看所有日志 +# make logs-backend 查看后端日志 +# make logs-frontend 查看前端日志 +# make logs-celery 查看 Celery 日志 +# make restart 重启所有服务 +# make restart-celery 重启 Celery +# ============================================================ + +# 默认环境 +ENV ?= dev + +# 路径配置 +PROJECT_ROOT := $(shell pwd) +BACKEND_DIR := $(PROJECT_ROOT)/backend +FRONTEND_DIR := $(PROJECT_ROOT)/web +SCRIPTS_DIR := $(PROJECT_ROOT)/scripts +LOG_DIR := $(BACKEND_DIR)/logs + +# PID 文件目录 +PID_DIR := $(PROJECT_ROOT)/.pids + +# 后端服务端口 +BACKEND_PORT := 8000 +FRONTEND_PORT := 8080 + +# 帮助信息 +.PHONY: help +help: + @echo "" + @echo "========================================" + @echo " PIS 项目启动脚本 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 启动命令:" + @echo " make dev 启动开发环境(前端+后端+Celery)" + @echo " make dev-backend 仅启动后端服务" + @echo " make dev-frontend 仅启动前端服务" + @echo " make dev-celery 仅启动 Celery" + @echo "" + @echo " 停止/重启:" + @echo " make stop 停止所有服务" + @echo " make restart 重启所有服务" + @echo " make restart-celery 重启 Celery" + @echo "" + @echo " 日志查看:" + @echo " make logs 查看所有日志" + @echo " make logs-backend 查看后端日志" + @echo " make logs-frontend 查看前端日志" + @echo " make logs-celery 查看 Celery 日志" + @echo "" + @echo " 状态查看:" + @echo " make status 查看所有服务状态" + @echo "" + @echo " 环境切换:" + @echo " ENV=prod make dev 使用生产环境配置启动" + @echo "" + @echo "========================================" + +# ============================================================ +# 创建必要的目录 +# ============================================================ +$(PID_DIR): + @mkdir -p $(PID_DIR) + +$(LOG_DIR): + @mkdir -p $(LOG_DIR) + +# ============================================================ +# 启动开发环境 - 全部服务 +# ============================================================ +.PHONY: dev +dev: $(PID_DIR) $(LOG_DIR) + @echo "========================================" + @echo " 启动开发环境 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 后端: http://localhost:$(BACKEND_PORT)" + @echo " 前端: http://localhost:$(FRONTEND_PORT)" + @echo " Celery: Worker + Beat" + @echo "" + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 启动后端 +# ============================================================ +.PHONY: dev-backend +dev-backend: $(PID_DIR) $(LOG_DIR) + @echo "启动后端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + +# ============================================================ +# 启动前端 +# ============================================================ +.PHONY: dev-frontend +dev-frontend: + @echo "启动前端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + +# ============================================================ +# 启动 Celery +# ============================================================ +.PHONY: dev-celery +dev-celery: $(PID_DIR) $(LOG_DIR) + @echo "启动 Celery (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 停止所有服务 +# ============================================================ +.PHONY: stop +stop: + @echo "停止所有服务..." + @bash $(SCRIPTS_DIR)/stop-all.sh + @echo "所有服务已停止" + +# ============================================================ +# 重启所有服务 +# ============================================================ +.PHONY: restart +restart: + @echo "重启所有服务..." + @$(MAKE) stop + @sleep 2 + @$(MAKE) dev + +# ============================================================ +# 重启 Celery +# ============================================================ +.PHONY: restart-celery +restart-celery: + @bash $(SCRIPTS_DIR)/stop-celery.sh + @sleep 1 + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 查看日志 +# ============================================================ +.PHONY: logs +logs: + @echo "========================================" + @echo " 查看所有日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh all + +.PHONY: logs-backend +logs-backend: + @echo "========================================" + @echo " 查看后端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh backend + +.PHONY: logs-frontend +logs-frontend: + @echo "========================================" + @echo " 查看前端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh frontend + +.PHONY: logs-celery +logs-celery: + @echo "========================================" + @echo " 查看 Celery 日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh celery + +# ============================================================ +# 查看服务状态 +# ============================================================ +.PHONY: status +status: + @echo "========================================" + @echo " 服务状态检查" + @echo "========================================" + @bash $(SCRIPTS_DIR)/check-status.sh + +# ============================================================ +# 清理日志 +# ============================================================ +.PHONY: clean-logs +clean-logs: + @echo "清理日志文件..." + @find $(LOG_DIR) -type f -name "*.log" -exec rm -f {} \; 2>/dev/null || true + @echo "日志已清理" + +# ============================================================ +# 清理 PID 文件(强制停止) +# ============================================================ +.PHONY: clean-pids +clean-pids: + @echo "清理 PID 文件..." + @rm -f $(PID_DIR)/*.pid 2>/dev/null || true + @echo "PID 文件已清理" diff --git a/docs/scripts-documentation.md b/docs/scripts-documentation.md new file mode 100644 index 0000000..198fa24 --- /dev/null +++ b/docs/scripts-documentation.md @@ -0,0 +1,257 @@ +# 脚本文档 + +PIS 项目跨平台服务管理脚本。 + +## 概述 + +| 脚本 | Windows (PowerShell) | Unix/Linux/macOS (Bash) | +|------|---------------------|------------------------| +| 启动后端 | `start-backend.ps1` | `start-backend.sh` | +| 启动前端 | `start-frontend.ps1` | `start-frontend.sh` | +| 启动 Celery | `start-celery.ps1` | `start-celery.sh` | +| 停止所有 | `stop-all.ps1` | `stop-all.sh` | +| 停止 Celery | `stop-celery.ps1` | `stop-celery.sh` | +| 查看日志 | `view-logs.ps1` | `view-logs.sh` | +| 检查状态 | `check-status.ps1` | `check-status.sh` | + +## 快速开始 + +### Windows (PowerShell) + +```powershell +# 启动所有服务 +.\make.ps1 dev + +# 停止所有服务 +.\make.ps1 stop + +# 查看日志 +.\make.ps1 logs + +# 检查状态 +.\make.ps1 status +``` + +### Unix/Linux/macOS (Bash) + +```bash +# 启动所有服务 +make dev + +# 停止所有服务 +make stop + +# 查看日志 +make logs + +# 检查状态 +make status +``` + +## 使用方法 + +### make.ps1 命令 (Windows PowerShell) + +| 命令 | 说明 | +|------|------| +| `.\make.ps1 help` | 显示所有可用命令 | +| `.\make.ps1 dev` | 启动开发环境(前端 + 后端 + Celery) | +| `.\make.ps1 dev-backend` | 仅启动后端 | +| `.\make.ps1 dev-frontend` | 仅启动前端 | +| `.\make.ps1 dev-celery` | 仅启动 Celery | +| `.\make.ps1 stop` | 停止所有服务 | +| `.\make.ps1 restart` | 重启所有服务 | +| `.\make.ps1 restart-celery` | 仅重启 Celery | +| `.\make.ps1 logs` | 查看所有日志 | +| `.\make.ps1 logs-backend` | 查看后端日志 | +| `.\make.ps1 logs-frontend` | 查看前端日志 | +| `.\make.ps1 logs-celery` | 查看 Celery 日志 | +| `.\make.ps1 status` | 检查服务状态 | +| `.\make.ps1 clean-logs` | 清理日志文件 | +| `.\make.ps1 clean-pids` | 清理 PID 文件 | + +### make 命令 (Unix/Linux/macOS Bash) + +| 命令 | 说明 | +|------|------| +| `make help` | 显示所有可用命令 | +| `make dev` | 启动开发环境(前端 + 后端 + Celery) | +| `make dev-backend` | 仅启动后端 | +| `make dev-frontend` | 仅启动前端 | +| `make dev-celery` | 仅启动 Celery | +| `make stop` | 停止所有服务 | +| `make restart` | 重启所有服务 | +| `make restart-celery` | 仅重启 Celery | +| `make logs` | 查看所有日志 | +| `make logs-backend` | 查看后端日志 | +| `make logs-frontend` | 查看前端日志 | +| `make logs-celery` | 查看 Celery 日志 | +| `make status` | 检查服务状态 | +| `make clean-logs` | 清理日志文件 | +| `make clean-pids` | 清理 PID 文件 | + +## 直接调用脚本 + +### Windows PowerShell + +```powershell +# 启动后端 +.\scripts\start-backend.ps1 [dev|prod] + +# 启动前端 +.\scripts\start-frontend.ps1 [dev|prod] + +# 启动 Celery +.\scripts\start-celery.ps1 [dev|prod] + +# 停止所有服务 +.\scripts\stop-all.ps1 + +# 仅停止 Celery +.\scripts\stop-celery.ps1 + +# 查看日志 +.\scripts\view-logs.ps1 [backend|frontend|celery|all] + +# 检查状态 +.\scripts\check-status.ps1 +``` + +### Unix/Linux/macOS Bash + +```bash +# 启动后端 +./scripts/start-backend.sh [dev|prod] + +# 启动前端 +./scripts/start-frontend.sh [dev|prod] + +# 启动 Celery +./scripts/start-celery.sh [dev|prod] + +# 停止所有服务 +./scripts/stop-all.sh + +# 仅停止 Celery +./scripts/stop-celery.sh + +# 查看日志 +./scripts/view-logs.sh [backend|frontend|celery|all] + +# 检查状态 +./scripts/check-status.sh +``` + +## 环境参数 + +- `dev`(默认):开发模式,带热重载 +- `prod`:生产模式,使用优化构建 + +### Windows PowerShell +```powershell +.\make.ps1 dev -ENV prod +``` + +### Unix/Linux/macOS +```bash +ENV=prod make dev +``` + +## 端口 + +| 服务 | 端口 | +|------|------| +| 后端 (Django) | 8000 | +| 前端 (Vue) | 8080 | + +## 日志文件 + +日志存储在 `backend/logs/`: + +- `backend.log` - 后端应用日志 +- `frontend.log` - 前端应用日志 +- `celery_worker.log` - Celery Worker 日志 +- `celery_beat.log` - Celery Beat 调度器日志 + +## PID 文件 + +PID 文件存储在 `.pids/`: + +- `backend.pid` +- `frontend.pid` +- `celery_worker.pid` +- `celery_beat.pid` + +## 跨平台兼容性 + +### Windows PowerShell +- 支持 Windows PowerShell 5.1 及 PowerShell 7+ +- 使用 `System.Net.Sockets.TcpClient` 检查端口 +- 使用 WMI 查询检测进程(兼容 PS 5.1 和 PS 7+) +- 所有输出使用英文(避免编码问题) + +### Unix/Linux/macOS +- 需要 Bash 4.0+ +- 使用 `nc`(netcat)检查端口(如果可用) +- 使用 `pgrep` 和 `pkill` 管理进程 +- 输出使用中文 + +## 依赖要求 + +### 后端 +- Python 3.10+ +- Django 4.2.14 +- uvicorn(开发)或 gunicorn(生产) +- Celery + Redis broker + +### 前端 +- Node.js 18+ +- Yarn 或 npm + +### Celery +- Redis 服务运行中 +- django-celery-beat + +## 故障排除 + +### 后端无法启动 +1. 检查端口 8000 是否被占用 +2. 检查 Python 依赖是否安装 +3. 检查 Django 配置:确保 `backend/conf/env.py` 已配置 + +### 前端无法启动 +1. 检查 Node.js 是否安装 +2. 安装依赖:`cd web && yarn install` +3. 检查端口 8080 是否被占用 + +### Celery 无法启动 +1. 检查 Redis 是否运行 +2. 执行数据库迁移:`python manage.py migrate` +3. 查看 Celery 日志 + +### 编码问题 +如果 PowerShell 显示乱码,确保使用 UTF-8 with BOM 编码保存的脚本文件。 + +## 脚本架构 + +``` +scripts/ +├── start-backend.ps1/.sh # 启动 Django 后端服务 +├── start-frontend.ps1/.sh # 启动 Vue 前端开发服务器 +├── start-celery.ps1/.sh # 启动 Celery worker 和 beat +├── stop-all.ps1/.sh # 停止所有服务 +├── stop-celery.ps1/.sh # 仅停止 Celery 服务 +├── view-logs.ps1/.sh # 查看和跟踪日志文件 +├── check-status.ps1/.sh # 检查服务运行状态 +├── Makefile # Unix/Linux/macOS 编排 +└── make.ps1 # Windows PowerShell 编排 +``` + +## 注意事项 + +- 所有脚本都是幂等的 - 多次运行是安全的 +- 脚本会检测服务是否已运行,已运行则跳过启动 +- 使用 PID 文件实现优雅关闭 +- 停止时会清理残留进程 +- PowerShell 脚本输出使用英文,避免编码问题 +- Bash 脚本输出使用中文 diff --git a/make.ps1 b/make.ps1 new file mode 100644 index 0000000..bd20567 --- /dev/null +++ b/make.ps1 @@ -0,0 +1,202 @@ +# ============================================================ +# PIS Project Startup Script - Windows PowerShell Makefile +# ============================================================ +# Usage: +# .\make.ps1 help Show all commands +# .\make.ps1 dev Start dev environment (frontend + backend + Celery) +# .\make.ps1 dev-backend Start backend only +# .\make.ps1 dev-frontend Start frontend only +# .\make.ps1 dev-celery Start Celery only +# .\make.ps1 stop Stop all services +# .\make.ps1 status Check service status +# .\make.ps1 logs View all logs +# .\make.ps1 logs-backend View backend logs +# .\make.ps1 logs-frontend View frontend logs +# .\make.ps1 logs-celery View Celery logs +# .\make.ps1 restart Restart all services +# .\make.ps1 restart-celery Restart Celery +# ============================================================ + +param( + [string]$Target = "help", + [string]$ENV = "dev" +) + +$ErrorActionPreference = "Continue" +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = $ScriptDir +$ScriptsDir = Join-Path $ProjectRoot "scripts" + +# Path configuration +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $ProjectRoot "backend\logs" + +function Ensure-Directories { + if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } + if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } +} + +function Run-Script { + param([string]$ScriptName) + $scriptPath = Join-Path $ScriptsDir "$ScriptName.ps1" + if (Test-Path $scriptPath) { + & $scriptPath $ENV + } else { + # fallback to bash if ps1 doesn't exist + $bashPath = Join-Path $ScriptsDir "$ScriptName.sh" + if (Test-Path $bashPath) { + bash $bashPath $ENV + } else { + Write-Host "[ERROR] Script not found: $ScriptName" + exit 1 + } + } +} + +switch ($Target.ToLower()) { + "help" { + Write-Host "" + Write-Host "========================================" + Write-Host " PIS Project Startup Script (ENV=$ENV)" + Write-Host "========================================" + Write-Host "" + Write-Host " Startup Commands:" + Write-Host " .\make.ps1 dev Start dev environment (frontend+backend+Celery)" + Write-Host " .\make.ps1 dev-backend Start backend only" + Write-Host " .\make.ps1 dev-frontend Start frontend only" + Write-Host " .\make.ps1 dev-celery Start Celery only" + Write-Host "" + Write-Host " Stop/Restart:" + Write-Host " .\make.ps1 stop Stop all services" + Write-Host " .\make.ps1 restart Restart all services" + Write-Host " .\make.ps1 restart-celery Restart Celery" + Write-Host "" + Write-Host " View Logs:" + Write-Host " .\make.ps1 logs View all logs" + Write-Host " .\make.ps1 logs-backend View backend logs" + Write-Host " .\make.ps1 logs-frontend View frontend logs" + Write-Host " .\make.ps1 logs-celery View Celery logs" + Write-Host "" + Write-Host " Status:" + Write-Host " .\make.ps1 status Check service status" + Write-Host "" + Write-Host " Environment:" + Write-Host " .\make.ps1 -ENV prod dev Use production environment" + Write-Host "" + Write-Host "========================================" + } + + "dev" { + Ensure-Directories + Write-Host "========================================" + Write-Host " Starting Dev Environment (ENV=$ENV)" + Write-Host "========================================" + Write-Host "" + Write-Host " Backend: http://localhost:8000" + Write-Host " Frontend: http://localhost:8080" + Write-Host " Celery: Worker + Beat" + Write-Host "" + Run-Script "start-backend" + Run-Script "start-frontend" + Run-Script "start-celery" + } + + "dev-backend" { + Ensure-Directories + Write-Host "Starting backend service (ENV=$ENV)..." + Run-Script "start-backend" + } + + "dev-frontend" { + Write-Host "Starting frontend service (ENV=$ENV)..." + Run-Script "start-frontend" + } + + "dev-celery" { + Ensure-Directories + Write-Host "Starting Celery (ENV=$ENV)..." + Run-Script "start-celery" + } + + "stop" { + Write-Host "Stopping all services..." + Run-Script "stop-all" + Write-Host "All services stopped" + } + + "restart" { + Write-Host "Restarting all services..." + Run-Script "stop-all" + Start-Sleep -Seconds 2 + Ensure-Directories + Run-Script "start-backend" + Run-Script "start-frontend" + Run-Script "start-celery" + } + + "restart-celery" { + Run-Script "stop-celery" + Start-Sleep -Seconds 1 + Ensure-Directories + Run-Script "start-celery" + } + + "logs" { + Write-Host "========================================" + Write-Host " Viewing All Logs (Ctrl+C to exit)" + Write-Host "========================================" + Run-Script "view-logs" + } + + "logs-backend" { + Write-Host "========================================" + Write-Host " Viewing Backend Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") backend + } + + "logs-frontend" { + Write-Host "========================================" + Write-Host " Viewing Frontend Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") frontend + } + + "logs-celery" { + Write-Host "========================================" + Write-Host " Viewing Celery Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") celery + } + + "status" { + Write-Host "========================================" + Write-Host " Service Status Check" + Write-Host "========================================" + Run-Script "check-status" + } + + "clean-logs" { + Write-Host "Cleaning log files..." + if (Test-Path $LOG_DIR) { + Get-ChildItem $LOG_DIR -Filter "*.log" | Remove-Item -Force -ErrorAction SilentlyContinue + } + Write-Host "Logs cleaned" + } + + "clean-pids" { + Write-Host "Cleaning PID files..." + if (Test-Path $PID_DIR) { + Get-ChildItem $PID_DIR -Filter "*.pid" | Remove-Item -Force -ErrorAction SilentlyContinue + } + Write-Host "PID files cleaned" + } + + default { + Write-Host "[ERROR] Unknown command: $Target" + Write-Host "Run '.\make.ps1 help' to see available commands" + exit 1 + } +} + +exit 0 diff --git a/scripts/check-status.ps1 b/scripts/check-status.ps1 new file mode 100644 index 0000000..52fdb1d --- /dev/null +++ b/scripts/check-status.ps1 @@ -0,0 +1,103 @@ +# ============================================================ +# Check Service Status (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Test-ServicePort { + param([int]$Port) + try { + $tcpClient = New-Object System.Net.Sockets.TcpClient + $tcpClient.Connect("localhost", $Port) + $tcpClient.Close() + return $true + } catch { + return $false + } +} + +function Check-Service { + param([string]$Name, [int]$Port = 0) + + Write-Host -NoNewline " $Name`: " + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "Running (PID: $pid)" + return 0 + } + } + } + + # Check process name - PowerShell 5.1 compatible using WMI + $patterns = @{ + "backend" = @("uvicorn", "gunicorn", "python") + "frontend" = @("node", "npm") + "celery_worker" = @("celery") + "celery_beat" = @("celery") + } + + if ($patterns.ContainsKey($Name)) { + foreach ($pattern in $patterns[$Name]) { + # Use WMI for reliable cross-version process matching + $wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%$pattern%'" + $procs = Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($procs) { + Write-Host "Running (PID: $($procs.ProcessId))" + return 0 + } + } + } + + # Check port + if ($Port -gt 0) { + if (Test-ServicePort -Port $Port) { + Write-Host "Running (Port: $Port)" + return 0 + } + } + + Write-Host "Not running" + return 1 +} + +Write-Host "" +Write-Host "========================================" +Write-Host " Service Status Check" +Write-Host "========================================" +Write-Host "" + +Check-Service "backend" 8000 +Check-Service "frontend" 8080 +Check-Service "celery_worker" +Check-Service "celery_beat" +Write-Host "" + +# Port check +Write-Host "Port Check:" + +Write-Host -NoNewline " 8000 (backend): " +if (Test-ServicePort -Port 8000) { + Write-Host "Open" +} else { + Write-Host "Closed" +} + +Write-Host -NoNewline " 8080 (frontend): " +if (Test-ServicePort -Port 8080) { + Write-Host "Open" +} else { + Write-Host "Closed" +} + +Write-Host "" +exit 0 diff --git a/scripts/check-status.sh b/scripts/check-status.sh new file mode 100644 index 0000000..afc91df --- /dev/null +++ b/scripts/check-status.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# ============================================================ +# Check Service Status +# 使用方法: ./check-status.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +check_service() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + local port=$2 + + echo -n " $name: " + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "运行中 (PID: $pid)" + return 0 + fi + fi + + # 检查进程名 + local pids=$(pgrep -f "$name" 2>/dev/null | head -1 || true) + if [ -n "$pids" ]; then + echo "运行中 (PID: $pids)" + return 0 + fi + + # 检查端口 + if [ -n "$port" ]; then + if nc -z localhost "$port" 2>/dev/null; then + echo "运行中 (端口: $port)" + return 0 + fi + fi + + echo "未运行" + return 1 +} + +echo "" +check_service "backend" "8000" +check_service "frontend" "8080" +check_service "celery_worker" "" +check_service "celery_beat" "" +echo "" + +# 端口检查 +echo "端口检查:" +echo -n " 8000 (后端): " +nc -z localhost 8000 2>/dev/null && echo "开放" || echo "未开放" + +echo -n " 8080 (前端): " +nc -z localhost 8080 2>/dev/null && echo "开放" || echo "未开放" + +echo "" diff --git a/scripts/start-backend.ps1 b/scripts/start-backend.ps1 new file mode 100644 index 0000000..f0ff906 --- /dev/null +++ b/scripts/start-backend.ps1 @@ -0,0 +1,78 @@ +# ============================================================ +# Start Backend Service (Windows PowerShell) +# Usage: .\start-backend.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $BackendDir "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$PID_FILE = Join-Path $PID_DIR "backend.pid" +$LOG_FILE = Join-Path $LOG_DIR "backend.log" + +# Check if already running +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + Write-Host "[Backend] Already running (PID: $PID), skipping" + exit 0 + } else { + Write-Host "[Backend] Cleaning stale PID file" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +Set-Location $BackendDir + +# Check dependencies +try { + python -c "import uvicorn" 2>$null + if ($LASTEXITCODE -ne 0) { throw "uvicorn not found" } +} catch { + Write-Host "[Error] uvicorn not installed. Run: cd backend && pip install uvicorn" + exit 1 +} + +# Set environment +$env:DJANGO_SETTINGS_MODULE = "application.settings" + +Write-Host "[Backend] Starting service (ENV=$ENV)..." + +# Select startup method based on environment +if ($ENV -eq "prod") { + Write-Host "[Backend] Using gunicorn (production mode)" + Start-Process -FilePath "python" -ArgumentList "-m gunicorn application.asgi:application -c gunicorn_conf.py --daemon -p $PID_FILE" -WorkingDirectory $BackendDir -RedirectStandardOutput $LOG_FILE -RedirectStandardError $LOG_FILE -WindowStyle Hidden +} else { + Write-Host "[Backend] Using uvicorn (development mode with hot reload)" + $proc = Start-Process -FilePath "python" -ArgumentList "main.py" -WorkingDirectory $BackendDir -PassThru -NoNewWindow + $proc.Id | Set-Content $PID_FILE +} + +# Wait for startup +Start-Sleep -Seconds 3 + +# Check if started successfully +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE + if (Get-Process -Id $PID -ErrorAction SilentlyContinue) { + Write-Host "[Backend] Started successfully (PID: $PID)" + Write-Host "[Backend] Log file: $LOG_FILE" + } else { + Write-Host "[Backend] Failed to start, check log: $LOG_FILE" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} else { + Write-Host "[Backend] Failed to start, check log: $LOG_FILE" +} + +exit 0 diff --git a/scripts/start-backend.sh b/scripts/start-backend.sh new file mode 100644 index 0000000..b8036ee --- /dev/null +++ b/scripts/start-backend.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# ============================================================ +# Start Backend Service +# 使用方法: ./start-backend.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$BACKEND_DIR/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +PID_FILE="$PID_DIR/backend.pid" +LOG_FILE="$LOG_DIR/backend.log" + +# 检查是否已运行 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[后端] 已运行 (PID: $PID),跳过启动" + exit 0 + else + echo "[后端] 清理旧的 PID 文件" + rm -f "$PID_FILE" + fi +fi + +cd "$BACKEND_DIR" + +# 检查依赖 +if ! python -c "import uvicorn" 2>/dev/null; then + echo "[错误] 未安装 uvicorn,请先: cd backend && pip install uvicorn" + exit 1 +fi + +# 设置环境变量 +export DJANGO_SETTINGS_MODULE="application.settings" + +echo "[后端] 启动服务 (ENV=$ENV)..." + +# 根据环境选择启动方式 +if [ "$ENV" = "prod" ]; then + # 生产环境使用 gunicorn + echo "[后端] 使用 gunicorn 启动 (生产模式)" + nohup python -m gunicorn application.asgi:application \ + -c gunicorn_conf.py \ + --daemon \ + -p "$PID_FILE" \ + >> "$LOG_FILE" 2>&1 +else + # 开发环境使用 uvicorn (带 reload) + echo "[后端] 使用 uvicorn 启动 (开发模式,带热重载)" + nohup python main.py \ + >> "$LOG_FILE" 2>&1 & + + echo $! > "$PID_FILE" +fi + +# 等待启动 +sleep 3 + +# 检查是否成功启动 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[后端] 启动成功 (PID: $PID)" + echo "[后端] 日志文件: $LOG_FILE" + else + echo "[后端] 启动失败,请检查日志: $LOG_FILE" + rm -f "$PID_FILE" + fi +else + # uvicorn 模式 + sleep 2 + if pgrep -f "uvicorn.*application.asgi" > "$PID_FILE" 2>/dev/null; then + echo "[后端] 启动成功 (PID: $(cat $PID_FILE))" + echo "[后端] 日志文件: $LOG_FILE" + else + echo "[后端] 启动失败,请检查日志: $LOG_FILE" + fi +fi diff --git a/scripts/start-celery.ps1 b/scripts/start-celery.ps1 new file mode 100644 index 0000000..c403e20 --- /dev/null +++ b/scripts/start-celery.ps1 @@ -0,0 +1,92 @@ +# ============================================================ +# Start Celery Services (Windows PowerShell) +# Usage: .\start-celery.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $BackendDir "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$WORKER_PID_FILE = Join-Path $PID_DIR "celery_worker.pid" +$BEAT_PID_FILE = Join-Path $PID_DIR "celery_beat.pid" +$WORKER_LOG = Join-Path $LOG_DIR "celery_worker.log" +$BEAT_LOG = Join-Path $LOG_DIR "celery_beat.log" + +Set-Location $BackendDir + +# Set Django environment +$env:DJANGO_SETTINGS_MODULE = "application.settings" + +# Check if Worker is already running +$workerRunning = $false +if (Test-Path $WORKER_PID_FILE) { + $PID = Get-Content $WORKER_PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + $workerRunning = $true + Write-Host "[Celery] Worker already running (PID: $PID), skipping" + } +} + +if (-not $workerRunning) { + Write-Host "[Celery] Starting Worker..." + + # Check if celery is installed + try { + python -c "import celery" 2>$null + if ($LASTEXITCODE -ne 0) { throw "celery not found" } + } catch { + Write-Host "[Error] Celery not installed" + exit 1 + } + + # Start Celery Worker + $proc = Start-Process -FilePath "celery" -ArgumentList "-A application worker -l info --logfile=$WORKER_LOG --pidfile=$WORKER_PID_FILE" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden + $proc.Id | Set-Content $WORKER_PID_FILE + Write-Host "[Celery] Worker started (PID: $($proc.Id))" +} + +# Check Beat +$beatRunning = $false +if (Test-Path $BEAT_PID_FILE) { + $PID = Get-Content $BEAT_PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + $beatRunning = $true + Write-Host "[Celery Beat] Already running (PID: $PID), skipping" + } +} + +if (-not $beatRunning) { + Write-Host "[Celery Beat] Starting scheduler..." + + # Start Celery Beat + $proc = Start-Process -FilePath "celery" -ArgumentList "-A application beat -l info --logfile=$BEAT_LOG --pidfile=$BEAT_PID_FILE --scheduler django_celery_beat.schedulers:DatabaseScheduler" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden + $proc.Id | Set-Content $BEAT_PID_FILE + Write-Host "[Celery Beat] Started (PID: $($proc.Id))" +} + +# Wait for startup +Start-Sleep -Seconds 3 + +# Show status +if (Test-Path $WORKER_PID_FILE) { + Write-Host "[Celery] Worker PID: $(Get-Content $WORKER_PID_FILE)" + Write-Host "[Celery] Worker log: $WORKER_LOG" +} + +if (Test-Path $BEAT_PID_FILE) { + Write-Host "[Celery Beat] PID: $(Get-Content $BEAT_PID_FILE)" + Write-Host "[Celery Beat] log: $BEAT_LOG" +} + +Write-Host "[Celery] Startup complete" +exit 0 diff --git a/scripts/start-celery.sh b/scripts/start-celery.sh new file mode 100644 index 0000000..3e42ae6 --- /dev/null +++ b/scripts/start-celery.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# ============================================================ +# Start Celery Services +# 使用方法: ./start-celery.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$BACKEND_DIR/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +WORKER_PID_FILE="$PID_DIR/celery_worker.pid" +BEAT_PID_FILE="$PID_DIR/celery_beat.pid" +WORKER_LOG="$LOG_DIR/celery_worker.log" +BEAT_LOG="$LOG_DIR/celery_beat.log" + +cd "$BACKEND_DIR" + +# 设置 Django 环境 +export DJANGO_SETTINGS_MODULE="application.settings" + +# 检查是否已运行 +check_celery() { + if [ -f "$WORKER_PID_FILE" ]; then + PID=$(cat "$WORKER_PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + return 0 + fi + fi + return 1 +} + +if check_celery; then + echo "[Celery] Worker 已运行,跳过启动" +else + echo "[Celery] 启动 Worker..." + + # 检查 celery 是否安装 + if ! python -c "import celery" 2>/dev/null; then + echo "[错误] Celery 未安装" + exit 1 + fi + + # 启动 Celery Worker + nohup celery -A application worker \ + -l info \ + --logfile="$WORKER_LOG" \ + --pidfile="$WORKER_PID_FILE" \ + >> "$WORKER_LOG" 2>&1 & + + echo "[Celery] Worker 启动命令已执行" +fi + +# 检查 Beat +if [ -f "$BEAT_PID_FILE" ]; then + PID=$(cat "$BEAT_PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[Celery Beat] 已运行 (PID: $PID),跳过启动" + else + rm -f "$BEAT_PID_FILE" + fi +else + echo "[Celery Beat] 启动调度器..." + + # 启动 Celery Beat + nohup celery -A application beat \ + -l info \ + --logfile="$BEAT_LOG" \ + --pidfile="$BEAT_PID_FILE" \ + --scheduler django_celery_beat.schedulers:DatabaseScheduler \ + >> "$BEAT_LOG" 2>&1 & + + echo "[Celery Beat] 启动命令已执行" +fi + +# 等待检查 +sleep 3 + +# 显示状态 +if [ -f "$WORKER_PID_FILE" ]; then + echo "[Celery] Worker PID: $(cat $WORKER_PID_FILE)" + echo "[Celery] Worker 日志: $WORKER_LOG" +fi + +if [ -f "$BEAT_PID_FILE" ]; then + echo "[Celery Beat] PID: $(cat $BEAT_PID_FILE)" + echo "[Celery Beat] 日志: $BEAT_LOG" +fi + +echo "[Celery] 启动完成" diff --git a/scripts/start-frontend.ps1 b/scripts/start-frontend.ps1 new file mode 100644 index 0000000..1dadf82 --- /dev/null +++ b/scripts/start-frontend.ps1 @@ -0,0 +1,98 @@ +# ============================================================ +# Start Frontend Service (Windows PowerShell) +# Usage: .\start-frontend.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$FrontendDir = Join-Path $ProjectRoot "web" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $ProjectRoot "backend" +$LOG_DIR = Join-Path $LOG_DIR "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$PID_FILE = Join-Path $PID_DIR "frontend.pid" +$LOG_FILE = Join-Path $LOG_DIR "frontend.log" + +# Check if already running +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + Write-Host "[Frontend] Already running (PID: $PID), skipping" + exit 0 + } else { + Write-Host "[Frontend] Cleaning stale PID file" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +Set-Location $FrontendDir + +# Check if yarn is available +$yarnCmd = Get-Command yarn -ErrorAction SilentlyContinue +if (!$yarnCmd) { + Write-Host "[Frontend] yarn not found, trying npm..." + $npmCmd = Get-Command npm -ErrorAction SilentlyContinue + if (!$npmCmd) { + Write-Host "[Error] npm not installed" + exit 1 + } + $useNpm = $true +} else { + $useNpm = $false +} + +# Check dependencies +if (!(Test-Path "node_modules")) { + Write-Host "[Frontend] Dependencies not installed, installing..." + if ($useNpm) { + npm install + } else { + yarn install + } +} + +Write-Host "[Frontend] Starting service (ENV=$ENV)..." + +# Select startup method based on environment +if ($ENV -eq "prod") { + Write-Host "[Frontend] Building production version..." + if ($useNpm) { + npm run build + } else { + yarn build + } + Write-Host "[Frontend] Production build complete, use static server" +} else { + if ($useNpm) { + $proc = Start-Process -FilePath "npm" -ArgumentList "run dev" -WorkingDirectory $FrontendDir -PassThru -NoNewWindow + } else { + $proc = Start-Process -FilePath "yarn" -ArgumentList "dev" -WorkingDirectory $FrontendDir -PassThru -NoNewWindow + } + $proc.Id | Set-Content $PID_FILE +} + +# Wait for startup +Start-Sleep -Seconds 5 + +# Check if started successfully +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE + if (Get-Process -Id $PID -ErrorAction SilentlyContinue) { + Write-Host "[Frontend] Started successfully (PID: $PID)" + Write-Host "[Frontend] Access: http://localhost:8080" + Write-Host "[Frontend] Log file: $LOG_FILE" + } else { + Write-Host "[Frontend] Failed to start, check log: $LOG_FILE" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +exit 0 diff --git a/scripts/start-frontend.sh b/scripts/start-frontend.sh new file mode 100644 index 0000000..fec6ccb --- /dev/null +++ b/scripts/start-frontend.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# ============================================================ +# Start Frontend Service +# 使用方法: ./start-frontend.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +FRONTEND_DIR="$PROJECT_ROOT/web" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$PROJECT_ROOT/backend/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +PID_FILE="$PID_DIR/frontend.pid" +LOG_FILE="$LOG_DIR/frontend.log" + +# 检查是否已运行 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[前端] 已运行 (PID: $PID),跳过启动" + exit 0 + else + echo "[前端] 清理旧的 PID 文件" + rm -f "$PID_FILE" + fi +fi + +cd "$FRONTEND_DIR" + +# 检查依赖 +if [ ! -d "node_modules" ]; then + echo "[前端] 依赖未安装,正在安装..." + yarn install +fi + +echo "[前端] 启动服务 (ENV=$ENV)..." + +# 根据环境选择启动方式 +if [ "$ENV" = "prod" ]; then + echo "[前端] 构建生产版本..." + yarn build + # 生产环境可以用 nginx 或静态服务器 served + echo "[前端] 生产构建完成,请使用静态服务器 serving" +else + # 开发环境 + nohup yarn dev >> "$LOG_FILE" 2>&1 & + echo $! > "$PID_FILE" +fi + +# 等待启动 +sleep 5 + +# 检查是否成功启动 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[前端] 启动成功 (PID: $PID)" + echo "[前端] 访问地址: http://localhost:8080" + echo "[前端] 日志文件: $LOG_FILE" + else + echo "[前端] 启动失败,请检查日志: $LOG_FILE" + rm -f "$PID_FILE" + fi +fi diff --git a/scripts/stop-all.ps1 b/scripts/stop-all.ps1 new file mode 100644 index 0000000..8505327 --- /dev/null +++ b/scripts/stop-all.ps1 @@ -0,0 +1,76 @@ +# ============================================================ +# Stop All Services (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Stop-ServiceByName { + param([string]$Name, [string]$Pattern) + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "[$Name] Stopping service (PID: $pid)..." + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + } + Write-Host "[$Name] Stopped" + } + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } + + # Try to find by process name + $wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%$Pattern%' OR CommandLine LIKE '%$Pattern%'" + $procs = Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue + if ($procs) { + foreach ($p in $procs) { + Write-Host "[$Name] Found by pattern PID: $($p.ProcessId), stopping..." + Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue + } + } +} + +# Stop each service +Stop-ServiceByName "backend" "uvicorn" +Stop-ServiceByName "frontend" "node" +Stop-ServiceByName "celery_worker" "celery" +Stop-ServiceByName "celery_beat" "celery" + +# Additional cleanup +Write-Host "[Cleanup] Checking for residual processes..." + +# Cleanup uvicorn +$wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%uvicorn%'" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping uvicorn (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +# Cleanup gunicorn +$wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%gunicorn%'" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping gunicorn (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +# Cleanup node/vite +$wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%node%' AND (CommandLine LIKE '%vite%' OR CommandLine LIKE '%dev%')" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping vite (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +Write-Host "[Done] All services stopped" +exit 0 diff --git a/scripts/stop-all.sh b/scripts/stop-all.sh new file mode 100644 index 0000000..7636c6a --- /dev/null +++ b/scripts/stop-all.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# ============================================================ +# Stop All Services +# 使用方法: ./stop-all.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +stop_service() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "[$name] 停止服务 (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + # 强制停止 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + echo "[$name] 已停止" + else + echo "[$name] 未运行" + fi + rm -f "$pid_file" + else + # 尝试通过进程名查找 + local pids=$(pgrep -f "$name" 2>/dev/null || true) + if [ -n "$pids" ]; then + echo "[$name] 通过进程名找到 PID: $pids,停止..." + echo "$pids" | xargs kill 2>/dev/null || true + fi + fi +} + +# 停止各个服务 +stop_service "backend" +stop_service "frontend" +stop_service "celery_worker" +stop_service "celery_beat" + +# 额外清理残留进程 +echo "[清理] 检查残留进程..." + +# 清理 uvicorn +pkill -f "uvicorn.*application.asgi" 2>/dev/null || true +pkill -f "gunicorn.*application.asgi" 2>/dev/null || true + +# 清理 celery +pkill -f "celery.*worker" 2>/dev/null || true +pkill -f "celery.*beat" 2>/dev/null || true + +# 清理 vite +pkill -f "vite" 2>/dev/null || true + +echo "[完成] 所有服务已停止" diff --git a/scripts/stop-celery.ps1 b/scripts/stop-celery.ps1 new file mode 100644 index 0000000..944226b --- /dev/null +++ b/scripts/stop-celery.ps1 @@ -0,0 +1,45 @@ +# ============================================================ +# Stop Celery Services (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Stop-CeleryService { + param([string]$Name) + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "[$Name] Stopping (PID: $pid)..." + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + } + } + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } + + # Cleanup residual + $wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%celery%'" + Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping celery (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue + } +} + +Stop-CeleryService "celery_worker" +Stop-CeleryService "celery_beat" + +Write-Host "[Done] Celery stopped" +exit 0 diff --git a/scripts/stop-celery.sh b/scripts/stop-celery.sh new file mode 100644 index 0000000..207ab21 --- /dev/null +++ b/scripts/stop-celery.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# ============================================================ +# Stop Celery Services +# 使用方法: ./stop-celery.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +stop_celery() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + local pattern="$2" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "[$name] 停止 (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + fi + rm -f "$pid_file" + fi + + # 清理残留 + pkill -f "celery.*$pattern" 2>/dev/null || true +} + +stop_celery "celery_worker" "worker" +stop_celery "celery_beat" "beat" + +echo "[完成] Celery 已停止" diff --git a/scripts/view-logs.ps1 b/scripts/view-logs.ps1 new file mode 100644 index 0000000..3542290 --- /dev/null +++ b/scripts/view-logs.ps1 @@ -0,0 +1,85 @@ +# ============================================================ +# View Logs (Windows PowerShell) +# Usage: .\view-logs.ps1 [backend|frontend|celery|all] +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$LOG_DIR = Join-Path $BackendDir "logs" + +$TARGET = if ($args[0]) { $args[0] } else { "all" } + +# Ensure log directory exists +if (!(Test-Path $LOG_DIR)) { + New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null +} + +# Create empty log files if they don't exist +$logFiles = @( + (Join-Path $LOG_DIR "backend.log"), + (Join-Path $LOG_DIR "frontend.log"), + (Join-Path $LOG_DIR "celery_worker.log"), + (Join-Path $LOG_DIR "celery_beat.log") +) +foreach ($log in $logFiles) { + if (!(Test-Path $log)) { New-Item -ItemType File -Path $log -Force | Out-Null } +} + +switch ($TARGET) { + "backend" { + Write-Host "Viewing backend logs: $LOG_DIR\backend.log" + Get-Content (Join-Path $LOG_DIR "backend.log") -Wait -Tail 20 + } + "frontend" { + Write-Host "Viewing frontend logs: $LOG_DIR\frontend.log" + Get-Content (Join-Path $LOG_DIR "frontend.log") -Wait -Tail 20 + } + "celery" { + Write-Host "Viewing Celery logs..." + Write-Host "=== Worker ===" + Get-Content (Join-Path $LOG_DIR "celery_worker.log") -Wait -Tail 10 + } + "all" { + Write-Host "Viewing all logs (Ctrl+C to exit)..." + Write-Host "" + Write-Host "=== Backend Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "backend.log")) -and (Get-Content (Join-Path $LOG_DIR "backend.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "backend.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Frontend Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "frontend.log")) -and (Get-Content (Join-Path $LOG_DIR "frontend.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "frontend.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Celery Worker Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "celery_worker.log")) -and (Get-Content (Join-Path $LOG_DIR "celery_worker.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "celery_worker.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Celery Beat Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "celery_beat.log")) -and (Get-Content (Join-Path $LOG_DIR "celery_beat.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "celery_beat.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "--- Live monitoring all logs (Ctrl+C to exit) ---" + Get-Content $logFiles -Wait + } + default { + Write-Host "Usage: $PSCommandPath [backend|frontend|celery|all]" + exit 1 + } +} + +exit 0 diff --git a/scripts/view-logs.sh b/scripts/view-logs.sh new file mode 100644 index 0000000..86ec940 --- /dev/null +++ b/scripts/view-logs.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# ============================================================ +# View Logs +# 使用方法: ./view-logs.sh [backend|frontend|celery|all] +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +LOG_DIR="$BACKEND_DIR/logs" + +TARGET=${1:-all} + +# 确保日志目录存在 +mkdir -p "$LOG_DIR" + +# 创建空的日志文件(如果不存在) +touch "$LOG_DIR/backend.log" 2>/dev/null || true +touch "$LOG_DIR/frontend.log" 2>/dev/null || true +touch "$LOG_DIR/celery_worker.log" 2>/dev/null || true +touch "$LOG_DIR/celery_beat.log" 2>/dev/null || true + +case "$TARGET" in + backend) + echo "查看后端日志: $LOG_DIR/backend.log" + tail -f "$LOG_DIR/backend.log" + ;; + frontend) + echo "查看前端日志: $LOG_DIR/frontend.log" + tail -f "$LOG_DIR/frontend.log" + ;; + celery) + echo "查看 Celery 日志..." + echo "=== Worker ===" + tail -f "$LOG_DIR/celery_worker.log" & + echo "=== Beat ===" + tail -f "$LOG_DIR/celery_beat.log" + ;; + all) + echo "查看所有日志 (Ctrl+C 退出)..." + echo "" + echo "=== 后端日志 ===" + tail -n 20 "$LOG_DIR/backend.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== 前端日志 ===" + tail -n 20 "$LOG_DIR/frontend.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== Celery Worker 日志 ===" + tail -n 20 "$LOG_DIR/celery_worker.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== Celery Beat 日志 ===" + tail -n 20 "$LOG_DIR/celery_beat.log" 2>/dev/null || echo "(空)" + echo "" + echo "--- 实时监控所有日志 (Ctrl+C 退出) ---" + tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" "$LOG_DIR/celery_worker.log" "$LOG_DIR/celery_beat.log" + ;; + *) + echo "用法: $0 [backend|frontend|celery|all]" + exit 1 + ;; +esac From 574d9557cf8499064a9cdeeff022b6e60b2aa7d0 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:21:39 +0800 Subject: [PATCH 059/111] fix(dashboard): use DashboardView instead of placeholder in urls.py The urls.py was still using a placeholder view function. Updated to import and use the proper DashboardView.as_view() from views.py. --- backend/apps/pisadmin/dashboard/urls.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/backend/apps/pisadmin/dashboard/urls.py b/backend/apps/pisadmin/dashboard/urls.py index fc297cf..4587499 100644 --- a/backend/apps/pisadmin/dashboard/urls.py +++ b/backend/apps/pisadmin/dashboard/urls.py @@ -1,13 +1,6 @@ from django.urls import path -from rest_framework.decorators import api_view -from rest_framework.response import Response - - -@api_view(['GET']) -def dashboard_view(request): - return Response({"status": "ok"}) - +from .views import DashboardView urlpatterns = [ - path('', dashboard_view, name='dashboard'), + path('', DashboardView.as_view(), name='dashboard'), ] From 7240d4f0c99abf462846e93b30c201d8be78dccf Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Wed, 1 Apr 2026 19:27:21 +0800 Subject: [PATCH 060/111] refactor(dashboard): rewrite views.py using DRF CBV pattern - Move serializers into views.py for self-contained CBV - Use DashboardViewSet with list() method for GET - Add proper docstrings - Use serializer for response validation --- backend/apps/pisadmin/dashboard/views.py | 341 ++++++++++++++--------- 1 file changed, 216 insertions(+), 125 deletions(-) diff --git a/backend/apps/pisadmin/dashboard/views.py b/backend/apps/pisadmin/dashboard/views.py index f79432b..eaba184 100644 --- a/backend/apps/pisadmin/dashboard/views.py +++ b/backend/apps/pisadmin/dashboard/views.py @@ -3,7 +3,7 @@ from datetime import timedelta from django.utils import timezone from django.db.models import Count from django.db.models.functions import TruncMonth -from rest_framework.views import APIView +from rest_framework import serializers, views from rest_framework.response import Response from apps.pisadmin.miscprocurement.models import Inquiry @@ -11,152 +11,243 @@ from apps.pissupplier.models import QuotationMaster from apps.pisadmin.basicinfo.models import Supplier -def get_buyer_kpi(user): - """采购方 KPI 计算""" - total_inquiries = Inquiry.objects.filter(create_user=user.username).count() - pending_inquiries = Inquiry.objects.filter(create_user=user.username, status__in=[1, 2, 3]).count() - completed_quotes = QuotationMaster.objects.filter( - inquiry_no__in=Inquiry.objects.filter(create_user=user.username).values('inquiry_no') - ).count() - total_suppliers = Supplier.objects.count() - return { - 'total_inquiries': total_inquiries, - 'pending_inquiries': pending_inquiries, - 'completed_quotes': completed_quotes, - 'total_suppliers': total_suppliers, - } +class BuyerKPISerializer(serializers.Serializer): + total_inquiries = serializers.IntegerField() + pending_inquiries = serializers.IntegerField() + completed_quotes = serializers.IntegerField() + total_suppliers = serializers.IntegerField() -def get_buyer_tasks(user): - """采购方待办任务""" - inquiries = Inquiry.objects.filter( - create_user=user.username, - status__in=[1, 2, 3] - ).order_by('-create_time')[:10] - return [ - { - 'id': i.id, - 'title': i.title, - 'inquiry_no': i.inquiry_no, - 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), - 'created_at': i.create_time, - } - for i in inquiries - ] +class BuyerTaskSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + inquiry_no = serializers.CharField() + status = serializers.CharField() + created_at = serializers.DateTimeField() -def get_supplier_kpi(user): - """供应商 KPI 计算""" - total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() - pending_quotes = QuotationMaster.objects.filter(supplier_code=user.username, status__in=[1, 2]).count() - won_quotes = QuotationMaster.objects.filter(supplier_code=user.username, is_awarded=1).count() - conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 - return { - 'total_quotes': total_quotes, - 'pending_quotes': pending_quotes, - 'won_quotes': won_quotes, - 'conversion_rate': round(conversion_rate, 2), - } +class SupplierKPISerializer(serializers.Serializer): + total_quotes = serializers.IntegerField() + pending_quotes = serializers.IntegerField() + won_quotes = serializers.IntegerField() + conversion_rate = serializers.FloatField() -def get_supplier_pending_quotes(user): - """供应商待报价清单""" - quotes = QuotationMaster.objects.filter( - supplier_code=user.username, - status__in=[1, 2] - ).order_by('-creattime')[:10] - result = [] - for q in quotes: - inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() - result.append({ - 'id': q.autoid, - 'inquiry_no': q.inquiry_no, - 'item_name': inquiry.title if inquiry else '', - 'quantity': 0, - 'unit': '', - 'deadline': q.quote_deadline, - }) - return result +class SupplierQuoteSerializer(serializers.Serializer): + id = serializers.IntegerField() + inquiry_no = serializers.CharField() + item_name = serializers.CharField() + unit = serializers.CharField() + deadline = serializers.DateTimeField() -def get_trend_data(user, is_buyer=True): - """近6个月趋势数据""" - six_months_ago = timezone.now() - timedelta(days=180) - if is_buyer: - data = ( - Inquiry.objects.filter( - create_user=user.username, - create_time__gte=six_months_ago - ) - .annotate(month=TruncMonth('create_time')) - .values('month') - .annotate(count=Count('id')) - .order_by('month') - ) - else: - data = ( - QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__gte=six_months_ago - ) - .annotate(month=TruncMonth('creattime')) - .values('month') - .annotate(quotes=Count('id')) - .order_by('month') - ) - # Add won counts - result = [] - for d in data: - won = QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__month=d['month'].month, - creattime__year=d['month'].year, - is_awarded=1 - ).count() - result.append({ - 'month': d['month'].strftime('%Y-%m'), - 'quotes': d['quotes'], - 'won': won, - }) - return result - return [{'month': d['month'].strftime('%Y-%m'), 'count': d['count']} for d in data] +class MessageSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + content = serializers.CharField() + is_read = serializers.BooleanField() + created_at = serializers.DateTimeField() -def get_messages(user): - """消息通知 - 占位实现,后续接入通知系统""" - return [] +class TrendSerializer(serializers.Serializer): + month = serializers.CharField() + count = serializers.IntegerField(required=False) + quotes = serializers.IntegerField(required=False) + won = serializers.IntegerField(required=False) -class DashboardView(APIView): +class BuyerDashboardSerializer(serializers.Serializer): + kpi = BuyerKPISerializer() + tasks = BuyerTaskSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class SupplierDashboardSerializer(serializers.Serializer): + kpi = SupplierKPISerializer() + pending_quotes = SupplierQuoteSerializer(many=True) + messages = MessageSerializer(many=True) + trend = TrendSerializer(many=True) + + +class DashboardResponseSerializer(serializers.Serializer): + buyer = BuyerDashboardSerializer(allow_null=True) + supplier = SupplierDashboardSerializer(allow_null=True) + + +class DashboardViewSet(views.APIView): """看板数据视图""" - def get(self, request): + def get_buyer_kpi(self, user): + """采购方 KPI 计算""" + total_inquiries = Inquiry.objects.filter(create_user=user.username).count() + pending_inquiries = Inquiry.objects.filter( + create_user=user.username, status__in=[1, 2, 3] + ).count() + completed_quotes = QuotationMaster.objects.filter( + inquiry_no__in=Inquiry.objects.filter( + create_user=user.username + ).values('inquiry_no') + ).count() + total_suppliers = Supplier.objects.count() + return { + 'total_inquiries': total_inquiries, + 'pending_inquiries': pending_inquiries, + 'completed_quotes': completed_quotes, + 'total_suppliers': total_suppliers, + } + + def get_buyer_tasks(self, user): + """采购方待办任务""" + inquiries = Inquiry.objects.filter( + create_user=user.username, + status__in=[1, 2, 3] + ).order_by('-create_time')[:10] + return [ + { + 'id': i.id, + 'title': i.title, + 'inquiry_no': i.inquiry_no, + 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), + 'created_at': i.create_time, + } + for i in inquiries + ] + + def get_supplier_kpi(self, user): + """供应商 KPI 计算""" + total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() + pending_quotes = QuotationMaster.objects.filter( + supplier_code=user.username, status__in=[1, 2] + ).count() + won_quotes = QuotationMaster.objects.filter( + supplier_code=user.username, is_awarded=1 + ).count() + conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 + return { + 'total_quotes': total_quotes, + 'pending_quotes': pending_quotes, + 'won_quotes': won_quotes, + 'conversion_rate': round(conversion_rate, 2), + } + + def get_supplier_pending_quotes(self, user): + """供应商待报价清单""" + quotes = QuotationMaster.objects.filter( + supplier_code=user.username, + status__in=[1, 2] + ).order_by('-creattime')[:10] + result = [] + for q in quotes: + inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() + result.append({ + 'id': q.autoid, + 'inquiry_no': q.inquiry_no, + 'item_name': inquiry.title if inquiry else '', + 'unit': '', + 'deadline': q.quote_deadline, + }) + return result + + def get_trend_data(self, user, is_buyer=True): + """近6个月趋势数据""" + six_months_ago = timezone.now() - timedelta(days=180) + if is_buyer: + data = ( + Inquiry.objects.filter( + create_user=user.username, + create_time__gte=six_months_ago + ) + .annotate(month=TruncMonth('create_time')) + .values('month') + .annotate(count=Count('id')) + .order_by('month') + ) + return [ + {'month': d['month'].strftime('%Y-%m'), 'count': d['count']} + for d in data + ] + else: + data = ( + QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__gte=six_months_ago + ) + .annotate(month=TruncMonth('creattime')) + .values('month') + .annotate(quotes=Count('id')) + .order_by('month') + ) + result = [] + for d in data: + won = QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__month=d['month'].month, + creattime__year=d['month'].year, + is_awarded=1 + ).count() + result.append({ + 'month': d['month'].strftime('%Y-%m'), + 'quotes': d['quotes'], + 'won': won, + }) + return result + + def get_messages(self, user): + """消息通知 - 占位实现,后续接入通知系统""" + return [] + + def get_buyer_data(self, user): + """获取采购方看板数据""" + return { + 'kpi': self.get_buyer_kpi(user), + 'tasks': self.get_buyer_tasks(user), + 'messages': self.get_messages(user), + 'trend': self.get_trend_data(user, is_buyer=True), + } + + def get_supplier_data(self, user): + """获取供应商看板数据""" + return { + 'kpi': self.get_supplier_kpi(user), + 'pending_quotes': self.get_supplier_pending_quotes(user), + 'messages': self.get_messages(user), + 'trend': self.get_trend_data(user, is_buyer=False), + } + + def list(self, request): + """获取看板数据 + + Returns role-based dashboard data based on user's permissions. + - Buyer role: returns buyer dashboard data + - Supplier role: returns supplier dashboard data + - Both roles: returns both + """ user = request.user if not user or not user.is_authenticated: return Response({'buyer': None, 'supplier': None}) # 判断用户角色 - # 供应商用户:有 supplier_code 在 QuotationMaster 中 - has_supplier_role = QuotationMaster.objects.filter(supplier_code=user.username).exists() - # 采购方用户:有 create_user 在 Inquiry 中 - has_buyer_role = Inquiry.objects.filter(create_user=user.username).exists() + has_supplier_role = QuotationMaster.objects.filter( + supplier_code=user.username + ).exists() + has_buyer_role = Inquiry.objects.filter( + create_user=user.username + ).exists() - response_data = {'buyer': None, 'supplier': None} + buyer_data = None + supplier_data = None if has_buyer_role: - response_data['buyer'] = { - 'kpi': get_buyer_kpi(user), - 'tasks': get_buyer_tasks(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=True), - } + buyer_data = self.get_buyer_data(user) if has_supplier_role: - response_data['supplier'] = { - 'kpi': get_supplier_kpi(user), - 'pending_quotes': get_supplier_pending_quotes(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=False), - } + supplier_data = self.get_supplier_data(user) - return Response(response_data) + response_data = {'buyer': buyer_data, 'supplier': supplier_data} + + # 使用序列化器验证响应数据 + serializer = DashboardResponseSerializer(data=response_data) + serializer.is_valid(raise_exception=True) + + return Response(serializer.validated_data) From 27b98bc73fea3d0f19ab771ff11d1ad6d4e5294a Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:20:12 +0800 Subject: [PATCH 061/111] feat(dashboard): complete buyer/supplier dashboard redesign Backend: - Fix JWT auth header format (use "JWT " prefix) - Fix DashboardView to return data for superadmin users - Return empty structure when no business data exists - Use serializers for response validation Frontend: - Redesign BuyerDashboard with new UI matching design spec - Redesign SupplierDashboard with consistent styling - Add role switcher for users with both buyer/supplier roles - Fix FontAwesome icon class names (fa vs fas) - Update tagsView title when switching roles via mitt event - Add i18n translations for dashboard Auth fixes: - Fix token storage to use sessionStorage instead of Cookies - Add route initialization guard to prevent duplicate routing - Fix login flow for token handling Files: - Add BuyerDashboard, SupplierDashboard, KpiCard, TrendChart components - Add dashboard API and Pinia store - Add mitt event type for tagsView name update --- backend/apps/pisadmin/dashboard/views.py | 56 +- backend/requirements.txt | 3 +- web/src/i18n/lang/en.ts | 1 + web/src/i18n/lang/zh-cn.ts | 1 + web/src/i18n/lang/zh-tw.ts | 1 + web/src/layout/navBars/tagsView/tagsView.vue | 11 + web/src/router/backEnd.ts | 5 +- web/src/router/index.ts | 10 +- web/src/router/route.ts | 8 + web/src/stores/modules/dashboard.ts | 5 +- web/src/types/mitt.d.ts | 1 + web/src/utils/request.ts | 4 +- web/src/utils/storage.ts | 11 +- .../pisadmin/dashboard/BuyerDashboard.vue | 690 +++++++++++++++--- .../pisadmin/dashboard/SupplierDashboard.vue | 648 +++++++++++++--- web/src/views/pisadmin/dashboard/index.vue | 67 +- .../views/system/login/component/account.vue | 74 +- 17 files changed, 1350 insertions(+), 246 deletions(-) diff --git a/backend/apps/pisadmin/dashboard/views.py b/backend/apps/pisadmin/dashboard/views.py index eaba184..b6b4efe 100644 --- a/backend/apps/pisadmin/dashboard/views.py +++ b/backend/apps/pisadmin/dashboard/views.py @@ -75,7 +75,7 @@ class DashboardResponseSerializer(serializers.Serializer): supplier = SupplierDashboardSerializer(allow_null=True) -class DashboardViewSet(views.APIView): +class DashboardView(views.APIView): """看板数据视图""" def get_buyer_kpi(self, user): @@ -215,34 +215,64 @@ class DashboardViewSet(views.APIView): 'trend': self.get_trend_data(user, is_buyer=False), } - def list(self, request): + def get(self, request): """获取看板数据 Returns role-based dashboard data based on user's permissions. - Buyer role: returns buyer dashboard data - Supplier role: returns supplier dashboard data - Both roles: returns both + - Superadmin or no business data: returns empty structure """ user = request.user if not user or not user.is_authenticated: return Response({'buyer': None, 'supplier': None}) - # 判断用户角色 - has_supplier_role = QuotationMaster.objects.filter( - supplier_code=user.username - ).exists() - has_buyer_role = Inquiry.objects.filter( - create_user=user.username - ).exists() + # 分别判断用户角色,避免一个表不存在影响另一个 + has_supplier_role = False + has_buyer_role = False + try: + has_supplier_role = QuotationMaster.objects.filter( + supplier_code=user.username + ).exists() + except Exception: + pass + + try: + has_buyer_role = Inquiry.objects.filter( + create_user=user.username + ).exists() + except Exception: + pass + + # 超级管理员或有任何角色,始终返回对应看板数据结构 buyer_data = None supplier_data = None - if has_buyer_role: - buyer_data = self.get_buyer_data(user) + # 采购方看板:超级管理员或有任何采购记录的用户 + if user.is_superuser or has_buyer_role: + try: + buyer_data = self.get_buyer_data(user) + except Exception: + buyer_data = { + 'kpi': {'total_inquiries': 0, 'pending_inquiries': 0, 'completed_quotes': 0, 'total_suppliers': 0}, + 'tasks': [], + 'messages': [], + 'trend': [], + } - if has_supplier_role: - supplier_data = self.get_supplier_data(user) + # 供应商看板:超级管理员或有任何供应商记录的用户 + if user.is_superuser or has_supplier_role: + try: + supplier_data = self.get_supplier_data(user) + except Exception: + supplier_data = { + 'kpi': {'total_quotes': 0, 'pending_quotes': 0, 'won_quotes': 0, 'conversion_rate': 0}, + 'pending_quotes': [], + 'messages': [], + 'trend': [], + } response_data = {'buyer': buyer_data, 'supplier': supplier_data} diff --git a/backend/requirements.txt b/backend/requirements.txt index dfa27c4..d1c4527 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,3 +1,4 @@ +setuptools>=69.0.0,<70 Django==4.2.14 django-comment-migrate==0.1.7 django-cors-headers==4.4.0 @@ -10,7 +11,6 @@ django-timezone-field==7.0 djangorestframework_simplejwt==5.4.0 drf-yasg==1.21.7 mssql-django==1.7 -mysqlclient==2.2.0 pypinyin==0.51.0 ua-parser==0.18.0 pyparsing==3.1.2 @@ -23,7 +23,6 @@ channels-redis==4.2.0 user-agents==2.2.0 six==1.16.0 whitenoise==6.7.0 -psycopg2==2.9.9 uvicorn==0.30.3 gunicorn==23.0.0 gevent==24.2.1 diff --git a/web/src/i18n/lang/en.ts b/web/src/i18n/lang/en.ts index 307beb4..01467c9 100644 --- a/web/src/i18n/lang/en.ts +++ b/web/src/i18n/lang/en.ts @@ -28,6 +28,7 @@ export default { limitsBackEnd: 'Backend Control', limitsBackEndEndPage: 'Page Permissions', personal: 'Personal Center', + dashboard: 'Dashboard', }, staticRoutes: { signIn: 'Login', diff --git a/web/src/i18n/lang/zh-cn.ts b/web/src/i18n/lang/zh-cn.ts index 82d7956..3d28ff1 100644 --- a/web/src/i18n/lang/zh-cn.ts +++ b/web/src/i18n/lang/zh-cn.ts @@ -28,6 +28,7 @@ export default { limitsBackEnd: '后端控制', limitsBackEndEndPage: '页面权限', personal: '个人中心', + dashboard: '看板', }, staticRoutes: { signIn: '登录', diff --git a/web/src/i18n/lang/zh-tw.ts b/web/src/i18n/lang/zh-tw.ts index 824a8d7..a19e526 100644 --- a/web/src/i18n/lang/zh-tw.ts +++ b/web/src/i18n/lang/zh-tw.ts @@ -28,6 +28,7 @@ export default { limitsBackEnd: '後端控制', limitsBackEndEndPage: '頁面權限', personal: '個人中心', + dashboard: '看板', }, staticRoutes: { signIn: '登錄', diff --git a/web/src/layout/navBars/tagsView/tagsView.vue b/web/src/layout/navBars/tagsView/tagsView.vue index b32904b..1e564c0 100644 --- a/web/src/layout/navBars/tagsView/tagsView.vue +++ b/web/src/layout/navBars/tagsView/tagsView.vue @@ -547,6 +547,15 @@ onBeforeMount(() => { }); } }); + // 监听更新 tagsView 名称(如 dashboard 切换角色) + mittBus.on('onUpdateTagsViewName', (data: { path: string; title: string }) => { + const item = state.tagsViewList.find((v: RouteItem) => v.path === data.path); + if (item) { + item.meta.title = data.title; + // 同时更新 Session 缓存 + addBrowserSetSession(state.tagsViewList); + } + }); }); // 页面卸载时 onUnmounted(() => { @@ -556,6 +565,8 @@ onUnmounted(() => { mittBus.off('openOrCloseSortable', () => {}); // 取消监听布局配置开启 TagsView 共用 mittBus.off('openShareTagsView', () => {}); + // 取消监听更新 tagsView 名称 + mittBus.off('onUpdateTagsViewName', () => {}); // 取消窗口 resize 监听 window.removeEventListener('resize', onSortableResize); }); diff --git a/web/src/router/backEnd.ts b/web/src/router/backEnd.ts index dd8a577..e159dea 100644 --- a/web/src/router/backEnd.ts +++ b/web/src/router/backEnd.ts @@ -127,7 +127,10 @@ export async function initBackEndControlRoutes() { // 界面 loading 动画开始执行 if (window.nextLoading === undefined) NextLoading.start(); // 无 token 停止执行下一步 - if (!Session.get('token')) return false; + const token = Session.get('token'); + if (!token) { + return false; + } // 触发初始化用户信息 pinia // https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP await useUserInfo().getApiUserInfo(); diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 37708c6..ef64ba8 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -109,6 +109,10 @@ const checkToken = ()=>{ } } + +// 防止重复初始化路由的标记 +let isRoutesInitializing = false; + // 路由加载前 router.beforeEach(async (to, from, next) => { // 检查浏览器本地版本与线上版本是否一致,判断是否需要刷新页面进行更新 @@ -136,17 +140,21 @@ router.beforeEach(async (to, from, next) => { } else { const storesRoutesList = useRoutesList(pinia); const {routesList} = storeToRefs(storesRoutesList); - if (routesList.value.length === 0) { + if (routesList.value.length === 0 && !isRoutesInitializing) { if (isRequestRoutes) { // 后端控制路由:路由数据初始化,防止刷新时丢失 + isRoutesInitializing = true; await initBackEndControlRoutes(); + isRoutesInitializing = false; // 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx' // to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理 next({ path: to.path, query: to.query }); } else { // https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP + isRoutesInitializing = true; await initFrontEndControlRoutes(); + isRoutesInitializing = false; next({ path: to.path, query: to.query }); } } else { diff --git a/web/src/router/route.ts b/web/src/router/route.ts index 24b20a2..eed6735 100644 --- a/web/src/router/route.ts +++ b/web/src/router/route.ts @@ -32,6 +32,14 @@ export const dynamicRoutes: Array = [ }, children: [], }, + { + path: '/home', + name: 'home', + redirect: '/dashboard', + meta: { + isKeepAlive: false, + }, + }, { path: '/personal', name: 'personal', diff --git a/web/src/stores/modules/dashboard.ts b/web/src/stores/modules/dashboard.ts index 0be5a99..d430923 100644 --- a/web/src/stores/modules/dashboard.ts +++ b/web/src/stores/modules/dashboard.ts @@ -61,8 +61,9 @@ export const useDashboardStore = defineStore('dashboard', { this.loading = true; try { const res: any = await getDashboard(); - this.buyer = res.data?.buyer || null; - this.supplier = res.data?.supplier || null; + // 后端直接返回 {buyer, supplier} 结构 + this.buyer = res?.buyer || null; + this.supplier = res?.supplier || null; } finally { this.loading = false; } diff --git a/web/src/types/mitt.d.ts b/web/src/types/mitt.d.ts index b68b80d..f5ad068 100644 --- a/web/src/types/mitt.d.ts +++ b/web/src/types/mitt.d.ts @@ -23,6 +23,7 @@ declare type MittType = { openShareTagsView?: string; onTagsViewRefreshRouterView?: T; onCurrentContextmenuClick?: T; + onUpdateTagsViewName?: T; }; // mitt 参数类型定义 diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index 08e6184..badca30 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -20,7 +20,7 @@ service.interceptors.request.use( (config: AxiosRequestConfig) => { // 在发送请求之前做些什么 token if (Session.get('token')) { - config.headers!['Authorization'] = `${Session.get('token')}`; + config.headers!['Authorization'] = `JWT ${Session.get('token')}`; } return config; }, @@ -44,7 +44,7 @@ service.interceptors.response.use( .then(() => {}) .catch(() => {}); } - return Promise.reject(service.interceptors.response); + return Promise.reject(error); } else { return response.data; } diff --git a/web/src/utils/storage.ts b/web/src/utils/storage.ts index 4aad867..3f3a8ab 100644 --- a/web/src/utils/storage.ts +++ b/web/src/utils/storage.ts @@ -37,23 +37,26 @@ export const Local = { export const Session = { // 设置临时缓存 set(key: string, val: any) { - if (key === 'token') return Cookies.set(key, val); + if (key === 'token') { + // 直接使用 sessionStorage 存储 token,避免 Cookies 的问题 + window.sessionStorage.setItem(key, val); + return; + } window.sessionStorage.setItem(key, JSON.stringify(val)); }, // 获取临时缓存 get(key: string) { - if (key === 'token') return Cookies.get(key); + if (key === 'token') return window.sessionStorage.getItem(key); let json = window.sessionStorage.getItem(key); return JSON.parse(json); }, // 移除临时缓存 remove(key: string) { - if (key === 'token') return Cookies.remove(key); + if (key === 'token') return window.sessionStorage.removeItem(key); window.sessionStorage.removeItem(key); }, // 移除全部临时缓存 clear() { - Cookies.remove('token'); window.sessionStorage.clear(); }, }; diff --git a/web/src/views/pisadmin/dashboard/BuyerDashboard.vue b/web/src/views/pisadmin/dashboard/BuyerDashboard.vue index 4888d69..9d2fc21 100644 --- a/web/src/views/pisadmin/dashboard/BuyerDashboard.vue +++ b/web/src/views/pisadmin/dashboard/BuyerDashboard.vue @@ -1,92 +1,136 @@ diff --git a/web/src/views/pisadmin/dashboard/SupplierDashboard.vue b/web/src/views/pisadmin/dashboard/SupplierDashboard.vue index abbc0fd..0f8517f 100644 --- a/web/src/views/pisadmin/dashboard/SupplierDashboard.vue +++ b/web/src/views/pisadmin/dashboard/SupplierDashboard.vue @@ -1,92 +1,133 @@ diff --git a/web/src/views/pisadmin/dashboard/index.vue b/web/src/views/pisadmin/dashboard/index.vue index f9f183e..d8b8a64 100644 --- a/web/src/views/pisadmin/dashboard/index.vue +++ b/web/src/views/pisadmin/dashboard/index.vue @@ -1,26 +1,27 @@ @@ -37,8 +83,9 @@ store.fetchDashboard(); .dashboard-index { padding: 20px; .role-select { + margin-bottom: 20px; .el-radio-group { - margin-bottom: 20px; + margin-bottom: 0; } } } diff --git a/web/src/views/system/login/component/account.vue b/web/src/views/system/login/component/account.vue index f6d224b..03701ac 100644 --- a/web/src/views/system/login/component/account.vue +++ b/web/src/views/system/login/component/account.vue @@ -144,66 +144,58 @@ export default defineComponent({ }; const loginClick = async () => { if (!formRef.value) return - await formRef.value.validate((valid: any) => { + await formRef.value.validate(async (valid: any) => { if (valid) { - loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }).then((res: any) => { + try { + const res = await loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }); if (res.code === 2000) { - const {data} = res - Cookies.set('username', res.data.username); - Session.set('token', res.data.access); - useUserInfo().setPwdChangeCount(data.pwd_change_count) - if(data.pwd_change_count==0){ - return router.push('/login'); - } + const { data } = res; + console.log('[DEBUG loginClick] res.data.access:', res.data.access); + Cookies.set('username', res.data.username); + Session.set('token', res.data.access); + console.log('[DEBUG loginClick] after Session.set, token:', Session.get('token')); + useUserInfo().setPwdChangeCount(data.pwd_change_count); if (!themeConfig.value.isRequestRoutes) { - // 前端控制路由,2、请注意执行顺序 initFrontEndControlRoutes(); - loginSuccess(); + loginSuccess(data.pwd_change_count); } else { - // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由 - // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/" - initBackEndControlRoutes(); - // 执行完 initBackEndControlRoutes,再执行 signInSuccess - loginSuccess(); + console.log('[DEBUG loginClick] calling initBackEndControlRoutes'); + await initBackEndControlRoutes(); + console.log('[DEBUG loginClick] after initBackEndControlRoutes'); + loginSuccess(data.pwd_change_count); } } - }).catch((err: any) => { - // 登录错误之后,刷新验证码 + } catch (err: any) { refreshCaptcha(); - }); + } } else { - errorMessage("请填写登录信息") + errorMessage("请填写登录信息"); } - }) - + }); }; // 登录成功后的跳转 - const loginSuccess = () => { + const loginSuccess = (pwd_change_count: number) => { //获取所有字典 DictionaryStore().getSystemDictionarys(); // 初始化登录成功时间问候语 let currentTimeInfo = currentTime.value; - // 登录成功,跳到转首页 - const pwd_change_count = userInfos.value.pwd_change_count ?? 0 - if(pwd_change_count > 0){ - // 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中 - if (route.query?.redirect) { - router.push({ - path: route.query?.redirect, - query: Object.keys(route.query?.params).length > 0 ? JSON.parse(route.query?.params) : '', - }); - } else { - router.push('/'); - } - // 登录成功提示 - // 关闭 loading - state.loading.signIn = true; - const signInText = t('message.signInText'); - ElMessage.success(`${currentTimeInfo},${signInText}`); - } + // 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中 + if (route.query?.redirect) { + router.push({ + path: route.query?.redirect, + query: Object.keys(route.query?.params).length > 0 ? JSON.parse(route.query?.params) : '', + }); + } else { + router.push('/'); + } + // 登录成功提示 + // 关闭 loading + state.loading.signIn = true; + const signInText = t('message.signInText'); + ElMessage.success(`${currentTimeInfo},${signInText}`); // 添加 loading,防止第一次进入界面时出现短暂空白 NextLoading.start(); }; From 32c068895180bdd488de92e5e83d70e3435a8712 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:22:02 +0800 Subject: [PATCH 062/111] chore: add .planning and docs/superpowers to .gitignore Remove tracked planning files from git index. --- .gitignore | 9 +- .planning/codebase/ARCHITECTURE.md | 208 ---- .planning/codebase/CONCERNS.md | 248 ---- .planning/codebase/CONVENTIONS.md | 316 ----- .planning/codebase/INTEGRATIONS.md | 295 ----- .planning/codebase/STACK.md | 227 ---- .planning/codebase/STRUCTURE.md | 247 ---- .planning/codebase/TESTING.md | 238 ---- .../2026-04-01-dashboard-redesign-plan.md | 1085 ----------------- .../2026-04-01-dashboard-redesign-design.md | 267 ---- 10 files changed, 8 insertions(+), 3132 deletions(-) delete mode 100644 .planning/codebase/ARCHITECTURE.md delete mode 100644 .planning/codebase/CONCERNS.md delete mode 100644 .planning/codebase/CONVENTIONS.md delete mode 100644 .planning/codebase/INTEGRATIONS.md delete mode 100644 .planning/codebase/STACK.md delete mode 100644 .planning/codebase/STRUCTURE.md delete mode 100644 .planning/codebase/TESTING.md delete mode 100644 docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md delete mode 100644 docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md diff --git a/.gitignore b/.gitignore index 12bc2b3..d5e0305 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,11 @@ .vscode/ web/package-lock.json -*.bat \ No newline at end of file +*.bat + +# GSD planning files +.planning/ + +# Superpowers files +docs/superpowers/ +.pids/ \ No newline at end of file diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md deleted file mode 100644 index 52e8bf6..0000000 --- a/.planning/codebase/ARCHITECTURE.md +++ /dev/null @@ -1,208 +0,0 @@ -# Architecture - -**Analysis Date:** 2026-04-01 - -## Pattern Overview - -**Overall:** Django REST Framework backend with Vue 3 SPA frontend, built on django-vue3-admin base - -**Key Characteristics:** -- Backend uses Django 4.2 with DRF for API-first design -- Frontend is Vue 3 + TypeScript + Vite SPA -- JWT authentication via SimpleJWT -- Real-time support via Django Channels (WebSocket) -- Backend-controlled routing (menu/permissions served via API) -- Custom apps extend dvadmin base RBAC system - -## Layers - -**Django Application Layer:** -- Purpose: Main Django project configuration and URL routing -- Location: `backend/application/` -- Contains: `settings.py` (imports config from `conf/env.py`), `urls.py`, `asgi.py`, `wsgi.py`, `celery.py` -- Depends on: All Django apps -- Used by: uvicorn/ASGI server - -**Custom Business Apps Layer (pisadmin, pissupplier):** -- Purpose: Procurement and supplier quotation business logic -- Location: `backend/apps/pisadmin/` and `backend/apps/pissupplier/` -- Contains: Models, Views (ViewSets), Serializers, URLs for procurement RFQ, cost templates, suppliers -- Depends on: dvadmin utils, Django REST Framework -- Used by: Frontend SPA via REST API - -**dvadmin Core Layer:** -- Purpose: Base RBAC system - users, roles, menus, departments, permissions -- Location: `backend/dvadmin/system/` -- Contains: Auth models (Users, Roles, Menus), views, serializers -- Depends on: Django contrib apps -- Used by: Custom apps, frontend - -**dvadmin Utils Layer:** -- Purpose: Reusable utilities - CustomModelViewSet, filters, permissions, pagination, exception handling -- Location: `backend/dvadmin/utils/` -- Contains: `viewset.py` (CustomModelViewSet base), `filters.py`, `permission.py`, `pagination.py`, `serializers.py` -- Depends on: Django REST Framework -- Used by: All ViewSets across apps - -**Frontend SPA Layer:** -- Purpose: Vue 3 single-page application for UI -- Location: `web/src/` -- Contains: Components, views, stores (Pinia), router, API clients -- Depends on: Vue 3, Element Plus, FastCRUD, Pinia, Vue Router - -## Data Flow - -**Frontend-to-Backend API Flow:** - -``` -Vue Component (Page) - -> Pinia Store (State Management) - -> API Client (src/api/*/index.ts) - -> HTTP Request (Axios wrapper) - -> Django REST Framework ViewSet - -> Django Serializer (Validation) - -> Django Model (Database) - -> Database Response - -> Serializer Response - -> JSON API Response - -> Pinia Store Update - -> Vue Component Re-render -``` - -**Authentication Flow:** - -``` -Login Page -> POST /api/login/ -> JWT Token Response - -> Token stored in Session storage - -> All subsequent requests include JWT header - -> Backend validates JWT, attaches user to request - -> Permission classes check role/menu permissions -``` - -**Menu/Routing Flow (Backend-Controlled):** - -``` -User Login -> GET /api/system/get_menu/ - -> Returns user-specific menu tree from Menu model - -> Frontend builds routes dynamically - -> Cached in frontendMenu store -``` - -**WebSocket Flow:** - -``` -Frontend connects to /ws// - -> Django Channels AuthMiddlewareStack validates JWT - -> URLRouter routes to MegCenter consumer - -> Consumer handles real-time messaging -``` - -## Key Design Patterns - -**CustomModelViewSet Pattern:** -All custom ViewSets inherit from `dvadmin.utils.viewset.CustomModelViewSet` which extends DRF's `ModelViewSet`. This provides: -- Standardized JSON responses (`SuccessResponse`, `DetailResponse`, `ErrorResponse`) -- Automatic filtering with `DataLevelPermissionMargeFilter` (data scope permissions) -- Field-level permissions via `FieldPermission` model -- Import/export mixins -- Custom serializer selection per action (`create_serializer_class`, `update_serializer_class`) - -**CoreModel Pattern:** -All models inherit from `dvadmin.utils.models.CoreModel` which provides: -- Audit fields: `creator`, `modifier`, `dept_belong_id`, `create_datetime`, `update_datetime` -- Auto-setting creator/modifier on create/update -- `SoftDeleteModel` support for soft deletes - -**Status Workflow Pattern (Inquiry/RFQ):** -The `Inquiry` model uses a status field with workflow transitions: -``` -1(开立) -> 2(确认) -> 3(发布) -> 4(报价中) -> 5(报价结束) - -> 6(比议价中) -> 7(价格审核) -> 8(核价通过) -9(落标结束) or 0(作废) -``` -Each transition is handled by a dedicated `@action` method on the ViewSet. - -**Template-Generated Quotation Pattern:** -- `CostEstimateTemplateHead` + `CostEstimateTemplateBody` define a cost structure template -- When Inquiry is published, `_create_supplier_quotations` iterates the template to generate `QuotationMaster` + child tables for each supplier -- Template fields with `is_computed=1` or `supplier_required in (1,2)` are prefilled in quotations - -## Module Responsibilities - -**pisadmin.miscprocurement:** -- `Inquiry`: RFQ (Request for Quotation) master with workflow states -- `InquirySupplier`: Supplier invite list per RFQ -- `InquiryMaterialCost`, `InquiryProcessCost`, `InquiryOtherCost`, `InquiryProfitCost`: Cost breakdown per RFQ line item -- `InquiryRfqItem`: Upper-level material items for the RFQ -- `CostEstimateTemplateHead/Body`: Cost template structure definition -- `MiscLowPriceHeader/Detail`: Comparison price records (lowest price tracking) -- `MiscNegotiationRecords`: Bargaining records -- `RFQOperationLogs`: Audit trail for RFQ operations - -**pisadmin.basicinfo:** -- `Company`: Company/factory information -- `Currency`: Currency and tax rate configuration -- `Supplier`: Supplier master data -- `SupplierUser`: Supplier user accounts -- `Unit`: Measurement units -- `SystemNoRule`: Document numbering rules (e.g., RFQ numbers, quotation numbers) -- `EmailNotice`: Email notification queue - -**pissupplier:** -- `QuotationMaster`: Supplier quotation header -- `QuotationMaterial`, `QuotationProcess`, `QuotationOther`, `QuotationProfit`: Quotation cost lines -- `QuotationItem`: Upper-level product items in quotation -- `QuotationAttachment`: Quotation attachments - -**dvadmin.system:** -- `Users`: User accounts with role/dept associations -- `Role`: Role definitions with permission keys -- `Menu`: Menu tree for UI and permission routing -- `MenuButton`: Button-level permissions on menus -- `MenuField`: Field-level permissions per model -- `Dept`: Department hierarchy -- `FieldPermission`: Role-field permission matrix -- `RoleMenuPermission`, `RoleMenuButtonPermission`: Role-menu assignments -- `OperationLog`, `LoginLog`: Audit logging -- `SystemConfig`: System configuration key-value store -- `Dictionary`: Dropdown value definitions -- `FileList`: File upload management -- `ApiWhiteList`: Public API endpoints bypassing auth - -## Error Handling - -**Backend Exception Handling:** -- Custom exception handler: `dvadmin.utils.exception.CustomExceptionHandler` -- DRF exceptions caught and formatted to standard JSON response -- Validation errors return 400 with field-specific messages - -**Frontend Error Handling:** -- Axios interceptor catches HTTP errors -- 401 redirects to login -- 403 shows permission denied -- 500 shows generic error message -- API errors display returned `msg` field - -## Cross-Cutting Concerns - -**Authentication:** SimpleJWT with 24-hour access tokens, 1-day refresh tokens. `CustomBackend` allows username OR email login. - -**Authorization:** -- Menu-level via `Menu` model and role assignments -- Button-level via `MenuButton` and `RoleMenuButtonPermission` -- Field-level via `MenuField` and `FieldPermission` -- Data scope via `DataLevelPermissionMargeFilter` (dept-based filtering) - -**Logging:** -- API access logged via `ApiLoggingMiddleware` -- Operation logs stored in `OperationLog` model -- RFQ operations logged to `RFQOperationLogs` - -**Validation:** -- Serializer-level validation in DRF -- Custom validators in `dvadmin.utils.validator` -- Business logic validation in ViewSet methods (e.g., workflow state checks) - ---- - -*Architecture analysis: 2026-04-01* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md deleted file mode 100644 index f81ab46..0000000 --- a/.planning/codebase/CONCERNS.md +++ /dev/null @@ -1,248 +0,0 @@ -# Codebase Concerns - -**Analysis Date:** 2026-04-01 - -## Security Concerns - -### Hardcoded Django SECRET_KEY - -**Severity:** Critical - -- **File:** `backend/application/settings.py:31` -- **Issue:** Django SECRET_KEY is hardcoded in source code: `"django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm"` -- **Impact:** If this code is committed to version control or accessible externally, attackers can forge session cookies, CSRF tokens, and other signed data. -- **Recommendation:** Move SECRET_KEY to `conf/env.py` and load from environment variable. Generate a new key for production. - -### DEBUG Mode Enabled in Production Configuration - -**Severity:** High - -- **File:** `backend/conf/env.py:39` -- **Issue:** `DEBUG = True` is set in the actual environment configuration file -- **Impact:** This enables Swagger UI, detailed error pages, and sets `permission_classes = [permissions.AllowAny]` on schema views (`backend/application/urls.py:45`). It also bypasses authentication requirements for API documentation. -- **Recommendation:** Set `DEBUG = False` in `conf/env.py` for production deployments. The code already has conditional logic to handle this. - -### ALLOWED_HOSTS = ["*"] - -**Severity:** High - -- **Files:** `backend/conf/env.py:48`, `backend/conf/env.example.py:48`, `backend/application/settings.py:44` -- **Issue:** Backend accepts requests from any host -- **Impact:** Allows host header attacks, including cache poisoning and CSS injection. -- **Recommendation:** Specify explicit hosts in `ALLOWED_HOSTS` for production. - -### CORS Allows All Origins - -**Severity:** High - -- **File:** `backend/application/settings.py:179-181` -- **Issue:** - ```python - CORS_ORIGIN_ALLOW_ALL = True - CORS_ALLOW_CREDENTIALS = True - ``` -- **Impact:** Combined with `AllowAny` permission classes, any website can make authenticated requests to the API, potentially leaking user data. -- **Recommendation:** Configure `CORS_ALLOWED_ORIGINS` with explicit allowed origins. - -### Weak Password Hashing (MD5 Fallback) - -**Severity:** Critical - -- **Files:** - - `backend/dvadmin/utils/backends.py:30-33` - - `backend/dvadmin/system/models.py:91` - - `backend/dvadmin/system/views/login.py:275` - -- **Issue:** Authentication backend falls back to MD5 hashing when PBKDF2 verification fails: - ```python - verify_password = check_password(password, user.password) - if not verify_password: - password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest() - verify_password = check_password(password, user.password) - ``` - The `set_password` method also uses MD5: - ```python - def set_password(self, raw_password): - if raw_password: - super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest()) - ``` - -- **Impact:** MD5 is cryptographically broken and provides no protection against rainbow table attacks. User passwords in the database are effectively stored in plaintext. -- **Recommendation:** Remove MD5 fallback entirely. Migrate existing MD5-stored passwords to proper bcrypt/PBKDF2 hashing on next login. - -### API Endpoint Allows Unauthenticated Access in DEBUG - -**Severity:** Medium - -- **File:** `backend/application/urls.py:45` -- **Issue:** Swagger schema view permission depends on DEBUG flag: - ```python - permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ] - ``` -- **Impact:** Since DEBUG is True, API documentation is publicly accessible, potentially revealing endpoint structure. -- **Recommendation:** Disable DEBUG or ensure permission classes are properly set regardless of DEBUG. - -### Exception Handler Exposes Stack Traces - -**Severity:** Medium - -- **File:** `backend/dvadmin/utils/exception.py:113` -- **Issue:** - ```python - elif isinstance(ex, Exception): - logger.exception(traceback.format_exc()) - msg = str(ex) - ``` -- **Impact:** In DEBUG mode, raw exception messages can reveal internal implementation details. -- **Recommendation:** Return generic error messages in production. Current implementation logs but still exposes `str(ex)`. - -## Code Quality Concerns - -### Debug Print Statements in Production Code - -**Severity:** Low - -- **Files:** Multiple files contain print statements: - - `backend/application/dispatch.py:87, 110` - "请先进行数据库迁移!" - - `backend/util/currency.py:41, 57, 73` - Raw SQL execution with no logging - - `backend/apps/pisadmin/miscprocurement/cost_template_builder.py:317-345` - Extensive debug output with Unicode symbols - - `backend/dvadmin/utils/core_initialize.py:36, 56, 60, 86` - Initialization status prints - - `backend/dvadmin/system/tests.py:21, 44, 51, 53` - Test debugging output - -- **Impact:** Print statements can interfere with JSON API responses if any are output during request handling. They also expose internal logic in logs. -- **Recommendation:** Replace print statements with proper logging calls using the configured logger. - -### TODO Comments Found in Code - -**Severity:** Low - -- **Files:** - - `backend/dvadmin/utils/filters.py:127` - "TODO Rename this here and in `filter_queryset`" - - `backend/dvadmin/utils/filters.py:322, 329` - "TODO: remove assertion in 2.1" - -- **Impact:** Incomplete refactoring work can lead to confusion or breaking changes if assumptions change. -- **Recommendation:** Address TODOs before major version releases or refactoring phases. - -### Bare Except Clauses - -**Severity:** Medium - -- **File:** `backend/application/websocketConfig.py:90` -- **Issue:** - ```python - except Exception: - pass - ``` -- **Impact:** Silently ignores all exceptions, making debugging impossible and masking failures. -- **Recommendation:** Log exceptions or handle them with specific exception types. - -## Performance & Scalability Concerns - -### In-Memory Channel Layer for WebSocket - -**Severity:** High - -- **File:** `backend/application/settings.py:187-190` -- **Issue:** - ```python - CHANNEL_LAYERS = { - "default": { - "BACKEND": "channels.layers.InMemoryChannelLayer" - } - } - ``` -- **Impact:** WebSocket connections work only within a single worker process. With multiple uvicorn workers (configured as 8 in CLAUDE.md), WebSocket functionality will fail unpredictably across workers. -- **Recommendation:** Use Redis channel layer for production: - ```python - CHANNEL_LAYERS = { - 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', - 'CONFIG': { - "hosts": [('127.0.0.1', 6379)], - }, - }, - } - ``` - Note: The commented-out configuration exists at lines 192-199 but is not active. - -### Celery Running as Root - -**Severity:** Medium - -- **File:** `backend/application/celery.py:22` -- **Issue:** `platforms.C_FORCE_ROOT = True` -- **Impact:** Security risk running Celery worker processes as root user. -- **Recommendation:** Configure proper user/group for Celery workers in deployment. - -## Dependency Risks - -### Potentially Outdated Dependencies - -**Severity:** Medium - -- **File:** `backend/requirements.txt` -- **Observations:** - - Django 4.2.14 (current latest is 4.2.x) - Acceptable but check for security patches - - SimpleJWT 5.4.0 (5.5.0 available) - Minor version behind - - DRF 3.15.2 (3.15.4 available) - Minor version behind - - Channels 4.1.0 (4.2.x available) - - PyInstaller 6.9.0 - Major build tool dependency - -- **Recommendation:** Run `pip list --outdated` to identify packages needing updates, particularly security patches. - -## Operational Concerns - -### Hardcoded Infrastructure IPs - -**Severity:** High - -- **File:** `backend/conf/env.py` -- **Issue:** - ```python - DATABASE_HOST = '192.168.80.29' - REDIS_HOST = '177.10.0.15' - REDIS_PASSWORD = 'redis_5pGXn2' - ``` - These are actual production IPs and credentials in the codebase. -- **Impact:** If committed to version control, infrastructure credentials are exposed. Also prevents portability across environments. -- **Recommendation:** Use environment variables for all infrastructure configuration. Never commit real credentials. - -### No Docker Compose for Infrastructure - -**Severity:** Medium - -- **Observation:** No `docker-compose.yml` found in project root (only `backend/Dockerfile` and `web/Dockerfile`) -- **Impact:** No documented way to spin up the full stack (Django, Redis, database) locally. Makes local development and testing harder. -- **Recommendation:** Create `docker-compose.yml` that orchestrates backend, Redis, and database containers. - -### Log Directory Runtime Creation - -**Severity:** Low - -- **File:** `backend/application/settings.py:209-210` -- **Issue:** - ```python - if not os.path.exists(os.path.join(BASE_DIR, "logs")): - os.makedirs(os.path.join(BASE_DIR, "logs")) - ``` -- **Impact:** If the logs directory cannot be created (permissions issue), the application will crash on startup. -- **Recommendation:** Ensure logs directory exists before deployment via CI/CD or container entrypoint. - -## Known Issues Summary - -| Area | Severity | Issue | Files | -|------|----------|-------|-------| -| Secrets | Critical | Hardcoded SECRET_KEY | `application/settings.py:31` | -| Auth | Critical | MD5 password hashing | `backends.py`, `models.py`, `login.py` | -| Config | High | DEBUG=True in prod env | `conf/env.py:39` | -| Config | High | ALLOWED_HOSTS=* | `conf/env.py:48` | -| CORS | High | Allow all origins | `settings.py:179-181` | -| WebSocket | High | In-memory channel layer | `settings.py:189` | -| Secrets | High | Hardcoded IPs/passwords | `conf/env.py` | -| Code Quality | Medium | Bare except clauses | `websocketConfig.py:90` | -| Code Quality | Low | Debug print statements | Multiple files | -| Dependencies | Medium | Outdated packages | `requirements.txt` | - ---- - -*Concerns audit: 2026-04-01* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md deleted file mode 100644 index 667c2e6..0000000 --- a/.planning/codebase/CONVENTIONS.md +++ /dev/null @@ -1,316 +0,0 @@ -# Coding Conventions - -**Analysis Date:** 2026-04-01 - -## Naming Patterns - -### Backend (Python/Django) - -**Files:** -- Python source files: snake_case (e.g., `email_utils.py`, `system_no_rule.py`) -- URL routing: `urls.py` -- Serializers: `serializers.py` -- Models: `models.py` -- Views: either single `views.py` or organized in `views/` directory with one file per resource - -**Classes:** -- Models: PascalCase (e.g., `Company`, `Supplier`, `SystemNoRule`) -- ViewSets: PascalCase ending with `ViewSet` (e.g., `CompanyViewSet`, `InquiryViewSet`) -- Serializers: PascalCase ending with `Serializer` (e.g., `CompanySerializer`, `InquirySerializer`) -- Custom serializers for create/update: `ModelNameCreateUpdateSerializer` - -**Functions/Methods:** -- snake_case (e.g., `allocate_system_number`, `get_request_username`) -- Private methods: prefixed with underscore (e.g., `_allocate_next_code_for_locked_rule`) - -**Database Fields:** -- snake_case (e.g., `company_code`, `supplier_name`, `create_datetime`) -- Primary key: `id` -- Foreign keys: `model_name_id` or just `field_name` (e.g., `quotation_no`) -- Timestamps: `create_datetime`, `update_datetime` -- User tracking: `createuser`, `updateuser` (char fields, not FK) - -**URL Patterns:** -- Router.register uses snake_case plural (e.g., `r'units'`, `r'companies'`, `r'inquiry'`) -- Full endpoint example: `/api/pisadmin/basicinfo/companies/` - -### Frontend (Vue 3/TypeScript) - -**Files:** -- Vue components: kebab-case in directory (e.g., `company/index.vue`) -- API modules: `api.ts` in same directory (e.g., `company/api.ts`) -- CRUD configuration: `.tsx` extension (e.g., `company/crud.tsx`) -- Stores: `userInfo.ts`, `dictionary.ts` -- Utilities: `service.ts`, `storage.ts`, `message.ts` - -**Components:** -- Vue components use ` - - -``` - -**FastCRUD Configuration Pattern:** -```typescript -export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { - return { - crudOptions: { - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => api.AddObj(form), - editRequest: async ({ form, row }) => api.UpdateObj({ ...form, id: row.id }), - delRequest: async ({ row }) => api.DelObj(row.id) - }, - columns: { - company_code: { - title: '公司代码', - type: 'input', - search: { show: true }, - form: { rules: [{ required: true, message: '请输入公司代码' }] } - } - } - } - } -} -``` - -**API Module Pattern:** -```typescript -// /@/utils/service exports `request` -import { request } from '/@/utils/service' - -const apiPrefix = '/api/pisadmin/basicinfo/companies/' - -export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) -export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) -``` - -**Pinia Store Pattern:** -```typescript -export const useUserInfo = defineStore('userInfo', { - state: (): UserInfosStates => ({ - userInfos: { /* initial state */ } - }), - actions: { - async setUserInfos() { /* action */ } - } -}) -``` - -## Linting and Formatting Configuration - -### Frontend (ESLint) - -**Config File:** `web/.eslintrc.js` - -**Parser:** `vue-eslint-parser` with `@typescript-eslint/parser` - -**Key Rules:** -- `@typescript-eslint/no-unused-vars`: `'off'` (disabled) -- `@typescript-eslint/no-explicit-any`: `'off'` (disabled) -- `no-console`: `'error'` (console.log is error, use ElMessage instead) -- Vue component rules mostly `'off'` for flexibility - -**Run Lint Fix:** -```bash -yarn lint-fix -``` - -### Frontend (Prettier) - -**Config File:** `web/.prettierrc.js` - -**Key Settings:** -```javascript -{ - printWidth: 150, - tabWidth: 2, - useTabs: false, - semi: true, - singleQuote: true, - trailingComma: 'es5', - bracketSpacing: true, - arrowParens: 'always', - endOfLine: 'lf' -} -``` - -### Backend (Python) - -No explicit Python linting configuration detected. Python code follows PEP 8 implicitly. - -## Error Handling - -### Backend - -- Use `try/except` blocks with specific exception handling -- Use `serializers.ValidationError` for API validation errors -- Use `ErrorResponse` from dvadmin for consistent error responses -- Log errors with `logger = logging.getLogger(__name__)` - -### Frontend - -- Use `ElMessage.error()` for user-facing error messages -- Use try/catch with async/await -- API errors handled in `service.ts` interceptors - -## Logging - -### Backend -```python -import logging -logger = logging.getLogger(__name__) - -logger.warning("message with %s", value) -logger.exception("error message") # includes traceback -``` - -### Frontend -- `console.log` allowed for debugging (stripped in production build) -- Production uses `ElMessage` for user feedback -- No structured logging library detected - -## Comments and Documentation - -### Backend -- Chinese docstrings for business logic (e.g., `"""公司信息-序列化器"""`) -- Complex methods have inline comments explaining logic -- Use `# pragma: no cover` for untested branches - -### Frontend -- Component `name` attribute for debugging -- Chinese comments for complex business logic -- JSDoc style not strictly enforced - ---- - -*Convention analysis: 2026-04-01* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md deleted file mode 100644 index 76532b2..0000000 --- a/.planning/codebase/INTEGRATIONS.md +++ /dev/null @@ -1,295 +0,0 @@ -# External Integrations - -**Analysis Date:** 2026-04-01 - -## Database Connection - -**Primary Database:** -- **Engine:** Microsoft SQL Server (via mssql-django 1.7) -- **Connection Driver:** ODBC Driver 18 for SQL Server -- **Database Name:** `pisdb` -- **Host:** `192.168.80.29` -- **Port:** `1433` -- **Credentials:** Configured in `backend/conf/env.py` -- **Configuration File:** `backend/conf/env.py` lines 14-24 - -**Connection Settings:** -```python -DATABASE_ENGINE = "mssql" -DATABASE_NAME = 'pisdb' -DATABASE_HOST = '192.168.80.29' -DATABASE_PORT = 1433 -DATABASE_USER = "test" -DATABASE_PASSWORD = '123456' -OPTIONS = { - "driver": "ODBC Driver 18 for SQL Server", - "extra_params": "Encrypt=yes;TrustServerCertificate=yes" -} -``` - -**Alternative Database Support:** -- MySQL 8.0 (via mysqlclient 2.2.0) -- PostgreSQL (via psycopg2 2.9.9) - -**Table Prefix:** `pis_` (Procurement Inquiry System) - -**Django Settings:** `backend/application/settings.py` lines 105-119 - -## Redis Configuration - -**Connection:** -- **Host:** `177.10.0.15` -- **Port:** `6379` -- **Password:** `redis_5pGXn2` -- **URL Format:** `redis://:redis_5pGXn2@177.10.0.15:6379` - -**Usage in Application:** -- Redis DB 1: General caching (`REDIS_DB`) -- Redis DB 3: Celery broker (`CELERY_BROKER_DB`) - -**Configuration File:** `backend/conf/env.py` lines 31-35 - -**Cache Backend Configuration:** -```python -REDIS_DB = 1 -CELERY_BROKER_DB = 3 -REDIS_PASSWORD = 'redis_5pGXn2' -REDIS_HOST = '177.10.0.15' -REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' -``` - -## Celery Task Queue - -**Broker:** Redis (same server as cache) - -**Configuration Source:** `backend/application/celery.py` - -**Celery Setup:** -- Auto-discovers tasks from all installed apps -- Uses Django settings namespace for configuration (`CELERY_*`) -- Supports tenant-aware mode if `django_tenants` is installed -- Runs with `C_FORCE_ROOT = True` for root execution - -**Retry Strategy:** -- Default retry delay: 180 seconds -- Maximum retries: 3 -- Decorator: `@retry_base_task_error()` - -**Task Result Storage:** Django Celery Results (`django_celery_results`) - -**Celery Beat Configuration:** -- Timezone: `Asia/Shanghai` -- Result backend stores task results in database - -**Django Settings Import:** `from django.conf:settings, namespace='CELERY'` - -**Installed Apps Integration:** -```python -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) -``` - -## WebSocket / Django Channels - -**ASGI Application:** `backend/application/asgi.py` - -**Protocol Support:** -- HTTP (via Django) -- WebSocket (via Channels) - -**WebSocket URL Pattern:** -- Route: `ws//` -- Consumer: `MegCenter` (from `application.websocketConfig`) -- Routing: `backend/application/ws_routing.py` - -**Channel Layers:** -- Default (development): `InMemoryChannelLayer` -- Production: `channels_redis.core.RedisChannelLayer` (commented out) - -**Authentication:** -- WebSocket connections validated via JWT token in `service_uid` parameter -- Token decoded using Django `SECRET_KEY` -- User ID extracted from decoded token -- Channel group format: `user_{user_id}` - -**WebSocket Consumer Classes:** -- `DvadminWebSocket` - Base WebSocket consumer with JWT auth -- `MegCenter` - Message center consumer extending base class - -**Message Flow:** -1. Client connects to `ws://host/ws//` -2. Token validated, user_id extracted -3. User added to channel group `user_{user_id}` -4. Messages pushed to user's channel group -5. `websocket_push()` function sends messages to specific users - -**File:** `backend/application/websocketConfig.py` lines 57-114 - -## Authentication & Authorization - -**JWT Authentication:** -- **Package:** djangorestframework-simplejwt 5.4.0 -- **Header Type:** `JWT` -- **Access Token Lifetime:** 1440 minutes (24 hours) -- **Refresh Token Lifetime:** 1 day -- **Rotate Refresh Tokens:** Enabled - -**JWT Configuration (settings.py lines 321-329):** -```python -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440), - "REFRESH_TOKEN_LIFETIME": timedelta(days=1), - "AUTH_HEADER_TYPES": ("JWT",), - "ROTATE_REFRESH_TOKENS": True, -} -``` - -**Authentication Backends:** -- `rest_framework_simplejwt.authentication.JWTAuthentication` -- `rest_framework.authentication.SessionAuthentication` - -**Custom Backend:** `dvadmin.utils.backends.CustomBackend` - -**REST Framework Default Authentication (settings.py lines 304-307):** -```python -"DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework_simplejwt.authentication.JWTAuthentication", - "rest_framework.authentication.SessionAuthentication", -), -``` - -**Permission:** `rest_framework.permissions.IsAuthenticated` - -## Email Configuration - -**SMTP Configuration (backend/conf/env.py lines 53-68):** -```python -EMAIL_FROM = "eip@avc.co" -EMAIL_HOST_USER = "" -EMAIL_HOST_PASSWORD = "" -EMAIL_HOST = "mailflow.avc.co" -EMAIL_PORT = 25 -EMAIL_USE_SSL = False -EMAIL_USE_TLS = False -``` - -**Email Features:** -- Plain text connection (no SSL/TLS) -- System emails sent from `eip@avc.co` - -## Cloud Storage Integrations - -**Alibaba Cloud OSS:** -- Package: `oss2 2.19.1` -- Used for file storage - -**Tencent Cloud COS:** -- Package: `cos-python-sdk-v5 1.9.37` -- Used for file storage - -## API Documentation - -**Swagger/OpenAPI:** -- Package: `drf-yasg 1.21.7` -- Login URL: `/apiLogin/` -- Logout URL: `/rest_framework:logout/` -- Auto schema generation with custom schema class -- JSON editor enabled in Swagger UI - -**Configuration (settings.py lines 334-354):** -```python -SWAGGER_SETTINGS = { - "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}}, - "LOGIN_URL": "apiLogin/", - "LOGOUT_URL": "rest_framework:logout", - "JSON_EDITOR": True, - "DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema", -} -``` - -## CORS Configuration - -**CORS Settings (settings.py lines 177-181):** -```python -CORS_ORIGIN_ALLOW_ALL = True -CORS_ALLOW_CREDENTIALS = True -``` - -**CORS Middleware:** `corsheaders.middleware.CorsMiddleware` - -## Static Files & Media - -**Static Files:** -- URL: `/static/` -- Storage: Whitenoise with compression -- Storage Class: `whitenoise.storage.CompressedStaticFilesStorage` - -**Media Files:** -- URL: `/media/` -- Root: `media` directory in project - -**Configuration (settings.py lines 156-169):** -```python -STATIC_URL = "/static/" -STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] -MEDIA_ROOT = "media" -MEDIA_URL = "/media/" -STATICFILES_FINDERS = ( - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder" -) -``` - -## Proxy Configuration (Development) - -**Vite Dev Server Proxy (vite.config.ts lines 37-44):** -```typescript -proxy: { - '/gitee': { - target: 'https://gitee.com', - ws: true, - changeOrigin: true, - rewrite: (path) => path.replace(/^\/gitee/, ''), - }, -} -``` - -## Environment Variables - -**Configuration Source:** `backend/conf/env.py` - -**Key Environment Variables:** -| Variable | Purpose | -|----------|---------| -| `DATABASE_ENGINE` | Database backend type | -| `DATABASE_HOST` | Database server host | -| `DATABASE_PORT` | Database server port | -| `DATABASE_USER` | Database username | -| `DATABASE_PASSWORD` | Database password | -| `REDIS_HOST` | Redis server host | -| `REDIS_PASSWORD` | Redis password | -| `DEBUG` | Debug mode flag | -| `EMAIL_HOST` | SMTP server | -| `EMAIL_PORT` | SMTP port | - -**Note:** Actual credentials are stored in `backend/conf/env.py` (not committed to version control per best practices). - -## Key Dependencies Between Systems - -``` -Frontend (Vue 3) - | - | HTTP/REST + WebSocket - v -Backend (Django + DRF) - | - +---> Database (SQL Server/MySQL/PostgreSQL) - +---> Cache (Redis) - +---> Task Queue (Celery + Redis) - +---> WebSocket (Channels + Redis) - +---> Cloud Storage (OSS/COS) - +---> Email (SMTP) -``` - ---- - -*Integration audit: 2026-04-01* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md deleted file mode 100644 index 60a2721..0000000 --- a/.planning/codebase/STACK.md +++ /dev/null @@ -1,227 +0,0 @@ -# Technology Stack - -**Analysis Date:** 2026-04-01 - -## Languages - -**Primary:** -- Python 3.x - Backend development -- TypeScript 4.9.4 - Frontend development -- JavaScript (ESNext) - Frontend runtime - -**Secondary:** -- Vue 3.4.38 - Frontend UI framework -- HTML/CSS - Frontend markup and styling - -## Backend Framework - -**Django Core:** -- Django 4.2.14 - Web framework -- Django REST Framework 3.15.2 - REST API development -- channels 4.1.0 - WebSocket support -- Celery 5.x (via dvadmin3-celery 3.1.6) - Async task queue - -**Authentication:** -- djangorestframework-simplejwt 5.4.0 - JWT token authentication -- django-simple-captcha 0.6.0 - CAPTCHA verification - -**Database:** -- mssql-django 1.7 - SQL Server driver (configured for pisdb) -- mysqlclient 2.2.0 - MySQL driver (also available) -- psycopg2 2.9.9 - PostgreSQL driver - -**Key Django Plugins:** -- django-cors-headers 4.4.0 - Cross-origin resource sharing -- django-filter 24.2 - Database query filtering -- django-ranged-response 0.2.0 - HTTP range response support -- django-restql 0.15.4 - GraphQL-like query language for REST -- drf-yasg 1.21.7 - Swagger/OpenAPI documentation -- django-timezone-field 7.0 - Timezone support -- django-comment-migrate 0.1.7 - Comment migration utility -- Pillow 10.4.0 - Image processing -- openpyxl 3.1.5 - Excel file handling -- requests 2.32.4 - HTTP client -- pypinyin 0.51.0 - Chinese pinyin conversion -- ua-parser 0.18.0 - User agent parsing -- user-agents 2.2.0 - User agent detection -- six 1.16.0 - Python 2/3 compatibility -- whitenoise 6.7.0 - Static file serving -- pyparsing 3.1.2 - Parsing expression grammar -- typing-extensions 4.12.2 - Type hint extensions -- tzlocal 5.2 - Timezone localization - -**Server:** -- uvicorn 0.30.3 - ASGI server -- gunicorn 23.0.0 - WSGI server -- gevent 24.2.1 - Async networking library - -**Cloud Storage:** -- oss2 2.19.1 - Alibaba Cloud OSS integration -- cos-python-sdk-v5 1.9.37 - Tencent Cloud COS integration - -## Frontend Framework - -**Core:** -- Vue 3.4.38 - Progressive JavaScript framework -- TypeScript 4.9.4 - Typed superset of JavaScript -- Vite 5.4.1 - Next-generation frontend build tool -- Pinia 2.0.28 - State management -- Vue Router 4.4.3 - SPA routing -- Vue I18n 9.14.0 - Internationalization - -**UI Component Libraries:** -- Element Plus 2.8.0 - UI component library -- @element-plus/icons-vue 2.3.1 - Element Plus icons -- Vant 4.9.19 - Mobile UI component library -- vant4-kit 1.0.3 - Vant utility kit - -**FastCRUD:** -- @fast-crud/fast-crud 1.21.2 - Quick CRUD development -- @fast-crud/fast-extends 1.21.2 - FastCRUD extensions -- @fast-crud/ui-element 1.21.2 - Element Plus UI for FastCRUD -- @fast-crud/ui-interface 1.21.2 - FastCRUD UI interface - -**Data Visualization:** -- echarts 5.5.1 - Interactive charting library -- echarts-gl 2.0.9 - ECharts 3D visualization -- echarts-wordcloud 2.1.0 - Word cloud for ECharts -- vxe-table 4.6.18 - Grid/table component -- xe-utils 3.5.30 - Utility functions for vxe-table - -**Build & Development:** -- @vitejs/plugin-vue 5.1.2 - Vue plugin for Vite -- @vitejs/plugin-vue-jsx 4.0.1 - Vue JSX support -- vite-plugin-vue-setup-extend 0.4.0 - Vue setup syntax extension -- sass 1.56.2 - CSS preprocessor -- less 4.3.0 - CSS preprocessor -- autoprefixer 10.4.20 - CSS vendor prefixing -- postcss 8.4.21 - CSS transformation tool -- tailwindcss 3.2.7 - Utility-first CSS framework -- rollup 4.60.1 - Module bundler - -**Utilities:** -- axios 1.7.4 - HTTP client -- qs 6.11.0 - Query string parsing -- lodash-es 4.17.21 - Utility library -- js-cookie 3.0.5 - Cookie handling -- mitt 3.0.1 - Tiny event emitter -- nprogress 0.2.0 - Progress bar -- sortablejs 1.15.0 - Drag and drop sorting -- vue-draggable-plus 0.6.0 - Vue draggable component -- screenfull 6.0.2 - Fullscreen API wrapper -- print-js 1.6.0 - Print functionality -- qrcodejs2-fixes 0.0.2 - QR code generation -- cropperjs 1.6.2 - Image cropping -- vue-cropper 1.0.8 - Vue image cropper -- countup.js 2.8.0 - Animated number counting -- e-icon-picker 2.1.1 - Icon picker -- element-tree-line 0.2.1 - Tree line for Element -- font-awesome 4.7.0 - Icon library -- @iconify/vue 4.1.2 - Icon library -- vue-clipboard3 2.0.0 - Clipboard API wrapper -- ts-md5 1.3.1 - MD5 hashing -- vue-grid-layout 3.0.0-beta1 - Grid layout -- vue-qr 4.0.9 - QR code for Vue -- jsplumb 2.15.6 - Visual connectivity -- @wangeditor/editor 5.1.23 - Rich text editor -- @wangeditor/editor-for-vue 5.1.12 - Vue wrapper for WangEditor -- js-table2excel 1.1.2 - Table to Excel export -- date-holidays 3.24.1 - Holiday data -- lunar-javascript 1.7.1 - Chinese lunar calendar -- upgrade 1.1.0 - Upgrade utility -- @great-dream/dvadmin3-celery-web 3.1.3 - Celery web UI - -**Development Tools:** -- eslint 8.57.1 - Linting -- eslint-plugin-vue 9.27.0 - Vue ESLint plugin -- @typescript-eslint/parser 5.46.0 - TypeScript ESLint parser -- @typescript-eslint/eslint-plugin 5.46.0 - TypeScript ESLint plugin -- prettier 2.8.1 - Code formatting -- vue-eslint-parser 9.4.3 - Vue template parser -- @types/node 18.19.42 - Node.js type definitions -- @types/nprogress 0.2.3 - NProgress type definitions -- @types/sortablejs 1.15.8 - SortableJS type definitions -- @types/lodash 4.17.7 - Lodash type definitions -- baseline-browser-mapping 2.9.19 - Browser capability mapping - -## Infrastructure - -**Database:** -- MySQL 8.0 - Primary relational database (configured) -- SQL Server (via ODBC Driver 18) - Alternative database option - -**Cache & Queue:** -- Redis - Caching, session storage, Celery broker -- Celery - Async task processing - -**Container:** -- Docker - Containerization -- Docker Compose - Multi-container orchestration - -## Configuration Files - -**Backend:** -- `backend/conf/env.py` - Environment configuration (DB, Redis, Email) -- `backend/application/settings.py` - Django settings (imports from conf/env.py) -- `backend/application/celery.py` - Celery configuration -- `backend/application/asgi.py` - ASGI configuration (WebSocket support) -- `backend/application/ws_routing.py` - WebSocket URL routing -- `backend/application/websocketConfig.py` - WebSocket consumer implementation - -**Frontend:** -- `web/package.json` - Node.js dependencies and scripts -- `web/vite.config.ts` - Vite build configuration -- `web/tsconfig.json` - TypeScript compiler options - -## Project Structure - -``` -pis/ -├── backend/ -│ ├── application/ # Django project configuration -│ │ ├── settings.py # Main Django settings -│ │ ├── urls.py # Root URL configuration -│ │ ├── asgi.py # ASGI application (HTTP + WebSocket) -│ │ ├── celery.py # Celery task configuration -│ │ ├── ws_routing.py # WebSocket routing -│ │ └── websocketConfig.py # WebSocket consumers -│ ├── apps/ -│ │ ├── pisadmin/ # Custom procurement app -│ │ │ ├── basicinfo/ # Company, currency, supplier, unit -│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections -│ │ └── pissupplier/ # Supplier quotation module -│ ├── dvadmin/ # Core RBAC system (dvadmin3) -│ │ └── system/ # Users, roles, menus, departments -│ └── conf/ -│ └── env.py # Environment configuration -├── web/ # Vue 3 frontend -│ ├── src/ -│ │ ├── views/ # Page components -│ │ ├── api/ # API clients -│ │ ├── stores/ # Pinia stores -│ │ └── router/ # Vue Router config -│ ├── vite.config.ts # Vite configuration -│ └── tsconfig.json # TypeScript config -└── docker-compose.yml # Docker deployment -``` - -## Build Commands - -**Backend:** -```bash -python manage.py makemigrations # Create migrations -python manage.py migrate # Apply migrations -python manage.py runserver # Development server -uvicorn application.asgi:application --workers 8 # Production -``` - -**Frontend:** -```bash -yarn dev # Development server (port 8080) -yarn build # Production build -yarn lint-fix # Lint and auto-fix -``` - ---- - -*Stack analysis: 2026-04-01* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md deleted file mode 100644 index 8072c21..0000000 --- a/.planning/codebase/STRUCTURE.md +++ /dev/null @@ -1,247 +0,0 @@ -# Codebase Structure - -**Analysis Date:** 2026-04-01 - -## Directory Layout - -``` -pis/ -├── backend/ -│ ├── application/ # Django project config (settings, urls, asgi) -│ ├── apps/ -│ │ ├── pisadmin/ # Custom procurement app -│ │ │ ├── basicinfo/ # Company, currency, supplier, unit, system no -│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections -│ │ └── pissupplier/ # Supplier quotation module -│ ├── conf/ -│ │ └── env.py # Environment config (DB, Redis, Email) -│ ├── dvadmin/ # Base RBAC system -│ │ ├── system/ # Users, roles, menus, departments -│ │ └── utils/ # CoreModel, CustomViewSet, filters, etc. -│ ├── plugins/ # Plugin modules (celery, etc.) -│ ├── static/ # Static files -│ ├── templates/ # Django templates -│ └── util/ # Utility scripts -├── web/ # Vue 3 frontend -│ └── src/ -│ ├── api/ # API client modules -│ ├── components/ # Reusable Vue components -│ ├── layout/ # App layout (header, sidebar, nav) -│ ├── router/ # Vue Router config -│ ├── stores/ # Pinia state stores -│ ├── utils/ # Helper functions -│ └── views/ # Page components -├── docker-compose.yml # Container deployment (not in repo root) -├── Makefile # Build scripts -└── scripts/ # Utility scripts -``` - -## Backend Directory Purposes - -**`backend/application/`:** -- Purpose: Django project configuration entry point -- Key files: - - `settings.py`: Main Django settings (imports from `conf/env.py`) - - `urls.py`: Root URL routing (includes all app URLs) - - `asgi.py`: ASGI config for HTTP + WebSocket - - `ws_routing.py`: WebSocket URL patterns - - `celery.py`: Celery task configuration - -**`backend/conf/`:** -- Purpose: Environment-specific configuration -- Key file: `env.py` - Contains DATABASE_*, REDIS_*, EMAIL_* settings -- This file contains credentials - DO NOT commit to version control - -**`backend/apps/pisadmin/basicinfo/`:** -- Purpose: Master data for procurement (companies, currencies, suppliers, units) -- Structure: - - `models.py`: Company, Currency, Supplier, SupplierUser, Unit, SystemNoRule, EmailNotice - - `views/`: company.py, currency.py, supplier.py, unit.py, system_no_rule.py, email_template.py, email_utils.py - - `urls.py`: Routes to views - - `admin.py`: Django admin registration - -**`backend/apps/pisadmin/miscprocurement/`:** -- Purpose: RFQ (Request for Quotation) and cost template management -- Structure: - - `models.py`: Inquiry, InquirySupplier, InquiryAttachment, InquiryMaterialCost, InquiryProcessCost, InquiryOtherCost, InquiryProfitCost, InquiryRfqItem, CostEstimateTemplateHead, CostEstimateTemplateBody, MiscLowPriceHeader, MiscLowPriceDetail, MiscNegotiationRecords, RFQOperationLogs - - `views.py`: ViewSets for all models (1500+ lines with complex workflow logic) - - `serializers.py`: DRF serializers - - `urls.py`: Routes to views - - `cost_template_builder.py`: Template construction utility - -**`backend/apps/pissupplier/`:** -- Purpose: Supplier quotation management (separate from pisadmin for supplier-facing access) -- Structure: - - `models.py`: QuotationMaster, QuotationMaterial, QuotationProcess, QuotationOther, QuotationProfit, QuotationItem, QuotationAttachment - - `views.py`: Quotation ViewSets - - `serializers.py`: DRF serializers - - `urls.py`: Routes to views - -**`backend/dvadmin/system/`:** -- Purpose: Base RBAC system (users, roles, menus, departments, logs) -- Structure: - - `models.py`: Users, Role, Menu, MenuButton, MenuField, FieldPermission, Dept, Post, OperationLog, LoginLog, SystemConfig, Dictionary, FileList, Area, ApiWhiteList, MessageCenter - - `views/`: ViewSets for each model - - `urls.py`: Routes to views - - `fixtures/`: Initial data fixtures - - `management/commands/`: Django commands (init, init_area, check_dept_users) - -**`backend/dvadmin/utils/`:** -- Purpose: Reusable utilities for all apps -- Key files: - - `viewset.py`: CustomModelViewSet base class with standardized responses - - `models.py`: CoreModel, SoftDeleteModel base classes - - `filters.py`: DataLevelPermissionMargeFilter, CoreModelFilterBankend - - `permission.py`: CustomPermission class - - `pagination.py`: CustomPagination class - - `serializers.py`: Common serializer utilities - - `exception.py`: Custom exception handler - - `json_response.py`: SuccessResponse, DetailResponse, ErrorResponse wrappers - -## Frontend Directory Purposes - -**`web/src/api/`:** -- Purpose: API client modules -- Structure: - - `login/index.ts`: Login/auth API - - `menu/index.ts`: Menu/permission API - - `miscprocurement/priceTemplate.ts`: Cost template API - - `procurement/priceTemplate.ts`: (duplicate or alternative) - -**`web/src/components/`:** -- Purpose: Reusable Vue components -- Key components: - - `table/index.vue`: Custom table component with FastCRUD - - `auth/auth*.vue`: Permission directive components - - `fileSelector/`: File upload component - - `foreignKey/`: Foreign key selector - - `importExcel/`: Excel import component - -**`web/src/layout/`:** -- Purpose: Application layout structure -- Subdirectories: - - `component/`: aside.vue, header.vue, main.vue - - `navBars/`: breadcrumb, tagsView, user menu - - `navMenu/`: vertical and horizontal menu components - - `main/`: classic, columns, defaults, transverse layout variants - - `routerView/`: iframes, link, parent route views - -**`web/src/router/`:** -- Purpose: Vue Router configuration -- Key files: - - `index.ts`: Router creation, beforeEach guards, token validation - - `route.ts`: Static and dynamic route definitions - - `frontEnd.ts`: Frontend-controlled routing logic - - `backEnd.ts`: Backend-controlled routing logic - -**`web/src/stores/`:** -- Purpose: Pinia state management -- Key stores: - - `userInfo.ts`: User authentication state - - `frontendMenu.ts`: Menu tree cache - - `themeConfig.ts`: UI theme settings - - `tagsViewRoutes.ts`: Open tab tracking - - `routesList.ts`: Available routes - - `permission.ts`: Permission state - -**`web/src/views/`:** -- Purpose: Page-level Vue components -- Structure mirrors backend apps: - - `pisadmin/basicinfo/`: company, currency, supplier, unit, systemno, emailnotice - - `pisadmin/miscprocurement/`: cost_template, misc_materials, misc_parts, misc_stations - - `system/`: admin pages (dept, menu, role, user, dictionary, config, etc.) - - `pissupplier/` (if exists): Supplier-facing pages - -## Key Configuration File Locations - -**Backend Configuration:** -- `backend/application/settings.py` - Main Django settings -- `backend/conf/env.py` - Environment variables (DB, Redis, Email) -- `backend/application/urls.py` - URL routing -- `backend/application/asgi.py` - ASGI/WebSocket config - -**Frontend Configuration:** -- `web/src/settings.ts` - Frontend settings (API base URLs, app info) -- `web/src/main.ts` - Vue app initialization -- `web/src/App.vue` - Root Vue component -- `web/vite.config.ts` - Vite build configuration - -**Environment/Secrets (DO NOT COMMIT):** -- `backend/conf/env.py` - Database, Redis, Email credentials -- `.env` files (if present) - -## Custom Apps vs Base dvadmin Structure - -**Base dvadmin (`dvadmin/`):** -- Provided by django-vue3-admin template -- Contains generic functionality: user management, role management, menu management, RBAC -- Should NOT be modified for custom business logic -- Extended by custom apps for domain-specific features - -**Custom Apps (`apps/pisadmin/`, `apps/pissupplier/`):** -- `pisadmin`: Internal procurement admin (RFQ, cost templates, supplier master, etc.) -- `pissupplier`: Supplier portal (quotations) -- Custom apps import and extend dvadmin's `CustomModelViewSet`, `CoreModel`, `CustomPermission` -- Custom apps define their own models, serializers, views, urls - -**Extension Pattern:** -```python -from dvadmin.utils.viewset import CustomModelViewSet - -class InquiryViewSet(CustomModelViewSet): - queryset = Inquiry.objects.all() - serializer_class = InquirySerializer - # Inherits: create, list, retrieve, update, destroy, multiple_delete, get_by_ids - # Custom actions added via @action decorator -``` - -## Docker/Container Structure - -The project uses Docker for deployment (docker-compose not present in current branch): - -**Backend Container:** -- Based on Python 3.x -- Runs Django via uvicorn (ASGI) -- Port: 8000 -- Depends on: MySQL, Redis - -**Frontend Container:** -- Based on Node.js for build -- nginx for serving built SPA -- Port: 8080 (dev), 80 (prod) - -**External Dependencies:** -- MySQL 8.0: Database (configured via `conf/env.py`) -- Redis: Caching and Celery broker -- SMTP: Email notifications - -## Where to Add New Code - -**New Model (Backend):** -1. Add to appropriate app's `models.py` -2. Inherit from `CoreModel` -3. Create serializer in `serializers.py` -4. Create ViewSet extending `CustomModelViewSet` -5. Add URL route in app's `urls.py` -6. Run migrations: `python manage.py makemigrations apps..` -7. Apply: `python manage.py migrate` - -**New API Endpoint (Backend):** -1. If CRUD: Add ViewSet method or use default actions -2. If custom: Add `@action` method to ViewSet -3. Register route in app `urls.py` with `router.register()` - -**New Page (Frontend):** -1. Create Vue component in `web/src/views///` -2. Add API client in `web/src/api//` -3. Menu added via backend `Menu` model (not frontend route config) -4. Component uses FastCRUD pattern with custom table and form components - -**New Store (Frontend):** -1. Create in `web/src/stores/modules/` or root stores -2. Use Pinia `defineStore` -3. Persist sensitive data to Session storage - ---- - -*Structure analysis: 2026-04-01* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md deleted file mode 100644 index 1300ff4..0000000 --- a/.planning/codebase/TESTING.md +++ /dev/null @@ -1,238 +0,0 @@ -# Testing Patterns - -**Analysis Date:** 2026-04-01 - -## Test Framework - -### Backend (Python/Django) - -**No dedicated test framework detected.** - -- No `tests.py` files in Django apps -- No `pytest` configuration -- No `unittest` test suites -- Migrations are generated via `python manage.py makemigrations` - -### Frontend (Vue 3/TypeScript) - -**No test framework detected.** - -- No Jest, Vitest, or other test runner configuration -- No `.test.ts`, `.spec.ts` files in `web/src/` -- Build and lint commands only, no test commands in `package.json` - -## Test File Organization - -### Backend Structure - -``` -backend/ -├── application/ # Django settings -├── apps/ -│ ├── pisadmin/ # Custom procurement app -│ │ ├── basicinfo/ -│ │ │ ├── models.py -│ │ │ ├── views/ -│ │ │ ├── urls.py -│ │ │ └── migrations/ -│ │ └── miscprocurement/ -│ │ ├── models.py -│ │ ├── serializers.py -│ │ ├── views.py -│ │ └── migrations/ -│ └── pissupplier/ -└── dvadmin/ # Core RBAC system -``` - -**No `tests.py` directories or test files in application code.** - -### Frontend Structure - -``` -web/ -├── src/ -│ ├── api/ -│ ├── assets/ -│ ├── components/ -│ ├── directive/ -│ ├── layout/ -│ ├── plugin/ -│ ├── router/ -│ ├── stores/ -│ ├── theme/ -│ ├── types/ -│ ├── utils/ -│ ├── views/ # Page components -│ │ ├── pisadmin/ -│ │ │ ├── basicinfo/ -│ │ │ │ ├── company/ -│ │ │ │ │ ├── index.vue -│ │ │ │ ├── api.ts -│ │ │ │ └── crud.tsx -│ │ │ └── miscprocurement/ -│ │ │ ├── rfqmiscellaneous/ -│ │ │ └── cost_template/ -│ │ ├── pissupplier/ -│ │ └── system/ -│ ├── App.vue -│ └── main.ts -└── package.json -``` - -**No test files (`*.test.ts`, `*.spec.ts`) in `web/src/` directory.** - -## Available Scripts - -### Frontend (`web/package.json`) - -```json -{ - "scripts": { - "dev": "vite --force", - "build:dev": "vite build --mode development", - "build": "vite build", - "build:local": "vite build --mode local_prod", - "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/", - "build:flowH5": "vite build --config flowH5.config.ts" - } -} -``` - -**No test-related scripts.** - -### Backend - -```bash -# Migrations -python manage.py makemigrations apps.pisadmin.apps.pisadmin -python manage.py makemigrations apps.pissupplier -python manage.py migrate - -# Development server -python manage.py runserver 0.0.0.0:8000 - -# Production-like -uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 -``` - -**No test commands.** - -## CI/CD Pipeline - -**No CI/CD configuration detected.** - -- No GitHub Actions workflows (`.github/workflows/`) -- No `.gitlab-ci.yml` -- No Jenkinsfile -- Docker Compose present but no automated testing in Dockerfile - -## Coverage - -**No coverage tools configured.** - -- No `coverage.py` configuration -- No `pytest-cov` or similar -- No Istanbul/nyc for frontend - -## Manual Testing Approach - -Based on project structure, manual testing likely involves: - -### Backend API Testing - -1. **Django Admin Interface** - Models registered in `admin.py` provide basic CRUD -2. **DRF Browsable API** - Available at `/api/` endpoints for interactive testing -3. **Direct HTTP requests** - Using curl or Postman - -### Frontend Testing - -1. **Browser testing** - Manual UI verification via `yarn dev` -2. **FastCRUD configuration** - CRUD options in `crud.tsx` files control behavior -3. **Vue DevTools** - For state inspection - -## What Could Be Tested - -### Backend - -- **ViewSet endpoints**: Each ViewSet handles CRUD + custom actions -- **Serializer validation**: Input/output transformation -- **Model methods**: Business logic like `SystemNoRule.allocate_system_number()` -- **Transaction integrity**: Database operations wrapped in `transaction.atomic()` -- **Status workflow**: Inquiry state machine (open -> confirmed -> published -> quoting -> quote_ended -> bargaining -> negotiated -> approved -> awarded) - -### Frontend - -- **FastCRUD options**: Configuration in `createCrudOptions()` -- **API integration**: Request/response handling -- **Store actions**: Pinia store mutations -- **Router guards**: Authentication and authorization checks - -## Recommendations for Testing Setup - -### Backend - -Add `pytest` with `pytest-django`: - -``` -# requirements.txt additions -pytest==7.4.0 -pytest-django==4.5.0 -pytest-cov==4.1.0 -``` - -Create test files: -``` -backend/apps/pisadmin/basicinfo/tests.py -backend/apps/pisadmin/miscprocurement/tests.py -``` - -### Frontend - -Add Vitest for testing: - -```bash -yarn add -D vitest @vue/test-utils jsdom -``` - -Add test script: -```json -{ - "scripts": { - "test": "vitest", - "coverage": "vitest run --coverage" - } -} -``` - -Create test files: -``` -web/src/views/pisadmin/basicinfo/company/__tests__/Company.spec.ts -``` - -## Build and Deployment - -### Build Commands - -**Frontend:** -```bash -cd web -yarn install -yarn build # Production build -yarn build:dev # Development build -``` - -**Backend:** -```bash -cd backend -pip install -r requirements.txt -python manage.py migrate -python manage.py init # Create superuser -``` - -### Docker - -The project uses Docker for deployment but no test containers. - ---- - -*Testing analysis: 2026-04-01* diff --git a/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md b/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md deleted file mode 100644 index ded7b06..0000000 --- a/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md +++ /dev/null @@ -1,1085 +0,0 @@ -# Dashboard Redesign Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Implement role-based buyer/supplier dashboard with unified API, real business data, and Vue 3 frontend components. - -**Architecture:** Role-based data filtering via unified `/api/dashboard/` endpoint. Backend computes KPIs from Inquiry/QuotationMaster/Supplier tables. Frontend uses Pinia store with separate buyer/supplier state branches. - -**Tech Stack:** Django REST Framework, Vue 3 + TypeScript + Pinia + ECharts - ---- - -## Backend Implementation - -### File Structure - -**Create:** -- `backend/apps/pisadmin/dashboard/__init__.py` -- `backend/apps/pisadmin/dashboard/apps.py` -- `backend/apps/pisadmin/dashboard/urls.py` -- `backend/apps/pisadmin/dashboard/serializers.py` -- `backend/apps/pisadmin/dashboard/views.py` - -**Modify:** -- `backend/application/urls.py` - add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` - ---- - -### Task 1: Create Dashboard App Skeleton - -- [ ] **Step 1: Create `backend/apps/pisadmin/dashboard/__init__.py`** -```python -``` - -- [ ] **Step 2: Create `backend/apps/pisadmin/dashboard/apps.py`** -```python -from django.apps import AppConfig - - -class DashboardConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.pisadmin.dashboard' -``` - -- [ ] **Step 3: Create `backend/apps/pisadmin/dashboard/urls.py`** -```python -from django.urls import path -from .views import DashboardView - -urlpatterns = [ - path('', DashboardView.as_view(), name='dashboard'), -] -``` - -- [ ] **Step 4: Register app in `backend/application/settings.py`** -Add `'apps.pisadmin.dashboard'` to `INSTALLED_APPS` list. - -- [ ] **Step 5: Add URL to `backend/application/urls.py`** -Add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` to urlpatterns. - -- [ ] **Step 6: Commit** -```bash -git add backend/apps/pisadmin/dashboard/ backend/application/urls.py backend/application/settings.py -git commit -m "feat(dashboard): create dashboard app skeleton with URL routing" -``` - ---- - -### Task 2: Implement Dashboard Serializers - -- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/serializers.py`** - -```python -from rest_framework import serializers - - -class BuyerKPISerializer(serializers.Serializer): - total_inquiries = serializers.IntegerField() - pending_inquiries = serializers.IntegerField() - completed_quotes = serializers.IntegerField() - total_suppliers = serializers.IntegerField() - - -class BuyerTaskSerializer(serializers.Serializer): - id = serializers.IntegerField() - title = serializers.CharField() - inquiry_no = serializers.CharField() - status = serializers.CharField() - created_at = serializers.DateTimeField() - - -class SupplierKPISerializer(serializers.Serializer): - total_quotes = serializers.IntegerField() - pending_quotes = serializers.IntegerField() - won_quotes = serializers.IntegerField() - conversion_rate = serializers.FloatField() - - -class SupplierQuoteSerializer(serializers.Serializer): - id = serializers.IntegerField() - inquiry_no = serializers.CharField() - item_name = serializers.CharField() - quantity = serializers.IntegerField() - unit = serializers.CharField() - deadline = serializers.DateTimeField() - - -class MessageSerializer(serializers.Serializer): - id = serializers.IntegerField() - title = serializers.CharField() - content = serializers.CharField() - is_read = serializers.BooleanField() - created_at = serializers.DateTimeField() - - -class TrendSerializer(serializers.Serializer): - month = serializers.CharField() - count = serializers.IntegerField(required=False) - quotes = serializers.IntegerField(required=False) - won = serializers.IntegerField(required=False) - - -class BuyerDashboardSerializer(serializers.Serializer): - kpi = BuyerKPISerializer() - tasks = BuyerTaskSerializer(many=True) - messages = MessageSerializer(many=True) - trend = TrendSerializer(many=True) - - -class SupplierDashboardSerializer(serializers.Serializer): - kpi = SupplierKPISerializer() - pending_quotes = SupplierQuoteSerializer(many=True) - messages = MessageSerializer(many=True) - trend = TrendSerializer(many=True) - - -class DashboardResponseSerializer(serializers.Serializer): - buyer = BuyerDashboardSerializer(allow_null=True) - supplier = SupplierDashboardSerializer(allow_null=True) -``` - -- [ ] **Step 2: Commit** -```bash -git add backend/apps/pisadmin/dashboard/serializers.py -git commit -m "feat(dashboard): add dashboard serializers" -``` - ---- - -### Task 3: Implement Dashboard View - -- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/views.py`** - -```python -from datetime import timedelta - -from django.utils import timezone -from django.db.models import Count -from django.db.models.functions import TruncMonth -from rest_framework.views import APIView -from rest_framework.response import Response - -from apps.pisadmin.miscprocurement.models import Inquiry -from apps.pissupplier.models import QuotationMaster -from apps.pisadmin.basicinfo.models import Supplier - - -def get_buyer_kpi(user): - """采购方 KPI 计算""" - total_inquiries = Inquiry.objects.filter(create_user=user.username).count() - pending_inquiries = Inquiry.objects.filter(create_user=user.username, status__in=[1, 2, 3]).count() - completed_quotes = QuotationMaster.objects.filter( - inquiry_no__in=Inquiry.objects.filter(create_user=user.username).values('inquiry_no') - ).count() - total_suppliers = Supplier.objects.count() - return { - 'total_inquiries': total_inquiries, - 'pending_inquiries': pending_inquiries, - 'completed_quotes': completed_quotes, - 'total_suppliers': total_suppliers, - } - - -def get_buyer_tasks(user): - """采购方待办任务""" - six_months_ago = timezone.now() - timedelta(days=180) - inquiries = Inquiry.objects.filter( - create_user=user.username, - status__in=[1, 2, 3] - ).order_by('-create_time')[:10] - return [ - { - 'id': i.id, - 'title': i.title, - 'inquiry_no': i.inquiry_no, - 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), - 'created_at': i.create_time, - } - for i in inquiries - ] - - -def get_supplier_kpi(user): - """供应商 KPI 计算""" - total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() - pending_quotes = QuotationMaster.objects.filter(supplier_code=user.username, status__in=[1, 2]).count() - won_quotes = QuotationMaster.objects.filter(supplier_code=user.username, is_awarded=1).count() - conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 - return { - 'total_quotes': total_quotes, - 'pending_quotes': pending_quotes, - 'won_quotes': won_quotes, - 'conversion_rate': round(conversion_rate, 2), - } - - -def get_supplier_pending_quotes(user): - """供应商待报价清单""" - quotes = QuotationMaster.objects.filter( - supplier_code=user.username, - status__in=[1, 2] - ).order_by('-creattime')[:10] - result = [] - for q in quotes: - inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() - result.append({ - 'id': q.autoid, - 'inquiry_no': q.inquiry_no, - 'item_name': inquiry.title if inquiry else '', - 'quantity': 0, - 'unit': '', - 'deadline': q.quote_deadline, - }) - return result - - -def get_trend_data(user, is_buyer=True): - """近6个月趋势数据""" - six_months_ago = timezone.now() - timedelta(days=180) - if is_buyer: - data = ( - Inquiry.objects.filter( - create_user=user.username, - create_time__gte=six_months_ago - ) - .annotate(month=TruncMonth('create_time')) - .values('month') - .annotate(count=Count('id')) - .order_by('month') - ) - else: - data = ( - QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__gte=six_months_ago - ) - .annotate(month=TruncMonth('creattime')) - .values('month') - .annotate(quotes=Count('id')) - .order_by('month') - ) - # Add won counts - result = [] - for d in data: - won = QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__month=d['month'].month, - creattime__year=d['month'].year, - is_awarded=1 - ).count() - result.append({ - 'month': d['month'].strftime('%Y-%m'), - 'quotes': d['quotes'], - 'won': won, - }) - return result - return [{'month': d['month'].strftime('%Y-%m'), 'count': d['count']} for d in data] - - -def get_messages(user): - """消息通知 - 占位实现,后续接入通知系统""" - return [] - - -class DashboardView(APIView): - """看板数据视图""" - - def get(self, request): - user = request.user - if not user or not user.is_authenticated: - return Response({'buyer': None, 'supplier': None}) - - # 判断用户角色 - # 这里需要根据实际角色系统判断,暂用用户名特征区分 - # 供应商用户:有 supplier_code 在 QuotationMaster 中 - has_supplier_role = QuotationMaster.objects.filter(supplier_code=user.username).exists() - # 采购方用户:有 create_user 在 Inquiry 中 - has_buyer_role = Inquiry.objects.filter(create_user=user.username).exists() - - response_data = {'buyer': None, 'supplier': None} - - if has_buyer_role: - response_data['buyer'] = { - 'kpi': get_buyer_kpi(user), - 'tasks': get_buyer_tasks(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=True), - } - - if has_supplier_role: - response_data['supplier'] = { - 'kpi': get_supplier_kpi(user), - 'pending_quotes': get_supplier_pending_quotes(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=False), - } - - return Response(response_data) -``` - -- [ ] **Step 2: Commit** -```bash -git add backend/apps/pisadmin/dashboard/views.py -git commit -m "feat(dashboard): implement dashboard view with role-based data filtering" -``` - ---- - -## Frontend Implementation - -### File Structure - -**Create:** -- `web/src/api/pisadmin/dashboard.ts` -- `web/src/stores/modules/dashboard.ts` -- `web/src/views/pisadmin/dashboard/index.vue` -- `web/src/views/pisadmin/dashboard/BuyerDashboard.vue` -- `web/src/views/pisadmin/dashboard/SupplierDashboard.vue` -- `web/src/components/pisadmin/dashboard/KpiCard.vue` -- `web/src/components/pisadmin/dashboard/TrendChart.vue` -- `web/src/components/pisadmin/dashboard/MessageList.vue` -- `web/src/components/pisadmin/dashboard/BuyerTaskList.vue` -- `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue` - -**Modify:** -- `web/src/router/route.ts` - add dashboard routes - ---- - -### Task 4: Create Frontend API and Store - -- [ ] **Step 1: Create `web/src/api/pisadmin/dashboard.ts`** - -```typescript -import request from '/@/utils/request'; - -export function getDashboard() { - return request({ - url: '/api/pisadmin/dashboard/', - method: 'get', - }); -} -``` - -- [ ] **Step 2: Create `web/src/stores/modules/dashboard.ts`** - -```typescript -import { defineStore } from 'pinia'; -import { getDashboard } from '/@/api/pisadmin/dashboard'; - -interface BuyerDashboard { - kpi: { - total_inquiries: number; - pending_inquiries: number; - completed_quotes: number; - total_suppliers: number; - } | null; - tasks: Array<{ - id: number; - title: string; - inquiry_no: string; - status: string; - created_at: string; - }>; - messages: Array<{ - id: number; - title: string; - content: string; - is_read: boolean; - created_at: string; - }>; - trend: Array<{ month: string; count: number }>; -} - -interface SupplierDashboard { - kpi: { - total_quotes: number; - pending_quotes: number; - won_quotes: number; - conversion_rate: number; - } | null; - pending_quotes: Array<{ - id: number; - inquiry_no: string; - item_name: string; - quantity: number; - unit: string; - deadline: string; - }>; - messages: Array<{ - id: number; - title: string; - content: string; - is_read: boolean; - created_at: string; - }>; - trend: Array<{ month: string; quotes: number; won: number }>; -} - -export const useDashboardStore = defineStore('dashboard', { - state: () => ({ - buyer: null as BuyerDashboard | null, - supplier: null as SupplierDashboard | null, - loading: false, - }), - actions: { - async fetchDashboard() { - this.loading = true; - try { - const res: any = await getDashboard(); - this.buyer = res.data?.buyer || null; - this.supplier = res.data?.supplier || null; - } finally { - this.loading = false; - } - }, - }, -}); -``` - -- [ ] **Step 3: Commit** -```bash -git add web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts -git commit -m "feat(dashboard): add dashboard API and Pinia store" -``` - ---- - -### Task 5: Create Dashboard Components - -- [ ] **Step 1: Create `web/src/components/pisadmin/dashboard/KpiCard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 2: Create `web/src/components/pisadmin/dashboard/TrendChart.vue`** - -```vue - - - - - -``` - -- [ ] **Step 3: Create `web/src/components/pisadmin/dashboard/MessageList.vue`** - -```vue - - - - - -``` - -- [ ] **Step 4: Create `web/src/components/pisadmin/dashboard/BuyerTaskList.vue`** - -```vue - - - -``` - -- [ ] **Step 5: Create `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue`** - -```vue - - - -``` - -- [ ] **Step 6: Commit** -```bash -git add web/src/components/pisadmin/dashboard/ web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts -git commit -m "feat(dashboard): add dashboard Vue components" -``` - ---- - -### Task 6: Create Dashboard Pages - -- [ ] **Step 1: Create `web/src/views/pisadmin/dashboard/index.vue`** - -```vue - - - - - -``` - -- [ ] **Step 2: Create `web/src/views/pisadmin/dashboard/BuyerDashboard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 3: Create `web/src/views/pisadmin/dashboard/SupplierDashboard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 4: Commit** -```bash -git add web/src/views/pisadmin/dashboard/ -git commit -m "feat(dashboard): add buyer/supplier dashboard pages" -``` - ---- - -### Task 7: Add Dashboard Routes - -- [ ] **Step 1: Modify `web/src/router/route.ts`** - -Add to `dynamicRoutes` array: -```typescript -{ - path: '/dashboard', - name: 'Dashboard', - component: () => import('/@/views/pisadmin/dashboard/index.vue'), - meta: { - title: 'message.router.dashboard', - isLink: '', - isHide: false, - isKeepAlive: true, - isAffix: false, - isIframe: false, - icon: 'iconfont icon-dashboard', - }, -}, -``` - -- [ ] **Step 2: Commit** -```bash -git add web/src/router/route.ts -git commit -m "feat(dashboard): add dashboard route" -``` - ---- - -## Verification Checklist - -- [ ] Backend: `GET /api/pisadmin/dashboard/` returns correct data structure -- [ ] Backend: Role-based filtering works (buyer only, supplier only, both) -- [ ] Frontend: Dashboard page loads without errors -- [ ] Frontend: KPI cards display correct data -- [ ] Frontend: Trend charts render correctly -- [ ] Frontend: Task/Quote lists display correctly -- [ ] Frontend: Role switching works for users with both roles diff --git a/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md b/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md deleted file mode 100644 index ac500ef..0000000 --- a/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md +++ /dev/null @@ -1,267 +0,0 @@ -# 控制台看板重构设计文档 - -## 1. 概述 - -### 1.1 背景 -当前系统首页使用硬编码模拟数据,需要重构为真实业务数据看板。 - -### 1.2 目标 -- 区分采购方看板和供应商看板两种视图 -- 基于用户角色动态返回对应的看板数据 -- 后端提供统一 API,前端按角色渲染 - -### 1.3 设计图 -- 采购方看板:`D:\AVC-PROJECT\docs\采购方看板.png` -- 供应商看板:`D:\AVC-PROJECT\docs\供应商看板.png` - ---- - -## 2. API 设计 - -### 2.1 接口信息 - -| 项目 | 值 | -|------|-----| -| 方法 | GET | -| 路径 | `/api/dashboard/` | -| 认证 | JWT Token | -| 响应格式 | JSON | - -### 2.2 响应结构 - -```json -{ - "buyer": { - "kpi": { - "total_inquiries": 0, - "pending_inquiries": 0, - "completed_quotes": 0, - "total_suppliers": 0 - }, - "tasks": [ - { - "id": 0, - "title": "string", - "inquiry_no": "string", - "status": "string", - "created_at": "datetime" - } - ], - "messages": [ - { - "id": 0, - "title": "string", - "content": "string", - "is_read": false, - "created_at": "datetime" - } - ], - "trend": [ - {"month": "2026-01", "count": 0} - ] - }, - "supplier": { - "kpi": { - "total_quotes": 0, - "pending_quotes": 0, - "won_quotes": 0, - "conversion_rate": 0.0 - }, - "pending_quotes": [ - { - "id": 0, - "inquiry_no": "string", - "item_name": "string", - "quantity": 0, - "unit": "string", - "deadline": "datetime" - } - ], - "messages": [], - "trend": [ - {"month": "2026-01", "quotes": 0, "won": 0} - ] - } -} -``` - -### 2.3 业务规则 -- 用户同时有采购方和供应商角色:两个模块都返回 -- 用户只有采购方角色:只返回 `buyer` 模块,`supplier` 为 `null` -- 用户只有供应商角色:只返回 `supplier` 模块,`buyer` 为 `null` - ---- - -## 3. 后端实现 - -### 3.1 应用位置 -新建 `backend/apps/pisadmin/dashboard/` 应用 - -### 3.2 数据来源 - -**采购方 KPI 计算:** -| 指标 | 数据来源 | -|------|----------| -| total_inquiries | Inquiry 表中当前用户创建的记录数 | -| pending_inquiries | Inquiry 状态为"待报价"的记录数 | -| completed_quotes | QuotationMaster 中已报价的记录数 | -| total_suppliers | Supplier 表总数 | - -**供应商 KPI 计算:** -| 指标 | 数据来源 | -|------|----------| -| total_quotes | QuotationMaster 中属于当前供应商的记录数 | -| pending_quotes | QuotationMaster 状态为"待报价"的记录数 | -| won_quotes | QuotationMaster 状态为"已中标"的记录数 | -| conversion_rate | won_quotes / total_quotes * 100 | - -**待办任务(采购方):** -- 查询条件:Inquiry 状态为"待处理",分配给当前用户 - -**待报价清单(供应商):** -- 查询条件:QuotationMaster 状态为"待报价",属于当前供应商 - -**消息通知:** -- 使用现有通知表,按时间倒序取最新 5 条 - -**趋势图数据:** -- 近 6 个月的记录创建数,按月聚合 - -### 3.3 序列化器 - -```python -# dashboard/serializers.py -class BuyerKPISerializer(serializers.Serializer): ... -class SupplierKPISerializer(serializers.Serializer): ... -class BuyerTaskSerializer(serializers.ModelSerializer): ... -class SupplierQuoteSerializer(serializers.ModelSerializer): ... -class MessageSerializer(serializers.ModelSerializer): ... -class TrendSerializer(serializers.Serializer): ... -class DashboardResponseSerializer(serializers.Serializer): ... -``` - -### 3.4 视图 - -```python -# dashboard/views.py -class DashboardView(APIView): - def get(self, request): - # 根据用户角色过滤返回数据 - ... -``` - -### 3.5 URL 配置 - -```python -# pisadmin/urls.py -path('dashboard/', include('apps.pisadmin.dashboard.urls')) -``` - ---- - -## 4. 前端实现 - -### 4.1 路由设计 - -| 路径 | 组件 | 说明 | -|------|------|------| -| `/dashboard` | `DashboardIndex.vue` | 根路由,根据角色重定向 | -| `/dashboard/buyer` | `BuyerDashboard.vue` | 采购方看板 | -| `/dashboard/supplier` | `SupplierDashboard.vue` | 供应商看板 | - -### 4.2 页面布局 - -``` -┌─────────────────────────────────────────────────┐ -│ Header: 欢迎语 + 当前日期 │ -├─────────────────────────────────────────────────┤ -│ KPI Cards (4个指标卡片横向排列) │ -├─────────────────────────────────────────────────┤ -│ Main Content (左右分栏) │ -│ ┌────────────────┐ ┌─────────────────────┐ │ -│ │ 待办/待报价清单 │ │ 趋势图 │ │ -│ │ (列表组件) │ │ (ECharts 折线图) │ │ -│ └────────────────┘ └─────────────────────┘ │ -├─────────────────────────────────────────────────┤ -│ 消息通知 (横向卡片列表) │ -└─────────────────────────────────────────────────┘ -``` - -### 4.3 组件结构 - -``` -web/src/ -├── views/ -│ └── pisadmin/ -│ └── dashboard/ -│ ├── index.vue # 重定向组件 -│ ├── BuyerDashboard.vue # 采购方看板 -│ └── SupplierDashboard.vue # 供应商看板 -├── components/ -│ └── dashboard/ -│ ├── KpiCard.vue # KPI 卡片 -│ ├── TrendChart.vue # 趋势图 -│ ├── MessageList.vue # 消息列表 -│ ├── BuyerTaskList.vue # 采购方待办 -│ └── SupplierQuoteList.vue # 供应商待报价 -└── stores/ - └── dashboard.ts # Pinia Store -``` - -### 4.4 Store 设计 - -```typescript -// stores/dashboard.ts -export const useDashboardStore = defineStore('dashboard', { - state: () => ({ - buyer: { - kpi: null, - tasks: [], - messages: [], - trend: [] - }, - supplier: { - kpi: null, - pendingQuotes: [], - messages: [], - trend: [] - }, - loading: false - }), - actions: { - async fetchDashboard() { ... } - } -}) -``` - -### 4.5 API 服务 - -```typescript -// api/dashboard.ts -export function getDashboard() { - return request.get('/api/dashboard/') -} -``` - ---- - -## 5. 权限控制 - -### 5.1 后端 -- 根据 `request.user` 的角色过滤返回数据 -- 无角色用户返回空数据 - -### 5.2 前端 -- 根据 `user.roles` 判断显示哪个看板菜单 -- 未授权用户访问看板路由时跳转至首页 - ---- - -## 6. 实现顺序 - -1. 后端 API 开发(dashboard 应用、序列化器、视图、URL) -2. 前端 API 服务和 Store -3. 通用组件(KpiCard、TrendChart、MessageList) -4. 采购方看板页面 -5. 供应商看板页面 -6. 路由配置和权限控制 From 2efd97144925787a900da08ec2750a71ec616666 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:32:16 +0800 Subject: [PATCH 063/111] chore: remove version upgrade log from user dropdown menu --- web/src/layout/navBars/breadcrumb/user.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/layout/navBars/breadcrumb/user.vue b/web/src/layout/navBars/breadcrumb/user.vue index 0e3703c..24e4d19 100644 --- a/web/src/layout/navBars/breadcrumb/user.vue +++ b/web/src/layout/navBars/breadcrumb/user.vue @@ -93,7 +93,6 @@ {{ $t('message.user.dropdown1') }} {{ $t('message.user.dropdown2') }} - 更新日志 {{ $t('message.user.dropdown5') }} From 9d581dd5d33cbb40f0470b277abe3981d3ec4eb7 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:48:46 +0800 Subject: [PATCH 064/111] feat(login): add SupplierLoginSerializer and SupplierLoginView for supplier portal --- backend/dvadmin/system/views/login.py | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/backend/dvadmin/system/views/login.py b/backend/dvadmin/system/views/login.py index aa5b207..da39cf2 100644 --- a/backend/dvadmin/system/views/login.py +++ b/backend/dvadmin/system/views/login.py @@ -140,6 +140,99 @@ class LoginSerializer(TokenObtainPairSerializer): return {"code": 2000, "msg": "请求成功", "data": data} +class SupplierLoginSerializer(TokenObtainPairSerializer): + """ + 供应商登录的序列化器 + 复用 LoginSerializer 的验证逻辑,额外校验 role.key = 'supplier' + """ + captcha = serializers.CharField( + max_length=6, required=False, allow_null=True, allow_blank=True + ) + + class Meta: + model = Users + fields = "__all__" + read_only_fields = ["id"] + + default_error_messages = {"no_active_account": _("账号/密码错误")} + + def validate(self, attrs): + captcha = self.initial_data.get("captcha", None) + if dispatch.get_system_config_values("base.captcha_state"): + if captcha is None: + raise CustomValidationError("验证码不能为空") + self.image_code = CaptchaStore.objects.filter( + id=self.initial_data["captchaKey"] + ).first() + five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) + if self.image_code and five_minute_ago > self.image_code.expiration: + self.image_code and self.image_code.delete() + raise CustomValidationError("验证码过期") + else: + if self.image_code and ( + self.image_code.response == captcha + or self.image_code.challenge == captcha + ): + self.image_code and self.image_code.delete() + else: + self.image_code and self.image_code.delete() + raise CustomValidationError("图片验证码错误") + try: + user = Users.objects.get( + Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username'])) + except Users.DoesNotExist: + raise CustomValidationError("您登录的账号不存在") + except Users.MultipleObjectsReturned: + raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性") + if not user.is_active: + raise CustomValidationError("账号已被锁定,联系管理员解锁") + # 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误 + attrs['username'] = user.username + + # 校验用户角色是否为供应商 + user_roles = user.role.all() + role_keys = [r.key for r in user_roles] + if 'supplier' not in role_keys: + raise CustomValidationError("该账号不是供应商账号,请使用采购方入口登录") + + try: + data = super().validate(attrs) + except Exception as e: + user.login_error_count += 1 + if user.login_error_count >= 5: + user.is_active = False + user.save() + raise CustomValidationError("账号已被锁定,联系管理员解锁") + user.save() + count = 5 - user.login_error_count + raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~") + data["username"] = self.user.username + data["name"] = self.user.name + data["userId"] = self.user.id + data["avatar"] = self.user.avatar + data['user_type'] = self.user.user_type + data['pwd_change_count'] = self.user.pwd_change_count + dept = Dept.objects.filter(id=self.user.dept_id).values("id", "name").first() if self.user.dept_id else None + if dept: + data['dept_info'] = { + 'dept_id': dept["id"], + 'dept_name': dept["name"], + } + role = getattr(self.user, 'role', None) + if role: + data['role_info'] = role.values('id', 'name', 'key') + request = self.context.get("request") + if request is not None: + request.user = self.user + try: + save_login_log(request=request) + except Exception: + logger.exception("记录登录日志失败: %s", self.user.username) + user.login_error_count = 0 + user.save() + return {"code": 2000, "msg": "请求成功", "data": data} + + class LoginView(TokenObtainPairView): """ 登录接口 @@ -147,6 +240,14 @@ class LoginView(TokenObtainPairView): serializer_class = LoginSerializer permission_classes = [] + +class SupplierLoginView(TokenObtainPairView): + """ + 供应商登录接口 + """ + serializer_class = SupplierLoginSerializer + permission_classes = [] + # def post(self, request, *args, **kwargs): # # username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱 # username = request.data.get('username',None) From 74ab2caf29bd89f98ba508b62b4d2e4232b4fdd6 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:50:49 +0800 Subject: [PATCH 065/111] feat(login): add /login/supplier/ route for supplier portal --- backend/dvadmin/system/urls.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/dvadmin/system/urls.py b/backend/dvadmin/system/urls.py index 6c4cc5a..7c0afd3 100644 --- a/backend/dvadmin/system/urls.py +++ b/backend/dvadmin/system/urls.py @@ -7,6 +7,7 @@ from dvadmin.system.views.clause import PrivacyView, TermsServiceView from dvadmin.system.views.dept import DeptViewSet from dvadmin.system.views.dictionary import DictionaryViewSet from dvadmin.system.views.file_list import FileViewSet +from dvadmin.system.views.login import SupplierLoginView from dvadmin.system.views.login_log import LoginLogViewSet from dvadmin.system.views.menu import MenuViewSet from dvadmin.system.views.menu_button import MenuButtonViewSet @@ -53,5 +54,7 @@ urlpatterns = [ # path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), path('clause/privacy.html', PrivacyView.as_view()), path('clause/terms_service.html', TermsServiceView.as_view()), + # 供应商登录 + path('login/supplier/', SupplierLoginView.as_view(), name='supplier_login'), ] urlpatterns += system_url.urls From bd58c714ea834ebdef8b5e4fb64407d1d60d0b3e Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:51:24 +0800 Subject: [PATCH 066/111] feat(login): add supplierLogin API method for supplier portal --- web/src/views/system/login/api.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/src/views/system/login/api.ts b/web/src/views/system/login/api.ts index 4502a75..9c74b6c 100644 --- a/web/src/views/system/login/api.ts +++ b/web/src/views/system/login/api.ts @@ -34,4 +34,12 @@ export function getBackends() { url: '/api/dvadmin3_social_oauth2/backend/get_login_backend/', method: 'get', }); +} + +export function supplierLogin(params: object) { + return request({ + url: '/api/login/supplier/', + method: 'post', + data: params + }); } \ No newline at end of file From 5ab5cca95e9494e0fa83f5a589d6287dad3008f4 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:51:53 +0800 Subject: [PATCH 067/111] feat(login): add /login/supplier route for supplier portal --- web/src/router/route.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/src/router/route.ts b/web/src/router/route.ts index eed6735..ed39d77 100644 --- a/web/src/router/route.ts +++ b/web/src/router/route.ts @@ -110,6 +110,14 @@ export const staticRoutes: Array = [ title: '登录', }, }, + { + path: '/login/supplier', + name: 'supplierLogin', + component: () => import('/@/views/system/login/supplier.vue'), + meta: { + title: '供应商登录', + }, + }, { path: '/demo', name: 'demo', From 0b27dd143e6ddee00e960a003b9781ae0360ccb5 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:54:14 +0800 Subject: [PATCH 068/111] feat(login): add supplier login page component - Create supplier.vue with green theme to distinguish from buyer portal - Modify account.vue to dynamically select login API based on URL --- .../views/system/login/component/account.vue | 5 +- web/src/views/system/login/supplier.vue | 174 ++++++++++++++++++ 2 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 web/src/views/system/login/supplier.vue diff --git a/web/src/views/system/login/component/account.vue b/web/src/views/system/login/component/account.vue index 03701ac..67bdcce 100644 --- a/web/src/views/system/login/component/account.vue +++ b/web/src/views/system/login/component/account.vue @@ -147,7 +147,10 @@ export default defineComponent({ await formRef.value.validate(async (valid: any) => { if (valid) { try { - const res = await loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }); + // 根据当前 URL 判断使用哪个登录 API + const isSupplierPortal = window.location.href.includes('/login/supplier'); + const loginMethod = isSupplierPortal ? loginApi.supplierLogin : loginApi.login; + const res = await loginMethod({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }); if (res.code === 2000) { const { data } = res; console.log('[DEBUG loginClick] res.data.access:', res.data.access); diff --git a/web/src/views/system/login/supplier.vue b/web/src/views/system/login/supplier.vue new file mode 100644 index 0000000..33d5940 --- /dev/null +++ b/web/src/views/system/login/supplier.vue @@ -0,0 +1,174 @@ + + + + + From 388efbe918e91a2b6220b6a054b405d9191aaa81 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:56:29 +0800 Subject: [PATCH 069/111] test: add pytest.ini configuration --- backend/pytest.ini | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 backend/pytest.ini diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..6062e09 --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +DJANGO_SETTINGS_MODULE = application.settings +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --tb=short +testpaths = apps tests From f09af5b0cea4192b3ca7f9023108dc3147bd13f0 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:56:41 +0800 Subject: [PATCH 070/111] test: add global tests directory structure --- backend/tests/__init__.py | 0 backend/tests/factories/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/factories/__init__.py diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/factories/__init__.py b/backend/tests/factories/__init__.py new file mode 100644 index 0000000..e69de29 From 23b892b05ec2847f740936d3156893060a0c2488 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:56:52 +0800 Subject: [PATCH 071/111] test: add global conftest.py with fixtures --- backend/tests/conftest.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 backend/tests/conftest.py diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..017389e --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,41 @@ +import pytest +from rest_framework.test import APIClient + + +@pytest.fixture +def api_client(): + """匿名 API 客户端""" + return APIClient() + + +@pytest.fixture +def authenticate(api_client, admin_user): + """已认证的 API 客户端(管理员)""" + api_client.force_authenticate(user=admin_user) + return api_client + + +@pytest.fixture +def admin_user(db): + """创建管理员用户(每个测试独立事务,测试后自动 rollback)""" + from dvadmin.system.models import Users + + user = Users.objects.create_user( + username="test_admin", + password="testpass123", + is_superuser=True, + ) + return user + + +@pytest.fixture +def normal_user(db): + """创建普通用户""" + from dvadmin.system.models import Users + + user = Users.objects.create_user( + username="test_normal_user", + password="testpass123", + is_superuser=False, + ) + return user From 6ba1f574533e8aaa6f1d409e38466a78f464d891 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:57:02 +0800 Subject: [PATCH 072/111] test: add UserFactory for system models --- backend/tests/factories/system_factory.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/tests/factories/system_factory.py diff --git a/backend/tests/factories/system_factory.py b/backend/tests/factories/system_factory.py new file mode 100644 index 0000000..36832b3 --- /dev/null +++ b/backend/tests/factories/system_factory.py @@ -0,0 +1,15 @@ +import factory +from dvadmin.system.models import Users + + +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = Users + + username = factory.Sequence(lambda n: f"user_{n}") + password = factory.Faker("password", length=12) + is_active = True + is_superuser = False + + class Params: + admin = factory.Trait(is_superuser=True) From dfc0c742c219f86a7c5e6b8e538facfe8ac79e98 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:57:10 +0800 Subject: [PATCH 073/111] test: add basicinfo tests directory --- backend/apps/pisadmin/basicinfo/tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/apps/pisadmin/basicinfo/tests/__init__.py diff --git a/backend/apps/pisadmin/basicinfo/tests/__init__.py b/backend/apps/pisadmin/basicinfo/tests/__init__.py new file mode 100644 index 0000000..e69de29 From 36065c33d4956dba44ce49f539e620f4264a2648 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:57:53 +0800 Subject: [PATCH 074/111] =?UTF-8?q?feat(login):=20add=20clear=20'=E9=87=87?= =?UTF-8?q?=E8=B4=AD=E6=96=B9=E7=99=BB=E5=BD=95'=20title=20to=20buyer=20lo?= =?UTF-8?q?gin=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/views/system/login/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/system/login/index.vue b/web/src/views/system/login/index.vue index 729ace3..3983355 100644 --- a/web/src/views/system/login/index.vue +++ b/web/src/views/system/login/index.vue @@ -14,7 +14,7 @@