Browse Source

new file: .gitignore
new file: .vscode/launch.json
new file: .vscode/settings.json
new file: BOM/__init__.py
new file: BOM/admin.py
new file: BOM/apps.py
new file: BOM/func.py
new file: BOM/migrations/0001_initial.py
new file: BOM/migrations/0002_vendor_part_comment_part_company_and_more.py
new file: BOM/migrations/0003_alter_partattachment_part_and_more.py
new file: BOM/migrations/0004_partattachment_company_partattachment_creator_and_more.py
new file: BOM/migrations/0005_vendor_company.py
new file: BOM/migrations/0006_vendor_creator_vendor_owner_alter_part_creator.py
new file: BOM/migrations/0007_bom.py
new file: BOM/migrations/0008_bom_qty_alter_part_version.py
new file: BOM/migrations/0009_ecn_bomsetting_part_ecn_default_approvers_and_more.py
new file: BOM/migrations/0010_alter_ecn_status.py
new file: BOM/migrations/0011_alter_ecnsignoff_status.py
new file: BOM/migrations/0012_partattachment_storage_type_and_more.py
new file: BOM/migrations/__init__.py
new file: BOM/model_handler.py
new file: BOM/models.py
new file: BOM/templates/BOM/ECN_part_detail.html
new file: BOM/templates/BOM/directPage.html
new file: BOM/templates/BOM/draft_parts.html
new file: BOM/templates/BOM/layout.html
new file: BOM/templates/BOM/my_ECN_list.html
new file: BOM/templates/BOM/my_ECN_signoff_list.html
new file: BOM/templates/BOM/my_part_list.html
new file: BOM/templates/BOM/new_ecn.html
new file: BOM/templates/BOM/new_part.html
new file: BOM/templates/BOM/new_vendor.html
new file: BOM/templates/BOM/part_detail.html
new file: BOM/templates/BOM/part_search.html
new file: BOM/templates/BOM/part_search_result.html
new file: BOM/templates/BOM/part_whereused.html
new file: BOM/tests.py
new file: BOM/urls.py
new file: BOM/views.py
new file: BOM/views_part_api.py
new file: BOM/views_vendor_api.py
new file: IT_service/__init__.py
new file: IT_service/admin.py
new file: IT_service/apps.py
new file: IT_service/migrations/0001_initial.py
new file: IT_service/migrations/0002_case_contact_mobile_case_contact_name.py
new file: IT_service/migrations/0003_remove_case_comment_case_description_and_more.py
new file: IT_service/migrations/0004_rename_part_caseattachment_case_alter_case_status.py
new file: IT_service/migrations/__init__.py
new file: IT_service/model_handler.py
new file: IT_service/models.py
new file: IT_service/templates/IT_service/layout.html
new file: IT_service/templates/IT_service/new_case.html
new file: IT_service/tests.py
new file: IT_service/urls.py
new file: IT_service/views.py
new file: IT_service/views_user_api.py
new file: Info/__init__.py
new file: Info/admin.py
new file: Info/apps.py
new file: Info/func.py
new file: Info/migrations/0001_initial.py
new file: Info/migrations/0002_userhandler_user_position_alter_user_name.py
new file: Info/migrations/0003_alter_user_role_delete_userhandler.py
new file: Info/migrations/0004_rename_role_user_admin.py
new file: Info/migrations/0005_alter_company_address_alter_company_license_id_and_more.py
new file: Info/migrations/0006_user_psw_change_required.py
new file: Info/migrations/0007_message.py
new file: Info/migrations/0008_modular_user_comment_modularenablement.py
new file: Info/migrations/0009_rename_modular_modularenablement_modular_and_more.py
new file: Info/migrations/0010_internaluser.py
new file: Info/migrations/__init__.py
new file: Info/model_handler.py
new file: Info/models.py
new file: Info/static/axios.min.js
new file: Info/static/babel.min.js
new file: Info/static/bootstrap.bundle.min.js
new file: Info/static/bootstrap.bundle.min.js.map
new file: Info/static/bootstrap.min.css
new file: Info/static/bootstrap.min.css.map
new file: Info/static/info/javascript/register.js
new file: Info/static/qs.js
new file: Info/static/react-dom.production.min.js
new file: Info/static/react.production.min.js
new file: Info/static/vue.global.prod.js
new file: Info/templates/info/bottom.html
new file: Info/templates/info/choicePage.html
new file: Info/templates/info/company/profile.html
new file: Info/templates/info/company/user_create.html
new file: Info/templates/info/company/user_manager.html
new file: Info/templates/info/directPage.html
new file: Info/templates/info/home.html
new file: Info/templates/info/internal/login.html
new file: Info/templates/info/layout.html
new file: Info/templates/info/login.html
new file: Info/templates/info/register.html
new file: Info/templates/info/user_password.html
new file: Info/templates/info/user_profile.html
new file: Info/tests.py
new file: Info/urls.py
new file: Info/view_internal.py
new file: Info/views.py
new file: Info/views_company.py
new file: Info/views_company_api.py
new file: PLM/__init__.py
new file: PLM/asgi.py
new file: PLM/settings.py
new file: PLM/urls.py
new file: PLM/wsgi.py
new file: manage.py
new file: middleware/my_middleware.py

Jees 2 years ago
commit
e1f7da2323
100 changed files with 7721 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 45 0
      .vscode/launch.json
  3. 3 0
      .vscode/settings.json
  4. 0 0
      BOM/__init__.py
  5. 3 0
      BOM/admin.py
  6. 6 0
      BOM/apps.py
  7. 52 0
      BOM/func.py
  8. 22 0
      BOM/migrations/0001_initial.py
  9. 128 0
      BOM/migrations/0002_vendor_part_comment_part_company_and_more.py
  10. 24 0
      BOM/migrations/0003_alter_partattachment_part_and_more.py
  11. 35 0
      BOM/migrations/0004_partattachment_company_partattachment_creator_and_more.py
  12. 20 0
      BOM/migrations/0005_vendor_company.py
  13. 30 0
      BOM/migrations/0006_vendor_creator_vendor_owner_alter_part_creator.py
  14. 26 0
      BOM/migrations/0007_bom.py
  15. 24 0
      BOM/migrations/0008_bom_qty_alter_part_version.py
  16. 68 0
      BOM/migrations/0009_ecn_bomsetting_part_ecn_default_approvers_and_more.py
  17. 18 0
      BOM/migrations/0010_alter_ecn_status.py
  18. 18 0
      BOM/migrations/0011_alter_ecnsignoff_status.py
  19. 23 0
      BOM/migrations/0012_partattachment_storage_type_and_more.py
  20. 0 0
      BOM/migrations/__init__.py
  21. 310 0
      BOM/model_handler.py
  22. 125 0
      BOM/models.py
  23. 321 0
      BOM/templates/BOM/ECN_part_detail.html
  24. 22 0
      BOM/templates/BOM/directPage.html
  25. 44 0
      BOM/templates/BOM/draft_parts.html
  26. 24 0
      BOM/templates/BOM/layout.html
  27. 50 0
      BOM/templates/BOM/my_ECN_list.html
  28. 51 0
      BOM/templates/BOM/my_ECN_signoff_list.html
  29. 52 0
      BOM/templates/BOM/my_part_list.html
  30. 202 0
      BOM/templates/BOM/new_ecn.html
  31. 537 0
      BOM/templates/BOM/new_part.html
  32. 201 0
      BOM/templates/BOM/new_vendor.html
  33. 159 0
      BOM/templates/BOM/part_detail.html
  34. 87 0
      BOM/templates/BOM/part_search.html
  35. 50 0
      BOM/templates/BOM/part_search_result.html
  36. 77 0
      BOM/templates/BOM/part_whereused.html
  37. 3 0
      BOM/tests.py
  38. 37 0
      BOM/urls.py
  39. 326 0
      BOM/views.py
  40. 170 0
      BOM/views_part_api.py
  41. 64 0
      BOM/views_vendor_api.py
  42. 0 0
      IT_service/__init__.py
  43. 3 0
      IT_service/admin.py
  44. 6 0
      IT_service/apps.py
  45. 55 0
      IT_service/migrations/0001_initial.py
  46. 25 0
      IT_service/migrations/0002_case_contact_mobile_case_contact_name.py
  47. 32 0
      IT_service/migrations/0003_remove_case_comment_case_description_and_more.py
  48. 23 0
      IT_service/migrations/0004_rename_part_caseattachment_case_alter_case_status.py
  49. 0 0
      IT_service/migrations/__init__.py
  50. 27 0
      IT_service/model_handler.py
  51. 39 0
      IT_service/models.py
  52. 24 0
      IT_service/templates/IT_service/layout.html
  53. 281 0
      IT_service/templates/IT_service/new_case.html
  54. 3 0
      IT_service/tests.py
  55. 16 0
      IT_service/urls.py
  56. 42 0
      IT_service/views.py
  57. 68 0
      IT_service/views_user_api.py
  58. 0 0
      Info/__init__.py
  59. 3 0
      Info/admin.py
  60. 6 0
      Info/apps.py
  61. 92 0
      Info/func.py
  62. 45 0
      Info/migrations/0001_initial.py
  63. 31 0
      Info/migrations/0002_userhandler_user_position_alter_user_name.py
  64. 21 0
      Info/migrations/0003_alter_user_role_delete_userhandler.py
  65. 18 0
      Info/migrations/0004_rename_role_user_admin.py
  66. 63 0
      Info/migrations/0005_alter_company_address_alter_company_license_id_and_more.py
  67. 18 0
      Info/migrations/0006_user_psw_change_required.py
  68. 26 0
      Info/migrations/0007_message.py
  69. 36 0
      Info/migrations/0008_modular_user_comment_modularenablement.py
  70. 28 0
      Info/migrations/0009_rename_modular_modularenablement_modular_and_more.py
  71. 34 0
      Info/migrations/0010_internaluser.py
  72. 0 0
      Info/migrations/__init__.py
  73. 92 0
      Info/model_handler.py
  74. 75 0
      Info/models.py
  75. 1 0
      Info/static/axios.min.js
  76. 0 0
      Info/static/babel.min.js
  77. 5 0
      Info/static/bootstrap.bundle.min.js
  78. 0 0
      Info/static/bootstrap.bundle.min.js.map
  79. 5 0
      Info/static/bootstrap.min.css
  80. 0 0
      Info/static/bootstrap.min.css.map
  81. 12 0
      Info/static/info/javascript/register.js
  82. 1903 0
      Info/static/qs.js
  83. 239 0
      Info/static/react-dom.production.min.js
  84. 32 0
      Info/static/react.production.min.js
  85. 0 0
      Info/static/vue.global.prod.js
  86. 6 0
      Info/templates/info/bottom.html
  87. 18 0
      Info/templates/info/choicePage.html
  88. 70 0
      Info/templates/info/company/profile.html
  89. 51 0
      Info/templates/info/company/user_create.html
  90. 189 0
      Info/templates/info/company/user_manager.html
  91. 22 0
      Info/templates/info/directPage.html
  92. 164 0
      Info/templates/info/home.html
  93. 47 0
      Info/templates/info/internal/login.html
  94. 24 0
      Info/templates/info/layout.html
  95. 48 0
      Info/templates/info/login.html
  96. 80 0
      Info/templates/info/register.html
  97. 40 0
      Info/templates/info/user_password.html
  98. 39 0
      Info/templates/info/user_profile.html
  99. 3 0
      Info/tests.py
  100. 30 0
      Info/urls.py

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*/__pycache__/*
+*.pyc
+/venv/
+db.sqlite3

+ 45 - 0
.vscode/launch.json

@@ -0,0 +1,45 @@
+{
+    // 使用 IntelliSense 了解相关属性。 
+    // 悬停以查看现有属性的描述。
+    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+
+        {
+            "name": "Run Server",
+            "type": "python",
+            "request": "launch",
+            "program": "${workspaceFolder}\\manage.py",
+            "args": [
+                "runserver"
+            ],
+            "django": true,
+            "justMyCode": true,
+            "python":"D:\\Users\\Jees\\Documents\\coding\\PLM\\PLM\\venv\\Scripts\\python.exe"
+        },
+        {
+            "name": "migrate",
+            "type": "python",
+            "request": "launch",
+            "program": "${workspaceFolder}\\manage.py",
+            "args": [
+                "migrate"
+            ],
+            "django": true,
+            "justMyCode": true,
+            "python":"D:\\Users\\Jees\\Documents\\coding\\PLM\\PLM\\venv\\Scripts\\python.exe"
+        },
+        {
+            "name": "make migrations",
+            "type": "python",
+            "request": "launch",
+            "program": "${workspaceFolder}\\manage.py",
+            "args": [
+                "makemigrations"
+            ],
+            "django": true,
+            "justMyCode": true,
+            "python":"D:\\Users\\Jees\\Documents\\coding\\PLM\\PLM\\venv\\Scripts\\python.exe"
+        },
+    ]
+}

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "python.analysis.typeCheckingMode": "off"
+}

+ 0 - 0
BOM/__init__.py


+ 3 - 0
BOM/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
BOM/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class BomConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'BOM'

+ 52 - 0
BOM/func.py

@@ -0,0 +1,52 @@
+from .model_handler import PartAttachmentHandler, EcnSignOffHandler, EcnHandler, PartHandler
+from .models import Part, ECN
+from django.forms import model_to_dict
+from Info.func import send_message
+from django.db import transaction
+
+
+def part_info(part: Part) -> list:
+    part_attachments = PartAttachmentHandler.search_by_part(part)
+    attachment_list = []
+    for p_a in part_attachments:
+        attachment = {'id': p_a.id, 'url': p_a.url,
+            'display_name': p_a.display_name}
+        attachment_list.append(attachment)
+    part_dict = model_to_dict(part)
+    
+    try:
+        part_dict['sub_parts'] = eval(part_dict['sub_parts'])
+    except:
+        part_dict['sub_parts'] = []
+
+    part_dict['attachment'] = attachment_list
+    vendor_detail = {}
+    if part.vendor:
+        vendor_detail = {
+            'id': part.vendor.id,
+            'code': part.vendor.code,
+            'name': part.vendor.name,
+        }
+    part_dict['vendor_detail'] = vendor_detail
+    
+    return part_dict
+
+
+@transaction.atomic
+def overall_ecn_signoff(ecn_id: int) -> bool:
+    # 检索是否所有的签核者都签核了
+    ecn = EcnHandler.get_by_id(ecn_id)
+    all_sign_off = EcnSignOffHandler.search_by_ecn(ecn)
+    ecn_all_approved = True
+    for s_o in all_sign_off:
+        if s_o.status != '批准':
+            ecn_all_approved = False
+    if ecn_all_approved:
+        ecn.status = '批准'
+        # todo 检索ECO对应的料号,把最新版本更新为released,旧的版本更新为expired
+        ecn.target_part.life_cycle = 'released'
+        PartHandler.expire_old_version(ecn.target_part)
+        ecn.save()
+        send_message(from_user=None, to_user=ecn.owner, title='ECN'+ecn.number+'签核完成')
+    
+    return ecn_all_approved

+ 22 - 0
BOM/migrations/0001_initial.py

@@ -0,0 +1,22 @@
+# Generated by Django 4.0.5 on 2022-07-04 07:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Part',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('part_number', models.TextField()),
+                ('description', models.TextField()),
+            ],
+        ),
+    ]

+ 128 - 0
BOM/migrations/0002_vendor_part_comment_part_company_and_more.py

@@ -0,0 +1,128 @@
+# Generated by Django 4.0.5 on 2022-07-08 04:58
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Vendor',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('code', models.TextField()),
+                ('name', models.TextField()),
+                ('contact', models.TextField()),
+                ('phone', models.TextField()),
+                ('email', models.TextField(blank=True, null=True)),
+                ('address', models.TextField()),
+                ('comment', models.TextField(blank=True, null=True)),
+                ('status', models.TextField(default='normal')),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='comment',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='company',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company'),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='create_datetime',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='creator',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='create_of_part', to='Info.user'),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='last_update_datetime',
+            field=models.DateTimeField(auto_now=True),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='life_cycle',
+            field=models.TextField(default='draft'),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='owner',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='owner_of_part', to='Info.user'),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='source_type',
+            field=models.TextField(blank=True, default='build', null=True),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='sub_parts',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='vendor_part_number',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='version',
+            field=models.TextField(default='AA'),
+        ),
+        migrations.CreateModel(
+            name='VendorAttachment',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('url', models.TextField()),
+                ('display_name', models.TextField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('vendor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='BOM.vendor')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='PartAttachment',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('url', models.TextField()),
+                ('display_name', models.TextField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='BOM.part')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='BOMSetting',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('auto_pn', models.BooleanField(default=True)),
+                ('auto_ver', models.BooleanField(default=True)),
+                ('auto_supplier_code', models.BooleanField(default=True)),
+                ('attachment_maxsize', models.IntegerField(default=1000)),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='part',
+            name='vendor',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='BOM.vendor'),
+        ),
+    ]

+ 24 - 0
BOM/migrations/0003_alter_partattachment_part_and_more.py

@@ -0,0 +1,24 @@
+# Generated by Django 4.0.5 on 2022-07-09 02:54
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('BOM', '0002_vendor_part_comment_part_company_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='partattachment',
+            name='part',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='BOM.part'),
+        ),
+        migrations.AlterField(
+            model_name='vendorattachment',
+            name='vendor',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='BOM.vendor'),
+        ),
+    ]

+ 35 - 0
BOM/migrations/0004_partattachment_company_partattachment_creator_and_more.py

@@ -0,0 +1,35 @@
+# Generated by Django 4.0.5 on 2022-07-09 04:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0003_alter_partattachment_part_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='partattachment',
+            name='company',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company'),
+        ),
+        migrations.AddField(
+            model_name='partattachment',
+            name='creator',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='Info.user'),
+        ),
+        migrations.AddField(
+            model_name='vendorattachment',
+            name='company',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company'),
+        ),
+        migrations.AddField(
+            model_name='vendorattachment',
+            name='creator',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='Info.user'),
+        ),
+    ]

+ 20 - 0
BOM/migrations/0005_vendor_company.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.0.5 on 2022-07-11 10:32
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0004_partattachment_company_partattachment_creator_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='vendor',
+            name='company',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company'),
+        ),
+    ]

+ 30 - 0
BOM/migrations/0006_vendor_creator_vendor_owner_alter_part_creator.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.0.5 on 2022-07-12 09:30
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0005_vendor_company'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='vendor',
+            name='creator',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='create_of_vendor', to='Info.user'),
+        ),
+        migrations.AddField(
+            model_name='vendor',
+            name='owner',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='owner_of_vendor', to='Info.user'),
+        ),
+        migrations.AlterField(
+            model_name='part',
+            name='creator',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='creator_of_part', to='Info.user'),
+        ),
+    ]

+ 26 - 0
BOM/migrations/0007_bom.py

@@ -0,0 +1,26 @@
+# Generated by Django 4.0.5 on 2022-07-13 05:40
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0006_vendor_creator_vendor_owner_alter_part_creator'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='BOM',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('child_pn', models.TextField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='Info.user')),
+                ('parent_part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parent_part', to='BOM.part')),
+            ],
+        ),
+    ]

+ 24 - 0
BOM/migrations/0008_bom_qty_alter_part_version.py

@@ -0,0 +1,24 @@
+# Generated by Django 4.0.5 on 2022-07-16 13:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('BOM', '0007_bom'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='bom',
+            name='qty',
+            field=models.DecimalField(decimal_places=4, default=1, max_digits=9),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='part',
+            name='version',
+            field=models.IntegerField(default=0),
+        ),
+    ]

+ 68 - 0
BOM/migrations/0009_ecn_bomsetting_part_ecn_default_approvers_and_more.py

@@ -0,0 +1,68 @@
+# Generated by Django 4.0.5 on 2022-07-27 13:22
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+        ('BOM', '0008_bom_qty_alter_part_version'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ECN',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('number', models.TextField()),
+                ('status', models.TextField(default='sign-off')),
+                ('comment', models.TextField(blank=True, null=True)),
+                ('category', models.TextField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+                ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='create_of_ecn', to='Info.user')),
+                ('target_part', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='BOM.part')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='bomsetting',
+            name='part_ecn_default_approvers',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='bomsetting',
+            name='vendor_ecn_default_approvers',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='bomsetting',
+            name='auto_pn',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AlterField(
+            model_name='vendor',
+            name='status',
+            field=models.TextField(default='sign-off'),
+        ),
+        migrations.CreateModel(
+            name='EcnSignOff',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('comment', models.TextField(blank=True, null=True)),
+                ('status', models.TextField(default='pending')),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+                ('ecn', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='BOM.ecn')),
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='ecn_sign_off_owner', to='Info.user')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='ecn',
+            name='target_vendor',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='BOM.vendor'),
+        ),
+    ]

+ 18 - 0
BOM/migrations/0010_alter_ecn_status.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.0.5 on 2022-07-31 03:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('BOM', '0009_ecn_bomsetting_part_ecn_default_approvers_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='ecn',
+            name='status',
+            field=models.TextField(default='签核中'),
+        ),
+    ]

+ 18 - 0
BOM/migrations/0011_alter_ecnsignoff_status.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.0.5 on 2022-08-14 11:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('BOM', '0010_alter_ecn_status'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='ecnsignoff',
+            name='status',
+            field=models.TextField(default='签核中'),
+        ),
+    ]

+ 23 - 0
BOM/migrations/0012_partattachment_storage_type_and_more.py

@@ -0,0 +1,23 @@
+# Generated by Django 4.0.5 on 2023-02-23 04:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('BOM', '0011_alter_ecnsignoff_status'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='partattachment',
+            name='storage_type',
+            field=models.TextField(default='local'),
+        ),
+        migrations.AddField(
+            model_name='vendorattachment',
+            name='storage_type',
+            field=models.TextField(default='local'),
+        ),
+    ]

+ 0 - 0
BOM/migrations/__init__.py


+ 310 - 0
BOM/model_handler.py

@@ -0,0 +1,310 @@
+from .models import *
+from Info.func import delete_file
+from Info.model_handler import UserHandler
+
+
+class VendorHandler():
+
+    @staticmethod
+    def get_by_code(code, company):
+        try:
+            vendor = Vendor.objects.get(code=code, company=company)
+            return vendor
+        except:
+            return None
+
+    @staticmethod
+    def search_like_name(keyword, company):
+        vendors = Vendor.objects.filter(name__contains = keyword, company=company)
+        return vendors
+
+    
+    @staticmethod
+    def search_by_name(search_name, company):
+        vendors = Vendor.objects.filter(company=company, name = search_name)
+        return vendors
+
+
+class VendorAttachmentHandler():      
+
+    @staticmethod
+    def get_by_id(ID):
+        try:
+            attachment = VendorAttachment.objects.get(id=ID)
+            return attachment
+        except:
+            return None
+
+
+class PartHandler():
+
+    @staticmethod
+    def my_part(user: User, life_cycle=None):
+        if life_cycle:
+            return Part.objects.filter(owner=user, life_cycle=life_cycle)
+        else:
+            return Part.objects.filter(owner=user, life_cycle='released')
+
+    @staticmethod
+    def get_by_id(ID):
+        try:
+            part = Part.objects.get(id=ID)
+            part = PartHandler.part_ver_display(part)
+            return part
+        except:
+            return None
+
+    @staticmethod
+    def exist_pn(pn, company):      # 检查料号是否已经使用过
+        try:
+            part = Part.objects.get(part_number=pn, company=company)
+            return part
+        except:
+            return None
+
+    @staticmethod
+    def get_by_pn(pn, company, ver=None):
+        try:
+            if ver:
+                part = Part.objects.get(part_number=pn, company=company, version=ver)
+            else:
+                parts= Part.objects.filter(part_number=pn, company=company, life_cycle='released').order_by('-version')
+                part = parts[0]
+
+            part = PartHandler.part_ver_display(part)    
+            return part
+        except:
+            return None
+
+    @staticmethod
+    def search_like_pn(keyword, company, lifecycle=None):
+        parts = Part.objects.filter(part_number__contains = keyword, company=company)
+        if lifecycle:
+            parts.filter(life_cycle__in=lifecycle)
+        else:
+            parts.filter(life_cycle__in=['released'])
+        return parts
+
+    @staticmethod
+    def my_draft_parts(creator):
+        parts = Part.objects.filter(creator=creator, life_cycle='draft')
+        return parts
+
+    @staticmethod
+    def all_ver_by_pn(part_number, company):
+        return Part.objects.filter(part_number=part_number, company=company).order_by('-version')
+
+    @staticmethod
+    def new_ver(old_part, company, user):
+        current_part = PartHandler.get_by_id(old_part.id)
+        new_part = Part()
+        new_part.part_number = current_part.part_number
+        new_part.description = current_part.description
+        new_part.version = current_part.version + 1
+        new_part.company = current_part.company
+        new_part.creator = user
+        new_part.owner = user
+        new_part.source_type = current_part.source_type
+        new_part.vendor = current_part.vendor
+        new_part.sub_parts = current_part.sub_parts
+        new_part.vendor_part_number = current_part.vendor_part_number
+        new_part.save()
+        return new_part
+
+    @staticmethod
+    def part_local_CHN(part: Part):     # 将part的信息中文化
+        if part.life_cycle == 'draft':
+            part.life_cycle = '草稿'
+        if part.life_cycle == 'sign-off':
+            part.life_cycle = '签核中'
+        if part.life_cycle == 'released':
+            part.life_cycle = '使用中'
+        if part.life_cycle == 'expired':
+            part.life_cycle = '停用'
+        if part.life_cycle == 'EOL':
+            part.life_cycle = '不再使用'
+
+        # if part.version < 10:
+        #     part.version_display = '0'+str(part.version)
+        # else:
+        #     part.version_display = part.version
+        
+        if part.source_type == 'build':
+            part.source_type = '自产'
+        if part.source_type == 'purchase':
+            part.source_type = '外购'
+
+        # part = PartHandler.part_ver_display(part)
+
+        return part
+
+    @staticmethod
+    def part_ver_display(part: Part):
+        if part.version < 10:
+            part.version_display = '0'+str(part.version)
+        else:
+            part.version_display = part.version
+        
+        return part
+
+    @staticmethod
+    def expire_old_version(part: Part):
+        old_version_parts = Part.objects.filter(version__gt = part.version)
+        old_version_parts.update(life_cycle='expired')
+
+    @staticmethod
+    def part_list_local_CHN(part_list):     # 将part_list的信息中文化
+        for part in part_list:
+            part = PartHandler.part_local_CHN(part)
+
+        return part_list
+
+    @staticmethod
+    def part_search(company, part_number, description, owner, include_EOL=False):
+        owner_list = UserHandler.search_like_name(owner, company)
+        if include_EOL:
+            life_cycle_allowed = ['released', 'EOL']
+        else:
+            life_cycle_allowed = ['released']
+        parts = Part.objects.filter(company=company, part_number__contains = part_number, description__contains = description, owner__in = owner_list, life_cycle__in = life_cycle_allowed)
+        for part in parts:
+            part = PartHandler.part_ver_display(part)
+        return parts
+
+
+class PartAttachmentHandler():      
+
+    @staticmethod
+    def get_by_id(ID):
+        try:
+            attachment = PartAttachment.objects.get(id=ID)
+            return attachment
+        except:
+            return None
+
+    @staticmethod
+    def search_by_part(part):
+        attchments = PartAttachment.objects.filter(part=part)
+        return attchments
+
+    @staticmethod
+    def flush_by_part(part):
+        attachments = PartAttachment.objects.filter(part=part)
+        for file in attachments:
+            delete_file(file.url)
+        attachments.delete()
+
+    @staticmethod
+    def del_by_id(ID):
+        try:
+            attachment = PartAttachment.objects.get(id=ID)
+            delete_file(attachment.url)
+            attachment.delete()
+            return True
+        except:
+            return False
+
+class BOMHandler():
+
+    @staticmethod
+    def retrieve_BOM(parent_part: Part, company: Company):
+        BOM_breakdown = BOM.objects.filter(parent_part=parent_part)
+        for item in BOM_breakdown:
+            part = PartHandler.get_by_pn(item.child_pn, company=company)
+            if part:
+                item.description = part.description
+            else:
+                item.description = None
+        return BOM_breakdown
+
+    @staticmethod
+    def where_used(child_pn, include_EOL=False):
+        if include_EOL:
+            parent_part_life_cycle_allowed = ['released', 'EOL']
+        else:
+            parent_part_life_cycle_allowed = ['released']
+        where_used_result = BOM.objects.filter(child_pn=child_pn, parent_part__life_cycle__in = parent_part_life_cycle_allowed)
+        parent_parts=[]
+        for result in where_used_result:
+            part = PartHandler.part_ver_display(result.parent_part)
+            parent_parts.append(part)
+        return parent_parts
+
+    @staticmethod
+    def clear_BOM(parent_part):
+        BOM.objects.filter(parent_part=parent_part).delete()
+
+    @staticmethod
+    def flush_and_save_BOM(parent_part, sub_parts:list,) -> bool:
+        try:
+            BOMHandler.clear_BOM(parent_part)
+            for s_p in sub_parts:
+                bom = BOM()
+                bom.parent_part = parent_part
+                bom.creator = parent_part.creator
+                bom.child_pn = s_p['part_number']
+                bom.qty = s_p['qty']
+                bom.save()
+            return True
+        except:
+            return False
+
+
+class EcnHandler:
+
+    @staticmethod
+    def generate_ecn_number(company):
+        qty = ECN.objects.filter(company=company).count()+1
+        ECN_number = str(qty).zfill(8)
+        return ECN_number
+
+    @staticmethod
+    def get_by_id(ID):
+        try:
+            ecn = ECN.objects.get(id=ID)
+            return ecn
+        except:
+            return None
+
+    @staticmethod
+    def my_ecn(user: User):
+        return ECN.objects.filter(creator=user)
+
+    
+class EcnSignOffHandler():
+
+    @staticmethod
+    def my_ECN(user, status=None):
+        ECNs = EcnSignOff.objects.filter(owner=user)
+        if status:
+            ECNs = ECNs.filter(status=status)
+        return ECNs
+
+    @staticmethod
+    def my_pending_eco_count(user):
+        ECNs = EcnSignOffHandler.my_ECN(user=user, status='签核中')
+        return ECNs.count()
+
+    @staticmethod
+    def get_by_owner_ecn(user: User, ecn: ECN):        # 通过user和ecn查找签核记录
+        try:
+            sign_off = EcnSignOff.objects.get(owner=user, ecn=ecn)
+            return sign_off
+        except:
+            return None
+
+    @staticmethod
+    def search_by_ecn(ecn: ECN):
+        sign_offs = EcnSignOff.objects.filter(ecn=ecn)
+        return sign_offs
+
+
+class BOMsettingHandler():
+
+    @staticmethod
+    def get_attachment_maxsize(company:Company) -> int:
+        try:
+            BOMsetting = BOMSetting.objects.get(company=company)
+            return BOMsetting.attachment_maxsize
+        except:
+            return None

+ 125 - 0
BOM/models.py

@@ -0,0 +1,125 @@
+from django.db import models
+from Info.models import User, Company
+
+# Create your models here.
+
+
+class Vendor(models.Model):
+    id = models.AutoField(primary_key=True)
+    code = models.TextField()
+    name = models.TextField()
+    contact = models.TextField()
+    phone = models.TextField()
+    email = models.TextField(blank=True, null=True)
+    address = models.TextField()
+    comment = models.TextField(blank=True, null=True)
+    status = models.TextField(default='sign-off')     # 状态 sign-off,normal,EOL
+    creator = models.ForeignKey(
+        to=User, on_delete=models.DO_NOTHING, related_name="create_of_vendor", null=True, blank=True)        # 创建人
+    owner = models.ForeignKey(
+        to=User, on_delete=models.DO_NOTHING, related_name="owner_of_vendor", null=True, blank=True)        # 现在的管理者
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=True)       # 料号归属公司
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class Part(models.Model):
+    id = models.AutoField(primary_key=True)
+    part_number = models.TextField()
+    description = models.TextField()
+    comment = models.TextField(blank=True, null=True)
+    version = models.IntegerField(default=0)
+    # 料号的状态,draft,review,production,EOL
+    life_cycle = models.TextField(default='draft')      
+    # draft sign-off released expired EOL 表示料号+版本处在生命周期。
+    # draft sign-off 只能有一个,表示BOM在签核中,
+    # released只能有一个。
+    # expired可以有多一个,表示旧的版本。
+    # 如果EOL的话,全部版本都要EOL
+    sub_parts = models.TextField(blank=True, null=True)      # 将一个包含下一阶料号ID的list转成string
+    creator = models.ForeignKey(
+        to=User, on_delete=models.DO_NOTHING, related_name="creator_of_part", null=True, blank=True)        # 创建人
+    owner = models.ForeignKey(
+        to=User, on_delete=models.DO_NOTHING, related_name="owner_of_part", null=True, blank=True)        # 现在的管理者
+    company = models.ForeignKey(
+        to=Company, on_delete=models.CASCADE, null=True)       # 料号归属公司
+    source_type = models.TextField(null=True, blank=True, default='build')     # 原料来源,采购,生产
+    vendor = models.ForeignKey(
+        to=Vendor, on_delete=models.CASCADE, blank=True, null=True)       # 料号采购公司
+    vendor_part_number = models.TextField(blank=True, null=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class PartAttachment(models.Model):
+    id = models.AutoField(primary_key=True)
+    part = models.ForeignKey(to=Part, on_delete=models.CASCADE, blank=True, null=True)
+    storage_type = models.TextField(default="local")    # 存储类型 local/cloud 默认为local
+    url = models.TextField()
+    display_name = models.TextField()
+    creator = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, null=True, blank=True)        # 创建人
+    company = models.ForeignKey( to=Company, on_delete=models.CASCADE, null=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class VendorAttachment(models.Model):
+    id = models.AutoField(primary_key=True)
+    vendor = models.ForeignKey(to=Vendor, on_delete=models.CASCADE, blank=True, null=True)
+    storage_type = models.TextField(default="local")    # 存储类型 local/cloud 默认为local
+    url = models.TextField()
+    display_name = models.TextField()
+    creator = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, null=True, blank=True)        # 创建人
+    company = models.ForeignKey( to=Company, on_delete=models.CASCADE, null=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class BOMSetting(models.Model):
+    id = models.AutoField(primary_key=True)
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=True)
+    auto_pn = models.BooleanField(default=False)
+    auto_ver = models.BooleanField(default=True)
+    auto_supplier_code = models.BooleanField(default=True)
+    part_ecn_default_approvers = models.TextField(blank=True, null=True)
+    vendor_ecn_default_approvers = models.TextField(blank=True, null=True)
+    attachment_maxsize = models.IntegerField(default=1000)      # 默认公司保存附件大小(单位M)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class BOM(models.Model):
+    id = models.AutoField(primary_key=True)
+    parent_part = models.ForeignKey(to=Part, on_delete=models.CASCADE, related_name="parent_part")
+    # child_part = models.ForeignKey(to=Part, on_delete=models.CASCADE, related_name="child_part")
+    child_pn = models.TextField()       # child PN 只记录子料号,不记录版本等其他信息。所以不适用foreign key
+    qty = models.DecimalField(max_digits=9, decimal_places=4)
+    creator = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, null=True, blank=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class ECN(models.Model):
+    id = models.AutoField(primary_key=True)
+    number = models.TextField()
+    creator = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, related_name="create_of_ecn", null=True, blank=True)        # 创建人
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=True)       # ECN归属公司
+    status = models.TextField(default='签核中')     # 状态,默认签核中,d,拒绝,撤回sign-off,release,reject,widthdraw。不能删除,否则将导致ECN number出错
+    comment = models.TextField(blank=True, null=True)
+    category = models.TextField()       # 类型 part, vendor
+    target_part = models.ForeignKey(to=Part, on_delete=models.CASCADE, null=True)
+    target_vendor = models.ForeignKey(to=Vendor, on_delete=models.CASCADE, null=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class EcnSignOff(models.Model):
+    id = models.AutoField(primary_key=True)
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=True)       # ECN sign off归属公司
+    ecn = models.ForeignKey(to=ECN, on_delete=models.CASCADE)
+    owner = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, related_name="ecn_sign_off_owner", null=True, blank=True)        # 签核的人
+    comment = models.TextField(blank=True, null=True)
+    status = models.TextField(default='签核中')     # 状态,默认签核中,批准,拒绝
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+

+ 321 - 0
BOM/templates/BOM/ECN_part_detail.html

@@ -0,0 +1,321 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div id="mainForm"></div>
+        <div class="card">
+            <div class="card-header">ECN总体签核状态</div>
+            <div class="card-body">
+                {% for sign_off in sign_offs %}
+                <div class="alert alert-secondary">
+                    <div>签核者:{{ sign_off.owner.name }}</div>
+                    <div>签核状态:{{ sign_off.status }}</div>
+                    <div>签核备注:<div>{{ sign_off.comment }}</div>
+                    </div>
+                </div>
+                {% endfor %}
+            </div>
+        </div>
+        {% if is_ecn_signoff %}
+        <div class="mb-3 mt-3">
+            <label for="comment" class="form-label">ECN签核备注:</label>
+            <textarea type="textarea" class="form-control" id="sign_off_comment" placeholder="输入ECN签核备注"
+                name="sign_off_comment" maxlength="256"></textarea>
+        </div>
+        {% endif %}
+        
+        <div id="form-button-area">
+            {% if is_ecn_signoff %}
+            <button type="submit" name="approve" value="approve" class="btn btn-success">批准</button>
+            <button type="submit" name="reject" value="reject" class="btn btn-danger">拒绝</button>
+                {% if is_ecn_creator %}
+                <button type="submit" name="cancel" value="cancel" class="btn btn-danger">取消</button>
+                {% endif %}
+            {% endif %}
+            {% comment %} <a href="/BOM/ECN/my_list/">返回列表</a> {% endcomment %}
+            <a href="#" onclick="window.history.back();">返回列表</a>
+        </div>
+
+    </form>
+</div>
+<script type="text/babel">
+
+    class MainForm extends React.Component {
+
+        constructor(props) {
+            super(props);
+            this.state = {
+                ecn_id: 0,
+                id: 0,
+                part_number: "",
+                description: "",
+                comment: "",
+                version: "",
+                sub_parts: [],
+                source_type: 'build',
+                vendor_name: "",
+                vendor_detail: {},
+                vendor_part_number: "",
+                attachment: []
+            }
+        }
+
+        componentDidMount() {
+            let id = this.getQueryVariable("ecn_id")
+            if (id) {
+                this.genEcnPartDetail(id)
+                this.forceUpdate()
+            }
+        }
+
+        genEcnPartDetail(id) {
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: { ecn_id: id },
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/BOM/part/ecn/part_detail/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    // 检测失败
+                    alert(response.data.content)
+                } else {
+                    // 检测成功
+                    console.log('content', response.data.content)
+                    let part_data = response.data.content.part
+                    if (part_data.version.toString().length < 2) {
+                        part_data.version = "0" + part_data.version
+                    }
+                    that.setState({
+                        id: part_data.id,
+                        part_number: part_data.part_number,
+                        description: part_data.description,
+                        comment: part_data.comment,
+                        version: part_data.version,
+                        sub_parts: part_data.sub_parts,
+                        source_type: part_data.source_type,
+                        vendor_name: part_data.vendor_name,
+                        vendor_detail: part_data.vendor_detail,
+                        vendor_part_number: part_data.vendor_part_number,
+                        attachment: part_data.attachment
+                    })
+                    document.getElementById("part_number").value = that.state.part_number
+                    document.getElementById("description").value = that.state.description
+                    document.getElementById("version").value = that.state.version
+                    document.getElementById("comment").value = that.state.comment
+                    document.getElementById("part_number").value = that.state.part_number
+                    document.getElementById("source_type").value = that.state.source_type
+                    if (that.state.source_type == 'purchase') {
+                        document.getElementById("vendor_name").value = that.state.vendor_detail.name
+                        document.getElementById("vendor_part_number").value = that.state.vendor_detail.code
+                    }
+                    that.setState({
+                        ecn_id: response.data.content.ecn_id
+                    })
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        sourceInfo() {
+            console.log("this.state.source_type", this.state.source_type)
+            if (this.state.source_type == 'build') {
+                return (
+                    <div></div>
+                )
+            }
+            if (this.state.source_type == 'purchase') {
+                return (
+                    <div>
+                        <div class="mb-3 mt-3">
+                            <label for="vendor_name" class="form-label">供应商:</label>
+                            <div class="supplier_search">
+                                <input type="text" class="form-control" id="vendor_name" placeholder="输入供应商" name="vendor_name" required maxlength="128" disabled="true" />
+                            </div>
+                        </div>
+                        <div class="mb-3 mt-3">
+                            <label for="vendor_part_number" class="form-label">供应商料号:</label>
+                            <input type="text" class="form-control" id="vendor_part_number" placeholder="输入供应商料号" name="vendor_part_number" maxlength="16" disabled="true" />
+                        </div>
+                    </div>
+
+                )
+            }
+            // this.forceUpdate()
+        }
+
+        attachmentList() {
+            if (this.state.attachment.length == 0) {
+                return (
+                    null
+                )
+            } else {
+                return (
+                    <div class="attachmentList">
+                        {this.state.attachment.map((row, index) =>
+                            <a href={row.url}>{row.display_name}</a>
+                        )}
+                    </div>
+                )
+            }
+        }
+
+        subPartForm() {
+            let formFrame = () => {
+                return (
+                    <div>
+                        <table className="table table-striped">
+                            <thead>
+                                <tr>
+                                    <th>料号</th>
+                                    <th>描述</th>
+                                    <th>数量</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {items()}
+                            </tbody>
+                        </table>
+
+                    </div>
+                )
+            }
+            let items = () => {
+                return (
+                    this.state.sub_parts.map((item, index) =>
+                        <tr key={index}>
+                            <td>{item.part_number}</td>
+                            <td>{item.description}</td>
+                            <td><input class="form-control BOM-qty" type="number" value={item.qty} disabled="true" /></td>
+                        </tr>
+                    )
+                )
+            }
+            if (this.state.sub_parts.length == 0) {
+                return ("")
+            } else {
+                return (
+                    <div>
+                        {formFrame()}
+                    </div>
+                )
+            }
+        }
+
+        showState() {
+            console.log(this.state)
+        }
+
+        getQueryVariable(variable) {
+            var query = window.location.search.substring(1);
+            var vars = query.split("&");
+            for (var i = 0; i < vars.length; i++) {
+                var pair = vars[i].split("=");
+                if (pair[0] == variable) { return pair[1]; }
+            }
+            return (false);
+        }
+
+
+        render() {
+            this.showState()
+            return (
+                <div>
+                    <input type="hidden" name="id" value={this.state.id} />
+                    <input type="hidden" name="ecn_id" value={this.state.ecn_id} />
+                    <div class="mb-3 mt-3">
+                        <label for="part_number" class="form-label">料号:</label>
+                        <input type="text" class="form-control" id="part_number" placeholder="输入料号" name="part_number" required maxlength="16" minlength="5" disabled="true" />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="description" class="form-label">描述:</label>
+                        <input type="text" class="form-control" id="description" placeholder="输入描述" name="description" required maxlength="128" disabled="true" />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="version" class="form-label">版本号:</label>
+                        <input type="text" class="form-control" id="version" placeholder="输入版本号" name="version" required maxlength="8" disabled="true" />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="source_type" class="form-label">来源:</label>
+                        <select class="form-select" id="source_type" name="source_type" disabled="true">
+                            <option value="build" >自产</option>
+                            <option value="purchase" >采购</option>
+                        </select>
+                    </div>
+                    {this.sourceInfo()}
+                    <div class="mb-3 mt-3">
+                        <label for="comment" class="form-label">料号备注:</label>
+                        <textarea type="textarea" class="form-control" id="comment" placeholder="输入备注" name="comment" maxlength="256" disabled="true" value={this.state.part_number}></textarea>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">附件</div>
+                        <div class="card-body">
+                            {this.attachmentList()}
+                        </div>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">下阶物料</div>
+                        <div class="card-body">
+                            {this.subPartForm()}
+                        </div>
+
+                    </div>
+
+                </div>
+            )
+        }
+    }
+
+    ReactDOM.render(<MainForm />, document.getElementById("mainForm"))
+
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+        flex-wrap: wrap;
+    }
+
+    div.supplier_search {
+        display: flex;
+        justify-content: flex-start;
+    }
+
+    .card {
+        margin-bottom: 10px;
+        width: 100%;
+    }
+
+    .BOM-qty {
+        width: 100px;
+    }
+</style>
+{% endblock body %}

+ 22 - 0
BOM/templates/BOM/directPage.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="utf-8" />
+    <title>directPage</title>
+</head>
+<body>
+    {% if alertMsg != None %}
+        <script type="text/javascript">
+            {% autoescape off %}
+            alert ('{{alertMsg}}');
+            {% endautoescape %}
+        </script>
+    {% endif %}
+    {% if dirLink %}
+        <script type="text/javascript">
+            window.location.href = '{{dirLink}}';
+        </script>
+    {% endif %}
+</body>
+</html>

+ 44 - 0
BOM/templates/BOM/draft_parts.html

@@ -0,0 +1,44 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+{% comment %} <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}" />
+<script src="{% static 'bootstrap.bundle.min.js' %}"></script> {% endcomment %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <table class="table table-striped">
+        <thead>
+            <tr>
+                <th>料号</th>
+                <th>描述</th>
+                <th>版本</th>
+                <th>编辑</th>
+                <th>删除</th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for item in draft_parts %}
+                <tr>
+                    <td>{{ item.part_number }}</td>
+                    <td>{{ item.description }}</td>
+                    <td>{% if item.version|length > 1 %} {{ item.version }} {% else %} 0{{ item.version }} {% endif %}  </td>
+                    <td><a href="/BOM/part/create/?edit_id={{ item.id }}">编辑</a></td>
+                    <td><a href="/BOM/part/delete/?edit_id={{ item.id }}">删除</a></td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+    <div id="button-area"><a href="/home/">返回</a></div>
+</div>
+
+<script type="text/babel">
+</script>
+<style type="text/css">
+
+    #button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 24 - 0
BOM/templates/BOM/layout.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta charset="utf-8" />
+        <meta http-equiv="X-UA-Compatible" content="chrome=1" />
+        <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
+        {% load static %}
+        <title>{% if title %} {{title}} {% else %} Nimble System {% endif %}</title>
+        <!-- 新 Bootstrap5 核心 CSS 文件 -->
+        {% comment %} <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css" /> {% endcomment %}
+        <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}" />
+        <!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
+        {% comment %} <script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script> {% endcomment %}
+        <script src="{% static 'bootstrap.bundle.min.js' %}"></script>
+        {% comment %} <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> {% endcomment %}
+        {% block head %}{% endblock head %}
+        {% block script %}{% endblock script %}
+    </head>
+    {% load static %}
+    <body>
+        {% block body %}{% endblock body %}
+        {% include 'info/bottom.html' %}
+    </body>
+</html>

+ 50 - 0
BOM/templates/BOM/my_ECN_list.html

@@ -0,0 +1,50 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <div>
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th>ECN号码</th>
+                    <th>料号</th>
+                    <th>版本</th>
+                    {% comment %} <th>发起者</th> {% endcomment %}
+                    <th>状态</th>
+                    <th>查看</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for ecn in ecn_list %}
+                <tr>
+                    <td>ECN{{ ecn.number }}</td>
+                    <td>{{ ecn.target_part.part_number }}</td>
+                    <!-- <td>{{ sign_off.ecn.target_part.version }}</td> -->
+                    <td>{% if ecn.target_part.version|length > 1 %} {{ ecn.target_part.version }} {% else %} 0{{ ecn.target_part.version }} {% endif %}  </td>
+                    {% comment %} <td>{{ ecn.creator.name }}</td> {% endcomment %}
+                    <td>{{ ecn.status }}</td>
+                    <td><a href="/BOM/ECN/detail/?ecn_id={{ ecn.id }}">查看</a></td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+    <div id="form-button-area">
+        {% comment %} <button type="submit" id="submit" class="btn btn-primary" disabled={!this.state.sign_off_owners.length}>提交</button> {% endcomment %}
+        <a href="/home/">返回</a>
+    </div>
+</div>
+<script type="text/babel">
+
+
+</script>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 51 - 0
BOM/templates/BOM/my_ECN_signoff_list.html

@@ -0,0 +1,51 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <div>
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th>ECN号码</th>
+                    <th>料号</th>
+                    <th>版本</th>
+                    <th>发起者</th>
+                    <th>状态</th>
+                    <th>查看</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for sign_off in sign_off_list %}
+                <tr>
+                    <td>ECN{{ sign_off.ecn.number }}</td>
+                    <td>{{ sign_off.ecn.target_part.part_number }}</td>
+                    <!-- <td>{{ sign_off.ecn.target_part.version }}</td> -->
+                    <td>{% if sign_off.ecn.target_part.version|length > 1 %} {{ sign_off.ecn.target_part.version }} {% else %} 0{{ sign_off.ecn.target_part.version }} {% endif %}  </td>
+                    <td>{{ sign_off.ecn.creator.name }}</td>
+                    <td>{{ sign_off.ecn.status }}</td>
+                    <td><a href="/BOM/ECN/detail/?ecn_id={{ sign_off.ecn.id }}">查看</a></td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+
+    <div id="form-button-area">
+        {% comment %} <button type="submit" id="submit" class="btn btn-primary" disabled={!this.state.sign_off_owners.length}>提交</button> {% endcomment %}
+        <a href="/home/">返回</a>
+    </div>
+</div>
+<script type="text/babel">
+
+
+</script>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 52 - 0
BOM/templates/BOM/my_part_list.html

@@ -0,0 +1,52 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <div>
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th>料号</th>
+                    <th>版本</th>
+                    <th>状态</th>
+                    <th>查看</th>
+                    <th>更新版本</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for part in part_list %}
+                <tr>
+                    <td>{{part.part_number }}</td>
+                    <td>{{ part.version }}  </td>
+                    <td>{{ part.life_cycle }}</td>
+                    <td><a href="/BOM/part/detail/?pn={{ part.part_number }}">查看</a></td>
+                    <td><a href="/BOM/part/upgrade/?pn={{ part.part_number }}">更新版本</a></td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+    <div>
+        <a href="/BOM/part/my_list/?life_cycle=EOL">查看停用料号</a>
+        <a href="/BOM/part/my_list/?life_cycle=sign-off">查看签核中的料号</a>
+        <a href="/BOM/part/edit/list/">查看编辑中的料号</a>
+    </div>
+    <div id="form-button-area">
+        {% comment %} <button type="submit" id="submit" class="btn btn-primary" disabled={!this.state.sign_off_owners.length}>提交</button> {% endcomment %}
+        <div><a href="/home/">返回</a></div>
+    </div>
+</div>
+<script type="text/babel">
+
+
+</script>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 202 - 0
BOM/templates/BOM/new_ecn.html

@@ -0,0 +1,202 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div>
+            <input type="hidden" name="part_id" value="{{ part.id }}" />
+            <input type="hidden" name="category" value="part" />
+            <div class="mb-3 mt-3">
+                <label for="part_number" class="form-label">料号:</label>
+                <input type="text" class="form-control" id="part_number" name="part_number" disabled="true" value="{{ part.part_number }}"/>
+            </div>
+            <div class="mb-3 mt-3">
+                <label for="description" class="form-label">描述:</label>
+                <input type="text" class="form-control" id="description" name="description" disabled value="{{ part.description }}" />
+            </div>
+            <div class="mb-3 mt-3">
+                <label for="version" class="form-label">版本号:</label>
+                <input type="text" class="form-control" id="version"  name="version" disabled value="{{ part.version }}"/>
+            </div>
+        </div>
+        <div id="mainForm"></div>
+    </form>
+</div>
+<script type="text/babel">
+
+    class MainForm extends React.Component {
+        constructor(props) {
+            super(props);
+            this.state = {
+                comment:"",
+                sign_off_owners:[]
+            }
+        }
+
+        formItemChange(e) {
+            var key = e.target.name.trim()
+            var value = e.target.value.trim()
+            console.log("form item chnange", key, value)
+            // this.setState({ key: value })
+            this.state[key] = value
+            this.forceUpdate()
+        }
+        
+        showState() {
+            console.log(this.state)
+        }
+
+        signOffOwnersForm() {
+            let formFrame = () => {
+                return (
+                    <div>
+                        <table className="table table-striped">
+                            <thead>
+                                <tr>
+                                    <th>姓名</th>
+                                    <th>职位</th>
+                                    <th>邮箱</th>
+                                    <th>删除</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {items()}
+                            </tbody>
+                        </table>
+
+                    </div>
+                )
+            }
+            let items = () => {
+                return (
+                    this.state.sign_off_owners.map((item, index) =>
+                        <tr key={index}>
+                            <td>{item.name}</td>
+                            <td>{item.position}</td>
+                            <td>{item.email}</td>
+                            <td onClick={() => this.deleteSignOffOwner(index)}>删除</td>
+                        </tr>
+                    )
+                )
+            }
+            if (this.state.sign_off_owners.length == 0) {
+                return ("")
+            } else {
+                return (
+                    <div>
+                        {formFrame()}
+                    </div>
+                )
+            }
+        }
+
+        deleteSignOffOwner(index) {
+            this.state.sign_off_owners.splice(index, 1)
+            this.forceUpdate()
+        }
+
+        addSignOffOwner() {
+            let input = window.prompt("输入签核者姓名")
+            if (input != null && input != "") {
+                var owner_name = input.trim()
+            }
+            if (owner_name) {
+                let that = this
+                axios({
+                    method: "GET",
+                    headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                    params: { user_name: owner_name },
+                    // data: Qs.stringify({ delete_user_id: user_id }),
+                    url: "/home/company/api/user/search/"
+                }).then(function (response) {
+                    if (response.data.code == 0) {
+                        let owners = response.data.content
+                        if (owners.length == 0) {
+                            alert("没有找到用户")
+                        }
+                        if (owners.length == 1) {
+                            console.log('owner is ', owners)
+                            let owner = {
+                                id: owners[0].id,
+                                name: owners[0].name,
+                                email: owners[0].email,
+                                position: owners[0].position,
+                            }
+                            for (let i = 0; i < that.state.sign_off_owners.length; i++) {
+                                if (that.state.sign_off_owners[i].id == owner.id) {
+                                    alert(owner.name + "已经添加了")
+                                    return
+                                }
+                            }
+                            that.setState({ sign_off_owners: that.state.sign_off_owners.concat([owner]) })
+                            // that.state.sub_parts.concat([sub_part])
+                            alert("找到" + owner.name )
+                            // that.forceUpdate()
+                        }
+                        if (owners.length > 1) {
+                            alert("请输入精确姓名")
+                        }
+                    }
+                }).catch(function (error) {
+                    console.log(error)
+                })
+            }
+
+        }
+
+        render() {
+            this.showState()
+            console.log(this.state.sign_off_owners.length)
+            return (
+                <div>
+                    <div class="mb-3 mt-3">
+                        <label for="comment" class="form-label">备注:</label>
+                        <textarea type="textarea" class="form-control" id="comment" placeholder="输入备注" name="comment" maxlength="256" onBlur={(e) => this.formItemChange(e)}></textarea>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">签核人员</div>
+                        <div class="card-body">
+                            {this.signOffOwnersForm()}
+                            <div onClick={() => this.addSignOffOwner()} >添加</div>
+                            <input name="sign_off_owners" type="hidden" value={JSON.stringify(this.state.sign_off_owners)}></input>
+                        </div>
+                    </div>
+                    <div id="form-button-area">
+                        <button type="submit" id="submit" class="btn btn-primary" disabled={!this.state.sign_off_owners.length}>提交</button>
+                        <a href="/home/">返回</a>
+                    </div>
+                </div>
+            )
+        }
+    }
+
+    ReactDOM.render(<MainForm />, document.getElementById("mainForm"))
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+</style>
+{% endblock body %}

+ 537 - 0
BOM/templates/BOM/new_part.html

@@ -0,0 +1,537 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div id="mainForm"></div>
+    </form>
+</div>
+<script type="text/babel">
+    class MainForm extends React.Component {
+
+        constructor(props) {
+            super(props);
+            this.state = {
+                id: 0,
+                part_number: "",
+                description: "",
+                comment: "",
+                version: "",
+                sub_parts: [],
+                source_type: 'build',
+                vendor_name: "",
+                vendor_detail: {},
+                vendor_part_number: "",
+                attachment_maxsize: 10,      // 上传附件文件大小限制(单位M)。Todo 以后开发限制上传文件大小功能
+                attachment: []
+            }
+        }
+
+        componentDidMount() {
+            let id = this.getQueryVariable("edit_id")
+            if (id) {
+                this.get_edit_part(id)
+                this.forceUpdate()
+            }
+            this.get_attachment_maxsize()
+        }
+
+        get_attachment_maxsize(){
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: {},
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/BOM/part/attachment/maxsize/"
+            }).then(function(response){
+                if (response.data.code != 0) {
+                    // 检测失败
+                    alert(response.data.content)
+                } else {
+                    console.log('content', response.data.content)
+                    that.setState({
+                        attachment_maxsize:response.data.content.attachment_maxsize
+                    })
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        get_edit_part(id) {
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: { edit_id: id },
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/BOM/part/pn/draft/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    // 检测失败
+                    alert(response.data.content)
+                } else {
+                    // 检测成功
+                    console.log('content', response.data.content)
+                    let part_data = response.data.content
+                    if (part_data.version.toString().length < 2) {
+                        part_data.version = "0" + part_data.version
+                    }
+                    that.setState({
+                        id: part_data.id,
+                        part_number: part_data.part_number,
+                        description: part_data.description,
+                        comment: part_data.comment,
+                        version: part_data.version,
+                        sub_parts: part_data.sub_parts,
+                        source_type: part_data.source_type,
+                        vendor_name: part_data.vendor_name,
+                        vendor_detail: part_data.vendor_detail,
+                        vendor_part_number: part_data.vendor_part_number,
+                        attachment: part_data.attachment
+                    })
+                    document.getElementById("part_number").value = that.state.part_number
+                    document.getElementById("description").value = that.state.description
+                    document.getElementById("version").value = that.state.version
+                    document.getElementById("comment").value = that.state.comment
+                    document.getElementById("part_number").value = that.state.part_number
+                    document.getElementById("source_type").value = that.state.source_type
+                    if (that.state.source_type == 'purchase') {
+                        document.getElementById("vendor_name").value = that.state.vendor_detail.name
+                        document.getElementById("vendor_part_number").value = that.state.vendor_detail.code
+                    }
+                    // document.getElementById("part_number").setAttribute("disabled", "true")
+
+                    // alert("这个料号可以使用")
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        sourceInfo() {
+            console.log("this.state.source_type", this.state.source_type)
+            if (this.state.source_type == 'build') {
+                return (
+                    <div></div>
+                )
+            }
+            if (this.state.source_type == 'purchase') {
+                return (
+                    <div>
+                        <div class="mb-3 mt-3">
+                            <label for="vendor_name" class="form-label">供应商:</label>
+                            <div class="supplier_search">
+                                <input type="text" class="form-control" id="vendor_name" placeholder="输入供应商" name="vendor_name" required maxlength="128" onBlur={(e) => this.checkVendorName(e)} />
+                            </div>
+                        </div>
+                        <div class="mb-3 mt-3">
+                            <label for="vendor_part_number" class="form-label">供应商料号:</label>
+                            <input type="text" class="form-control" id="vendor_part_number" placeholder="输入供应商料号" name="vendor_part_number" maxlength="16" onBlur={(e) => this.formItemChange(e)} />
+                        </div>
+                    </div>
+
+                )
+            }
+            // this.forceUpdate()
+        }
+
+        subPartForm() {
+            let formFrame = () => {
+                return (
+                    <div>
+                        <table className="table table-striped">
+                            <thead>
+                                <tr>
+                                    <th>料号</th>
+                                    <th>描述</th>
+                                    <th>数量</th>
+                                    <th>删除</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                {items()}
+                            </tbody>
+                        </table>
+
+                    </div>
+                )
+            }
+            let items = () => {
+                return (
+                    this.state.sub_parts.map((item, index) =>
+                        <tr key={index}>
+                            <td>{item.part_number}</td>
+                            <td>{item.description}</td>
+                            <td><input class="form-control BOM-qty" type="number" value={item.qty} onChange={(e) => this.bomQtyChange(index, e)} /></td>
+                            <td onClick={() => this.deleteSubPart(index)}>删除</td>
+                        </tr>
+                    )
+                )
+            }
+            if (this.state.sub_parts.length == 0) {
+                return ("")
+            } else {
+                return (
+                    <div>{formFrame()}</div>
+                )
+            }
+        }
+
+        bomQtyChange(index, e) {
+            // var key = e.target.name.trim()
+            var value = e.target.value.trim()
+            console.log("form item chnange", index, value)
+            // this.setState({ key: value })
+            this.state.sub_parts[index].qty = value
+            this.forceUpdate()
+        }
+
+        addSubPart() {
+            let input = window.prompt("输入子料号")
+            if (input != null && input != "") {
+                var sub_pn = input.trim()
+            }
+            if (sub_pn) {
+                let that = this
+                axios({
+                    method: "GET",
+                    headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                    params: { part_number: sub_pn },
+                    // data: Qs.stringify({ delete_user_id: user_id }),
+                    url: "/BOM/part/pn/search/"
+                }).then(function (response) {
+                    if (response.data.code == 0) {
+                        let parts = response.data.content
+                        if (parts.length == 0) {
+                            alert("没有找到料号")
+                        }
+                        if (parts.length == 1) {
+                            console.log('parts is ', parts)
+                            let sub_part = {
+                                id: parts[0].id,
+                                part_number: parts[0].part_number,
+                                description: parts[0].description,
+                                qty: 1
+                            }
+                            for (let i = 0; i < that.state.sub_parts.length; i++) {
+                                if (that.state.sub_parts[i].id == sub_part.id) {
+                                    alert(sub_part.part_number + "已经添加了")
+                                    return
+                                }
+                            }
+                            that.setState({ sub_parts: that.state.sub_parts.concat([sub_part]) })
+                            // that.state.sub_parts.concat([sub_part])
+                            alert("找到" + sub_part.part_number)
+                            // that.forceUpdate()
+                        }
+                        if (parts.length > 1) {
+                            that.state.vendor_detail = null
+                            alert("请输入精确料号")
+                        }
+                    }
+                }).catch(function (error) {
+                    console.log(error)
+                })
+            }
+
+        }
+
+        deleteSubPart(index) {
+            this.state.sub_parts.splice(index, 1)
+            this.forceUpdate()
+        }
+
+        isPartNumberAvailable(target_pn) {
+            // console.log("is part number available? --- Start---", target_pn)
+            if (target_pn.length > 4) {
+                var that = this
+                axios({
+                    method: "GET",
+                    headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                    params: { part_number: target_pn.trim() },
+                    // data: Qs.stringify({ delete_user_id: user_id }),
+                    url: "/BOM/part/pn/check/"
+                }).then(function (response) {
+                    if (response.data.code != 0) {
+                        // 检测失败
+                        that.setState({ code: null })
+                        document.getElementById("part_number").value = ""
+                        alert(response.data.content)
+                    } else {
+                        // 检测成功
+                        that.setState({ part_number: target_pn.trim() })
+                        that.setState({ version: "00" })
+                        document.getElementById("version").value = that.state.version
+                        // document.getElementById("part_number").setAttribute("disabled", "true")
+                        alert("这个料号可以使用")
+                    }
+                }).catch(function (error) {
+                    console.log(error)
+                })
+            }
+        }
+
+        checkVendorName(e) {
+            this.formItemChange(e)
+            var that = this
+            let vendor_name = that.state.vendor_name
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: { vendor_name: vendor_name.trim() },
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/BOM/vendor/name/search/"
+            }).then(function (response) {
+                if (response.data.code == 0) {
+                    let vendors = response.data.content
+                    if (vendors.length == 0) {
+                        that.state.vendor_detail = null
+                        document.getElementById("vendor_name").value = ""
+                        that.setState({ vendor_name: {} })
+                        alert("没有找到供应商")
+                    }
+                    if (vendors.length == 1) {
+                        console.log('vendors is ', vendors)
+                        that.state.vendor_detail = {
+                            id: vendors[0].id,
+                            code: vendors[0].code,
+                            name: vendors[0].name
+                        }
+                        document.getElementById("vendor_name").value = vendors[0].name
+                        that.setState({ vendor_name: vendors[0].name })
+                        alert("找到供应商")
+                    }
+                    if (vendors.length > 1) {
+                        that.state.vendor_detail = {}
+                        alert("请输入更多关键字")
+                    }
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        attachmentList() {
+            if (this.state.attachment.length == 0) {
+                return (
+                    null
+                )
+            } else {
+                return (
+                    <div>
+                        {this.state.attachment.map((row, index) =>
+                            <div class="attachmentList">
+                                <a href={row.url}>{row.display_name}</a>
+                                <p onClick={() => { this.deleteAttachment(row.id) }}>删除</p>
+                            </div>
+                        )}
+
+                    </div>
+                )
+            }
+        }
+
+        formItemChange(e) {
+            var key = e.target.name.trim()
+            var value = e.target.value.trim()
+            console.log("form item chnange", key, value)
+            // this.setState({ key: value })
+            this.state[key] = value
+            this.forceUpdate()
+        }
+
+        addAttachment(e) {
+            let formData = new FormData()
+            if (e.currentTarget.files[0].size > this.state.attachment_maxsize * 1024 * 1024) {
+                alert("上传文件大小超过系统限制。当前限制是".concat(this.state.attachment_maxsize, "MB。"))
+            } else {
+                formData.append('files', e.currentTarget.files[0])
+                var that = this
+                axios({
+                    method: "POST",
+                    headers: { 'content-type': 'multipart/form-data' },     // 文件上传header
+                    // params: { vendor_code: target_code.trim() },
+                    data: formData,
+                    url: "/BOM/part/file/"
+                }).then(function (response) {
+                    if (response.data.code != 0) {
+                        alert(response.data.content)
+                    } else {
+                        alert("附件上传成功")
+                        var new_attachment = {
+                            id: response.data.content.id,
+                            url: response.data.content.url,
+                            display_name: response.data.content.display_name,
+                        }
+                        that.setState({
+                            attachment: that.state.attachment.concat([new_attachment])
+                        })
+                    }
+                }).catch(function (error) {
+                    console.log(error)
+                })
+                console.log("this.state.attachment", this.state.attachment)
+            }
+        }
+
+        deleteAttachment(delete_id) {
+            for (let i = 0; i < this.state.attachment.length; i++) {
+                if (this.state.attachment[i].id == delete_id) {
+                    var that = this
+                    axios({
+                        method: "DELETE",
+                        headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                        // params: { vendor_code: target_code.trim() },
+                        data: { delete_attachment_id: delete_id },
+                        url: "/BOM/part/file/"
+                    }).then(function (response) {
+                        if (response.data.code != 0) {
+                            // 删除附件失败
+                            console.log(response.data.content)
+                        } else {
+                            console.log("delete attachment", that.state.attachment[i])
+                            that.state.attachment.splice(i, 1);
+                            i--;
+                            that.forceUpdate()
+                        }
+                    }).catch(function (error) {
+                        console.log(error)
+                    })
+                }
+            }
+        }
+
+        showState() {
+            console.log(this.state)
+        }
+
+        // 获取url里面的参数
+        getQueryVariable(variable) {
+            var query = window.location.search.substring(1);
+            var vars = query.split("&");
+            for (var i = 0; i < vars.length; i++) {
+                var pair = vars[i].split("=");
+                if (pair[0] == variable) { return pair[1]; }
+            }
+            return (false);
+        }
+
+
+        render() {
+            this.showState()
+            return (
+                <div>
+                    <input type="hidden" name="id" value={this.state.id} />
+                    <div class="mb-3 mt-3">
+                        <label for="part_number" class="form-label">料号:</label>
+                        <input type="text" class="form-control" id="part_number" placeholder="输入料号" name="part_number" required maxlength="16" minlength="5" onBlur={(e) => this.isPartNumberAvailable(e.target.value)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="description" class="form-label">描述:</label>
+                        <input type="text" class="form-control" id="description" placeholder="输入描述" name="description" required maxlength="128" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="version" class="form-label">版本号:</label>
+                        <input type="text" class="form-control" id="version" placeholder="输入版本号" name="version" required maxlength="8" disabled="true" />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="source_type" class="form-label">来源:</label>
+                        <select class="form-select" id="source_type" name="source_type" onChange={(e) => this.formItemChange(e)} >
+                            <option value="build">自产</option>
+                            <option value="purchase">采购</option>
+                        </select>
+                    </div>
+                    {this.sourceInfo()}
+                    <input type="hidden" name="vendor_detail" value={JSON.stringify(this.state.vendor_detail)} />
+                    <div class="mb-3 mt-3">
+                        <label for="comment" class="form-label">备注:</label>
+                        <textarea type="textarea" class="form-control" id="comment" placeholder="输入备注" name="comment" maxlength="256" onBlur={(e) => this.formItemChange(e)} value={this.state.comment}></textarea>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">附件</div>
+                        <div class="card-body">
+                            <input type="file"
+                                id="fileElem"
+                                class="visually-hidden"
+                                onChange={(e) => this.addAttachment(e, this)}
+                            />
+                            <label for="fileElem">添加附件</label>
+                            {this.attachmentList()}
+                            <input type="hidden"
+                                name="attachment"
+                                value={JSON.stringify(this.state.attachment)}
+                            />
+                        </div>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">下阶物料</div>
+                        <div class="card-body">
+                            {this.subPartForm()}
+                            <div onClick={() => this.addSubPart()} >添加</div>
+                        </div>
+                    </div>
+                    <input type="hidden"
+                        name="sub_parts"
+                        value={JSON.stringify(this.state.sub_parts)}
+                    />
+                    <div id="form-button-area">
+                        <button type="submit" name="create" value="create" class="btn btn-primary">提交签核</button>
+                        <button type="submit" name="save" value="save" class="btn btn-secondary">保存</button>
+                        <a href="/home/">返回</a>
+                    </div>
+                </div>
+            )
+        }
+    }
+
+    ReactDOM.render(<MainForm />, document.getElementById("mainForm"))
+
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    div.supplier_search {
+        display: flex;
+        justify-content: flex-start;
+    }
+
+    .card {
+        margin-bottom: 10px;
+        width: 100%;
+    }
+
+    .BOM-qty {
+        width: 100px;
+    }
+</style>
+{% endblock body %}

+ 201 - 0
BOM/templates/BOM/new_vendor.html

@@ -0,0 +1,201 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div id="mainForm"></div>
+    </form>
+</div>
+<script type="text/babel">
+
+    class MainForm extends React.Component {
+        constructor(props) {
+            super(props);
+            this.state = {
+                code: null,
+                name: null,
+                contact: null,
+                phone: null,
+                email: null,
+                address: null,
+                comment: null,
+                attachment: new Array
+            }
+        }
+
+        attachmentList() {
+            if (this.state.attachment.length == 0) {
+                return (
+                    null
+                )
+            } else {
+                return (
+                    <div>
+                        {this.state.attachment.map((row, index) =>
+                            <div class="attachmentList">
+                                <a href={row.url}>{row.display_name}</a>
+                                <p onClick={()=>{this.deleteAttachment(row.id)}}>删除</p>
+                            </div>
+                        )}
+                        <input type="hidden" name="attachment" value={JSON.stringify(this.state.attachment)} />
+                    </div>
+                )
+            }
+        }
+
+        isVendorCodeAvailable(target_code) {
+            // console.log("is vendor code available? --- Start---", target_code)
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: { vendor_code: target_code.trim() },
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/BOM/vendor/code/check/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    that.setState({ code: null })
+                    document.getElementById("code").value = ""
+                    alert(response.data.content)
+                }else{
+                    that.setState({ code: target_code.trim() })
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        formItemChange(e) {
+            var key = e.target.name.trim()
+            var value = e.target.value.trim()
+            console.log("form item chnange", key, value)
+            // this.setState({ key: value })
+            this.state[key]=value
+        }
+
+        addAttachment(e, files) {
+            let formData = new FormData()
+            formData.append('files', e.currentTarget.files[0])
+            var that = this
+            axios({
+                method: "POST",
+                headers: { 'content-type': 'multipart/form-data' },     // 文件上传header
+                // params: { vendor_code: target_code.trim() },
+                data: formData,
+                url: "/BOM/vendor/file/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    alert(response.data.content)
+                } else {
+                    alert("附件上传成功")
+                    var new_attachment = {
+                        id: response.data.content.id,
+                        url: response.data.content.url,
+                        display_name: response.data.content.display_name,
+                    }
+                    that.setState({
+                        attachment: that.state.attachment.concat([new_attachment])
+                    })
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+            console.log("this.state.attachment", this.state.attachment)
+        }
+
+        deleteAttachment(delete_id){
+            for (let i=0; i<this.state.attachment.length; i++){
+                if (this.state.attachment[i].id == delete_id){
+                    console.log("delete attachment", this.state.attachment[i])
+                    this.state.attachment.splice(i, 1);
+                    i--;
+                    this.forceUpdate()
+                }
+            }
+        }
+
+        showState() {
+            console.log(this.state)
+        }
+
+        render() {
+            return (
+                <div>
+                    <div class="mb-3 mt-3">
+                        <label for="code" class="form-label">代码:</label>
+                        <input type="text" class="form-control" id="code" placeholder="输入公司代码" name="code" required maxlength="16" onBlur={(e) => this.isVendorCodeAvailable(e.target.value)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="name" class="form-label">供应商名:</label>
+                        <input type="text" class="form-control" id="name" placeholder="输入供应商名" name="name" required maxlength="128" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="contact" class="form-label">联系人:</label>
+                        <input type="text" class="form-control" id="contact" placeholder="输入联系人" name="contact" required maxlength="32" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="phone" class="form-label">联系电话:</label>
+                        <input type="text" class="form-control" id="phone" placeholder="输入联系电话" name="phone" required maxlength="32" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="email" class="form-label">邮箱地址:</label>
+                        <input type="email" class="form-control" id="email" placeholder="输入邮箱地址" name="email" maxlength="128" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="address" class="form-label">地址:</label>
+                        <input type="text" class="form-control" id="address" placeholder="输入地址" name="address" required maxlength="256" onBlur={(e) => this.formItemChange(e)} />
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="comment" class="form-label">备注:</label>
+                        <textarea type="textarea" class="form-control" id="comment" placeholder="输入备注" name="comment" maxlength="256" onBlur={(e) => this.formItemChange(e)}></textarea>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">附件</div>
+                        <div class="card-body">
+                            <input type="file" id="fileElem" class="visually-hidden" onChange={(e) => this.addAttachment(e, this)} />
+                            <label for="fileElem">添加附件</label>
+                            {this.attachmentList()}
+                        </div>
+                    </div>
+                    <div id="form-button-area">
+                        <button type="submit" name="create" class="btn btn-primary" onClick={(e) => {return false}}>创建</button>
+                        <a href="/home/">返回</a>
+                    </div>
+                </div>
+            )
+        }
+    }
+
+    ReactDOM.render(<MainForm />, document.getElementById("mainForm"))
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+    div.attachmentList{
+        display: flex;
+        justify-content: space-around;
+    }
+</style>
+{% endblock body %}

+ 159 - 0
BOM/templates/BOM/part_detail.html

@@ -0,0 +1,159 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<!-- <script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script> -->
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <div class="card">
+        <div class="card-header">基本信息</div>
+        <div class="card-body">
+            <div class="row">
+                <div class="col-sm-4 title">料号</div>
+                <div class="col-sm-8 content">{{ part.part_number }}</div>
+                <div class="col-sm-4 title">描述</div>
+                <div class="col-sm-8 content">{{ part.description }}</div>
+                <div class="col-sm-4 title">版本</div>
+                <div class="col-sm-8 content">{{ part.version }}</div>
+                <div class="col-sm-4 title">状态</div>
+                <div class="col-sm-8 content">{{ part.life_cycle }}</div>
+                <div class="col-sm-4 title">创建者</div>
+                <div class="col-sm-8 content">{{ part.creator.name }}</div>
+                <div class="col-sm-4 title">管理者</div>
+                <div class="col-sm-8 content">{{ part.owner.name }}</div>
+                <div class="col-sm-4 title">来源</div>
+                <div class="col-sm-8 content">{{ part.source_type }}</div>
+                <div class="col-sm-4 title">供应商</div>
+                <div class="col-sm-8 content">{{ part.vendor.name }}</div>
+                <div class="col-sm-4 title">创建时间</div>
+                <div class="col-sm-8 content">{{ part.create_datetime }}</div>
+                <div class="col-sm-4 title">最新更新时间</div>
+                <div class="col-sm-8 content">{{ part.last_update_datetime }}</div>
+            </div>
+        </div>
+    </div>
+    <div class="card">
+        <div class="card-header">下阶物料</div>
+        <div class="card-body">
+            {% if bom|length > 0 %}
+
+            <table>
+                <thead>
+                    <tr>
+                        <th>料号</th>
+                        <th>版本</th>
+                        <th>数量</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for sub_part in bom %}
+                    <tr>
+                        <td>{{ child_pn }}</td>
+                        <td>{{ child_desc }}</td>
+                        <td>{{ qty }}</td>
+                    </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+            {% else %}
+            <p>没有下阶物料</p>
+            {% endif %}
+        </div>
+    </div>
+    <div class="card">
+        <div class="card-header">附件</div>
+        <div class="card-body">
+            {% for attachment in attachments %}
+            <a href={{ attachment.url }}>{{ attachment.display_name }}</a>
+            {% endfor %}
+        </div>
+    </div>
+    <div class="card">
+        <div class="card-header">查看历史版本</div>
+        <div class="card-body">
+            {% for v in ver_list %}
+            <a href="/BOM/part/detail/?pn={{ part.part_number }}&ver={{ v.ver }}">{{ v.ver_display }}</a>
+            {% endfor %}
+        </div>
+    </div>
+    {% if is_owner %}
+    <div class="card">
+        <div class="card-header">管理料号</div>
+        <div class="card-body">
+            <a href="/BOM/part/upgrade/?pn={{ part.part_number }}">更新版本</a>
+            {% if part.life_cycle == '使用中' %}
+            <a href="#" onclick="EOL_part('{{ part.part_number }}')">停用料号</a>
+            {% endif %}
+        </div>
+    </div>
+    {% endif %}
+</div>
+<div id="form-button-area">
+    <div><a href="#" onclick="window.history.back();">后退</a></div>
+    <div><a href="/home/">返回首页</a></div>
+</div>
+<script type="text/javascript">
+    function EOL_part(part_number){
+        var my_confirm = confirm("确认停用这个料号吗?")
+        if (my_confirm == true){
+            console.log(part_number)
+            axios({
+                method: "POST",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },     // 文件上传header
+                // params: { vendor_code: target_code.trim() },
+                data: {EOL_pn: part_number},
+                url: "/BOM/part/EOL/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    alert(response.data.msg)
+                } else {
+                    alert(response.data.msg)
+                    window.location.href = "/home/"
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+    }
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+        flex-direction: column;
+        margin-left: 10px;
+        margin-right: 10px;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    .title {
+        background-color: lightgray;
+        border: 1px;
+        border-style: solid;
+        margin-bottom: 2px;
+    }
+
+    .content {
+        border: 1px;
+        border-style: solid;
+        margin-bottom: 2px;
+    }
+    .card{
+        margin-bottom: 8px;
+    }
+</style>
+{% endblock body %}

+ 87 - 0
BOM/templates/BOM/part_search.html

@@ -0,0 +1,87 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="part_number" class="form-label">料号:</label>
+            <input type="text" class="form-control" id="part_number" placeholder="输入料号" name="part_number" maxlength="16" onblur="contentCheck()"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="description" class="form-label">描述:</label>
+            <input type="text" class="form-control" id="description" placeholder="输入描述" name="description" maxlength="128" onblur="contentCheck()"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="description" class="form-label">管理者:</label>
+            <input type="text" class="form-control" id="owner" placeholder="输入料号管理者" name="owner" maxlength="128" onblur="contentCheck()"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="description" class="form-label"><input class="form-check-input" type="checkbox" name="include_EOL_parts">包含停用的料号</label>
+        </div>
+        <div id="form-button-area">
+            <button type="submit" name="search" value="create" id="search" class="btn btn-primary" >搜索</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<script>
+    function contentCheck(){
+        console.log("contentCheck start")
+        var part_number = document.getElementById("part_number").value.trim()
+        var description = document.getElementById("description").value.trim()
+        var owner = document.getElementById("owner").value.trim()
+        if (part_number.length == 0 & description.length == 0 & owner.length ==0){
+            console.log("contentCheck fail")
+            document.getElementById("search").setAttribute("disabled","disabled")
+        }else{
+            console.log("contentCheck pass")
+            document.getElementById("search").removeAttribute("disabled")
+        }
+    }
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    div.supplier_search {
+        display: flex;
+        justify-content: flex-start;
+    }
+
+    .card {
+        margin-bottom: 10px;
+        width: 100%;
+    }
+
+    .BOM-qty {
+        width: 100px;
+    }
+</style>
+{% endblock body %}

+ 50 - 0
BOM/templates/BOM/part_search_result.html

@@ -0,0 +1,50 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <div>
+        <table class="table table-striped">
+            <thead>
+                <tr>
+                    <th>料号</th>
+                    <th>版本</th>
+                    <th>状态</th>
+                    <th>查看</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for part in part_list %}
+                <tr>
+                    <td>{{part.part_number }}</td>
+                    <td>{{ part.version_display }}  </td>
+                    <td>{{ part.life_cycle }}</td>
+                    <td><a href="/BOM/part/detail/?pn={{ part.part_number }}{% if part.life_cycle == 'EOL' %}&ver={{ part.version }}{% endif %}">查看</a></td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+    <!-- <div>
+        <a href="/BOM/part/my_list/?life_cycle=EOL">查看停用料号</a>
+        <a href="/BOM/part/my_list/?life_cycle=sign-off">查看签核中的料号</a>
+        <a href="/BOM/part/edit/list/">查看编辑中的料号</a>
+    </div> -->
+    <div id="form-button-area">
+        <div><a href="#" onclick="window.history.back();">后退</a></div>
+        <div><a href="/home/">返回首页</a></div>
+    </div>
+</div>
+<script type="text/babel">
+
+
+</script>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 77 - 0
BOM/templates/BOM/part_whereused.html

@@ -0,0 +1,77 @@
+{% extends "BOM/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="part_number" class="form-label">料号:</label>
+            <input type="text" class="form-control" id="part_number" placeholder="输入料号" name="part_number" maxlength="16" onblur="contentCheck()"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="description" class="form-label"><input class="form-check-input" type="checkbox" name="include_EOL_parts">包含停用的料号</label>
+        </div>
+        <div id="form-button-area">
+            <button type="submit" name="search" value="create" id="search" class="btn btn-primary" >搜索</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<script>
+    function contentCheck(){
+        console.log("contentCheck start")
+        var part_number = document.getElementById("part_number").value.trim()
+        if (part_number.length == 0){
+            console.log("contentCheck fail")
+            document.getElementById("search").setAttribute("disabled","disabled")
+        }else{
+            console.log("contentCheck pass")
+            document.getElementById("search").removeAttribute("disabled")
+        }
+    }
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    div.supplier_search {
+        display: flex;
+        justify-content: flex-start;
+    }
+
+    .card {
+        margin-bottom: 10px;
+        width: 100%;
+    }
+
+    .BOM-qty {
+        width: 100px;
+    }
+</style>
+{% endblock body %}

+ 3 - 0
BOM/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 37 - 0
BOM/urls.py

@@ -0,0 +1,37 @@
+from django.urls import path
+from . import views, views_vendor_api, views_part_api
+
+urlpatterns = [
+    # path('api/<str:role>/<str:action>/', views.api),
+    path('vendor/create/', views.new_vendor),
+    path('part/create/', views.new_part),
+    path('part/edit/list/', views.my_draft_parts),
+    path('part/ecn/create/', views.new_part_ECN),
+    path('part/detail/', views.part_detail),
+    path('part/upgrade/', views.part_upgrade),      # todo 更新版本号
+    path('part/my_list/', views.my_part_list),
+    path('ECN/my_signoff/', views.my_ECN_signoff_list),
+    path('ECN/my_list/', views.my_ECN_list),
+    path('ECN/detail/', views.ECN_detail),
+    path('part/search/', views.part_search),
+    path('part/where_used/', views.part_whereused),
+
+]
+
+# API urls (vendor)
+urlpatterns += [
+    path('vendor/code/check/', views_vendor_api.code_check),
+    path('vendor/file/', views_vendor_api.file),
+    path('vendor/name/search/', views_vendor_api.search_vendor),
+]
+
+# API urls (part)
+urlpatterns += [
+    path('part/pn/check/', views_part_api.pn_check),
+    path('part/pn/draft/', views_part_api.get_draft_part),
+    path('part/file/', views_part_api.file),
+    path('part/EOL/', views_part_api.part_EOL),
+    path('part/pn/search/', views_part_api.search_part_number),
+    path('part/ecn/part_detail/', views_part_api.ecn_part_detail),
+    path('part/attachment/maxsize/', views_part_api.attachment_maxsize),
+]

+ 326 - 0
BOM/views.py

@@ -0,0 +1,326 @@
+from django.http import HttpResponse, HttpResponseRedirect
+from django.db import transaction
+from django.shortcuts import render
+from .models import ECN, EcnSignOff, Vendor, Part
+from .model_handler import BOMHandler, EcnHandler, EcnSignOffHandler, PartHandler, VendorAttachmentHandler, VendorHandler, PartAttachmentHandler
+from Info.model_handler import CompanyHandler, UserHandler
+from .func import overall_ecn_signoff
+from Info.func import send_message
+# Create your views here.
+
+
+@transaction.atomic
+def new_vendor(request):
+    if request.method == 'GET':
+        return render(request, 'BOM/new_vendor.html')
+    if request.method == 'POST':
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+
+        # 搜索重名的公司
+        same_name_vendor = VendorHandler.search_by_name(request.POST.get('name'), company)
+        if len(same_name_vendor) > 0:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '系统已经存在同名的供应商了', 'dirLink': '/BOM/vendor/create/'})
+        # 检索vendor code是否已经被使用过了   
+        exist_vendor = VendorHandler.get_by_code(request.GET.get('code'), company)
+        if exist_vendor:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '供应商编号已经使用过了', 'dirLink': '/BOM/vendor/create/'})
+        
+        new_vendor = Vendor()
+        new_vendor.code = request.POST.get('code')
+        new_vendor.name = request.POST.get('name')
+        new_vendor.contact = request.POST.get('contact')
+        new_vendor.phone = request.POST.get('phone')
+        new_vendor.email = request.POST.get('email')
+        new_vendor.address = request.POST.get('address')
+        new_vendor.comment = request.POST.get('comment')
+        new_vendor.save()
+
+        attachments = eval(request.POST.get('attachment'))
+        for attachment in attachments:
+            v_file = VendorAttachmentHandler.get_by_id(attachment['id'])
+            v_file.vendor = new_vendor
+            v_file.save()
+
+        return render(request, 'BOM/directPage.html', {'alertMsg': '供应商添加成功', 'dirLink': '/home/'})
+
+
+def new_part(request):
+    if request.method == 'GET':
+        return render(request, 'BOM/new_part.html')
+
+    if request.method == 'POST':
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        user = UserHandler.get_by_id(request.session.get('user_id'))
+
+        if request.POST.get('id') == 0:      # 0表示是全新提交的料号,非0表示是编辑以前的草稿
+            # 搜索重复PN
+            same_part_number = PartHandler.exist_pn(request.POST.get('part_number'), company)
+            if same_part_number:
+                return render(request, 'BOM/directPage.html', {'alertMsg': '系统已经存在同样的料号了', 'dirLink': '/home/'})
+            new_part = Part()
+        else:
+            new_part = PartHandler.get_by_id(request.POST.get('id'))
+
+        new_part.company = company
+        new_part.part_number= request.POST.get('part_number')
+        new_part.description= request.POST.get('description')
+        new_part.comment= request.POST.get('comment')
+        # new_part.version= 0
+        new_part.creator= user
+        new_part.owner= user
+        new_part.source_type= request.POST.get('source_type')
+        new_part.vendor_part_number= request.POST.get('vendor_part_number')
+
+        if new_part.source_type=='purchase':        # vendor detail可能提交上来内容是空的,主要是自产物料
+            vendor_post_data = eval(request.POST.get('vendor_detail'))
+            vendor = VendorHandler.get_by_code(vendor_post_data['code'], company)
+            new_part.vendor = vendor
+
+        new_part.sub_parts= eval(request.POST.get('sub_parts'))
+        new_part.save()
+        
+        # PartAttachmentHandler.flush_by_part(new_part)       # 先清掉之前保存在数据库里面的附件
+        attachments = eval(request.POST.get('attachment'))
+        for attachment in attachments:
+            p_file = PartAttachmentHandler.get_by_id(attachment['id'])
+            p_file.part = new_part
+            p_file.save()
+        
+        if BOMHandler.flush_and_save_BOM(parent_part=new_part, sub_parts=new_part.sub_parts):     # 将BOM保存到数据库里
+            if 'save' in request.POST:        # POST上来保存指令
+                return render(request, 'BOM/directPage.html', {'alertMsg': '保存成功', 'dirLink': '/home/'})
+            if 'create' in request.POST:        # POST上来创建指令,指引下一步去ECN签核
+                # request.session['ECN_new_part_id'] = new_part.id
+                return render(request, 'BOM/directPage.html', {'dirLink': '/BOM/part/ecn/create/?part_id={}'.format(new_part.id)})
+        else:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '添加失败,系统错误,请联系管理员', 'dirLink': '/home/'})
+
+
+@transaction.atomic
+def new_part_ECN(request):
+    if request.method == 'GET':
+        part = PartHandler.get_by_id(request.GET.get('part_id'))
+        if part.life_cycle != 'draft':
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号状态错误', 'dirLink': '/home/'})
+        return render(request, 'BOM/new_ecn.html', {'part':part})
+    
+    if request.method == 'POST':
+        part = PartHandler.get_by_id(request.POST.get('part_id'))
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        user = UserHandler.get_by_id(request.session.get('user_id'))
+        ecn = ECN()
+        ecn.creator = user
+        ecn.company = company
+        ecn.comment = request.POST.get('comment')
+        ecn.category = request.POST.get('category')
+        
+        ecn.target_part = part
+        ecn.number = EcnHandler.generate_ecn_number(company=company)
+        ecn.save()
+        
+        sign_off_list = eval(request.POST.get('sign_off_owners').replace('null','None'))
+        for owner in sign_off_list:
+            ecn_sign_off = EcnSignOff()
+            ecn_sign_off.company = company
+            ecn_sign_off.ecn = ecn
+            ecn_sign_off.owner = UserHandler.get_by_id(owner['id'])
+            ecn_sign_off.save()
+        
+        part.life_cycle = 'sign-off'
+        part.save()
+
+        return render(request, 'BOM/directPage.html', {'alertMsg': 'ECN{},提交成功。'.format(ecn.number), 'dirLink': '/home/'})
+
+
+def my_draft_parts(request):
+    if request.method == 'GET':
+        user = UserHandler.get_by_id(request.session.get('user_id'))
+        draft_parts = PartHandler.my_draft_parts(creator=user)
+        return render(request, 'BOM/draft_parts.html', {'draft_parts':draft_parts})
+    return HttpResponse('new my_draft_parts page')
+
+
+def my_ECN_signoff_list(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    if request.method == 'GET':
+        if request.GET.get('ECN_type')=='all':
+            sign_off_list = EcnSignOffHandler.my_ECN(user=user)
+        else:
+            sign_off_list = EcnSignOffHandler.my_ECN(user=user, status='签核中')
+
+        return render(request, 'BOM/my_ECN_signoff_list.html', {'sign_off_list':sign_off_list})
+
+
+def my_ECN_list(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    if request.method == 'GET':
+        my_ecn_list = EcnHandler.my_ecn(user=user)
+        return render(request, 'BOM/my_ECN_list.html', {'ecn_list':my_ecn_list})
+
+
+def ECN_detail(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    if request.method == 'GET':
+        ECN_id = request.GET.get('ecn_id')
+        if ECN_id:
+            ecn = EcnHandler.get_by_id(ECN_id)
+
+            # 判断是否是签核人
+            this_sign_off = EcnSignOffHandler.get_by_owner_ecn(user=user, ecn=ecn)
+            if this_sign_off:
+                is_ecn_signoff = True
+            else:
+                is_ecn_signoff = False
+            
+            # 判断是否是ECN owner,直接取消ECN
+            is_ecn_creator = False    
+            if ecn.creator == user:
+                is_ecn_creator = True
+
+            # 获取当前ECN签核状态
+            sign_offs = EcnSignOffHandler.search_by_ecn(ecn=ecn)    
+            return render(request, 'BOM/ECN_part_detail.html',{'is_ecn_signoff': is_ecn_signoff, 'is_ecn_creator': is_ecn_creator, 'sign_offs': sign_offs})
+        else:
+            return render(request, 'BOM/directPage.html', {'alertMsg': 'ECN号码缺失', 'dirLink': '/home/'})
+    
+    if request.method == 'POST':
+
+        ECN_id = request.POST.get('ecn_id') 
+        ecn = EcnHandler.get_by_id(ECN_id)
+        # 批准ECN
+        if 'approve' in request.POST:
+            this_sign_off = EcnSignOffHandler.get_by_owner_ecn(user=user, ecn=ecn)
+            if not this_sign_off:
+                return render(request, 'BOM/directPage.html', {'alertMsg': '非ECN签核人', 'dirLink': '/home/'})
+            else:
+                this_sign_off.comment = request.POST.get('sign_off_comment')
+                this_sign_off.status = '批准'
+                this_sign_off.save()
+                overall_ecn_signoff(ECN_id)     # 检查是否所有签核都完成了
+                return render(request, 'BOM/directPage.html', {'alertMsg': '签核完成', 'dirLink': '/home/'})
+        # 拒接ECN
+        if 'reject' in request.POST:
+            this_sign_off = EcnSignOffHandler.get_by_owner_ecn(user=user, ecn=ecn)
+            if not this_sign_off:
+                return render(request, 'BOM/directPage.html', {'alertMsg': '非ECN签核人', 'dirLink': '/home/'})
+            else:
+                this_sign_off.comment = request.POST.get('sign_off_comment')
+                this_sign_off.status = '拒绝'
+                this_sign_off.save()
+                ecn.status = '拒绝'
+                ecn.save()
+                send_message(from_user=None, to_user=ecn.owner, company=ecn.company, title='ECN'+ecn.number+'签核被拒绝')
+                return render(request, 'BOM/directPage.html', {'alertMsg': '签核完成', 'dirLink': '/home/'})
+        # 取消ECN,只有创建者可以操作
+        if 'cancel' in request.POST:
+            ecn.status = '取消'
+            ecn.delete()
+            return render(request, 'BOM/directPage.html', {'alertMsg': 'ECN取消成功', 'dirLink': '/home/'})
+
+
+def part_detail(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    if request.method == 'GET':
+        part_number = request.GET.get('pn')
+        if not part_number:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号不能为空', 'dirLink': '/home/'})
+        version = request.GET.get('ver')
+        if version:
+            part = PartHandler.get_by_pn(pn=part_number, company=company, ver=version)
+        else:
+            part = PartHandler.get_by_pn(pn=part_number, company=company)
+        if not part:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号没有查询到', 'dirLink': '/home/'})
+        else:
+            part_version = part.version
+            part_CHN = PartHandler.part_local_CHN(part=part)
+        bom = BOMHandler.retrieve_BOM(parent_part=part, company=company)
+        attachments = PartAttachmentHandler.search_by_part(part=part)
+        part_all_ver = PartHandler.all_ver_by_pn(part_number, company)
+        ver_list = []
+        for p in part_all_ver:
+            if p.version != part.version:   # 不重复计算当前现实的版本
+                v_display = part.version_display
+                # if p.version <10:   # 显示的版本号,如果不超过10,需要补零
+                #     v_display = '0' + str(p.version)
+                # else:
+                #     v_display = p.version
+                if p.version < part_version:
+                    ver_list.append({'ver':p.version, 'ver_display':v_display})
+        
+        is_owner = False        # 判断是否是part管理员
+        if user == part.owner:
+            is_owner = True
+        return render(request, 'BOM/part_detail.html', {'part':part_CHN, 'BOM':bom, 'attachments': attachments, 'ver_list': ver_list, 'is_owner': is_owner})
+
+
+def my_part_list(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    if request.method == 'GET':
+        if request.GET.get('life_cycle'):
+            my_part_list = PartHandler.my_part(user=user, life_cycle=request.GET.get('life_cycle'))
+        else:
+            my_part_list = PartHandler.my_part(user=user)
+
+        my_part_list = PartHandler.part_list_local_CHN(my_part_list)
+        return render(request, 'BOM/my_part_list.html', {'part_list':my_part_list})
+
+
+def part_upgrade(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    if request.method == 'GET':
+        part_number = request.GET.get('pn')
+        if not part_number:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号不能为空', 'dirLink': '/home/'})
+        part = PartHandler.get_by_pn(pn=part_number, company=company)
+        if not part:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号没有查询到', 'dirLink': '/home/'})
+        all_parts = PartHandler.all_ver_by_pn(part_number, company)
+
+        # 检索是否存在已有的draft状态的料号,如果有的话,直接跳转
+        for part in all_parts:
+            if part.life_cycle == 'draft':
+                return HttpResponseRedirect('/BOM/part/create/?edit_id={}'.format(part.id))
+        
+        # 如果不存在就创建新的part
+        new_part = PartHandler.new_ver(part, company, user)
+        return HttpResponseRedirect('/BOM/part/create/?edit_id={}'.format(new_part.id))
+
+
+def part_search(request):
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    if request.method == 'GET':
+        return render(request, 'BOM/part_search.html')
+    
+    if request.method == 'POST':
+        part_number = request.POST.get('part_number').strip()
+        description = request.POST.get('description').strip()
+        owner = request.POST.get('owner').strip() 
+        if request.POST.get('include_EOL_parts'):
+            include_EOL = True
+        else:
+            include_EOL = False
+        parts  = PartHandler.part_search(company, part_number, description, owner, include_EOL)
+        return render(request, 'BOM/part_search_result.html', {'part_list': parts})
+
+
+def part_whereused(request):
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    if request.method == 'GET':
+        return render(request, 'BOM/part_whereused.html')
+
+    if request.method == 'POST':
+        child_pn = request.POST.get('part_number').strip()
+        if request.POST.get('include_EOL_parts'):
+            include_EOL = True
+        else:
+            include_EOL = False
+        child_part = PartHandler.get_by_pn(child_pn, company)
+        
+        if child_part:
+            parent_parts = BOMHandler.where_used(child_pn, include_EOL)
+            return render(request, 'BOM/part_search_result.html', {'part_list': parent_parts})
+        else:
+            return render(request, 'BOM/directPage.html', {'alertMsg': '料号没有查询到', 'dirLink': '/home/'})

+ 170 - 0
BOM/views_part_api.py

@@ -0,0 +1,170 @@
+from django.http import HttpResponse, JsonResponse, QueryDict
+from django.views.decorators.csrf import csrf_exempt
+from django.forms import model_to_dict
+from .model_handler import PartAttachmentHandler, PartHandler, VendorHandler, EcnHandler, BOMsettingHandler
+from .models import PartAttachment, VendorAttachment
+from Info.model_handler import CompanyHandler, UserHandler
+from Info.func import file_upload, get_file
+from .func import part_info
+
+
+def pn_check(request):
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    result = {'code': 1, 'content': None}
+    if request.method == 'GET':
+        part = PartHandler.exist_pn(request.GET.get('part_number'), company)
+        if part:
+            result = {'code': 1, 'content': "这个料号已经使用了。"}
+        else:
+            result = {'code': 0, 'content': None}
+
+        return JsonResponse(result)
+
+
+@csrf_exempt
+def file(request):
+    result = {'code': 1, 'content': None}
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+
+    if request.method == 'GET':
+        return get_file(request)
+
+    if request.method == 'POST':
+        upload_result = file_upload(request, ['BOM', 'part'])
+        if upload_result['code'] == 0:
+            p_attachment = PartAttachment()
+            p_attachment.company = company
+            p_attachment.creator = user
+            p_attachment.display_name = upload_result['target_file_name']
+            p_attachment.url = upload_result['target_file_path'] + \
+                upload_result['target_file_name']
+            p_attachment.save()
+            content = {'id': p_attachment.id, 'url': p_attachment.url,
+                       'display_name': p_attachment.display_name}
+            result['code'] = 0
+            result['content'] = content
+        else:
+            result['content'] = '上传失败'
+        return JsonResponse(result)
+
+    if request.method == 'DELETE':
+        post_data = eval(request.body)
+        if PartAttachmentHandler.del_by_id(post_data['delete_attachment_id']):
+            result = {'code': 0, 'content': None}
+        else:
+            result = {'code': 1, 'content': '删除失败'}
+        return JsonResponse(result)
+
+
+def search_part_number(request):
+    if request.method == 'GET':
+        result = {'code': 1, 'content': None, 'msg': None}
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        part_number_keyword = request.GET.get('part_number')
+        parts = PartHandler.search_like_pn(part_number_keyword, company)
+        part_list = []
+        for p in parts:
+            part_list.append(model_to_dict(p))
+        result['code'] = 0
+        result['content'] = part_list
+        return JsonResponse(result)
+
+
+def get_draft_part(request):        # 读取draft状态下的part数据
+    if request.method == 'GET':
+        result = {'code': 1, 'content': None, 'msg': None}
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        part = PartHandler.get_by_id(request.GET.get('edit_id'))
+        if part.company != company:     # 校验part归属问题
+            result = {'code': 1, 'content': None,
+                      'msg': 'part is not for this company'}
+            return JsonResponse(result)
+        if part:
+            # part_attachments = PartAttachmentHandler.search_by_part(part)
+            # attachment_list = []
+            # for p_a in part_attachments:
+            #     attachment = {'id':p_a.id , 'url': p_a.url, 'display_name': p_a.display_name}
+            #     attachment_list.append(attachment)
+            # part_dict = model_to_dict(part)
+            # part_dict['sub_parts'] = eval(part_dict['sub_parts'])
+            # part_dict['attachment'] = attachment_list
+            # vendor_detail = {}
+            # if part.vendor:
+            #     vendor_detail = {
+            #         'id': part.vendor.id,
+            #         'code': part.vendor.code,
+            #         'name': part.vendor.name,
+            #     }
+            # part_dict['vendor_detail'] = vendor_detail
+            part_dict = part_info(part)
+            result = {'code': 0, 'content': part_dict, 'msg': 'success'}
+        return JsonResponse(result)
+
+
+def ecn_part_detail(request):       # 读取ECN具体签核料号的信息
+    if request.method == 'GET':
+        result = {'code': 1, 'content': None, 'msg': None}
+        ECN_id = request.GET.get('ecn_id')
+        if ECN_id:
+            ecn = EcnHandler.get_by_id(ECN_id)
+            company = CompanyHandler.get_by_id(
+                request.session.get('company_id'))
+            if ecn.target_part.company != company:     # 校验part归属问题
+                result = {'code': 1, 'content': None,
+                          'msg': 'part is not for this company'}
+                return JsonResponse(result)
+            content = {
+                'ecn_id': ecn.id,
+                'part': part_info(ecn.target_part)
+            }
+            result['content'] = content
+            result['code'] = 0
+        else:
+            result['msg'] = 'ECN没有找到'
+
+        return JsonResponse(result)
+
+
+@csrf_exempt
+def part_EOL(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    result = {'code': 1, 'content': None, 'msg': None}
+    if request.method == 'POST':
+        post_data = eval(request.body)
+        EOL_pn = post_data['EOL_pn']
+        part = PartHandler.get_by_pn(EOL_pn, company)
+        all_ver_parts = PartHandler.all_ver_by_pn(EOL_pn, company)
+        if user == part.owner:
+            # 停用released料号
+            part.life_cycle = 'EOL'
+            part.save()
+            # 删除签核中和草稿中的料号
+            for p in all_ver_parts:
+                if p.life_cycle in ['draft', 'sign-off']:
+                    p.delete()
+            result['msg'] = '料号{}停用成功'.format(EOL_pn)
+            result['code'] = 0
+            # 会直接删除料号相关的,签核中的ECN。关系是on_delete.cascade
+            # 会直接删除料号相关签核中的签核。关系是on_delete.cascade
+        else:
+            result['msg'] = '料号停用失败'
+            result['content'] = '非料号用户本人'
+
+        return JsonResponse(result)
+
+
+def attachment_maxsize(request):        # 获取BOM附件大小限制
+    # user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    result = {'code': 1, 'content': None, 'msg': None}
+
+    attachment_maxsize = BOMsettingHandler.get_attachment_maxsize(company)
+    if attachment_maxsize:
+        result['code'] = 0
+        result['content'] = {
+            'company_id': company.id,
+            'attachment_maxsize': attachment_maxsize
+        }
+    return JsonResponse(result)

+ 64 - 0
BOM/views_vendor_api.py

@@ -0,0 +1,64 @@
+from django.forms import model_to_dict
+# from django.forms.models import model_to_dict
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from .model_handler import PartHandler, VendorHandler
+from .models import VendorAttachment
+from Info.model_handler import CompanyHandler, UserHandler
+from Info.func import file_upload, get_file
+
+
+def code_check(request):
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    result = {'code': 1, 'content': None}
+    if request.method == 'GET':
+        vendor = VendorHandler.get_by_code(request.GET.get('vendor_code'), company)
+        if vendor:
+            result = {'code': 1, 'content': "这个供应商编号已经使用了。"}
+        else:
+            result = {'code': 0, 'content': None }
+        
+        return JsonResponse(result)
+
+
+@csrf_exempt
+def file(request):
+    result = {'code': 1, 'content': None}
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    if request.method == 'GET':
+        return get_file(request)
+    if request.method == 'POST':
+        upload_result = file_upload(request, ['BOM', 'vendor'])
+        if upload_result['code'] == 0:
+            v_attachment = VendorAttachment()
+            v_attachment.company = company
+            v_attachment.creator = user
+            v_attachment.display_name = upload_result['target_file_name']
+            v_attachment.url = upload_result['target_file_path'] + upload_result['target_file_name']
+            v_attachment.save()
+            content = {'id':v_attachment.id , 'url': v_attachment.url, 'display_name': v_attachment.display_name}
+            result['code'] = 0
+            result['content'] = content
+        else:
+            result['content'] = '上传失败'
+        
+        return JsonResponse(result)
+
+
+def search_vendor(request):
+    if request.method == 'GET':
+        result = {'code': 1, 'content': None, 'msg':None}
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        vendor_name_keyword = request.GET.get('vendor_name')
+        vendors = VendorHandler.search_like_name(vendor_name_keyword, company)
+        vendor_list = []
+        for v in vendors:
+            vendor_list.append(model_to_dict(v))
+        result['code'] = 0
+        result['content'] = vendor_list
+        return JsonResponse(result)
+
+
+
+

+ 0 - 0
IT_service/__init__.py


+ 3 - 0
IT_service/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
IT_service/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ItServiceConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'IT_service'

+ 55 - 0
IT_service/migrations/0001_initial.py

@@ -0,0 +1,55 @@
+# Generated by Django 4.0.5 on 2023-02-23 04:29
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('Info', '0009_rename_modular_modularenablement_modular_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Case',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('status', models.TextField(default='submit')),
+                ('urgency_level', models.IntegerField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('due_datetime', models.DateTimeField()),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('comment', models.TextField(blank=True, null=True)),
+                ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owner_of_case', to='Info.user')),
+                ('submitter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submitter_of_case', to='Info.user')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Setting',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('default_urgency_level', models.IntegerField(default=3)),
+                ('company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+                ('default_escalation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='escalation_contact', to='Info.user')),
+                ('default_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='default_engineer', to='Info.user')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='CaseAttachment',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('storage_type', models.TextField(default='local')),
+                ('url', models.TextField()),
+                ('display_name', models.TextField()),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='Info.company')),
+                ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='Info.user')),
+                ('part', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='IT_service.case')),
+            ],
+        ),
+    ]

+ 25 - 0
IT_service/migrations/0002_case_contact_mobile_case_contact_name.py

@@ -0,0 +1,25 @@
+# Generated by Django 4.0.5 on 2023-02-24 13:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('IT_service', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='case',
+            name='contact_mobile',
+            field=models.BigIntegerField(default=13012345678),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='case',
+            name='contact_name',
+            field=models.TextField(default='default user'),
+            preserve_default=False,
+        ),
+    ]

+ 32 - 0
IT_service/migrations/0003_remove_case_comment_case_description_and_more.py

@@ -0,0 +1,32 @@
+# Generated by Django 4.0.5 on 2023-02-26 11:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('IT_service', '0002_case_contact_mobile_case_contact_name'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='case',
+            name='comment',
+        ),
+        migrations.AddField(
+            model_name='case',
+            name='description',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='setting',
+            name='max_attachment_size',
+            field=models.IntegerField(default=20),
+        ),
+        migrations.AlterField(
+            model_name='setting',
+            name='default_urgency_level',
+            field=models.IntegerField(default=4),
+        ),
+    ]

+ 23 - 0
IT_service/migrations/0004_rename_part_caseattachment_case_alter_case_status.py

@@ -0,0 +1,23 @@
+# Generated by Django 4.0.5 on 2023-02-26 12:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('IT_service', '0003_remove_case_comment_case_description_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='caseattachment',
+            old_name='part',
+            new_name='case',
+        ),
+        migrations.AlterField(
+            model_name='case',
+            name='status',
+            field=models.TextField(default='open'),
+        ),
+    ]

+ 0 - 0
IT_service/migrations/__init__.py


+ 27 - 0
IT_service/model_handler.py

@@ -0,0 +1,27 @@
+from .models import *
+from Info.func import delete_file
+from Info.model_handler import UserHandler
+
+
+class SettingHandler():
+
+    @staticmethod
+    def get_by_company(company: Company):
+        try:
+            setting = Setting.objects.get(company=company)
+            return setting
+        except:
+            return None
+
+
+class CaseAttachmentHandler():
+
+    @staticmethod
+    def del_by_id(ID):
+        try:
+            attachment = CaseAttachment.objects.get(id=ID)
+            delete_file(attachment.url)
+            attachment.delete()
+            return True
+        except:
+            return False

+ 39 - 0
IT_service/models.py

@@ -0,0 +1,39 @@
+from django.db import models
+from Info.models import User, Company
+
+# Create your models here.
+
+class Setting(models.Model):
+    id = models.AutoField(primary_key=True)
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=True)
+    default_owner = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='default_engineer', null=False, blank=False)        # 默认IT服务工程师
+    default_escalation = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='escalation_contact', null=False, blank=False)        # 默认IT服务工程师
+    default_urgency_level = models.IntegerField(default=4)      # 紧急程度默认为4
+    max_attachment_size = models.IntegerField(default=20)      # 默认公司保存附件大小(单位M)
+
+
+class Case(models.Model):       # IT 提交的case
+    id = models.AutoField(primary_key=True)
+    submitter = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="submitter_of_case", null=False, blank=False)        # 创建人
+    contact_name = models.TextField()       # 可能提交人未必是登陆人,所以必须自定义联系人姓名和手机号码(contact_mobile)
+    contact_mobile = models.BigIntegerField()
+    description = models.TextField(blank=True, null=True)       # 问题描述
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, null=False) 
+    owner = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="owner_of_case", null=True, blank=True)        # 负责IT服务工程师
+    status = models.TextField(default='open')     # 状态 open,closed
+    urgency_level = models.IntegerField(null=False, blank=False)      # 从setting里面读取
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    due_datetime = models.DateTimeField(null=False, blank=False)        # 根据紧急程度自动计算
+    last_update_datetime = models.DateTimeField(auto_now=True)
+    
+
+class CaseAttachment(models.Model):
+    id = models.AutoField(primary_key=True)
+    case = models.ForeignKey(to=Case, on_delete=models.CASCADE, blank=True, null=True)
+    storage_type = models.TextField(default="local")    # 存储类型 local/cloud 默认为local
+    url = models.TextField()
+    display_name = models.TextField()
+    creator = models.ForeignKey(to=User, on_delete=models.DO_NOTHING, null=True, blank=True)        # 创建人
+    company = models.ForeignKey( to=Company, on_delete=models.CASCADE, null=True)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)

+ 24 - 0
IT_service/templates/IT_service/layout.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta charset="utf-8" />
+        <meta http-equiv="X-UA-Compatible" content="chrome=1" />
+        <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
+        {% load static %}
+        <title>{% if title %} {{title}} {% else %} Nimble System {% endif %}</title>
+        <!-- 新 Bootstrap5 核心 CSS 文件 -->
+        {% comment %} <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css" /> {% endcomment %}
+        <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}" />
+        <!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
+        {% comment %} <script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script> {% endcomment %}
+        <script src="{% static 'bootstrap.bundle.min.js' %}"></script>
+        {% comment %} <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> {% endcomment %}
+        {% block head %}{% endblock head %}
+        {% block script %}{% endblock script %}
+    </head>
+    {% load static %}
+    <body>
+        {% block body %}{% endblock body %}
+        {% include 'info/bottom.html' %}
+    </body>
+</html>

+ 281 - 0
IT_service/templates/IT_service/new_case.html

@@ -0,0 +1,281 @@
+{% extends "IT_service/layout.html" %}
+{% load static %}
+{% block script %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div id="mainForm"></div>
+    </form>
+</div>
+<script type="text/babel">
+    class MainForm extends React.Component {
+
+        constructor(props) {
+            super(props);
+            this.state = {
+                contact_name: "",
+                contact_mobile: "",
+                description: "",
+                urgency_level: 3,
+                attachment_maxsize: 10,      // 上传附件文件大小限制(单位M)。Todo 以后开发限制上传文件大小功能
+                attachment: []
+            }
+        }
+
+        componentDidMount() {
+            this.get_attachment_maxsize()
+            this.get_user_info()
+            // this.forceUpdate()
+        }
+
+        get_attachment_maxsize(){
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: {},
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/IT_service/case/attachment/maxsize/"
+            }).then(function(response){
+                if (response.data.code != 0) {
+                    // 检测失败
+                    // alert(response.data.content)
+                } else {
+                    // console.log('content', response.data.content)
+                    that.setState({
+                        attachment_maxsize:response.data.content.attachment_maxsize
+                    })
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        get_user_info(id) {
+            var that = this
+            axios({
+                method: "GET",
+                headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                params: { edit_id: id },
+                // data: Qs.stringify({ delete_user_id: user_id }),
+                url: "/IT_service/case/user/info/"
+            }).then(function (response) {
+                if (response.data.code != 0) {
+                    // 检测失败
+                    alert(response.data.content)
+                } else {
+                    // 检测成功
+                    console.log('content', response.data.content)
+                    let user_info = response.data.content
+                    // if (part_data.version.toString().length < 2) {
+                    //     part_data.version = "0" + part_data.version
+                    // }
+                    that.setState({
+                        contact_name: user_info.contact_name,
+                        contact_mobile: user_info.contact_mobile,
+
+                    })
+                    document.getElementById("contact_name").value = that.state.contact_name
+                    document.getElementById("contact_mobile").value = that.state.contact_mobile
+                }
+            }).catch(function (error) {
+                console.log(error)
+            })
+        }
+
+        
+        attachmentList() {
+            if (this.state.attachment.length == 0) {
+                return (
+                    null
+                )
+            } else {
+                return (
+                    <div>
+                        {this.state.attachment.map((row, index) =>
+                            <div class="attachmentList">
+                                <a href={row.url}>{row.display_name}</a>
+                                <p onClick={() => { this.deleteAttachment(row.id) }}>删除</p>
+                            </div>
+                        )}
+
+                    </div>
+                )
+            }
+        }
+
+        formItemChange(e) {
+            var key = e.target.name.trim()
+            var value = e.target.value.trim()
+            console.log("form item chnange", key, value)
+            // this.setState({ key: value })
+            this.state[key] = value
+            this.forceUpdate()
+        }
+
+        addAttachment(e) {
+            let formData = new FormData()
+            if (e.currentTarget.files[0].size > this.state.attachment_maxsize * 1024 * 1024) {
+                alert("上传文件大小超过系统限制。当前限制是".concat(this.state.attachment_maxsize, "MB。"))
+            } else {
+                formData.append('files', e.currentTarget.files[0])
+                var that = this
+                axios({
+                    method: "POST",
+                    headers: { 'content-type': 'multipart/form-data' },     // 文件上传header
+                    // params: { vendor_code: target_code.trim() },
+                    data: formData,
+                    url: "/IT_service/case/user/file/"
+                }).then(function (response) {
+                    if (response.data.code != 0) {
+                        alert(response.data.content)
+                    } else {
+                        alert("附件上传成功")
+                        var new_attachment = {
+                            id: response.data.content.id,
+                            url: response.data.content.url,
+                            display_name: response.data.content.display_name,
+                        }
+                        that.setState({
+                            attachment: that.state.attachment.concat([new_attachment])
+                        })
+                    }
+                }).catch(function (error) {
+                    console.log(error)
+                })
+                console.log("this.state.attachment", this.state.attachment)
+            }
+        }
+
+        deleteAttachment(delete_id) {
+            for (let i = 0; i < this.state.attachment.length; i++) {
+                if (this.state.attachment[i].id == delete_id) {
+                    var that = this
+                    axios({
+                        method: "DELETE",
+                        headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                        // params: { vendor_code: target_code.trim() },
+                        data: { delete_attachment_id: delete_id },
+                        url: "/IT_service/case/user/file/"
+                    }).then(function (response) {
+                        if (response.data.code != 0) {
+                            // 删除附件失败
+                            console.log(response.data.content)
+                        } else {
+                            console.log("delete attachment", that.state.attachment[i])
+                            that.state.attachment.splice(i, 1);
+                            i--;
+                            that.forceUpdate()
+                        }
+                    }).catch(function (error) {
+                        console.log(error)
+                    })
+                }
+            }
+        }
+
+        showState() {
+            console.log(this.state)
+        }
+
+
+        render() {
+            this.showState()
+            return (
+                <div>
+                    <div class="mb-3 mt-3">
+                        <label for="contact_name" class="form-label">联系人:</label>
+                        <input type="text" class="form-control" id="contact_name" placeholder="输入联系人" name="contact_name" required maxlength="32"/>
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="contact_mobile" class="form-label">联系电话号码:</label>
+                        <input type="text" class="form-control" id="contact_mobile" placeholder="输入联系电话号码" name="contact_mobile" required maxlength="32"/>
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="description" class="form-label">描述:</label>
+                        
+                        <textarea type="textarea" class="form-control" id="description" placeholder="描述一下遇到的问题" name="description" maxlength="256" onBlur={(e) => this.formItemChange(e)}></textarea>
+                    </div>
+                    <div class="mb-3 mt-3">
+                        <label for="urgency_level" class="form-label">紧急程度:</label>
+                        <select class="form-select" id="urgency_level" name="urgency_level" value="4" onChange={(e) => this.formItemChange(e)} >
+                            <option value="1">紧急(影响公司业务)</option>
+                            <option value="2">重要(影响团队业务)</option>
+                            <option value="3">普通(影响个人业务)</option>
+                            <option value="4">低(仅影响工作效率)</option>
+                        </select>
+                    </div>
+                    <div class="card">
+                        <div class="card-header">附件</div>
+                        <div class="card-body">
+                            <input type="file"
+                                id="fileElem"
+                                class="visually-hidden"
+                                accept="image/*"
+                                onChange={(e) => this.addAttachment(e, this)}
+                            />
+                            <label for="fileElem">添加附件</label>
+                            {this.attachmentList()}
+                            <input type="hidden"
+                                name="attachment"
+                                value={JSON.stringify(this.state.attachment)}
+                            />
+                        </div>
+                    </div>
+                    <div id="form-button-area">
+                        <button type="submit" name="create" value="create" class="btn btn-primary">提交</button>
+                        <a href="/home/">返回</a>
+                    </div>
+                </div>
+            )
+        }
+    }
+
+    ReactDOM.render(<MainForm />, document.getElementById("mainForm"))
+
+</script>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 50px;
+        width: inherit;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    div.attachmentList {
+        display: flex;
+        justify-content: space-around;
+    }
+
+    div.supplier_search {
+        display: flex;
+        justify-content: flex-start;
+    }
+
+    .card {
+        margin-bottom: 10px;
+        width: 100%;
+    }
+
+    .BOM-qty {
+        width: 100px;
+    }
+</style>
+{% endblock body %}

+ 3 - 0
IT_service/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 16 - 0
IT_service/urls.py

@@ -0,0 +1,16 @@
+from django.urls import path
+from . import views, views_user_api
+
+urlpatterns = [
+    # path('api/<str:role>/<str:action>/', views.api),
+    path('user/request/create/', views.new_case)
+
+]
+
+
+# API urls (user)
+urlpatterns += [
+    path('case/attachment/maxsize/', views_user_api.attachment_maxsize),
+    path('case/user/info/', views_user_api.user_info),
+    path('case/user/file/', views_user_api.file),
+]

+ 42 - 0
IT_service/views.py

@@ -0,0 +1,42 @@
+from django.shortcuts import render
+from django.http import HttpResponse
+from datetime import datetime, timedelta
+from Info.model_handler import CompanyHandler, UserHandler
+from .models import Case
+from .model_handler import SettingHandler, CaseAttachmentHandler
+
+# Create your views here.
+def new_case(request):
+    if request.method == 'GET':
+        return render(request, 'IT_service/new_case.html')
+
+    if request.method == 'POST':
+        company = CompanyHandler.get_by_id(request.session.get('company_id'))
+        user = UserHandler.get_by_id(request.session.get('user_id'))
+        setting = SettingHandler.get_by_company(company)
+
+        case =  Case()      # 保存case
+        case.submitter = user
+        case.contact_name = request.POST.get('contact_name')
+        case.contact_mobile = request.POST.get('contact_mobile')
+        case.description = request.POST.get('description')
+        case.owner = setting.default_owner
+        case.urgency_level = int(request.POST.get('urgency_level'))
+        if case.urgency_level == 1:
+            response_hours = 4
+        elif case.urgency_level == 2:
+            response_hours = 8
+        elif case.urgency_level == 3:
+            response_hours = 24
+        elif case.urgency_level == 4:
+            response_hours = 72
+        case.due_datetime = datetime.now() + timedelta(hours=response_hours)
+        case.save()
+
+        attachments = eval(request.POST.get('attachment'))      # 保存附件
+        for attachment in attachments:
+            c_file = CaseAttachmentHandler.get_by_id(attachment['id'])
+            c_file.case = case
+            c_file.save()
+
+        return render(request, 'info/directPage.html', {'alertMsg': '保存成功', 'dirLink': '/home/'})

+ 68 - 0
IT_service/views_user_api.py

@@ -0,0 +1,68 @@
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.forms import model_to_dict
+from .model_handler import SettingHandler, CaseAttachmentHandler
+from .models import CaseAttachment
+from Info.model_handler import CompanyHandler, UserHandler
+from Info.func import file_upload, get_file
+
+
+def attachment_maxsize(request):        # 获取BOM附件大小限制
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    result = {'code': 1, 'content': None, 'msg': None}
+
+    company_setting = SettingHandler.get_by_company(company)
+    if company_setting:
+        result['code'] = 0
+        result['content'] = {
+            'company_id': company_setting.company.id,
+            'attachment_maxsize': company_setting.max_attachment_size
+        }
+    return JsonResponse(result)
+
+
+def user_info(request):     # 从user数据中读取默认的姓名和手机号码
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    result = {'code': 1, 'content': None, 'msg': None}
+    result['code'] = 0
+    result['content'] = {
+        'contact_name': user.name,
+        'contact_mobile': user.mobile
+    }
+    return JsonResponse(result)
+
+
+@csrf_exempt
+def file(request):
+    result = {'code': 1, 'content': None}
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+
+    if request.method == 'GET':
+        return get_file(request)
+
+    if request.method == 'POST':
+        upload_result = file_upload(request, ['IT_service', 'case'])
+        if upload_result['code'] == 0:
+            case_attachment = CaseAttachment()
+            case_attachment.company = company
+            case_attachment.creator = user
+            case_attachment.display_name = upload_result['target_file_name']
+            case_attachment.url = upload_result['target_file_path'] + \
+                upload_result['target_file_name']
+            case_attachment.save()
+            content = {'id': case_attachment.id, 'url': case_attachment.url,
+                       'display_name': case_attachment.display_name}
+            result['code'] = 0
+            result['content'] = content
+        else:
+            result['content'] = '上传失败'
+        return JsonResponse(result)
+
+    if request.method == 'DELETE':
+        post_data = eval(request.body)
+        if CaseAttachmentHandler.del_by_id(post_data['delete_attachment_id']):
+            result = {'code': 0, 'content': None}
+        else:
+            result = {'code': 1, 'content': '删除失败'}
+        return JsonResponse(result)

+ 0 - 0
Info/__init__.py


+ 3 - 0
Info/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
Info/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class InfoConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'Info'

+ 92 - 0
Info/func.py

@@ -0,0 +1,92 @@
+from Info.models import Company, User, Message
+from .model_handler import UserHandler, CompanyHandler
+from django.conf import settings
+from datetime import datetime
+import os
+from django.http import FileResponse
+
+
+def is_login(request):
+    if request.session.get('user_id') and request.session.get('company_id'):
+        user = UserHandler.get_by_id(request.session.get('user_id'))
+        if user.company.id == request.session.get('company_id'):
+            return True
+        else:
+            return False
+    else:
+        return False
+
+
+def is_admin(request):
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    if not user.admin or user.company != company:
+        return False
+    else:
+        return True
+
+
+def file_upload(request, group: list):
+    # group 参数表示上传文件的分类,会体现在上传的文件夹
+    result = {'code':1, 'target_file_path':None, 'target_file_name':None, 'result':'fail'}
+    target_dir = settings.BASE_DIR.parent
+    target_file_path = '/upload/'
+    for g in group:
+        target_file_path += g
+        target_file_path += '/'
+    user = UserHandler.get_by_id(request.session.get('user_id'))
+    company = CompanyHandler.get_by_id(request.session.get('company_id'))
+    target_file_path = target_file_path + str(company.id) + '/'
+    target_file_path = target_file_path + str(user.id) + '/'
+    target_file_path = target_file_path + datetime.now().strftime('%Y%m%d_%H%M%S') + '/'
+    target_dir = str(target_dir).replace('\\', '/') + target_file_path
+    target_file_name = request.FILES['files'].name
+    if not os.path.isdir(target_dir):
+        os.makedirs(target_dir)
+    
+    result = {'code':1, 'target_file_path':target_file_path, 'target_file_name':target_file_name, 'result':'fail'}
+    try:
+        destination = open(target_dir+target_file_name, 'wb+')
+        for chunk in request.FILES['files'].chunks():
+            destination.write(chunk)
+        destination.close()
+        result['code'] = 0
+        result['result'] = 'pass'
+    except:
+        pass
+
+    return result
+
+
+def get_file(request):
+    target_dir = settings.BASE_DIR.parent
+    target_file_path = request.get_full_path()
+    target_file = str(target_dir).replace('\\', '/') + target_file_path
+    return FileResponse(open(target_file, 'rb'), as_attachment=True)
+
+
+def delete_file(url):
+    target_dir = settings.BASE_DIR.parent
+    target_file = str(target_dir).replace('\\', '/') + url
+    if os.path.exists(target_file):
+        os.remove(target_file)
+        return True
+    else:
+        return False
+
+
+def file_host(request):
+    return request.get_host() + '/upload/'
+    
+
+def send_message(from_user:User, to_user:User, title:str, content:str = None):
+    # 未来可能扩展出短信,微信等多个功能
+    message = Message()
+    if from_user:
+        message.user_from = from_user
+    message.user_to = to_user
+    # message.company = company
+    message.title = title
+    if content:
+        message.body = content
+    message.save()

+ 45 - 0
Info/migrations/0001_initial.py

@@ -0,0 +1,45 @@
+# Generated by Django 4.0.5 on 2022-06-26 10:36
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Company',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=128)),
+                ('address', models.CharField(blank=True, max_length=256, null=True)),
+                ('phone', models.BigIntegerField(unique=True)),
+                ('license_id', models.CharField(max_length=128)),
+                ('primary_contact_name', models.CharField(max_length=128)),
+                ('primary_contact_mobile', models.BigIntegerField(unique=True)),
+                ('primary_contact_email', models.CharField(max_length=256)),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='user',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=128)),
+                ('password', models.CharField(max_length=128)),
+                ('mobile', models.BigIntegerField(blank=True, null=True, unique=True)),
+                ('email', models.CharField(max_length=256, unique=True)),
+                ('wechat_id', models.CharField(max_length=128, null=True)),
+                ('role', models.CharField(default='user', max_length=32)),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_to_company', to='Info.company')),
+            ],
+        ),
+    ]

+ 31 - 0
Info/migrations/0002_userhandler_user_position_alter_user_name.py

@@ -0,0 +1,31 @@
+# Generated by Django 4.0.5 on 2022-06-29 06:47
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='UserHandler',
+            fields=[
+                ('user_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='Info.user')),
+            ],
+            bases=('Info.user',),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='position',
+            field=models.CharField(blank=True, max_length=128, null=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='name',
+            field=models.CharField(max_length=32),
+        ),
+    ]

+ 21 - 0
Info/migrations/0003_alter_user_role_delete_userhandler.py

@@ -0,0 +1,21 @@
+# Generated by Django 4.0.5 on 2022-07-03 08:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0002_userhandler_user_position_alter_user_name'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='role',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.DeleteModel(
+            name='UserHandler',
+        ),
+    ]

+ 18 - 0
Info/migrations/0004_rename_role_user_admin.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.0.5 on 2022-07-03 08:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0003_alter_user_role_delete_userhandler'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='user',
+            old_name='role',
+            new_name='admin',
+        ),
+    ]

+ 63 - 0
Info/migrations/0005_alter_company_address_alter_company_license_id_and_more.py

@@ -0,0 +1,63 @@
+# Generated by Django 4.0.5 on 2022-07-04 07:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0004_rename_role_user_admin'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='company',
+            name='address',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='company',
+            name='license_id',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='company',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='company',
+            name='primary_contact_email',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='company',
+            name='primary_contact_name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='email',
+            field=models.TextField(unique=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='name',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='password',
+            field=models.TextField(),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='position',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='wechat_id',
+            field=models.TextField(max_length=128, null=True),
+        ),
+    ]

+ 18 - 0
Info/migrations/0006_user_psw_change_required.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.0.5 on 2022-07-31 03:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0005_alter_company_address_alter_company_license_id_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='user',
+            name='psw_change_required',
+            field=models.BooleanField(default=False),
+        ),
+    ]

+ 26 - 0
Info/migrations/0007_message.py

@@ -0,0 +1,26 @@
+# Generated by Django 4.0.5 on 2022-08-14 11:12
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0006_user_psw_change_required'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Message',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('title', models.TextField()),
+                ('body', models.TextField(blank=True, null=True)),
+                ('is_read', models.BooleanField(default=False)),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('user_from', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='message_sender', to='Info.user')),
+                ('user_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='message_receiver', to='Info.user')),
+            ],
+        ),
+    ]

+ 36 - 0
Info/migrations/0008_modular_user_comment_modularenablement.py

@@ -0,0 +1,36 @@
+# Generated by Django 4.0.5 on 2023-02-09 07:43
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0007_message'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Modular',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.TextField()),
+                ('developer', models.TextField(blank=True, null=True)),
+            ],
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='comment',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.CreateModel(
+            name='ModularEnablement',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('expiration_date', models.DateTimeField(auto_now_add=True)),
+                ('Modular', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modular_id', to='Info.modular')),
+                ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modular_of_company', to='Info.company')),
+            ],
+        ),
+    ]

+ 28 - 0
Info/migrations/0009_rename_modular_modularenablement_modular_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.0.5 on 2023-02-10 03:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0008_modular_user_comment_modularenablement'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='modularenablement',
+            old_name='Modular',
+            new_name='modular',
+        ),
+        migrations.AddField(
+            model_name='modular',
+            name='life_cycle',
+            field=models.TextField(default='development'),
+        ),
+        migrations.AddField(
+            model_name='modularenablement',
+            name='comment',
+            field=models.TextField(blank=True, null=True),
+        ),
+    ]

+ 34 - 0
Info/migrations/0010_internaluser.py

@@ -0,0 +1,34 @@
+# Generated by Django 4.0.5 on 2023-02-24 13:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('Info', '0009_rename_modular_modularenablement_modular_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='InternalUser',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('name', models.TextField()),
+                ('password', models.TextField()),
+                ('psw_change_required', models.BooleanField(default=False)),
+                ('mobile', models.BigIntegerField(blank=True, null=True, unique=True)),
+                ('email', models.TextField(unique=True)),
+                ('create_internal_user_authority', models.BooleanField(default=False)),
+                ('edit_internal_user_authority', models.BooleanField(default=False)),
+                ('delete_internal_user_authority', models.BooleanField(default=False)),
+                ('create_company_authority', models.BooleanField(default=False)),
+                ('delete_company_authority', models.BooleanField(default=False)),
+                ('create_modular_authority', models.BooleanField(default=False)),
+                ('delete_modular_authority', models.BooleanField(default=False)),
+                ('create_datetime', models.DateTimeField(auto_now_add=True)),
+                ('last_update_datetime', models.DateTimeField(auto_now=True)),
+                ('comment', models.TextField(blank=True, null=True)),
+            ],
+        ),
+    ]

+ 0 - 0
Info/migrations/__init__.py


+ 92 - 0
Info/model_handler.py

@@ -0,0 +1,92 @@
+from .models import Message, User, Company, Modular, ModularEnablement
+from django.contrib.auth.hashers import check_password
+from datetime import datetime
+
+
+class UserHandler():
+
+    @staticmethod
+    def check_exist_email(email: str):
+        try:
+            User.objects.get(email = email)
+            return True
+        except:
+            return False
+
+    @staticmethod
+    def login(email: str, password: str):
+        try:
+            user = User.objects.get(email = email)
+            if check_password(password, user.password):
+                return user
+            else:
+                return None
+        except Exception as e:
+            print(e)
+            return None
+
+    @staticmethod
+    def get_by_id(id):
+        try:
+            user = User.objects.get(id = id)
+            return user
+        except:
+            return None
+
+    @staticmethod
+    def search_by_company(company):
+        return User.objects.filter(company=company)
+
+    @staticmethod
+    def search_like_name(name, company):
+        return User.objects.filter(name__contains=name, company=company)
+
+
+class CompanyHandler():
+
+    @staticmethod
+    def get_by_id(id):
+        try:
+            company = Company.objects.get(id = id)
+            return company
+        except:
+            return None
+
+
+class MessageHandler():
+
+    @staticmethod
+    def my_msg(user, is_read: bool=False):
+        msg = Message.objects.filter(user_to=user)
+        if is_read:
+            msg = msg.filter(is_read=is_read)
+        
+        return msg
+
+    @staticmethod
+    def unread_msg_count(user) -> int:
+        msg = MessageHandler.my_msg(user=user, is_read=False)
+        return msg.count()
+
+
+class ModularEnablementHandler():
+
+    @staticmethod
+    def search_by_company(company: Company):
+        enabled_modulars = ModularEnablement.objects.filter(company=company, expiration_date__gte = datetime.now())
+        return enabled_modulars
+
+
+class InternalUserHandler():
+
+    @staticmethod
+    def login(name: str, password: str):
+        try:
+            user = User.objects.get(name = name)
+            if check_password(password, user.password):
+                return user
+            else:
+                return None
+        except Exception as e:
+            print(e)
+            return None 

+ 75 - 0
Info/models.py

@@ -0,0 +1,75 @@
+from django.db import models
+
+# Create your models here.
+class Company(models.Model):
+    id = models.AutoField(primary_key=True)
+    name = models.TextField()  # 公司名称
+    address = models.TextField(blank=True, null=True)  # 公司地址
+    phone = models.BigIntegerField(unique=True)
+    license_id = models.TextField() 
+    primary_contact_name = models.TextField()
+    primary_contact_mobile = models.BigIntegerField(unique=True)
+    primary_contact_email = models.TextField()  # 邮箱
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+
+
+class User(models.Model):
+    id = models.AutoField(primary_key=True)
+    name = models.TextField()  # 姓名
+    password = models.TextField()
+    psw_change_required = models.BooleanField(default=False)        # 是否需要跳转更换密码
+    mobile = models.BigIntegerField(unique=True, blank=True, null=True)  # 手机号码
+    email = models.TextField(unique=True)  # 邮箱
+    position = models.TextField(blank=True, null=True)  # 职位
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, related_name='user_to_company')
+    wechat_id = models.TextField(null=True, max_length=128)  # 微信openid
+    admin = models.BooleanField(default=False)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+    comment = models.TextField(null=True, blank=True)       # 备注
+
+
+class Message(models.Model):
+    id = models.AutoField(primary_key=True)
+    # company = models.ForeignKey(to=Company, on_delete=models.CASCADE, related_name='message_of_company')
+    user_from = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='message_sender', null=True, blank=True)
+    user_to = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='message_receiver')
+    title = models.TextField()
+    body = models.TextField(null=True, blank=True)
+    is_read = models.BooleanField(default=False)
+    create_datetime = models.DateTimeField(auto_now_add=True)
+
+
+class Modular(models.Model):        # 每个模块定义
+    id = models.AutoField(primary_key=True)
+    name = models.TextField()
+    life_cycle = models.TextField(default='development')        # 模块的生命周期 development/active/EOL
+    developer = models.TextField(null=True, blank=True)
+
+
+class ModularEnablement(models.Model):        # 针对每个公司对应的模块是否激活表格
+    id = models.AutoField(primary_key=True)
+    company = models.ForeignKey(to=Company, on_delete=models.CASCADE, related_name='modular_of_company')
+    modular = models.ForeignKey(to=Modular, on_delete=models.CASCADE, related_name='modular_id')
+    expiration_date = models.DateTimeField(auto_now_add=True)
+    comment = models.TextField(null=True, blank=True)
+
+
+class InternalUser(models.Model):       # 公司后台管理账户
+    id = models.AutoField(primary_key=True)
+    name = models.TextField()  # 姓名
+    password = models.TextField()
+    psw_change_required = models.BooleanField(default=False)        # 是否需要跳转更换密码
+    mobile = models.BigIntegerField(unique=True, blank=True, null=True)  # 手机号码
+    email = models.TextField(unique=True)  # 邮箱
+    create_internal_user_authority = models.BooleanField(default=False)      # 创建内部用户权限
+    edit_internal_user_authority = models.BooleanField(default=False)      # 编辑内部用户权限
+    delete_internal_user_authority = models.BooleanField(default=False)      # 删除内部用户权限
+    create_company_authority = models.BooleanField(default=False)      # 创建公司权限
+    delete_company_authority = models.BooleanField(default=False)      # 删除公司权限
+    create_modular_authority = models.BooleanField(default=False)       # 创建模块权限
+    delete_modular_authority = models.BooleanField(default=False)       # 删除模块权限
+    create_datetime = models.DateTimeField(auto_now_add=True)
+    last_update_datetime = models.DateTimeField(auto_now=True)
+    comment = models.TextField(null=True, blank=True)       # 备注

File diff suppressed because it is too large
+ 1 - 0
Info/static/axios.min.js


File diff suppressed because it is too large
+ 0 - 0
Info/static/babel.min.js


File diff suppressed because it is too large
+ 5 - 0
Info/static/bootstrap.bundle.min.js


File diff suppressed because it is too large
+ 0 - 0
Info/static/bootstrap.bundle.min.js.map


File diff suppressed because it is too large
+ 5 - 0
Info/static/bootstrap.min.css


File diff suppressed because it is too large
+ 0 - 0
Info/static/bootstrap.min.css.map


+ 12 - 0
Info/static/info/javascript/register.js

@@ -0,0 +1,12 @@
+function IsSamePassword() {
+    password_1 = document.getElementById("psw").value
+    password_2 = document.getElementById("psw2").value
+    if (password_1 != password_2){
+        // alert("两次输入密码不一致!")
+        document.getElementById("password_alert").innerHTML = "<font color='red'>两次输入密码不一致!</font>"
+        document.getElementById("submit_btn").disabled = true
+    }else{
+        document.getElementById("password_alert").innerHTML = ""
+        document.getElementById("submit_btn").disabled = false
+    }
+}

+ 1903 - 0
Info/static/qs.js

@@ -0,0 +1,1903 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Qs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+'use strict';
+
+var replace = String.prototype.replace;
+var percentTwenties = /%20/g;
+
+var Format = {
+    RFC1738: 'RFC1738',
+    RFC3986: 'RFC3986'
+};
+
+module.exports = {
+    'default': Format.RFC3986,
+    formatters: {
+        RFC1738: function (value) {
+            return replace.call(value, percentTwenties, '+');
+        },
+        RFC3986: function (value) {
+            return String(value);
+        }
+    },
+    RFC1738: Format.RFC1738,
+    RFC3986: Format.RFC3986
+};
+
+},{}],2:[function(require,module,exports){
+'use strict';
+
+var stringify = require('./stringify');
+var parse = require('./parse');
+var formats = require('./formats');
+
+module.exports = {
+    formats: formats,
+    parse: parse,
+    stringify: stringify
+};
+
+},{"./formats":1,"./parse":3,"./stringify":4}],3:[function(require,module,exports){
+'use strict';
+
+var utils = require('./utils');
+
+var has = Object.prototype.hasOwnProperty;
+var isArray = Array.isArray;
+
+var defaults = {
+    allowDots: false,
+    allowPrototypes: false,
+    allowSparse: false,
+    arrayLimit: 20,
+    charset: 'utf-8',
+    charsetSentinel: false,
+    comma: false,
+    decoder: utils.decode,
+    delimiter: '&',
+    depth: 5,
+    ignoreQueryPrefix: false,
+    interpretNumericEntities: false,
+    parameterLimit: 1000,
+    parseArrays: true,
+    plainObjects: false,
+    strictNullHandling: false
+};
+
+var interpretNumericEntities = function (str) {
+    return str.replace(/&#(\d+);/g, function ($0, numberStr) {
+        return String.fromCharCode(parseInt(numberStr, 10));
+    });
+};
+
+var parseArrayValue = function (val, options) {
+    if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
+        return val.split(',');
+    }
+
+    return val;
+};
+
+// This is what browsers will submit when the ✓ character occurs in an
+// application/x-www-form-urlencoded body and the encoding of the page containing
+// the form is iso-8859-1, or when the submitted form has an accept-charset
+// attribute of iso-8859-1. Presumably also with other charsets that do not contain
+// the ✓ character, such as us-ascii.
+var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('&#10003;')
+
+// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.
+var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')
+
+var parseValues = function parseQueryStringValues(str, options) {
+    var obj = {};
+    var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
+    var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
+    var parts = cleanStr.split(options.delimiter, limit);
+    var skipIndex = -1; // Keep track of where the utf8 sentinel was found
+    var i;
+
+    var charset = options.charset;
+    if (options.charsetSentinel) {
+        for (i = 0; i < parts.length; ++i) {
+            if (parts[i].indexOf('utf8=') === 0) {
+                if (parts[i] === charsetSentinel) {
+                    charset = 'utf-8';
+                } else if (parts[i] === isoSentinel) {
+                    charset = 'iso-8859-1';
+                }
+                skipIndex = i;
+                i = parts.length; // The eslint settings do not allow break;
+            }
+        }
+    }
+
+    for (i = 0; i < parts.length; ++i) {
+        if (i === skipIndex) {
+            continue;
+        }
+        var part = parts[i];
+
+        var bracketEqualsPos = part.indexOf(']=');
+        var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
+
+        var key, val;
+        if (pos === -1) {
+            key = options.decoder(part, defaults.decoder, charset, 'key');
+            val = options.strictNullHandling ? null : '';
+        } else {
+            key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');
+            val = utils.maybeMap(
+                parseArrayValue(part.slice(pos + 1), options),
+                function (encodedVal) {
+                    return options.decoder(encodedVal, defaults.decoder, charset, 'value');
+                }
+            );
+        }
+
+        if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
+            val = interpretNumericEntities(val);
+        }
+
+        if (part.indexOf('[]=') > -1) {
+            val = isArray(val) ? [val] : val;
+        }
+
+        if (has.call(obj, key)) {
+            obj[key] = utils.combine(obj[key], val);
+        } else {
+            obj[key] = val;
+        }
+    }
+
+    return obj;
+};
+
+var parseObject = function (chain, val, options, valuesParsed) {
+    var leaf = valuesParsed ? val : parseArrayValue(val, options);
+
+    for (var i = chain.length - 1; i >= 0; --i) {
+        var obj;
+        var root = chain[i];
+
+        if (root === '[]' && options.parseArrays) {
+            obj = [].concat(leaf);
+        } else {
+            obj = options.plainObjects ? Object.create(null) : {};
+            var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
+            var index = parseInt(cleanRoot, 10);
+            if (!options.parseArrays && cleanRoot === '') {
+                obj = { 0: leaf };
+            } else if (
+                !isNaN(index)
+                && root !== cleanRoot
+                && String(index) === cleanRoot
+                && index >= 0
+                && (options.parseArrays && index <= options.arrayLimit)
+            ) {
+                obj = [];
+                obj[index] = leaf;
+            } else {
+                obj[cleanRoot] = leaf;
+            }
+        }
+
+        leaf = obj;
+    }
+
+    return leaf;
+};
+
+var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {
+    if (!givenKey) {
+        return;
+    }
+
+    // Transform dot notation to bracket notation
+    var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
+
+    // The regex chunks
+
+    var brackets = /(\[[^[\]]*])/;
+    var child = /(\[[^[\]]*])/g;
+
+    // Get the parent
+
+    var segment = options.depth > 0 && brackets.exec(key);
+    var parent = segment ? key.slice(0, segment.index) : key;
+
+    // Stash the parent if it exists
+
+    var keys = [];
+    if (parent) {
+        // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties
+        if (!options.plainObjects && has.call(Object.prototype, parent)) {
+            if (!options.allowPrototypes) {
+                return;
+            }
+        }
+
+        keys.push(parent);
+    }
+
+    // Loop through children appending to the array until we hit depth
+
+    var i = 0;
+    while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {
+        i += 1;
+        if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
+            if (!options.allowPrototypes) {
+                return;
+            }
+        }
+        keys.push(segment[1]);
+    }
+
+    // If there's a remainder, just add whatever is left
+
+    if (segment) {
+        keys.push('[' + key.slice(segment.index) + ']');
+    }
+
+    return parseObject(keys, val, options, valuesParsed);
+};
+
+var normalizeParseOptions = function normalizeParseOptions(opts) {
+    if (!opts) {
+        return defaults;
+    }
+
+    if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') {
+        throw new TypeError('Decoder has to be a function.');
+    }
+
+    if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
+        throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
+    }
+    var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;
+
+    return {
+        allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
+        allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,
+        allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,
+        arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,
+        charset: charset,
+        charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
+        comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,
+        decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,
+        delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,
+        // eslint-disable-next-line no-implicit-coercion, no-extra-parens
+        depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,
+        ignoreQueryPrefix: opts.ignoreQueryPrefix === true,
+        interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,
+        parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,
+        parseArrays: opts.parseArrays !== false,
+        plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,
+        strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
+    };
+};
+
+module.exports = function (str, opts) {
+    var options = normalizeParseOptions(opts);
+
+    if (str === '' || str === null || typeof str === 'undefined') {
+        return options.plainObjects ? Object.create(null) : {};
+    }
+
+    var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
+    var obj = options.plainObjects ? Object.create(null) : {};
+
+    // Iterate over the keys and setup the new object
+
+    var keys = Object.keys(tempObj);
+    for (var i = 0; i < keys.length; ++i) {
+        var key = keys[i];
+        var newObj = parseKeys(key, tempObj[key], options, typeof str === 'string');
+        obj = utils.merge(obj, newObj, options);
+    }
+
+    if (options.allowSparse === true) {
+        return obj;
+    }
+
+    return utils.compact(obj);
+};
+
+},{"./utils":5}],4:[function(require,module,exports){
+'use strict';
+
+var getSideChannel = require('side-channel');
+var utils = require('./utils');
+var formats = require('./formats');
+var has = Object.prototype.hasOwnProperty;
+
+var arrayPrefixGenerators = {
+    brackets: function brackets(prefix) {
+        return prefix + '[]';
+    },
+    comma: 'comma',
+    indices: function indices(prefix, key) {
+        return prefix + '[' + key + ']';
+    },
+    repeat: function repeat(prefix) {
+        return prefix;
+    }
+};
+
+var isArray = Array.isArray;
+var push = Array.prototype.push;
+var pushToArray = function (arr, valueOrArray) {
+    push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
+};
+
+var toISO = Date.prototype.toISOString;
+
+var defaultFormat = formats['default'];
+var defaults = {
+    addQueryPrefix: false,
+    allowDots: false,
+    charset: 'utf-8',
+    charsetSentinel: false,
+    delimiter: '&',
+    encode: true,
+    encoder: utils.encode,
+    encodeValuesOnly: false,
+    format: defaultFormat,
+    formatter: formats.formatters[defaultFormat],
+    // deprecated
+    indices: false,
+    serializeDate: function serializeDate(date) {
+        return toISO.call(date);
+    },
+    skipNulls: false,
+    strictNullHandling: false
+};
+
+var isNonNullishPrimitive = function isNonNullishPrimitive(v) {
+    return typeof v === 'string'
+        || typeof v === 'number'
+        || typeof v === 'boolean'
+        || typeof v === 'symbol'
+        || typeof v === 'bigint';
+};
+
+var stringify = function stringify(
+    object,
+    prefix,
+    generateArrayPrefix,
+    strictNullHandling,
+    skipNulls,
+    encoder,
+    filter,
+    sort,
+    allowDots,
+    serializeDate,
+    format,
+    formatter,
+    encodeValuesOnly,
+    charset,
+    sideChannel
+) {
+    var obj = object;
+
+    if (sideChannel.has(object)) {
+        throw new RangeError('Cyclic object value');
+    }
+
+    if (typeof filter === 'function') {
+        obj = filter(prefix, obj);
+    } else if (obj instanceof Date) {
+        obj = serializeDate(obj);
+    } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
+        obj = utils.maybeMap(obj, function (value) {
+            if (value instanceof Date) {
+                return serializeDate(value);
+            }
+            return value;
+        });
+    }
+
+    if (obj === null) {
+        if (strictNullHandling) {
+            return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix;
+        }
+
+        obj = '';
+    }
+
+    if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
+        if (encoder) {
+            var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);
+            return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];
+        }
+        return [formatter(prefix) + '=' + formatter(String(obj))];
+    }
+
+    var values = [];
+
+    if (typeof obj === 'undefined') {
+        return values;
+    }
+
+    var objKeys;
+    if (generateArrayPrefix === 'comma' && isArray(obj)) {
+        // we need to join elements in
+        objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }];
+    } else if (isArray(filter)) {
+        objKeys = filter;
+    } else {
+        var keys = Object.keys(obj);
+        objKeys = sort ? keys.sort(sort) : keys;
+    }
+
+    for (var i = 0; i < objKeys.length; ++i) {
+        var key = objKeys[i];
+        var value = typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
+
+        if (skipNulls && value === null) {
+            continue;
+        }
+
+        var keyPrefix = isArray(obj)
+            ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
+            : prefix + (allowDots ? '.' + key : '[' + key + ']');
+
+        sideChannel.set(object, true);
+        pushToArray(values, stringify(
+            value,
+            keyPrefix,
+            generateArrayPrefix,
+            strictNullHandling,
+            skipNulls,
+            encoder,
+            filter,
+            sort,
+            allowDots,
+            serializeDate,
+            format,
+            formatter,
+            encodeValuesOnly,
+            charset,
+            sideChannel
+        ));
+    }
+
+    return values;
+};
+
+var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
+    if (!opts) {
+        return defaults;
+    }
+
+    if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
+        throw new TypeError('Encoder has to be a function.');
+    }
+
+    var charset = opts.charset || defaults.charset;
+    if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
+        throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
+    }
+
+    var format = formats['default'];
+    if (typeof opts.format !== 'undefined') {
+        if (!has.call(formats.formatters, opts.format)) {
+            throw new TypeError('Unknown format option provided.');
+        }
+        format = opts.format;
+    }
+    var formatter = formats.formatters[format];
+
+    var filter = defaults.filter;
+    if (typeof opts.filter === 'function' || isArray(opts.filter)) {
+        filter = opts.filter;
+    }
+
+    return {
+        addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,
+        allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
+        charset: charset,
+        charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
+        delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
+        encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
+        encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
+        encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,
+        filter: filter,
+        format: format,
+        formatter: formatter,
+        serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,
+        skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
+        sort: typeof opts.sort === 'function' ? opts.sort : null,
+        strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
+    };
+};
+
+module.exports = function (object, opts) {
+    var obj = object;
+    var options = normalizeStringifyOptions(opts);
+
+    var objKeys;
+    var filter;
+
+    if (typeof options.filter === 'function') {
+        filter = options.filter;
+        obj = filter('', obj);
+    } else if (isArray(options.filter)) {
+        filter = options.filter;
+        objKeys = filter;
+    }
+
+    var keys = [];
+
+    if (typeof obj !== 'object' || obj === null) {
+        return '';
+    }
+
+    var arrayFormat;
+    if (opts && opts.arrayFormat in arrayPrefixGenerators) {
+        arrayFormat = opts.arrayFormat;
+    } else if (opts && 'indices' in opts) {
+        arrayFormat = opts.indices ? 'indices' : 'repeat';
+    } else {
+        arrayFormat = 'indices';
+    }
+
+    var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
+
+    if (!objKeys) {
+        objKeys = Object.keys(obj);
+    }
+
+    if (options.sort) {
+        objKeys.sort(options.sort);
+    }
+
+    var sideChannel = getSideChannel();
+    for (var i = 0; i < objKeys.length; ++i) {
+        var key = objKeys[i];
+
+        if (options.skipNulls && obj[key] === null) {
+            continue;
+        }
+        pushToArray(keys, stringify(
+            obj[key],
+            key,
+            generateArrayPrefix,
+            options.strictNullHandling,
+            options.skipNulls,
+            options.encode ? options.encoder : null,
+            options.filter,
+            options.sort,
+            options.allowDots,
+            options.serializeDate,
+            options.format,
+            options.formatter,
+            options.encodeValuesOnly,
+            options.charset,
+            sideChannel
+        ));
+    }
+
+    var joined = keys.join(options.delimiter);
+    var prefix = options.addQueryPrefix === true ? '?' : '';
+
+    if (options.charsetSentinel) {
+        if (options.charset === 'iso-8859-1') {
+            // encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
+            prefix += 'utf8=%26%2310003%3B&';
+        } else {
+            // encodeURIComponent('✓')
+            prefix += 'utf8=%E2%9C%93&';
+        }
+    }
+
+    return joined.length > 0 ? prefix + joined : '';
+};
+
+},{"./formats":1,"./utils":5,"side-channel":16}],5:[function(require,module,exports){
+'use strict';
+
+var formats = require('./formats');
+
+var has = Object.prototype.hasOwnProperty;
+var isArray = Array.isArray;
+
+var hexTable = (function () {
+    var array = [];
+    for (var i = 0; i < 256; ++i) {
+        array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());
+    }
+
+    return array;
+}());
+
+var compactQueue = function compactQueue(queue) {
+    while (queue.length > 1) {
+        var item = queue.pop();
+        var obj = item.obj[item.prop];
+
+        if (isArray(obj)) {
+            var compacted = [];
+
+            for (var j = 0; j < obj.length; ++j) {
+                if (typeof obj[j] !== 'undefined') {
+                    compacted.push(obj[j]);
+                }
+            }
+
+            item.obj[item.prop] = compacted;
+        }
+    }
+};
+
+var arrayToObject = function arrayToObject(source, options) {
+    var obj = options && options.plainObjects ? Object.create(null) : {};
+    for (var i = 0; i < source.length; ++i) {
+        if (typeof source[i] !== 'undefined') {
+            obj[i] = source[i];
+        }
+    }
+
+    return obj;
+};
+
+var merge = function merge(target, source, options) {
+    /* eslint no-param-reassign: 0 */
+    if (!source) {
+        return target;
+    }
+
+    if (typeof source !== 'object') {
+        if (isArray(target)) {
+            target.push(source);
+        } else if (target && typeof target === 'object') {
+            if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
+                target[source] = true;
+            }
+        } else {
+            return [target, source];
+        }
+
+        return target;
+    }
+
+    if (!target || typeof target !== 'object') {
+        return [target].concat(source);
+    }
+
+    var mergeTarget = target;
+    if (isArray(target) && !isArray(source)) {
+        mergeTarget = arrayToObject(target, options);
+    }
+
+    if (isArray(target) && isArray(source)) {
+        source.forEach(function (item, i) {
+            if (has.call(target, i)) {
+                var targetItem = target[i];
+                if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
+                    target[i] = merge(targetItem, item, options);
+                } else {
+                    target.push(item);
+                }
+            } else {
+                target[i] = item;
+            }
+        });
+        return target;
+    }
+
+    return Object.keys(source).reduce(function (acc, key) {
+        var value = source[key];
+
+        if (has.call(acc, key)) {
+            acc[key] = merge(acc[key], value, options);
+        } else {
+            acc[key] = value;
+        }
+        return acc;
+    }, mergeTarget);
+};
+
+var assign = function assignSingleSource(target, source) {
+    return Object.keys(source).reduce(function (acc, key) {
+        acc[key] = source[key];
+        return acc;
+    }, target);
+};
+
+var decode = function (str, decoder, charset) {
+    var strWithoutPlus = str.replace(/\+/g, ' ');
+    if (charset === 'iso-8859-1') {
+        // unescape never throws, no try...catch needed:
+        return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
+    }
+    // utf-8
+    try {
+        return decodeURIComponent(strWithoutPlus);
+    } catch (e) {
+        return strWithoutPlus;
+    }
+};
+
+var encode = function encode(str, defaultEncoder, charset, kind, format) {
+    // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
+    // It has been adapted here for stricter adherence to RFC 3986
+    if (str.length === 0) {
+        return str;
+    }
+
+    var string = str;
+    if (typeof str === 'symbol') {
+        string = Symbol.prototype.toString.call(str);
+    } else if (typeof str !== 'string') {
+        string = String(str);
+    }
+
+    if (charset === 'iso-8859-1') {
+        return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
+            return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
+        });
+    }
+
+    var out = '';
+    for (var i = 0; i < string.length; ++i) {
+        var c = string.charCodeAt(i);
+
+        if (
+            c === 0x2D // -
+            || c === 0x2E // .
+            || c === 0x5F // _
+            || c === 0x7E // ~
+            || (c >= 0x30 && c <= 0x39) // 0-9
+            || (c >= 0x41 && c <= 0x5A) // a-z
+            || (c >= 0x61 && c <= 0x7A) // A-Z
+            || (format === formats.RFC1738 && (c === 0x28 || c === 0x29)) // ( )
+        ) {
+            out += string.charAt(i);
+            continue;
+        }
+
+        if (c < 0x80) {
+            out = out + hexTable[c];
+            continue;
+        }
+
+        if (c < 0x800) {
+            out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]);
+            continue;
+        }
+
+        if (c < 0xD800 || c >= 0xE000) {
+            out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);
+            continue;
+        }
+
+        i += 1;
+        c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
+        out += hexTable[0xF0 | (c >> 18)]
+            + hexTable[0x80 | ((c >> 12) & 0x3F)]
+            + hexTable[0x80 | ((c >> 6) & 0x3F)]
+            + hexTable[0x80 | (c & 0x3F)];
+    }
+
+    return out;
+};
+
+var compact = function compact(value) {
+    var queue = [{ obj: { o: value }, prop: 'o' }];
+    var refs = [];
+
+    for (var i = 0; i < queue.length; ++i) {
+        var item = queue[i];
+        var obj = item.obj[item.prop];
+
+        var keys = Object.keys(obj);
+        for (var j = 0; j < keys.length; ++j) {
+            var key = keys[j];
+            var val = obj[key];
+            if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
+                queue.push({ obj: obj, prop: key });
+                refs.push(val);
+            }
+        }
+    }
+
+    compactQueue(queue);
+
+    return value;
+};
+
+var isRegExp = function isRegExp(obj) {
+    return Object.prototype.toString.call(obj) === '[object RegExp]';
+};
+
+var isBuffer = function isBuffer(obj) {
+    if (!obj || typeof obj !== 'object') {
+        return false;
+    }
+
+    return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
+};
+
+var combine = function combine(a, b) {
+    return [].concat(a, b);
+};
+
+var maybeMap = function maybeMap(val, fn) {
+    if (isArray(val)) {
+        var mapped = [];
+        for (var i = 0; i < val.length; i += 1) {
+            mapped.push(fn(val[i]));
+        }
+        return mapped;
+    }
+    return fn(val);
+};
+
+module.exports = {
+    arrayToObject: arrayToObject,
+    assign: assign,
+    combine: combine,
+    compact: compact,
+    decode: decode,
+    encode: encode,
+    isBuffer: isBuffer,
+    isRegExp: isRegExp,
+    maybeMap: maybeMap,
+    merge: merge
+};
+
+},{"./formats":1}],6:[function(require,module,exports){
+
+},{}],7:[function(require,module,exports){
+'use strict';
+
+var GetIntrinsic = require('get-intrinsic');
+
+var callBind = require('./');
+
+var $indexOf = callBind(GetIntrinsic('String.prototype.indexOf'));
+
+module.exports = function callBoundIntrinsic(name, allowMissing) {
+	var intrinsic = GetIntrinsic(name, !!allowMissing);
+	if (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {
+		return callBind(intrinsic);
+	}
+	return intrinsic;
+};
+
+},{"./":8,"get-intrinsic":11}],8:[function(require,module,exports){
+'use strict';
+
+var bind = require('function-bind');
+var GetIntrinsic = require('get-intrinsic');
+
+var $apply = GetIntrinsic('%Function.prototype.apply%');
+var $call = GetIntrinsic('%Function.prototype.call%');
+var $reflectApply = GetIntrinsic('%Reflect.apply%', true) || bind.call($call, $apply);
+
+var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);
+var $defineProperty = GetIntrinsic('%Object.defineProperty%', true);
+var $max = GetIntrinsic('%Math.max%');
+
+if ($defineProperty) {
+	try {
+		$defineProperty({}, 'a', { value: 1 });
+	} catch (e) {
+		// IE 8 has a broken defineProperty
+		$defineProperty = null;
+	}
+}
+
+module.exports = function callBind(originalFunction) {
+	var func = $reflectApply(bind, $call, arguments);
+	if ($gOPD && $defineProperty) {
+		var desc = $gOPD(func, 'length');
+		if (desc.configurable) {
+			// original length, plus the receiver, minus any additional arguments (after the receiver)
+			$defineProperty(
+				func,
+				'length',
+				{ value: 1 + $max(0, originalFunction.length - (arguments.length - 1)) }
+			);
+		}
+	}
+	return func;
+};
+
+var applyBind = function applyBind() {
+	return $reflectApply(bind, $apply, arguments);
+};
+
+if ($defineProperty) {
+	$defineProperty(module.exports, 'apply', { value: applyBind });
+} else {
+	module.exports.apply = applyBind;
+}
+
+},{"function-bind":10,"get-intrinsic":11}],9:[function(require,module,exports){
+'use strict';
+
+/* eslint no-invalid-this: 1 */
+
+var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
+var slice = Array.prototype.slice;
+var toStr = Object.prototype.toString;
+var funcType = '[object Function]';
+
+module.exports = function bind(that) {
+    var target = this;
+    if (typeof target !== 'function' || toStr.call(target) !== funcType) {
+        throw new TypeError(ERROR_MESSAGE + target);
+    }
+    var args = slice.call(arguments, 1);
+
+    var bound;
+    var binder = function () {
+        if (this instanceof bound) {
+            var result = target.apply(
+                this,
+                args.concat(slice.call(arguments))
+            );
+            if (Object(result) === result) {
+                return result;
+            }
+            return this;
+        } else {
+            return target.apply(
+                that,
+                args.concat(slice.call(arguments))
+            );
+        }
+    };
+
+    var boundLength = Math.max(0, target.length - args.length);
+    var boundArgs = [];
+    for (var i = 0; i < boundLength; i++) {
+        boundArgs.push('$' + i);
+    }
+
+    bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);
+
+    if (target.prototype) {
+        var Empty = function Empty() {};
+        Empty.prototype = target.prototype;
+        bound.prototype = new Empty();
+        Empty.prototype = null;
+    }
+
+    return bound;
+};
+
+},{}],10:[function(require,module,exports){
+'use strict';
+
+var implementation = require('./implementation');
+
+module.exports = Function.prototype.bind || implementation;
+
+},{"./implementation":9}],11:[function(require,module,exports){
+'use strict';
+
+var undefined;
+
+var $SyntaxError = SyntaxError;
+var $Function = Function;
+var $TypeError = TypeError;
+
+// eslint-disable-next-line consistent-return
+var getEvalledConstructor = function (expressionSyntax) {
+	try {
+		return $Function('"use strict"; return (' + expressionSyntax + ').constructor;')();
+	} catch (e) {}
+};
+
+var $gOPD = Object.getOwnPropertyDescriptor;
+if ($gOPD) {
+	try {
+		$gOPD({}, '');
+	} catch (e) {
+		$gOPD = null; // this is IE 8, which has a broken gOPD
+	}
+}
+
+var throwTypeError = function () {
+	throw new $TypeError();
+};
+var ThrowTypeError = $gOPD
+	? (function () {
+		try {
+			// eslint-disable-next-line no-unused-expressions, no-caller, no-restricted-properties
+			arguments.callee; // IE 8 does not throw here
+			return throwTypeError;
+		} catch (calleeThrows) {
+			try {
+				// IE 8 throws on Object.getOwnPropertyDescriptor(arguments, '')
+				return $gOPD(arguments, 'callee').get;
+			} catch (gOPDthrows) {
+				return throwTypeError;
+			}
+		}
+	}())
+	: throwTypeError;
+
+var hasSymbols = require('has-symbols')();
+
+var getProto = Object.getPrototypeOf || function (x) { return x.__proto__; }; // eslint-disable-line no-proto
+
+var needsEval = {};
+
+var TypedArray = typeof Uint8Array === 'undefined' ? undefined : getProto(Uint8Array);
+
+var INTRINSICS = {
+	'%AggregateError%': typeof AggregateError === 'undefined' ? undefined : AggregateError,
+	'%Array%': Array,
+	'%ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined : ArrayBuffer,
+	'%ArrayIteratorPrototype%': hasSymbols ? getProto([][Symbol.iterator]()) : undefined,
+	'%AsyncFromSyncIteratorPrototype%': undefined,
+	'%AsyncFunction%': needsEval,
+	'%AsyncGenerator%': needsEval,
+	'%AsyncGeneratorFunction%': needsEval,
+	'%AsyncIteratorPrototype%': needsEval,
+	'%Atomics%': typeof Atomics === 'undefined' ? undefined : Atomics,
+	'%BigInt%': typeof BigInt === 'undefined' ? undefined : BigInt,
+	'%Boolean%': Boolean,
+	'%DataView%': typeof DataView === 'undefined' ? undefined : DataView,
+	'%Date%': Date,
+	'%decodeURI%': decodeURI,
+	'%decodeURIComponent%': decodeURIComponent,
+	'%encodeURI%': encodeURI,
+	'%encodeURIComponent%': encodeURIComponent,
+	'%Error%': Error,
+	'%eval%': eval, // eslint-disable-line no-eval
+	'%EvalError%': EvalError,
+	'%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array,
+	'%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array,
+	'%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry,
+	'%Function%': $Function,
+	'%GeneratorFunction%': needsEval,
+	'%Int8Array%': typeof Int8Array === 'undefined' ? undefined : Int8Array,
+	'%Int16Array%': typeof Int16Array === 'undefined' ? undefined : Int16Array,
+	'%Int32Array%': typeof Int32Array === 'undefined' ? undefined : Int32Array,
+	'%isFinite%': isFinite,
+	'%isNaN%': isNaN,
+	'%IteratorPrototype%': hasSymbols ? getProto(getProto([][Symbol.iterator]())) : undefined,
+	'%JSON%': typeof JSON === 'object' ? JSON : undefined,
+	'%Map%': typeof Map === 'undefined' ? undefined : Map,
+	'%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols ? undefined : getProto(new Map()[Symbol.iterator]()),
+	'%Math%': Math,
+	'%Number%': Number,
+	'%Object%': Object,
+	'%parseFloat%': parseFloat,
+	'%parseInt%': parseInt,
+	'%Promise%': typeof Promise === 'undefined' ? undefined : Promise,
+	'%Proxy%': typeof Proxy === 'undefined' ? undefined : Proxy,
+	'%RangeError%': RangeError,
+	'%ReferenceError%': ReferenceError,
+	'%Reflect%': typeof Reflect === 'undefined' ? undefined : Reflect,
+	'%RegExp%': RegExp,
+	'%Set%': typeof Set === 'undefined' ? undefined : Set,
+	'%SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols ? undefined : getProto(new Set()[Symbol.iterator]()),
+	'%SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined : SharedArrayBuffer,
+	'%String%': String,
+	'%StringIteratorPrototype%': hasSymbols ? getProto(''[Symbol.iterator]()) : undefined,
+	'%Symbol%': hasSymbols ? Symbol : undefined,
+	'%SyntaxError%': $SyntaxError,
+	'%ThrowTypeError%': ThrowTypeError,
+	'%TypedArray%': TypedArray,
+	'%TypeError%': $TypeError,
+	'%Uint8Array%': typeof Uint8Array === 'undefined' ? undefined : Uint8Array,
+	'%Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined : Uint8ClampedArray,
+	'%Uint16Array%': typeof Uint16Array === 'undefined' ? undefined : Uint16Array,
+	'%Uint32Array%': typeof Uint32Array === 'undefined' ? undefined : Uint32Array,
+	'%URIError%': URIError,
+	'%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap,
+	'%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef,
+	'%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet
+};
+
+var doEval = function doEval(name) {
+	var value;
+	if (name === '%AsyncFunction%') {
+		value = getEvalledConstructor('async function () {}');
+	} else if (name === '%GeneratorFunction%') {
+		value = getEvalledConstructor('function* () {}');
+	} else if (name === '%AsyncGeneratorFunction%') {
+		value = getEvalledConstructor('async function* () {}');
+	} else if (name === '%AsyncGenerator%') {
+		var fn = doEval('%AsyncGeneratorFunction%');
+		if (fn) {
+			value = fn.prototype;
+		}
+	} else if (name === '%AsyncIteratorPrototype%') {
+		var gen = doEval('%AsyncGenerator%');
+		if (gen) {
+			value = getProto(gen.prototype);
+		}
+	}
+
+	INTRINSICS[name] = value;
+
+	return value;
+};
+
+var LEGACY_ALIASES = {
+	'%ArrayBufferPrototype%': ['ArrayBuffer', 'prototype'],
+	'%ArrayPrototype%': ['Array', 'prototype'],
+	'%ArrayProto_entries%': ['Array', 'prototype', 'entries'],
+	'%ArrayProto_forEach%': ['Array', 'prototype', 'forEach'],
+	'%ArrayProto_keys%': ['Array', 'prototype', 'keys'],
+	'%ArrayProto_values%': ['Array', 'prototype', 'values'],
+	'%AsyncFunctionPrototype%': ['AsyncFunction', 'prototype'],
+	'%AsyncGenerator%': ['AsyncGeneratorFunction', 'prototype'],
+	'%AsyncGeneratorPrototype%': ['AsyncGeneratorFunction', 'prototype', 'prototype'],
+	'%BooleanPrototype%': ['Boolean', 'prototype'],
+	'%DataViewPrototype%': ['DataView', 'prototype'],
+	'%DatePrototype%': ['Date', 'prototype'],
+	'%ErrorPrototype%': ['Error', 'prototype'],
+	'%EvalErrorPrototype%': ['EvalError', 'prototype'],
+	'%Float32ArrayPrototype%': ['Float32Array', 'prototype'],
+	'%Float64ArrayPrototype%': ['Float64Array', 'prototype'],
+	'%FunctionPrototype%': ['Function', 'prototype'],
+	'%Generator%': ['GeneratorFunction', 'prototype'],
+	'%GeneratorPrototype%': ['GeneratorFunction', 'prototype', 'prototype'],
+	'%Int8ArrayPrototype%': ['Int8Array', 'prototype'],
+	'%Int16ArrayPrototype%': ['Int16Array', 'prototype'],
+	'%Int32ArrayPrototype%': ['Int32Array', 'prototype'],
+	'%JSONParse%': ['JSON', 'parse'],
+	'%JSONStringify%': ['JSON', 'stringify'],
+	'%MapPrototype%': ['Map', 'prototype'],
+	'%NumberPrototype%': ['Number', 'prototype'],
+	'%ObjectPrototype%': ['Object', 'prototype'],
+	'%ObjProto_toString%': ['Object', 'prototype', 'toString'],
+	'%ObjProto_valueOf%': ['Object', 'prototype', 'valueOf'],
+	'%PromisePrototype%': ['Promise', 'prototype'],
+	'%PromiseProto_then%': ['Promise', 'prototype', 'then'],
+	'%Promise_all%': ['Promise', 'all'],
+	'%Promise_reject%': ['Promise', 'reject'],
+	'%Promise_resolve%': ['Promise', 'resolve'],
+	'%RangeErrorPrototype%': ['RangeError', 'prototype'],
+	'%ReferenceErrorPrototype%': ['ReferenceError', 'prototype'],
+	'%RegExpPrototype%': ['RegExp', 'prototype'],
+	'%SetPrototype%': ['Set', 'prototype'],
+	'%SharedArrayBufferPrototype%': ['SharedArrayBuffer', 'prototype'],
+	'%StringPrototype%': ['String', 'prototype'],
+	'%SymbolPrototype%': ['Symbol', 'prototype'],
+	'%SyntaxErrorPrototype%': ['SyntaxError', 'prototype'],
+	'%TypedArrayPrototype%': ['TypedArray', 'prototype'],
+	'%TypeErrorPrototype%': ['TypeError', 'prototype'],
+	'%Uint8ArrayPrototype%': ['Uint8Array', 'prototype'],
+	'%Uint8ClampedArrayPrototype%': ['Uint8ClampedArray', 'prototype'],
+	'%Uint16ArrayPrototype%': ['Uint16Array', 'prototype'],
+	'%Uint32ArrayPrototype%': ['Uint32Array', 'prototype'],
+	'%URIErrorPrototype%': ['URIError', 'prototype'],
+	'%WeakMapPrototype%': ['WeakMap', 'prototype'],
+	'%WeakSetPrototype%': ['WeakSet', 'prototype']
+};
+
+var bind = require('function-bind');
+var hasOwn = require('has');
+var $concat = bind.call(Function.call, Array.prototype.concat);
+var $spliceApply = bind.call(Function.apply, Array.prototype.splice);
+var $replace = bind.call(Function.call, String.prototype.replace);
+var $strSlice = bind.call(Function.call, String.prototype.slice);
+
+/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */
+var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g;
+var reEscapeChar = /\\(\\)?/g; /** Used to match backslashes in property paths. */
+var stringToPath = function stringToPath(string) {
+	var first = $strSlice(string, 0, 1);
+	var last = $strSlice(string, -1);
+	if (first === '%' && last !== '%') {
+		throw new $SyntaxError('invalid intrinsic syntax, expected closing `%`');
+	} else if (last === '%' && first !== '%') {
+		throw new $SyntaxError('invalid intrinsic syntax, expected opening `%`');
+	}
+	var result = [];
+	$replace(string, rePropName, function (match, number, quote, subString) {
+		result[result.length] = quote ? $replace(subString, reEscapeChar, '$1') : number || match;
+	});
+	return result;
+};
+/* end adaptation */
+
+var getBaseIntrinsic = function getBaseIntrinsic(name, allowMissing) {
+	var intrinsicName = name;
+	var alias;
+	if (hasOwn(LEGACY_ALIASES, intrinsicName)) {
+		alias = LEGACY_ALIASES[intrinsicName];
+		intrinsicName = '%' + alias[0] + '%';
+	}
+
+	if (hasOwn(INTRINSICS, intrinsicName)) {
+		var value = INTRINSICS[intrinsicName];
+		if (value === needsEval) {
+			value = doEval(intrinsicName);
+		}
+		if (typeof value === 'undefined' && !allowMissing) {
+			throw new $TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!');
+		}
+
+		return {
+			alias: alias,
+			name: intrinsicName,
+			value: value
+		};
+	}
+
+	throw new $SyntaxError('intrinsic ' + name + ' does not exist!');
+};
+
+module.exports = function GetIntrinsic(name, allowMissing) {
+	if (typeof name !== 'string' || name.length === 0) {
+		throw new $TypeError('intrinsic name must be a non-empty string');
+	}
+	if (arguments.length > 1 && typeof allowMissing !== 'boolean') {
+		throw new $TypeError('"allowMissing" argument must be a boolean');
+	}
+
+	var parts = stringToPath(name);
+	var intrinsicBaseName = parts.length > 0 ? parts[0] : '';
+
+	var intrinsic = getBaseIntrinsic('%' + intrinsicBaseName + '%', allowMissing);
+	var intrinsicRealName = intrinsic.name;
+	var value = intrinsic.value;
+	var skipFurtherCaching = false;
+
+	var alias = intrinsic.alias;
+	if (alias) {
+		intrinsicBaseName = alias[0];
+		$spliceApply(parts, $concat([0, 1], alias));
+	}
+
+	for (var i = 1, isOwn = true; i < parts.length; i += 1) {
+		var part = parts[i];
+		var first = $strSlice(part, 0, 1);
+		var last = $strSlice(part, -1);
+		if (
+			(
+				(first === '"' || first === "'" || first === '`')
+				|| (last === '"' || last === "'" || last === '`')
+			)
+			&& first !== last
+		) {
+			throw new $SyntaxError('property names with quotes must have matching quotes');
+		}
+		if (part === 'constructor' || !isOwn) {
+			skipFurtherCaching = true;
+		}
+
+		intrinsicBaseName += '.' + part;
+		intrinsicRealName = '%' + intrinsicBaseName + '%';
+
+		if (hasOwn(INTRINSICS, intrinsicRealName)) {
+			value = INTRINSICS[intrinsicRealName];
+		} else if (value != null) {
+			if (!(part in value)) {
+				if (!allowMissing) {
+					throw new $TypeError('base intrinsic for ' + name + ' exists, but the property is not available.');
+				}
+				return void undefined;
+			}
+			if ($gOPD && (i + 1) >= parts.length) {
+				var desc = $gOPD(value, part);
+				isOwn = !!desc;
+
+				// By convention, when a data property is converted to an accessor
+				// property to emulate a data property that does not suffer from
+				// the override mistake, that accessor's getter is marked with
+				// an `originalValue` property. Here, when we detect this, we
+				// uphold the illusion by pretending to see that original data
+				// property, i.e., returning the value rather than the getter
+				// itself.
+				if (isOwn && 'get' in desc && !('originalValue' in desc.get)) {
+					value = desc.get;
+				} else {
+					value = value[part];
+				}
+			} else {
+				isOwn = hasOwn(value, part);
+				value = value[part];
+			}
+
+			if (isOwn && !skipFurtherCaching) {
+				INTRINSICS[intrinsicRealName] = value;
+			}
+		}
+	}
+	return value;
+};
+
+},{"function-bind":10,"has":14,"has-symbols":12}],12:[function(require,module,exports){
+'use strict';
+
+var origSymbol = typeof Symbol !== 'undefined' && Symbol;
+var hasSymbolSham = require('./shams');
+
+module.exports = function hasNativeSymbols() {
+	if (typeof origSymbol !== 'function') { return false; }
+	if (typeof Symbol !== 'function') { return false; }
+	if (typeof origSymbol('foo') !== 'symbol') { return false; }
+	if (typeof Symbol('bar') !== 'symbol') { return false; }
+
+	return hasSymbolSham();
+};
+
+},{"./shams":13}],13:[function(require,module,exports){
+'use strict';
+
+/* eslint complexity: [2, 18], max-statements: [2, 33] */
+module.exports = function hasSymbols() {
+	if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; }
+	if (typeof Symbol.iterator === 'symbol') { return true; }
+
+	var obj = {};
+	var sym = Symbol('test');
+	var symObj = Object(sym);
+	if (typeof sym === 'string') { return false; }
+
+	if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; }
+	if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; }
+
+	// temp disabled per https://github.com/ljharb/object.assign/issues/17
+	// if (sym instanceof Symbol) { return false; }
+	// temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4
+	// if (!(symObj instanceof Symbol)) { return false; }
+
+	// if (typeof Symbol.prototype.toString !== 'function') { return false; }
+	// if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; }
+
+	var symVal = 42;
+	obj[sym] = symVal;
+	for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop
+	if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; }
+
+	if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; }
+
+	var syms = Object.getOwnPropertySymbols(obj);
+	if (syms.length !== 1 || syms[0] !== sym) { return false; }
+
+	if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; }
+
+	if (typeof Object.getOwnPropertyDescriptor === 'function') {
+		var descriptor = Object.getOwnPropertyDescriptor(obj, sym);
+		if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; }
+	}
+
+	return true;
+};
+
+},{}],14:[function(require,module,exports){
+'use strict';
+
+var bind = require('function-bind');
+
+module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty);
+
+},{"function-bind":10}],15:[function(require,module,exports){
+var hasMap = typeof Map === 'function' && Map.prototype;
+var mapSizeDescriptor = Object.getOwnPropertyDescriptor && hasMap ? Object.getOwnPropertyDescriptor(Map.prototype, 'size') : null;
+var mapSize = hasMap && mapSizeDescriptor && typeof mapSizeDescriptor.get === 'function' ? mapSizeDescriptor.get : null;
+var mapForEach = hasMap && Map.prototype.forEach;
+var hasSet = typeof Set === 'function' && Set.prototype;
+var setSizeDescriptor = Object.getOwnPropertyDescriptor && hasSet ? Object.getOwnPropertyDescriptor(Set.prototype, 'size') : null;
+var setSize = hasSet && setSizeDescriptor && typeof setSizeDescriptor.get === 'function' ? setSizeDescriptor.get : null;
+var setForEach = hasSet && Set.prototype.forEach;
+var hasWeakMap = typeof WeakMap === 'function' && WeakMap.prototype;
+var weakMapHas = hasWeakMap ? WeakMap.prototype.has : null;
+var hasWeakSet = typeof WeakSet === 'function' && WeakSet.prototype;
+var weakSetHas = hasWeakSet ? WeakSet.prototype.has : null;
+var booleanValueOf = Boolean.prototype.valueOf;
+var objectToString = Object.prototype.toString;
+var functionToString = Function.prototype.toString;
+var match = String.prototype.match;
+var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null;
+var gOPS = Object.getOwnPropertySymbols;
+var symToString = typeof Symbol === 'function' ? Symbol.prototype.toString : null;
+var isEnumerable = Object.prototype.propertyIsEnumerable;
+
+var inspectCustom = require('./util.inspect').custom;
+var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null;
+
+module.exports = function inspect_(obj, options, depth, seen) {
+    var opts = options || {};
+
+    if (has(opts, 'quoteStyle') && (opts.quoteStyle !== 'single' && opts.quoteStyle !== 'double')) {
+        throw new TypeError('option "quoteStyle" must be "single" or "double"');
+    }
+    if (
+        has(opts, 'maxStringLength') && (typeof opts.maxStringLength === 'number'
+            ? opts.maxStringLength < 0 && opts.maxStringLength !== Infinity
+            : opts.maxStringLength !== null
+        )
+    ) {
+        throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');
+    }
+    var customInspect = has(opts, 'customInspect') ? opts.customInspect : true;
+    if (typeof customInspect !== 'boolean') {
+        throw new TypeError('option "customInspect", if provided, must be `true` or `false`');
+    }
+
+    if (
+        has(opts, 'indent')
+        && opts.indent !== null
+        && opts.indent !== '\t'
+        && !(parseInt(opts.indent, 10) === opts.indent && opts.indent > 0)
+    ) {
+        throw new TypeError('options "indent" must be "\\t", an integer > 0, or `null`');
+    }
+
+    if (typeof obj === 'undefined') {
+        return 'undefined';
+    }
+    if (obj === null) {
+        return 'null';
+    }
+    if (typeof obj === 'boolean') {
+        return obj ? 'true' : 'false';
+    }
+
+    if (typeof obj === 'string') {
+        return inspectString(obj, opts);
+    }
+    if (typeof obj === 'number') {
+        if (obj === 0) {
+            return Infinity / obj > 0 ? '0' : '-0';
+        }
+        return String(obj);
+    }
+    if (typeof obj === 'bigint') {
+        return String(obj) + 'n';
+    }
+
+    var maxDepth = typeof opts.depth === 'undefined' ? 5 : opts.depth;
+    if (typeof depth === 'undefined') { depth = 0; }
+    if (depth >= maxDepth && maxDepth > 0 && typeof obj === 'object') {
+        return isArray(obj) ? '[Array]' : '[Object]';
+    }
+
+    var indent = getIndent(opts, depth);
+
+    if (typeof seen === 'undefined') {
+        seen = [];
+    } else if (indexOf(seen, obj) >= 0) {
+        return '[Circular]';
+    }
+
+    function inspect(value, from, noIndent) {
+        if (from) {
+            seen = seen.slice();
+            seen.push(from);
+        }
+        if (noIndent) {
+            var newOpts = {
+                depth: opts.depth
+            };
+            if (has(opts, 'quoteStyle')) {
+                newOpts.quoteStyle = opts.quoteStyle;
+            }
+            return inspect_(value, newOpts, depth + 1, seen);
+        }
+        return inspect_(value, opts, depth + 1, seen);
+    }
+
+    if (typeof obj === 'function') {
+        var name = nameOf(obj);
+        var keys = arrObjKeys(obj, inspect);
+        return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + keys.join(', ') + ' }' : '');
+    }
+    if (isSymbol(obj)) {
+        var symString = symToString.call(obj);
+        return typeof obj === 'object' ? markBoxed(symString) : symString;
+    }
+    if (isElement(obj)) {
+        var s = '<' + String(obj.nodeName).toLowerCase();
+        var attrs = obj.attributes || [];
+        for (var i = 0; i < attrs.length; i++) {
+            s += ' ' + attrs[i].name + '=' + wrapQuotes(quote(attrs[i].value), 'double', opts);
+        }
+        s += '>';
+        if (obj.childNodes && obj.childNodes.length) { s += '...'; }
+        s += '</' + String(obj.nodeName).toLowerCase() + '>';
+        return s;
+    }
+    if (isArray(obj)) {
+        if (obj.length === 0) { return '[]'; }
+        var xs = arrObjKeys(obj, inspect);
+        if (indent && !singleLineValues(xs)) {
+            return '[' + indentedJoin(xs, indent) + ']';
+        }
+        return '[ ' + xs.join(', ') + ' ]';
+    }
+    if (isError(obj)) {
+        var parts = arrObjKeys(obj, inspect);
+        if (parts.length === 0) { return '[' + String(obj) + ']'; }
+        return '{ [' + String(obj) + '] ' + parts.join(', ') + ' }';
+    }
+    if (typeof obj === 'object' && customInspect) {
+        if (inspectSymbol && typeof obj[inspectSymbol] === 'function') {
+            return obj[inspectSymbol]();
+        } else if (typeof obj.inspect === 'function') {
+            return obj.inspect();
+        }
+    }
+    if (isMap(obj)) {
+        var mapParts = [];
+        mapForEach.call(obj, function (value, key) {
+            mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));
+        });
+        return collectionOf('Map', mapSize.call(obj), mapParts, indent);
+    }
+    if (isSet(obj)) {
+        var setParts = [];
+        setForEach.call(obj, function (value) {
+            setParts.push(inspect(value, obj));
+        });
+        return collectionOf('Set', setSize.call(obj), setParts, indent);
+    }
+    if (isWeakMap(obj)) {
+        return weakCollectionOf('WeakMap');
+    }
+    if (isWeakSet(obj)) {
+        return weakCollectionOf('WeakSet');
+    }
+    if (isNumber(obj)) {
+        return markBoxed(inspect(Number(obj)));
+    }
+    if (isBigInt(obj)) {
+        return markBoxed(inspect(bigIntValueOf.call(obj)));
+    }
+    if (isBoolean(obj)) {
+        return markBoxed(booleanValueOf.call(obj));
+    }
+    if (isString(obj)) {
+        return markBoxed(inspect(String(obj)));
+    }
+    if (!isDate(obj) && !isRegExp(obj)) {
+        var ys = arrObjKeys(obj, inspect);
+        if (ys.length === 0) { return '{}'; }
+        if (indent) {
+            return '{' + indentedJoin(ys, indent) + '}';
+        }
+        return '{ ' + ys.join(', ') + ' }';
+    }
+    return String(obj);
+};
+
+function wrapQuotes(s, defaultStyle, opts) {
+    var quoteChar = (opts.quoteStyle || defaultStyle) === 'double' ? '"' : "'";
+    return quoteChar + s + quoteChar;
+}
+
+function quote(s) {
+    return String(s).replace(/"/g, '&quot;');
+}
+
+function isArray(obj) { return toStr(obj) === '[object Array]'; }
+function isDate(obj) { return toStr(obj) === '[object Date]'; }
+function isRegExp(obj) { return toStr(obj) === '[object RegExp]'; }
+function isError(obj) { return toStr(obj) === '[object Error]'; }
+function isSymbol(obj) { return toStr(obj) === '[object Symbol]'; }
+function isString(obj) { return toStr(obj) === '[object String]'; }
+function isNumber(obj) { return toStr(obj) === '[object Number]'; }
+function isBigInt(obj) { return toStr(obj) === '[object BigInt]'; }
+function isBoolean(obj) { return toStr(obj) === '[object Boolean]'; }
+
+var hasOwn = Object.prototype.hasOwnProperty || function (key) { return key in this; };
+function has(obj, key) {
+    return hasOwn.call(obj, key);
+}
+
+function toStr(obj) {
+    return objectToString.call(obj);
+}
+
+function nameOf(f) {
+    if (f.name) { return f.name; }
+    var m = match.call(functionToString.call(f), /^function\s*([\w$]+)/);
+    if (m) { return m[1]; }
+    return null;
+}
+
+function indexOf(xs, x) {
+    if (xs.indexOf) { return xs.indexOf(x); }
+    for (var i = 0, l = xs.length; i < l; i++) {
+        if (xs[i] === x) { return i; }
+    }
+    return -1;
+}
+
+function isMap(x) {
+    if (!mapSize || !x || typeof x !== 'object') {
+        return false;
+    }
+    try {
+        mapSize.call(x);
+        try {
+            setSize.call(x);
+        } catch (s) {
+            return true;
+        }
+        return x instanceof Map; // core-js workaround, pre-v2.5.0
+    } catch (e) {}
+    return false;
+}
+
+function isWeakMap(x) {
+    if (!weakMapHas || !x || typeof x !== 'object') {
+        return false;
+    }
+    try {
+        weakMapHas.call(x, weakMapHas);
+        try {
+            weakSetHas.call(x, weakSetHas);
+        } catch (s) {
+            return true;
+        }
+        return x instanceof WeakMap; // core-js workaround, pre-v2.5.0
+    } catch (e) {}
+    return false;
+}
+
+function isSet(x) {
+    if (!setSize || !x || typeof x !== 'object') {
+        return false;
+    }
+    try {
+        setSize.call(x);
+        try {
+            mapSize.call(x);
+        } catch (m) {
+            return true;
+        }
+        return x instanceof Set; // core-js workaround, pre-v2.5.0
+    } catch (e) {}
+    return false;
+}
+
+function isWeakSet(x) {
+    if (!weakSetHas || !x || typeof x !== 'object') {
+        return false;
+    }
+    try {
+        weakSetHas.call(x, weakSetHas);
+        try {
+            weakMapHas.call(x, weakMapHas);
+        } catch (s) {
+            return true;
+        }
+        return x instanceof WeakSet; // core-js workaround, pre-v2.5.0
+    } catch (e) {}
+    return false;
+}
+
+function isElement(x) {
+    if (!x || typeof x !== 'object') { return false; }
+    if (typeof HTMLElement !== 'undefined' && x instanceof HTMLElement) {
+        return true;
+    }
+    return typeof x.nodeName === 'string' && typeof x.getAttribute === 'function';
+}
+
+function inspectString(str, opts) {
+    if (str.length > opts.maxStringLength) {
+        var remaining = str.length - opts.maxStringLength;
+        var trailer = '... ' + remaining + ' more character' + (remaining > 1 ? 's' : '');
+        return inspectString(str.slice(0, opts.maxStringLength), opts) + trailer;
+    }
+    // eslint-disable-next-line no-control-regex
+    var s = str.replace(/(['\\])/g, '\\$1').replace(/[\x00-\x1f]/g, lowbyte);
+    return wrapQuotes(s, 'single', opts);
+}
+
+function lowbyte(c) {
+    var n = c.charCodeAt(0);
+    var x = {
+        8: 'b',
+        9: 't',
+        10: 'n',
+        12: 'f',
+        13: 'r'
+    }[n];
+    if (x) { return '\\' + x; }
+    return '\\x' + (n < 0x10 ? '0' : '') + n.toString(16).toUpperCase();
+}
+
+function markBoxed(str) {
+    return 'Object(' + str + ')';
+}
+
+function weakCollectionOf(type) {
+    return type + ' { ? }';
+}
+
+function collectionOf(type, size, entries, indent) {
+    var joinedEntries = indent ? indentedJoin(entries, indent) : entries.join(', ');
+    return type + ' (' + size + ') {' + joinedEntries + '}';
+}
+
+function singleLineValues(xs) {
+    for (var i = 0; i < xs.length; i++) {
+        if (indexOf(xs[i], '\n') >= 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+function getIndent(opts, depth) {
+    var baseIndent;
+    if (opts.indent === '\t') {
+        baseIndent = '\t';
+    } else if (typeof opts.indent === 'number' && opts.indent > 0) {
+        baseIndent = Array(opts.indent + 1).join(' ');
+    } else {
+        return null;
+    }
+    return {
+        base: baseIndent,
+        prev: Array(depth + 1).join(baseIndent)
+    };
+}
+
+function indentedJoin(xs, indent) {
+    if (xs.length === 0) { return ''; }
+    var lineJoiner = '\n' + indent.prev + indent.base;
+    return lineJoiner + xs.join(',' + lineJoiner) + '\n' + indent.prev;
+}
+
+function arrObjKeys(obj, inspect) {
+    var isArr = isArray(obj);
+    var xs = [];
+    if (isArr) {
+        xs.length = obj.length;
+        for (var i = 0; i < obj.length; i++) {
+            xs[i] = has(obj, i) ? inspect(obj[i], obj) : '';
+        }
+    }
+    for (var key in obj) { // eslint-disable-line no-restricted-syntax
+        if (!has(obj, key)) { continue; } // eslint-disable-line no-restricted-syntax, no-continue
+        if (isArr && String(Number(key)) === key && key < obj.length) { continue; } // eslint-disable-line no-restricted-syntax, no-continue
+        if ((/[^\w$]/).test(key)) {
+            xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj));
+        } else {
+            xs.push(key + ': ' + inspect(obj[key], obj));
+        }
+    }
+    if (typeof gOPS === 'function') {
+        var syms = gOPS(obj);
+        for (var j = 0; j < syms.length; j++) {
+            if (isEnumerable.call(obj, syms[j])) {
+                xs.push('[' + inspect(syms[j]) + ']: ' + inspect(obj[syms[j]], obj));
+            }
+        }
+    }
+    return xs;
+}
+
+},{"./util.inspect":6}],16:[function(require,module,exports){
+'use strict';
+
+var GetIntrinsic = require('get-intrinsic');
+var callBound = require('call-bind/callBound');
+var inspect = require('object-inspect');
+
+var $TypeError = GetIntrinsic('%TypeError%');
+var $WeakMap = GetIntrinsic('%WeakMap%', true);
+var $Map = GetIntrinsic('%Map%', true);
+
+var $weakMapGet = callBound('WeakMap.prototype.get', true);
+var $weakMapSet = callBound('WeakMap.prototype.set', true);
+var $weakMapHas = callBound('WeakMap.prototype.has', true);
+var $mapGet = callBound('Map.prototype.get', true);
+var $mapSet = callBound('Map.prototype.set', true);
+var $mapHas = callBound('Map.prototype.has', true);
+
+/*
+ * This function traverses the list returning the node corresponding to the
+ * given key.
+ *
+ * That node is also moved to the head of the list, so that if it's accessed
+ * again we don't need to traverse the whole list. By doing so, all the recently
+ * used nodes can be accessed relatively quickly.
+ */
+var listGetNode = function (list, key) { // eslint-disable-line consistent-return
+	for (var prev = list, curr; (curr = prev.next) !== null; prev = curr) {
+		if (curr.key === key) {
+			prev.next = curr.next;
+			curr.next = list.next;
+			list.next = curr; // eslint-disable-line no-param-reassign
+			return curr;
+		}
+	}
+};
+
+var listGet = function (objects, key) {
+	var node = listGetNode(objects, key);
+	return node && node.value;
+};
+var listSet = function (objects, key, value) {
+	var node = listGetNode(objects, key);
+	if (node) {
+		node.value = value;
+	} else {
+		// Prepend the new node to the beginning of the list
+		objects.next = { // eslint-disable-line no-param-reassign
+			key: key,
+			next: objects.next,
+			value: value
+		};
+	}
+};
+var listHas = function (objects, key) {
+	return !!listGetNode(objects, key);
+};
+
+module.exports = function getSideChannel() {
+	var $wm;
+	var $m;
+	var $o;
+	var channel = {
+		assert: function (key) {
+			if (!channel.has(key)) {
+				throw new $TypeError('Side channel does not contain ' + inspect(key));
+			}
+		},
+		get: function (key) { // eslint-disable-line consistent-return
+			if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
+				if ($wm) {
+					return $weakMapGet($wm, key);
+				}
+			} else if ($Map) {
+				if ($m) {
+					return $mapGet($m, key);
+				}
+			} else {
+				if ($o) { // eslint-disable-line no-lonely-if
+					return listGet($o, key);
+				}
+			}
+		},
+		has: function (key) {
+			if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
+				if ($wm) {
+					return $weakMapHas($wm, key);
+				}
+			} else if ($Map) {
+				if ($m) {
+					return $mapHas($m, key);
+				}
+			} else {
+				if ($o) { // eslint-disable-line no-lonely-if
+					return listHas($o, key);
+				}
+			}
+			return false;
+		},
+		set: function (key, value) {
+			if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
+				if (!$wm) {
+					$wm = new $WeakMap();
+				}
+				$weakMapSet($wm, key, value);
+			} else if ($Map) {
+				if (!$m) {
+					$m = new $Map();
+				}
+				$mapSet($m, key, value);
+			} else {
+				if (!$o) {
+					/*
+					 * Initialize the linked list as an empty node, so that we don't have
+					 * to special-case handling of the first node: we can always refer to
+					 * it as (previous node).next, instead of something like (list).head
+					 */
+					$o = { key: {}, next: null };
+				}
+				listSet($o, key, value);
+			}
+		}
+	};
+	return channel;
+};
+
+},{"call-bind/callBound":7,"get-intrinsic":11,"object-inspect":15}]},{},[2])(2)
+});

+ 239 - 0
Info/static/react-dom.production.min.js

@@ -0,0 +1,239 @@
+/** @license React v16.14.0
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+/*
+ Modernizr 3.0.0pre (Custom Build) | MIT
+*/
+'use strict';(function(I,ea){"object"===typeof exports&&"undefined"!==typeof module?ea(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],ea):(I=I||self,ea(I.ReactDOM={},I.React))})(this,function(I,ea){function k(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
+function ji(a,b,c,d,e,f,g,h,m){yb=!1;gc=null;ki.apply(li,arguments)}function mi(a,b,c,d,e,f,g,h,m){ji.apply(this,arguments);if(yb){if(yb){var n=gc;yb=!1;gc=null}else throw Error(k(198));hc||(hc=!0,pd=n)}}function lf(a,b,c){var d=a.type||"unknown-event";a.currentTarget=mf(c);mi(d,b,void 0,a);a.currentTarget=null}function nf(){if(ic)for(var a in cb){var b=cb[a],c=ic.indexOf(a);if(!(-1<c))throw Error(k(96,a));if(!jc[c]){if(!b.extractEvents)throw Error(k(97,a));jc[c]=b;c=b.eventTypes;for(var d in c){var e=
+void 0;var f=c[d],g=b,h=d;if(qd.hasOwnProperty(h))throw Error(k(99,h));qd[h]=f;var m=f.phasedRegistrationNames;if(m){for(e in m)m.hasOwnProperty(e)&&of(m[e],g,h);e=!0}else f.registrationName?(of(f.registrationName,g,h),e=!0):e=!1;if(!e)throw Error(k(98,d,a));}}}}function of(a,b,c){if(db[a])throw Error(k(100,a));db[a]=b;rd[a]=b.eventTypes[c].dependencies}function pf(a){var b=!1,c;for(c in a)if(a.hasOwnProperty(c)){var d=a[c];if(!cb.hasOwnProperty(c)||cb[c]!==d){if(cb[c])throw Error(k(102,c));cb[c]=
+d;b=!0}}b&&nf()}function qf(a){if(a=rf(a)){if("function"!==typeof sd)throw Error(k(280));var b=a.stateNode;b&&(b=td(b),sd(a.stateNode,a.type,b))}}function sf(a){eb?fb?fb.push(a):fb=[a]:eb=a}function tf(){if(eb){var a=eb,b=fb;fb=eb=null;qf(a);if(b)for(a=0;a<b.length;a++)qf(b[a])}}function ud(){if(null!==eb||null!==fb)vd(),tf()}function uf(a,b,c){if(wd)return a(b,c);wd=!0;try{return vf(a,b,c)}finally{wd=!1,ud()}}function ni(a){if(wf.call(xf,a))return!0;if(wf.call(yf,a))return!1;if(oi.test(a))return xf[a]=
+!0;yf[a]=!0;return!1}function pi(a,b,c,d){if(null!==c&&0===c.type)return!1;switch(typeof b){case "function":case "symbol":return!0;case "boolean":if(d)return!1;if(null!==c)return!c.acceptsBooleans;a=a.toLowerCase().slice(0,5);return"data-"!==a&&"aria-"!==a;default:return!1}}function qi(a,b,c,d){if(null===b||"undefined"===typeof b||pi(a,b,c,d))return!0;if(d)return!1;if(null!==c)switch(c.type){case 3:return!b;case 4:return!1===b;case 5:return isNaN(b);case 6:return isNaN(b)||1>b}return!1}function L(a,
+b,c,d,e,f){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f}function xd(a,b,c,d){var e=E.hasOwnProperty(b)?E[b]:null;var f=null!==e?0===e.type:d?!1:!(2<b.length)||"o"!==b[0]&&"O"!==b[0]||"n"!==b[1]&&"N"!==b[1]?!1:!0;f||(qi(b,c,e,d)&&(c=null),d||null===e?ni(b)&&(null===c?a.removeAttribute(b):a.setAttribute(b,""+c)):e.mustUseProperty?a[e.propertyName]=null===c?3===e.type?!1:"":c:(b=e.attributeName,
+d=e.attributeNamespace,null===c?a.removeAttribute(b):(e=e.type,c=3===e||4===e&&!0===c?"":""+c,d?a.setAttributeNS(d,b,c):a.setAttribute(b,c))))}function zb(a){if(null===a||"object"!==typeof a)return null;a=zf&&a[zf]||a["@@iterator"];return"function"===typeof a?a:null}function ri(a){if(-1===a._status){a._status=0;var b=a._ctor;b=b();a._result=b;b.then(function(b){0===a._status&&(b=b.default,a._status=1,a._result=b)},function(b){0===a._status&&(a._status=2,a._result=b)})}}function na(a){if(null==a)return null;
+if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Ma:return"Fragment";case gb:return"Portal";case kc:return"Profiler";case Af:return"StrictMode";case lc:return"Suspense";case yd:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case Bf:return"Context.Consumer";case Cf:return"Context.Provider";case zd:var b=a.render;b=b.displayName||b.name||"";return a.displayName||(""!==b?"ForwardRef("+b+")":"ForwardRef");case Ad:return na(a.type);
+case Df:return na(a.render);case Ef:if(a=1===a._status?a._result:null)return na(a)}return null}function Bd(a){var b="";do{a:switch(a.tag){case 3:case 4:case 6:case 7:case 10:case 9:var c="";break a;default:var d=a._debugOwner,e=a._debugSource,f=na(a.type);c=null;d&&(c=na(d.type));d=f;f="";e?f=" (at "+e.fileName.replace(si,"")+":"+e.lineNumber+")":c&&(f=" (created by "+c+")");c="\n    in "+(d||"Unknown")+f}b+=c;a=a.return}while(a);return b}function va(a){switch(typeof a){case "boolean":case "number":case "object":case "string":case "undefined":return a;
+default:return""}}function Ff(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)}function ti(a){var b=Ff(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,
+b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=null;delete a[b]}}}}function mc(a){a._valueTracker||(a._valueTracker=ti(a))}function Gf(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=Ff(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Cd(a,b){var c=b.checked;return M({},b,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=
+c?c:a._wrapperState.initialChecked})}function Hf(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=va(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function If(a,b){b=b.checked;null!=b&&xd(a,"checked",b,!1)}function Dd(a,b){If(a,b);var c=va(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=c)a.value=""+c}else a.value!==
+""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?Ed(a,b.type,c):b.hasOwnProperty("defaultValue")&&Ed(a,b.type,va(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function Jf(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;c||b===a.value||(a.value=
+b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function Ed(a,b,c){if("number"!==b||a.ownerDocument.activeElement!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function ui(a){var b="";ea.Children.forEach(a,function(a){null!=a&&(b+=a)});return b}function Fd(a,b){a=M({children:void 0},b);if(b=ui(b.children))a.children=b;return a}function hb(a,b,c,d){a=a.options;if(b){b={};
+for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=!0)}else{c=""+va(c);b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}function Gd(a,b){if(null!=b.dangerouslySetInnerHTML)throw Error(k(91));return M({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}
+function Kf(a,b){var c=b.value;if(null==c){c=b.children;b=b.defaultValue;if(null!=c){if(null!=b)throw Error(k(92));if(Array.isArray(c)){if(!(1>=c.length))throw Error(k(93));c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:va(c)}}function Lf(a,b){var c=va(b.value),d=va(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function Mf(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==
+b&&null!==b&&(a.value=b)}function Nf(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function Hd(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?Nf(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function nc(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;return c}function oc(a){if(Id[a])return Id[a];
+if(!ib[a])return a;var b=ib[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Of)return Id[a]=b[c];return a}function Jd(a){var b=Pf.get(a);void 0===b&&(b=new Map,Pf.set(a,b));return b}function Na(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.effectTag&1026)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function Qf(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function Rf(a){if(Na(a)!==
+a)throw Error(k(188));}function vi(a){var b=a.alternate;if(!b){b=Na(a);if(null===b)throw Error(k(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return Rf(e),a;if(f===d)return Rf(e),b;f=f.sibling}throw Error(k(188));}if(c.return!==d.return)c=e,d=f;else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=
+f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(k(189));}}if(c.alternate!==d)throw Error(k(190));}if(3!==c.tag)throw Error(k(188));return c.stateNode.current===c?a:b}function Sf(a){a=vi(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child.return=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}}return null}function jb(a,b){if(null==
+b)throw Error(k(30));if(null==a)return b;if(Array.isArray(a)){if(Array.isArray(b))return a.push.apply(a,b),a;a.push(b);return a}return Array.isArray(b)?[a].concat(b):[a,b]}function Kd(a,b,c){Array.isArray(a)?a.forEach(b,c):a&&b.call(c,a)}function pc(a){null!==a&&(Ab=jb(Ab,a));a=Ab;Ab=null;if(a){Kd(a,wi);if(Ab)throw Error(k(95));if(hc)throw a=pd,hc=!1,pd=null,a;}}function Ld(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:
+a}function Tf(a){if(!wa)return!1;a="on"+a;var b=a in document;b||(b=document.createElement("div"),b.setAttribute(a,"return;"),b="function"===typeof b[a]);return b}function Uf(a){a.topLevelType=null;a.nativeEvent=null;a.targetInst=null;a.ancestors.length=0;10>qc.length&&qc.push(a)}function Vf(a,b,c,d){if(qc.length){var e=qc.pop();e.topLevelType=a;e.eventSystemFlags=d;e.nativeEvent=b;e.targetInst=c;return e}return{topLevelType:a,eventSystemFlags:d,nativeEvent:b,targetInst:c,ancestors:[]}}function Wf(a){var b=
+a.targetInst,c=b;do{if(!c){a.ancestors.push(c);break}var d=c;if(3===d.tag)d=d.stateNode.containerInfo;else{for(;d.return;)d=d.return;d=3!==d.tag?null:d.stateNode.containerInfo}if(!d)break;b=c.tag;5!==b&&6!==b||a.ancestors.push(c);c=Bb(d)}while(c);for(c=0;c<a.ancestors.length;c++){b=a.ancestors[c];var e=Ld(a.nativeEvent);d=a.topLevelType;var f=a.nativeEvent,g=a.eventSystemFlags;0===c&&(g|=64);for(var h=null,m=0;m<jc.length;m++){var n=jc[m];n&&(n=n.extractEvents(d,b,f,e,g))&&(h=jb(h,n))}pc(h)}}function Md(a,
+b,c){if(!c.has(a)){switch(a){case "scroll":Cb(b,"scroll",!0);break;case "focus":case "blur":Cb(b,"focus",!0);Cb(b,"blur",!0);c.set("blur",null);c.set("focus",null);break;case "cancel":case "close":Tf(a)&&Cb(b,a,!0);break;case "invalid":case "submit":case "reset":break;default:-1===Db.indexOf(a)&&w(a,b)}c.set(a,null)}}function xi(a,b){var c=Jd(b);Nd.forEach(function(a){Md(a,b,c)});yi.forEach(function(a){Md(a,b,c)})}function Od(a,b,c,d,e){return{blockedOn:a,topLevelType:b,eventSystemFlags:c|32,nativeEvent:e,
+container:d}}function Xf(a,b){switch(a){case "focus":case "blur":xa=null;break;case "dragenter":case "dragleave":ya=null;break;case "mouseover":case "mouseout":za=null;break;case "pointerover":case "pointerout":Eb.delete(b.pointerId);break;case "gotpointercapture":case "lostpointercapture":Fb.delete(b.pointerId)}}function Gb(a,b,c,d,e,f){if(null===a||a.nativeEvent!==f)return a=Od(b,c,d,e,f),null!==b&&(b=Hb(b),null!==b&&Yf(b)),a;a.eventSystemFlags|=d;return a}function zi(a,b,c,d,e){switch(b){case "focus":return xa=
+Gb(xa,a,b,c,d,e),!0;case "dragenter":return ya=Gb(ya,a,b,c,d,e),!0;case "mouseover":return za=Gb(za,a,b,c,d,e),!0;case "pointerover":var f=e.pointerId;Eb.set(f,Gb(Eb.get(f)||null,a,b,c,d,e));return!0;case "gotpointercapture":return f=e.pointerId,Fb.set(f,Gb(Fb.get(f)||null,a,b,c,d,e)),!0}return!1}function Ai(a){var b=Bb(a.target);if(null!==b){var c=Na(b);if(null!==c)if(b=c.tag,13===b){if(b=Qf(c),null!==b){a.blockedOn=b;Pd(a.priority,function(){Bi(c)});return}}else if(3===b&&c.stateNode.hydrate){a.blockedOn=
+3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function rc(a){if(null!==a.blockedOn)return!1;var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);if(null!==b){var c=Hb(b);null!==c&&Yf(c);a.blockedOn=b;return!1}return!0}function Zf(a,b,c){rc(a)&&c.delete(b)}function Ci(){for(Rd=!1;0<fa.length;){var a=fa[0];if(null!==a.blockedOn){a=Hb(a.blockedOn);null!==a&&Di(a);break}var b=Qd(a.topLevelType,a.eventSystemFlags,a.container,a.nativeEvent);null!==b?a.blockedOn=b:fa.shift()}null!==
+xa&&rc(xa)&&(xa=null);null!==ya&&rc(ya)&&(ya=null);null!==za&&rc(za)&&(za=null);Eb.forEach(Zf);Fb.forEach(Zf)}function Ib(a,b){a.blockedOn===b&&(a.blockedOn=null,Rd||(Rd=!0,$f(ag,Ci)))}function bg(a){if(0<fa.length){Ib(fa[0],a);for(var b=1;b<fa.length;b++){var c=fa[b];c.blockedOn===a&&(c.blockedOn=null)}}null!==xa&&Ib(xa,a);null!==ya&&Ib(ya,a);null!==za&&Ib(za,a);b=function(b){return Ib(b,a)};Eb.forEach(b);Fb.forEach(b);for(b=0;b<Jb.length;b++)c=Jb[b],c.blockedOn===a&&(c.blockedOn=null);for(;0<Jb.length&&
+(b=Jb[0],null===b.blockedOn);)Ai(b),null===b.blockedOn&&Jb.shift()}function Sd(a,b){for(var c=0;c<a.length;c+=2){var d=a[c],e=a[c+1],f="on"+(e[0].toUpperCase()+e.slice(1));f={phasedRegistrationNames:{bubbled:f,captured:f+"Capture"},dependencies:[d],eventPriority:b};Td.set(d,b);cg.set(d,f);dg[e]=f}}function w(a,b){Cb(b,a,!1)}function Cb(a,b,c){var d=Td.get(b);switch(void 0===d?2:d){case 0:d=Ei.bind(null,b,1,a);break;case 1:d=Fi.bind(null,b,1,a);break;default:d=sc.bind(null,b,1,a)}c?a.addEventListener(b,
+d,!0):a.addEventListener(b,d,!1)}function Ei(a,b,c,d){Oa||vd();var e=sc,f=Oa;Oa=!0;try{eg(e,a,b,c,d)}finally{(Oa=f)||ud()}}function Fi(a,b,c,d){Gi(Hi,sc.bind(null,a,b,c,d))}function sc(a,b,c,d){if(tc)if(0<fa.length&&-1<Nd.indexOf(a))a=Od(null,a,b,c,d),fa.push(a);else{var e=Qd(a,b,c,d);if(null===e)Xf(a,d);else if(-1<Nd.indexOf(a))a=Od(e,a,b,c,d),fa.push(a);else if(!zi(e,a,b,c,d)){Xf(a,d);a=Vf(a,d,null,b);try{uf(Wf,a)}finally{Uf(a)}}}}function Qd(a,b,c,d){c=Ld(d);c=Bb(c);if(null!==c){var e=Na(c);if(null===
+e)c=null;else{var f=e.tag;if(13===f){c=Qf(e);if(null!==c)return c;c=null}else if(3===f){if(e.stateNode.hydrate)return 3===e.tag?e.stateNode.containerInfo:null;c=null}else e!==c&&(c=null)}}a=Vf(a,d,c,b);try{uf(Wf,a)}finally{Uf(a)}return null}function fg(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||Kb.hasOwnProperty(a)&&Kb[a]?(""+b).trim():b+"px"}function gg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=fg(c,b[c],d);"float"===
+c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function Ud(a,b){if(b){if(Ii[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(k(137,a,""));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(k(60));if(!("object"===typeof b.dangerouslySetInnerHTML&&"__html"in b.dangerouslySetInnerHTML))throw Error(k(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(k(62,""));}}function Vd(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;
+default:return!0}}function oa(a,b){a=9===a.nodeType||11===a.nodeType?a:a.ownerDocument;var c=Jd(a);b=rd[b];for(var d=0;d<b.length;d++)Md(b[d],a,c)}function uc(){}function Wd(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function hg(a){for(;a&&a.firstChild;)a=a.firstChild;return a}function ig(a,b){var c=hg(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;if(a<=b&&d>=b)return{node:c,
+offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=hg(c)}}function jg(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?jg(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function kg(){for(var a=window,b=Wd();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=Wd(a.document)}return b}
+function Xd(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function lg(a,b){switch(a){case "button":case "input":case "select":case "textarea":return!!b.autoFocus}return!1}function Yd(a,b){return"textarea"===a||"option"===a||"noscript"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===typeof b.dangerouslySetInnerHTML&&
+null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}function kb(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break}return a}function mg(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if(c===ng||c===Zd||c===$d){if(0===b)return a;b--}else c===og&&b++}a=a.previousSibling}return null}function Bb(a){var b=a[Aa];if(b)return b;for(var c=a.parentNode;c;){if(b=c[Lb]||c[Aa]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=mg(a);null!==
+a;){if(c=a[Aa])return c;a=mg(a)}return b}a=c;c=a.parentNode}return null}function Hb(a){a=a[Aa]||a[Lb];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Pa(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(k(33));}function ae(a){return a[vc]||null}function pa(a){do a=a.return;while(a&&5!==a.tag);return a?a:null}function pg(a,b){var c=a.stateNode;if(!c)return null;var d=td(c);if(!d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":case "onMouseEnter":(d=
+!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;if(c&&"function"!==typeof c)throw Error(k(231,b,typeof c));return c}function qg(a,b,c){if(b=pg(a,c.dispatchConfig.phasedRegistrationNames[b]))c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a)}function Ji(a){if(a&&a.dispatchConfig.phasedRegistrationNames){for(var b=a._targetInst,c=[];b;)c.push(b),b=pa(b);for(b=c.length;0<b--;)qg(c[b],
+"captured",a);for(b=0;b<c.length;b++)qg(c[b],"bubbled",a)}}function be(a,b,c){a&&c&&c.dispatchConfig.registrationName&&(b=pg(a,c.dispatchConfig.registrationName))&&(c._dispatchListeners=jb(c._dispatchListeners,b),c._dispatchInstances=jb(c._dispatchInstances,a))}function Ki(a){a&&a.dispatchConfig.registrationName&&be(a._targetInst,null,a)}function lb(a){Kd(a,Ji)}function rg(){if(wc)return wc;var a,b=ce,c=b.length,d,e="value"in Ba?Ba.value:Ba.textContent,f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=
+c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);return wc=e.slice(a,1<d?1-d:void 0)}function xc(){return!0}function yc(){return!1}function R(a,b,c,d){this.dispatchConfig=a;this._targetInst=b;this.nativeEvent=c;a=this.constructor.Interface;for(var e in a)a.hasOwnProperty(e)&&((b=a[e])?this[e]=b(c):"target"===e?this.target=d:this[e]=c[e]);this.isDefaultPrevented=(null!=c.defaultPrevented?c.defaultPrevented:!1===c.returnValue)?xc:yc;this.isPropagationStopped=yc;return this}function Li(a,b,c,d){if(this.eventPool.length){var e=
+this.eventPool.pop();this.call(e,a,b,c,d);return e}return new this(a,b,c,d)}function Mi(a){if(!(a instanceof this))throw Error(k(279));a.destructor();10>this.eventPool.length&&this.eventPool.push(a)}function sg(a){a.eventPool=[];a.getPooled=Li;a.release=Mi}function tg(a,b){switch(a){case "keyup":return-1!==Ni.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "blur":return!0;default:return!1}}function ug(a){a=a.detail;return"object"===typeof a&&"data"in
+a?a.data:null}function Oi(a,b){switch(a){case "compositionend":return ug(b);case "keypress":if(32!==b.which)return null;vg=!0;return wg;case "textInput":return a=b.data,a===wg&&vg?null:a;default:return null}}function Pi(a,b){if(mb)return"compositionend"===a||!de&&tg(a,b)?(a=rg(),wc=ce=Ba=null,mb=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;
+case "compositionend":return xg&&"ko"!==b.locale?null:b.data;default:return null}}function yg(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!Qi[a.type]:"textarea"===b?!0:!1}function zg(a,b,c){a=R.getPooled(Ag.change,a,b,c);a.type="change";sf(c);lb(a);return a}function Ri(a){pc(a)}function zc(a){var b=Pa(a);if(Gf(b))return a}function Si(a,b){if("change"===a)return b}function Bg(){Mb&&(Mb.detachEvent("onpropertychange",Cg),Nb=Mb=null)}function Cg(a){if("value"===a.propertyName&&
+zc(Nb))if(a=zg(Nb,a,Ld(a)),Oa)pc(a);else{Oa=!0;try{ee(Ri,a)}finally{Oa=!1,ud()}}}function Ti(a,b,c){"focus"===a?(Bg(),Mb=b,Nb=c,Mb.attachEvent("onpropertychange",Cg)):"blur"===a&&Bg()}function Ui(a,b){if("selectionchange"===a||"keyup"===a||"keydown"===a)return zc(Nb)}function Vi(a,b){if("click"===a)return zc(b)}function Wi(a,b){if("input"===a||"change"===a)return zc(b)}function Xi(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=Yi[a])?!!b[a]:!1}function fe(a){return Xi}
+function Zi(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}function Ob(a,b){if(Qa(a,b))return!0;if("object"!==typeof a||null===a||"object"!==typeof b||null===b)return!1;var c=Object.keys(a),d=Object.keys(b);if(c.length!==d.length)return!1;for(d=0;d<c.length;d++)if(!$i.call(b,c[d])||!Qa(a[c[d]],b[c[d]]))return!1;return!0}function Dg(a,b){var c=b.window===b?b.document:9===b.nodeType?b:b.ownerDocument;if(ge||null==nb||nb!==Wd(c))return null;c=nb;"selectionStart"in c&&Xd(c)?c={start:c.selectionStart,
+end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset});return Pb&&Ob(Pb,c)?null:(Pb=c,a=R.getPooled(Eg.select,he,a,b),a.type="select",a.target=nb,lb(a),a)}function Ac(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}function q(a,b){0>ob||(a.current=ie[ob],ie[ob]=null,ob--)}function y(a,b,c){ob++;
+ie[ob]=a.current;a.current=b}function pb(a,b){var c=a.type.contextTypes;if(!c)return Ca;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function N(a){a=a.childContextTypes;return null!==a&&void 0!==a}function Fg(a,b,c){if(B.current!==Ca)throw Error(k(168));y(B,b);y(G,c)}
+function Gg(a,b,c){var d=a.stateNode;a=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(k(108,na(b)||"Unknown",e));return M({},c,{},d)}function Bc(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Ca;Ra=B.current;y(B,a);y(G,G.current);return!0}function Hg(a,b,c){var d=a.stateNode;if(!d)throw Error(k(169));c?(a=Gg(a,b,Ra),d.__reactInternalMemoizedMergedChildContext=a,q(G),q(B),y(B,a)):q(G);y(G,c)}function Cc(){switch(aj()){case Dc:return 99;
+case Ig:return 98;case Jg:return 97;case Kg:return 96;case Lg:return 95;default:throw Error(k(332));}}function Mg(a){switch(a){case 99:return Dc;case 98:return Ig;case 97:return Jg;case 96:return Kg;case 95:return Lg;default:throw Error(k(332));}}function Da(a,b){a=Mg(a);return bj(a,b)}function Ng(a,b,c){a=Mg(a);return je(a,b,c)}function Og(a){null===qa?(qa=[a],Ec=je(Dc,Pg)):qa.push(a);return Qg}function ha(){if(null!==Ec){var a=Ec;Ec=null;Rg(a)}Pg()}function Pg(){if(!ke&&null!==qa){ke=!0;var a=0;
+try{var b=qa;Da(99,function(){for(;a<b.length;a++){var c=b[a];do c=c(!0);while(null!==c)}});qa=null}catch(c){throw null!==qa&&(qa=qa.slice(a+1)),je(Dc,ha),c;}finally{ke=!1}}}function Fc(a,b,c){c/=10;return 1073741821-(((1073741821-a+b/10)/c|0)+1)*c}function aa(a,b){if(a&&a.defaultProps){b=M({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c])}return b}function le(){Gc=qb=Hc=null}function me(a){var b=Ic.current;q(Ic);a.type._context._currentValue=b}function Sg(a,b){for(;null!==a;){var c=
+a.alternate;if(a.childExpirationTime<b)a.childExpirationTime=b,null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);else if(null!==c&&c.childExpirationTime<b)c.childExpirationTime=b;else break;a=a.return}}function rb(a,b){Hc=a;Gc=qb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(a.expirationTime>=b&&(ia=!0),a.firstContext=null)}function W(a,b){if(Gc!==a&&!1!==b&&0!==b){if("number"!==typeof b||1073741823===b)Gc=a,b=1073741823;b={context:a,observedBits:b,next:null};if(null===qb){if(null===
+Hc)throw Error(k(308));qb=b;Hc.dependencies={expirationTime:0,firstContext:b,responders:null}}else qb=qb.next=b}return a._currentValue}function ne(a){a.updateQueue={baseState:a.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}function oe(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue={baseState:a.baseState,baseQueue:a.baseQueue,shared:a.shared,effects:a.effects})}function Ea(a,b){a={expirationTime:a,suspenseConfig:b,tag:Tg,payload:null,callback:null,next:null};return a.next=
+a}function Fa(a,b){a=a.updateQueue;if(null!==a){a=a.shared;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}}function Ug(a,b){var c=a.alternate;null!==c&&oe(c,a);a=a.updateQueue;c=a.baseQueue;null===c?(a.baseQueue=b.next=b,b.next=b):(b.next=c.next,c.next=b)}function Qb(a,b,c,d){var e=a.updateQueue;Ga=!1;var f=e.baseQueue,g=e.shared.pending;if(null!==g){if(null!==f){var h=f.next;f.next=g.next;g.next=h}f=g;e.shared.pending=null;h=a.alternate;null!==h&&(h=h.updateQueue,null!==h&&
+(h.baseQueue=g))}if(null!==f){h=f.next;var m=e.baseState,n=0,k=null,ba=null,l=null;if(null!==h){var p=h;do{g=p.expirationTime;if(g<d){var t={expirationTime:p.expirationTime,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null};null===l?(ba=l=t,k=m):l=l.next=t;g>n&&(n=g)}else{null!==l&&(l=l.next={expirationTime:1073741823,suspenseConfig:p.suspenseConfig,tag:p.tag,payload:p.payload,callback:p.callback,next:null});Vg(g,p.suspenseConfig);a:{var q=a,r=p;g=b;t=c;switch(r.tag){case 1:q=
+r.payload;if("function"===typeof q){m=q.call(t,m,g);break a}m=q;break a;case 3:q.effectTag=q.effectTag&-4097|64;case Tg:q=r.payload;g="function"===typeof q?q.call(t,m,g):q;if(null===g||void 0===g)break a;m=M({},m,g);break a;case Jc:Ga=!0}}null!==p.callback&&(a.effectTag|=32,g=e.effects,null===g?e.effects=[p]:g.push(p))}p=p.next;if(null===p||p===h)if(g=e.shared.pending,null===g)break;else p=f.next=g.next,g.next=h,e.baseQueue=f=g,e.shared.pending=null}while(1)}null===l?k=m:l.next=ba;e.baseState=k;e.baseQueue=
+l;Kc(n);a.expirationTime=n;a.memoizedState=m}}function Wg(a,b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;b<a.length;b++){var d=a[b],e=d.callback;if(null!==e){d.callback=null;d=e;e=c;if("function"!==typeof d)throw Error(k(191,d));d.call(e)}}}function Lc(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:M({},b,c);a.memoizedState=c;0===a.expirationTime&&(a.updateQueue.baseState=c)}function Xg(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,
+f,g):b.prototype&&b.prototype.isPureReactComponent?!Ob(c,d)||!Ob(e,f):!0}function Yg(a,b,c){var d=!1,e=Ca;var f=b.contextType;"object"===typeof f&&null!==f?f=W(f):(e=N(b)?Ra:B.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?pb(a,e):Ca);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Mc;a.stateNode=b;b._reactInternalFiber=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function Zg(a,
+b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Mc.enqueueReplaceState(b,b.state,null)}function pe(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs=$g;ne(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=W(f):(f=N(b)?Ra:B.current,e.context=pb(a,f));Qb(a,c,e,d);e.state=a.memoizedState;f=b.getDerivedStateFromProps;
+"function"===typeof f&&(Lc(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Mc.enqueueReplaceState(e,e.state,null),Qb(a,c,e,d),e.state=a.memoizedState);"function"===
+typeof e.componentDidMount&&(a.effectTag|=4)}function Rb(a,b,c){a=c.ref;if(null!==a&&"function"!==typeof a&&"object"!==typeof a){if(c._owner){c=c._owner;if(c){if(1!==c.tag)throw Error(k(309));var d=c.stateNode}if(!d)throw Error(k(147,a));var e=""+a;if(null!==b&&null!==b.ref&&"function"===typeof b.ref&&b.ref._stringRef===e)return b.ref;b=function(a){var b=d.refs;b===$g&&(b=d.refs={});null===a?delete b[e]:b[e]=a};b._stringRef=e;return b}if("string"!==typeof a)throw Error(k(284));if(!c._owner)throw Error(k(290,
+a));}return a}function Nc(a,b){if("textarea"!==a.type)throw Error(k(31,"[object Object]"===Object.prototype.toString.call(b)?"object with keys {"+Object.keys(b).join(", ")+"}":b,""));}function ah(a){function b(b,c){if(a){var d=b.lastEffect;null!==d?(d.nextEffect=c,b.lastEffect=c):b.firstEffect=b.lastEffect=c;c.nextEffect=null;c.effectTag=8}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,
+b),b=b.sibling;return a}function e(a,b){a=Sa(a,b);a.index=0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.effectTag=2,c):d;b.effectTag=2;return c}function g(b){a&&null===b.alternate&&(b.effectTag=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=qe(c,a.mode,d),b.return=a,b;b=e(b,c);b.return=a;return b}function m(a,b,c,d){if(null!==b&&b.elementType===c.type)return d=e(b,c.props),d.ref=Rb(a,b,c),d.return=a,d;d=Oc(c.type,
+c.key,c.props,null,a.mode,d);d.ref=Rb(a,b,c);d.return=a;return d}function n(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==c.containerInfo||b.stateNode.implementation!==c.implementation)return b=re(c,a.mode,d),b.return=a,b;b=e(b,c.children||[]);b.return=a;return b}function l(a,b,c,d,f){if(null===b||7!==b.tag)return b=Ha(c,a.mode,d,f),b.return=a,b;b=e(b,c);b.return=a;return b}function ba(a,b,c){if("string"===typeof b||"number"===typeof b)return b=qe(""+b,a.mode,c),b.return=a,b;if("object"===
+typeof b&&null!==b){switch(b.$$typeof){case Pc:return c=Oc(b.type,b.key,b.props,null,a.mode,c),c.ref=Rb(a,null,b),c.return=a,c;case gb:return b=re(b,a.mode,c),b.return=a,b}if(Qc(b)||zb(b))return b=Ha(b,a.mode,c,null),b.return=a,b;Nc(a,b)}return null}function p(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c||"number"===typeof c)return null!==e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case Pc:return c.key===e?c.type===Ma?l(a,b,c.props.children,d,e):m(a,b,c,
+d):null;case gb:return c.key===e?n(a,b,c,d):null}if(Qc(c)||zb(c))return null!==e?null:l(a,b,c,d,null);Nc(a,c)}return null}function t(a,b,c,d,e){if("string"===typeof d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case Pc:return a=a.get(null===d.key?c:d.key)||null,d.type===Ma?l(b,a,d.props.children,e,d.key):m(b,a,d,e);case gb:return a=a.get(null===d.key?c:d.key)||null,n(b,a,d,e)}if(Qc(d)||zb(d))return a=a.get(c)||null,l(b,a,d,e,null);
+Nc(b,d)}return null}function q(e,g,h,m){for(var n=null,k=null,l=g,r=g=0,C=null;null!==l&&r<h.length;r++){l.index>r?(C=l,l=null):C=l.sibling;var O=p(e,l,h[r],m);if(null===O){null===l&&(l=C);break}a&&l&&null===O.alternate&&b(e,l);g=f(O,g,r);null===k?n=O:k.sibling=O;k=O;l=C}if(r===h.length)return c(e,l),n;if(null===l){for(;r<h.length;r++)l=ba(e,h[r],m),null!==l&&(g=f(l,g,r),null===k?n=l:k.sibling=l,k=l);return n}for(l=d(e,l);r<h.length;r++)C=t(l,e,r,h[r],m),null!==C&&(a&&null!==C.alternate&&l.delete(null===
+C.key?r:C.key),g=f(C,g,r),null===k?n=C:k.sibling=C,k=C);a&&l.forEach(function(a){return b(e,a)});return n}function w(e,g,h,n){var m=zb(h);if("function"!==typeof m)throw Error(k(150));h=m.call(h);if(null==h)throw Error(k(151));for(var l=m=null,r=g,C=g=0,O=null,v=h.next();null!==r&&!v.done;C++,v=h.next()){r.index>C?(O=r,r=null):O=r.sibling;var q=p(e,r,v.value,n);if(null===q){null===r&&(r=O);break}a&&r&&null===q.alternate&&b(e,r);g=f(q,g,C);null===l?m=q:l.sibling=q;l=q;r=O}if(v.done)return c(e,r),m;
+if(null===r){for(;!v.done;C++,v=h.next())v=ba(e,v.value,n),null!==v&&(g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);return m}for(r=d(e,r);!v.done;C++,v=h.next())v=t(r,e,C,v.value,n),null!==v&&(a&&null!==v.alternate&&r.delete(null===v.key?C:v.key),g=f(v,g,C),null===l?m=v:l.sibling=v,l=v);a&&r.forEach(function(a){return b(e,a)});return m}return function(a,d,f,h){var m="object"===typeof f&&null!==f&&f.type===Ma&&null===f.key;m&&(f=f.props.children);var n="object"===typeof f&&null!==f;if(n)switch(f.$$typeof){case Pc:a:{n=
+f.key;for(m=d;null!==m;){if(m.key===n){switch(m.tag){case 7:if(f.type===Ma){c(a,m.sibling);d=e(m,f.props.children);d.return=a;a=d;break a}break;default:if(m.elementType===f.type){c(a,m.sibling);d=e(m,f.props);d.ref=Rb(a,m,f);d.return=a;a=d;break a}}c(a,m);break}else b(a,m);m=m.sibling}f.type===Ma?(d=Ha(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=Oc(f.type,f.key,f.props,null,a.mode,h),h.ref=Rb(a,d,f),h.return=a,a=h)}return g(a);case gb:a:{for(m=f.key;null!==d;){if(d.key===m)if(4===d.tag&&d.stateNode.containerInfo===
+f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=re(f,a.mode,h);d.return=a;a=d}return g(a)}if("string"===typeof f||"number"===typeof f)return f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=qe(f,a.mode,h),d.return=a,a=d),g(a);if(Qc(f))return q(a,d,f,h);if(zb(f))return w(a,d,f,h);n&&Nc(a,f);if("undefined"===typeof f&&!m)switch(a.tag){case 1:case 0:throw a=
+a.type,Error(k(152,a.displayName||a.name||"Component"));}return c(a,d)}}function Ta(a){if(a===Sb)throw Error(k(174));return a}function se(a,b){y(Tb,b);y(Ub,a);y(ja,Sb);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Hd(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=Hd(b,a)}q(ja);y(ja,b)}function tb(a){q(ja);q(Ub);q(Tb)}function bh(a){Ta(Tb.current);var b=Ta(ja.current);var c=Hd(b,a.type);b!==c&&(y(Ub,a),y(ja,c))}function te(a){Ub.current===
+a&&(q(ja),q(Ub))}function Rc(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||c.data===$d||c.data===Zd))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.effectTag&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function ue(a,b){return{responder:a,props:b}}
+function S(){throw Error(k(321));}function ve(a,b){if(null===b)return!1;for(var c=0;c<b.length&&c<a.length;c++)if(!Qa(a[c],b[c]))return!1;return!0}function we(a,b,c,d,e,f){Ia=f;z=b;b.memoizedState=null;b.updateQueue=null;b.expirationTime=0;Sc.current=null===a||null===a.memoizedState?dj:ej;a=c(d,e);if(b.expirationTime===Ia){f=0;do{b.expirationTime=0;if(!(25>f))throw Error(k(301));f+=1;J=K=null;b.updateQueue=null;Sc.current=fj;a=c(d,e)}while(b.expirationTime===Ia)}Sc.current=Tc;b=null!==K&&null!==K.next;
+Ia=0;J=K=z=null;Uc=!1;if(b)throw Error(k(300));return a}function ub(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===J?z.memoizedState=J=a:J=J.next=a;return J}function vb(){if(null===K){var a=z.alternate;a=null!==a?a.memoizedState:null}else a=K.next;var b=null===J?z.memoizedState:J.next;if(null!==b)J=b,K=a;else{if(null===a)throw Error(k(310));K=a;a={memoizedState:K.memoizedState,baseState:K.baseState,baseQueue:K.baseQueue,queue:K.queue,next:null};null===J?z.memoizedState=
+J=a:J=J.next=a}return J}function Ua(a,b){return"function"===typeof b?b(a):b}function Vc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=K,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){e=e.next;d=d.baseState;var h=g=f=null,m=e;do{var n=m.expirationTime;if(n<Ia){var l={expirationTime:m.expirationTime,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,
+next:null};null===h?(g=h=l,f=d):h=h.next=l;n>z.expirationTime&&(z.expirationTime=n,Kc(n))}else null!==h&&(h=h.next={expirationTime:1073741823,suspenseConfig:m.suspenseConfig,action:m.action,eagerReducer:m.eagerReducer,eagerState:m.eagerState,next:null}),Vg(n,m.suspenseConfig),d=m.eagerReducer===a?m.eagerState:a(d,m.action);m=m.next}while(null!==m&&m!==e);null===h?f=d:h.next=g;Qa(d,b.memoizedState)||(ia=!0);b.memoizedState=d;b.baseState=f;b.baseQueue=h;c.lastRenderedState=d}return[b.memoizedState,
+c.dispatch]}function Wc(a,b,c){b=vb();c=b.queue;if(null===c)throw Error(k(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);Qa(f,b.memoizedState)||(ia=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function xe(a){var b=ub();"function"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a=b.queue={pending:null,dispatch:null,lastRenderedReducer:Ua,
+lastRenderedState:a};a=a.dispatch=ch.bind(null,z,a);return[b.memoizedState,a]}function ye(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};b=z.updateQueue;null===b?(b={lastEffect:null},z.updateQueue=b,b.lastEffect=a.next=a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}function dh(a){return vb().memoizedState}function ze(a,b,c,d){var e=ub();z.effectTag|=a;e.memoizedState=ye(1|b,c,void 0,void 0===d?null:d)}function Ae(a,b,c,d){var e=vb();
+d=void 0===d?null:d;var f=void 0;if(null!==K){var g=K.memoizedState;f=g.destroy;if(null!==d&&ve(d,g.deps)){ye(b,c,f,d);return}}z.effectTag|=a;e.memoizedState=ye(1|b,c,f,d)}function eh(a,b){return ze(516,4,a,b)}function Xc(a,b){return Ae(516,4,a,b)}function fh(a,b){return Ae(4,2,a,b)}function gh(a,b){if("function"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function hh(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;
+return Ae(4,2,gh.bind(null,b,a),c)}function Be(a,b){}function ih(a,b){ub().memoizedState=[a,void 0===b?null:b];return a}function Yc(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];c.memoizedState=[a,b];return a}function jh(a,b){var c=vb();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&ve(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function Ce(a,b,c){var d=Cc();Da(98>d?98:d,function(){a(!0)});Da(97<d?97:d,function(){var d=
+X.suspense;X.suspense=void 0===b?null:b;try{a(!1),c()}finally{X.suspense=d}})}function ch(a,b,c){var d=ka(),e=Vb.suspense;d=Va(d,a,e);e={expirationTime:d,suspenseConfig:e,action:c,eagerReducer:null,eagerState:null,next:null};var f=b.pending;null===f?e.next=e:(e.next=f.next,f.next=e);b.pending=e;f=a.alternate;if(a===z||null!==f&&f===z)Uc=!0,e.expirationTime=Ia,z.expirationTime=Ia;else{if(0===a.expirationTime&&(null===f||0===f.expirationTime)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
+h=f(g,c);e.eagerReducer=f;e.eagerState=h;if(Qa(h,g))return}catch(m){}finally{}Ja(a,d)}}function kh(a,b){var c=la(5,null,null,0);c.elementType="DELETED";c.type="DELETED";c.stateNode=b;c.return=a;c.effectTag=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect=a.lastEffect=c}function lh(a,b){switch(a.tag){case 5:var c=a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?
+null:b,null!==b?(a.stateNode=b,!0):!1;case 13:return!1;default:return!1}}function De(a){if(Wa){var b=Ka;if(b){var c=b;if(!lh(a,b)){b=kb(c.nextSibling);if(!b||!lh(a,b)){a.effectTag=a.effectTag&-1025|2;Wa=!1;ra=a;return}kh(ra,c)}ra=a;Ka=kb(b.firstChild)}else a.effectTag=a.effectTag&-1025|2,Wa=!1,ra=a}}function mh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;ra=a}function Zc(a){if(a!==ra)return!1;if(!Wa)return mh(a),Wa=!0,!1;var b=a.type;if(5!==a.tag||"head"!==b&&"body"!==
+b&&!Yd(b,a.memoizedProps))for(b=Ka;b;)kh(a,b),b=kb(b.nextSibling);mh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(k(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if(c===og){if(0===b){Ka=kb(a.nextSibling);break a}b--}else c!==ng&&c!==Zd&&c!==$d||b++}a=a.nextSibling}Ka=null}}else Ka=ra?kb(a.stateNode.nextSibling):null;return!0}function Ee(){Ka=ra=null;Wa=!1}function T(a,b,c,d){b.child=null===a?Fe(b,null,c,d):wb(b,a.child,c,d)}function nh(a,
+b,c,d,e){c=c.render;var f=b.ref;rb(b,e);d=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,d,e);return b.child}function oh(a,b,c,d,e,f){if(null===a){var g=c.type;if("function"===typeof g&&!Ge(g)&&void 0===g.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=g,ph(a,b,g,d,e,f);a=Oc(c.type,null,d,null,b.mode,f);a.ref=b.ref;a.return=b;return b.child=a}g=a.child;if(e<
+f&&(e=g.memoizedProps,c=c.compare,c=null!==c?c:Ob,c(e,d)&&a.ref===b.ref))return sa(a,b,f);b.effectTag|=1;a=Sa(g,d);a.ref=b.ref;a.return=b;return b.child=a}function ph(a,b,c,d,e,f){return null!==a&&Ob(a.memoizedProps,d)&&a.ref===b.ref&&(ia=!1,e<f)?(b.expirationTime=a.expirationTime,sa(a,b,f)):He(a,b,c,d,f)}function qh(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.effectTag|=128}function He(a,b,c,d,e){var f=N(c)?Ra:B.current;f=pb(b,f);rb(b,e);c=we(a,b,c,d,f,e);if(null!==a&&!ia)return b.updateQueue=
+a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),sa(a,b,e);b.effectTag|=1;T(a,b,c,e);return b.child}function rh(a,b,c,d,e){if(N(c)){var f=!0;Bc(b)}else f=!1;rb(b,e);if(null===b.stateNode)null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),Yg(b,c,d),pe(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var m=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n));var l=c.getDerivedStateFromProps,k="function"===
+typeof l||"function"===typeof g.getSnapshotBeforeUpdate;k||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n);Ga=!1;var p=b.memoizedState;g.state=p;Qb(b,d,g,e);m=b.memoizedState;h!==d||p!==m||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),m=b.memoizedState),(h=Ga||Xg(b,c,h,d,p,m,n))?(k||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||("function"===typeof g.componentWillMount&&
+g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.effectTag|=4)):("function"===typeof g.componentDidMount&&(b.effectTag|=4),b.memoizedProps=d,b.memoizedState=m),g.props=d,g.state=m,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.effectTag|=4),d=!1)}else g=b.stateNode,oe(a,b),h=b.memoizedProps,g.props=b.type===b.elementType?h:aa(b.type,h),m=g.context,n=c.contextType,"object"===typeof n&&null!==
+n?n=W(n):(n=N(c)?Ra:B.current,n=pb(b,n)),l=c.getDerivedStateFromProps,(k="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||m!==n)&&Zg(b,g,d,n),Ga=!1,m=b.memoizedState,g.state=m,Qb(b,d,g,e),p=b.memoizedState,h!==d||m!==p||G.current||Ga?("function"===typeof l&&(Lc(b,c,l,d),p=b.memoizedState),(l=Ga||Xg(b,c,h,d,m,p,n))?(k||"function"!==typeof g.UNSAFE_componentWillUpdate&&
+"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,p,n),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,p,n)),"function"===typeof g.componentDidUpdate&&(b.effectTag|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.effectTag|=256)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===
+a.memoizedState||(b.effectTag|=256),b.memoizedProps=d,b.memoizedState=p),g.props=d,g.state=p,g.context=n,d=l):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&m===a.memoizedState||(b.effectTag|=256),d=!1);return Ie(a,b,c,d,f,e)}function Ie(a,b,c,d,e,f){qh(a,b);var g=0!==(b.effectTag&64);if(!d&&!g)return e&&Hg(b,c,!1),sa(a,b,f);d=b.stateNode;gj.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?
+null:d.render();b.effectTag|=1;null!==a&&g?(b.child=wb(b,a.child,null,f),b.child=wb(b,null,h,f)):T(a,b,h,f);b.memoizedState=d.state;e&&Hg(b,c,!0);return b.child}function sh(a){var b=a.stateNode;b.pendingContext?Fg(a,b.pendingContext,b.pendingContext!==b.context):b.context&&Fg(a,b.context,!1);se(a,b.containerInfo)}function th(a,b,c){var d=b.mode,e=b.pendingProps,f=D.current,g=!1,h;(h=0!==(b.effectTag&64))||(h=0!==(f&2)&&(null===a||null!==a.memoizedState));h?(g=!0,b.effectTag&=-65):null!==a&&null===
+a.memoizedState||void 0===e.fallback||!0===e.unstable_avoidThisFallback||(f|=1);y(D,f&1);if(null===a){void 0!==e.fallback&&De(b);if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;b.memoizedState=Je;b.child=e;return c}d=e.children;b.memoizedState=null;return b.child=Fe(b,null,d,c)}if(null!==a.memoizedState){a=a.child;d=a.sibling;if(g){e=e.fallback;
+c=Sa(a,a.pendingProps);c.return=b;if(0===(b.mode&2)&&(g=null!==b.memoizedState?b.child.child:b.child,g!==a.child))for(c.child=g;null!==g;)g.return=c,g=g.sibling;d=Sa(d,e);d.return=b;c.sibling=d;c.childExpirationTime=0;b.memoizedState=Je;b.child=c;return d}c=wb(b,a.child,e.children,c);b.memoizedState=null;return b.child=c}a=a.child;if(g){g=e.fallback;e=Ha(null,d,0,null);e.return=b;e.child=a;null!==a&&(a.return=e);if(0===(b.mode&2))for(a=null!==b.memoizedState?b.child.child:b.child,e.child=a;null!==
+a;)a.return=e,a=a.sibling;c=Ha(g,d,c,null);c.return=b;e.sibling=c;c.effectTag|=2;e.childExpirationTime=0;b.memoizedState=Je;b.child=e;return c}b.memoizedState=null;return b.child=wb(b,a,e.children,c)}function uh(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);Sg(a.return,b)}function Ke(a,b,c,d,e,f){var g=a.memoizedState;null===g?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailExpiration:0,tailMode:e,
+lastEffect:f}:(g.isBackwards=b,g.rendering=null,g.renderingStartTime=0,g.last=d,g.tail=c,g.tailExpiration=0,g.tailMode=e,g.lastEffect=f)}function vh(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;T(a,b,d.children,c);d=D.current;if(0!==(d&2))d=d&1|2,b.effectTag|=64;else{if(null!==a&&0!==(a.effectTag&64))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&uh(a,c);else if(19===a.tag)uh(a,c);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===
+a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(D,d);if(0===(b.mode&2))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===Rc(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ke(b,!1,e,c,f,b.lastEffect);break;case "backwards":c=null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===Rc(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ke(b,
+!0,c,null,f,b.lastEffect);break;case "together":Ke(b,!1,null,null,void 0,b.lastEffect);break;default:b.memoizedState=null}return b.child}function sa(a,b,c){null!==a&&(b.dependencies=a.dependencies);var d=b.expirationTime;0!==d&&Kc(d);if(b.childExpirationTime<c)return null;if(null!==a&&b.child!==a.child)throw Error(k(153));if(null!==b.child){a=b.child;c=Sa(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=Sa(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}
+function $c(a,b){switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function hj(a,b,c){var d=b.pendingProps;switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return N(b.type)&&(q(G),q(B)),
+null;case 3:return tb(),q(G),q(B),c=b.stateNode,c.pendingContext&&(c.context=c.pendingContext,c.pendingContext=null),null!==a&&null!==a.child||!Zc(b)||(b.effectTag|=4),wh(b),null;case 5:te(b);c=Ta(Tb.current);var e=b.type;if(null!==a&&null!=b.stateNode)ij(a,b,e,d,c),a.ref!==b.ref&&(b.effectTag|=128);else{if(!d){if(null===b.stateNode)throw Error(k(166));return null}a=Ta(ja.current);if(Zc(b)){d=b.stateNode;e=b.type;var f=b.memoizedProps;d[Aa]=b;d[vc]=f;switch(e){case "iframe":case "object":case "embed":w("load",
+d);break;case "video":case "audio":for(a=0;a<Db.length;a++)w(Db[a],d);break;case "source":w("error",d);break;case "img":case "image":case "link":w("error",d);w("load",d);break;case "form":w("reset",d);w("submit",d);break;case "details":w("toggle",d);break;case "input":Hf(d,f);w("invalid",d);oa(c,"onChange");break;case "select":d._wrapperState={wasMultiple:!!f.multiple};w("invalid",d);oa(c,"onChange");break;case "textarea":Kf(d,f),w("invalid",d),oa(c,"onChange")}Ud(e,f);a=null;for(var g in f)if(f.hasOwnProperty(g)){var h=
+f[g];"children"===g?"string"===typeof h?d.textContent!==h&&(a=["children",h]):"number"===typeof h&&d.textContent!==""+h&&(a=["children",""+h]):db.hasOwnProperty(g)&&null!=h&&oa(c,g)}switch(e){case "input":mc(d);Jf(d,f,!0);break;case "textarea":mc(d);Mf(d);break;case "select":case "option":break;default:"function"===typeof f.onClick&&(d.onclick=uc)}c=a;b.updateQueue=c;null!==c&&(b.effectTag|=4)}else{g=9===c.nodeType?c:c.ownerDocument;"http://www.w3.org/1999/xhtml"===a&&(a=Nf(e));"http://www.w3.org/1999/xhtml"===
+a?"script"===e?(a=g.createElement("div"),a.innerHTML="<script>\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(e,{is:d.is}):(a=g.createElement(e),"select"===e&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,e);a[Aa]=b;a[vc]=d;jj(a,b,!1,!1);b.stateNode=a;g=Vd(e,d);switch(e){case "iframe":case "object":case "embed":w("load",a);h=d;break;case "video":case "audio":for(h=0;h<Db.length;h++)w(Db[h],a);h=d;break;case "source":w("error",a);
+h=d;break;case "img":case "image":case "link":w("error",a);w("load",a);h=d;break;case "form":w("reset",a);w("submit",a);h=d;break;case "details":w("toggle",a);h=d;break;case "input":Hf(a,d);h=Cd(a,d);w("invalid",a);oa(c,"onChange");break;case "option":h=Fd(a,d);break;case "select":a._wrapperState={wasMultiple:!!d.multiple};h=M({},d,{value:void 0});w("invalid",a);oa(c,"onChange");break;case "textarea":Kf(a,d);h=Gd(a,d);w("invalid",a);oa(c,"onChange");break;default:h=d}Ud(e,h);var m=h;for(f in m)if(m.hasOwnProperty(f)){var n=
+m[f];"style"===f?gg(a,n):"dangerouslySetInnerHTML"===f?(n=n?n.__html:void 0,null!=n&&xh(a,n)):"children"===f?"string"===typeof n?("textarea"!==e||""!==n)&&Wb(a,n):"number"===typeof n&&Wb(a,""+n):"suppressContentEditableWarning"!==f&&"suppressHydrationWarning"!==f&&"autoFocus"!==f&&(db.hasOwnProperty(f)?null!=n&&oa(c,f):null!=n&&xd(a,f,n,g))}switch(e){case "input":mc(a);Jf(a,d,!1);break;case "textarea":mc(a);Mf(a);break;case "option":null!=d.value&&a.setAttribute("value",""+va(d.value));break;case "select":a.multiple=
+!!d.multiple;c=d.value;null!=c?hb(a,!!d.multiple,c,!1):null!=d.defaultValue&&hb(a,!!d.multiple,d.defaultValue,!0);break;default:"function"===typeof h.onClick&&(a.onclick=uc)}lg(e,d)&&(b.effectTag|=4)}null!==b.ref&&(b.effectTag|=128)}return null;case 6:if(a&&null!=b.stateNode)kj(a,b,a.memoizedProps,d);else{if("string"!==typeof d&&null===b.stateNode)throw Error(k(166));c=Ta(Tb.current);Ta(ja.current);Zc(b)?(c=b.stateNode,d=b.memoizedProps,c[Aa]=b,c.nodeValue!==d&&(b.effectTag|=4)):(c=(9===c.nodeType?
+c:c.ownerDocument).createTextNode(d),c[Aa]=b,b.stateNode=c)}return null;case 13:q(D);d=b.memoizedState;if(0!==(b.effectTag&64))return b.expirationTime=c,b;c=null!==d;d=!1;null===a?void 0!==b.memoizedProps.fallback&&Zc(b):(e=a.memoizedState,d=null!==e,c||null===e||(e=a.child.sibling,null!==e&&(f=b.firstEffect,null!==f?(b.firstEffect=e,e.nextEffect=f):(b.firstEffect=b.lastEffect=e,e.nextEffect=null),e.effectTag=8)));if(c&&!d&&0!==(b.mode&2))if(null===a&&!0!==b.memoizedProps.unstable_avoidThisFallback||
+0!==(D.current&1))F===Xa&&(F=ad);else{if(F===Xa||F===ad)F=bd;0!==Xb&&null!==U&&(Ya(U,P),yh(U,Xb))}if(c||d)b.effectTag|=4;return null;case 4:return tb(),wh(b),null;case 10:return me(b),null;case 17:return N(b.type)&&(q(G),q(B)),null;case 19:q(D);d=b.memoizedState;if(null===d)return null;e=0!==(b.effectTag&64);f=d.rendering;if(null===f)if(e)$c(d,!1);else{if(F!==Xa||null!==a&&0!==(a.effectTag&64))for(f=b.child;null!==f;){a=Rc(f);if(null!==a){b.effectTag|=64;$c(d,!1);e=a.updateQueue;null!==e&&(b.updateQueue=
+e,b.effectTag|=4);null===d.lastEffect&&(b.firstEffect=null);b.lastEffect=d.lastEffect;for(d=b.child;null!==d;)e=d,f=c,e.effectTag&=2,e.nextEffect=null,e.firstEffect=null,e.lastEffect=null,a=e.alternate,null===a?(e.childExpirationTime=0,e.expirationTime=f,e.child=null,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null):(e.childExpirationTime=a.childExpirationTime,e.expirationTime=a.expirationTime,e.child=a.child,e.memoizedProps=a.memoizedProps,e.memoizedState=a.memoizedState,
+e.updateQueue=a.updateQueue,f=a.dependencies,e.dependencies=null===f?null:{expirationTime:f.expirationTime,firstContext:f.firstContext,responders:f.responders}),d=d.sibling;y(D,D.current&1|2);return b.child}f=f.sibling}}else{if(!e)if(a=Rc(f),null!==a){if(b.effectTag|=64,e=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.effectTag|=4),$c(d,!0),null===d.tail&&"hidden"===d.tailMode&&!f.alternate)return b=b.lastEffect=d.lastEffect,null!==b&&(b.nextEffect=null),null}else 2*Y()-d.renderingStartTime>d.tailExpiration&&
+1<c&&(b.effectTag|=64,e=!0,$c(d,!1),b.expirationTime=b.childExpirationTime=c-1);d.isBackwards?(f.sibling=b.child,b.child=f):(c=d.last,null!==c?c.sibling=f:b.child=f,d.last=f)}return null!==d.tail?(0===d.tailExpiration&&(d.tailExpiration=Y()+500),c=d.tail,d.rendering=c,d.tail=c.sibling,d.lastEffect=b.lastEffect,d.renderingStartTime=Y(),c.sibling=null,b=D.current,y(D,e?b&1|2:b&1),c):null}throw Error(k(156,b.tag));}function lj(a,b){switch(a.tag){case 1:return N(a.type)&&(q(G),q(B)),b=a.effectTag,b&4096?
+(a.effectTag=b&-4097|64,a):null;case 3:tb();q(G);q(B);b=a.effectTag;if(0!==(b&64))throw Error(k(285));a.effectTag=b&-4097|64;return a;case 5:return te(a),null;case 13:return q(D),b=a.effectTag,b&4096?(a.effectTag=b&-4097|64,a):null;case 19:return q(D),null;case 4:return tb(),null;case 10:return me(a),null;default:return null}}function Le(a,b){return{value:a,source:b,stack:Bd(b)}}function Me(a,b){var c=b.source,d=b.stack;null===d&&null!==c&&(d=Bd(c));null!==c&&na(c.type);b=b.value;null!==a&&1===a.tag&&
+na(a.type);try{console.error(b)}catch(e){setTimeout(function(){throw e;})}}function mj(a,b){try{b.props=a.memoizedProps,b.state=a.memoizedState,b.componentWillUnmount()}catch(c){Za(a,c)}}function zh(a){var b=a.ref;if(null!==b)if("function"===typeof b)try{b(null)}catch(c){Za(a,c)}else b.current=null}function nj(a,b){switch(b.tag){case 0:case 11:case 15:case 22:return;case 1:if(b.effectTag&256&&null!==a){var c=a.memoizedProps,d=a.memoizedState;a=b.stateNode;b=a.getSnapshotBeforeUpdate(b.elementType===
+b.type?c:aa(b.type,c),d);a.__reactInternalSnapshotBeforeUpdate=b}return;case 3:case 5:case 6:case 4:case 17:return}throw Error(k(163));}function Ah(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.destroy;c.destroy=void 0;void 0!==d&&d()}c=c.next}while(c!==b)}}function Bh(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function oj(a,b,c,d){switch(c.tag){case 0:case 11:case 15:case 22:Bh(3,
+c);return;case 1:a=c.stateNode;c.effectTag&4&&(null===b?a.componentDidMount():(d=c.elementType===c.type?b.memoizedProps:aa(c.type,b.memoizedProps),a.componentDidUpdate(d,b.memoizedState,a.__reactInternalSnapshotBeforeUpdate)));b=c.updateQueue;null!==b&&Wg(c,b,a);return;case 3:b=c.updateQueue;if(null!==b){a=null;if(null!==c.child)switch(c.child.tag){case 5:a=c.child.stateNode;break;case 1:a=c.child.stateNode}Wg(c,b,a)}return;case 5:a=c.stateNode;null===b&&c.effectTag&4&&lg(c.type,c.memoizedProps)&&
+a.focus();return;case 6:return;case 4:return;case 12:return;case 13:null===c.memoizedState&&(c=c.alternate,null!==c&&(c=c.memoizedState,null!==c&&(c=c.dehydrated,null!==c&&bg(c))));return;case 19:case 17:case 20:case 21:return}throw Error(k(163));}function Ch(a,b,c){"function"===typeof Ne&&Ne(b);switch(b.tag){case 0:case 11:case 14:case 15:case 22:a=b.updateQueue;if(null!==a&&(a=a.lastEffect,null!==a)){var d=a.next;Da(97<c?97:c,function(){var a=d;do{var c=a.destroy;if(void 0!==c){var g=b;try{c()}catch(h){Za(g,
+h)}}a=a.next}while(a!==d)})}break;case 1:zh(b);c=b.stateNode;"function"===typeof c.componentWillUnmount&&mj(b,c);break;case 5:zh(b);break;case 4:Dh(a,b,c)}}function Eh(a){var b=a.alternate;a.return=null;a.child=null;a.memoizedState=null;a.updateQueue=null;a.dependencies=null;a.alternate=null;a.firstEffect=null;a.lastEffect=null;a.pendingProps=null;a.memoizedProps=null;a.stateNode=null;null!==b&&Eh(b)}function Fh(a){return 5===a.tag||3===a.tag||4===a.tag}function Gh(a){a:{for(var b=a.return;null!==
+b;){if(Fh(b)){var c=b;break a}b=b.return}throw Error(k(160));}b=c.stateNode;switch(c.tag){case 5:var d=!1;break;case 3:b=b.containerInfo;d=!0;break;case 4:b=b.containerInfo;d=!0;break;default:throw Error(k(161));}c.effectTag&16&&(Wb(b,""),c.effectTag&=-17);a:b:for(c=a;;){for(;null===c.sibling;){if(null===c.return||Fh(c.return)){c=null;break a}c=c.return}c.sibling.return=c.return;for(c=c.sibling;5!==c.tag&&6!==c.tag&&18!==c.tag;){if(c.effectTag&2)continue b;if(null===c.child||4===c.tag)continue b;
+else c.child.return=c,c=c.child}if(!(c.effectTag&2)){c=c.stateNode;break a}}d?Oe(a,c,b):Pe(a,c,b)}function Oe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=uc));else if(4!==d&&(a=a.child,null!==a))for(Oe(a,b,c),a=a.sibling;null!==a;)Oe(a,b,c),a=a.sibling}
+function Pe(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?c.insertBefore(a,b):c.appendChild(a);else if(4!==d&&(a=a.child,null!==a))for(Pe(a,b,c),a=a.sibling;null!==a;)Pe(a,b,c),a=a.sibling}function Dh(a,b,c){for(var d=b,e=!1,f,g;;){if(!e){e=d.return;a:for(;;){if(null===e)throw Error(k(160));f=e.stateNode;switch(e.tag){case 5:g=!1;break a;case 3:f=f.containerInfo;g=!0;break a;case 4:f=f.containerInfo;g=!0;break a}e=e.return}e=!0}if(5===d.tag||6===d.tag){a:for(var h=
+a,m=d,n=c,l=m;;)if(Ch(h,l,n),null!==l.child&&4!==l.tag)l.child.return=l,l=l.child;else{if(l===m)break a;for(;null===l.sibling;){if(null===l.return||l.return===m)break a;l=l.return}l.sibling.return=l.return;l=l.sibling}g?(h=f,m=d.stateNode,8===h.nodeType?h.parentNode.removeChild(m):h.removeChild(m)):f.removeChild(d.stateNode)}else if(4===d.tag){if(null!==d.child){f=d.stateNode.containerInfo;g=!0;d.child.return=d;d=d.child;continue}}else if(Ch(a,d,c),null!==d.child){d.child.return=d;d=d.child;continue}if(d===
+b)break;for(;null===d.sibling;){if(null===d.return||d.return===b)return;d=d.return;4===d.tag&&(e=!1)}d.sibling.return=d.return;d=d.sibling}}function Qe(a,b){switch(b.tag){case 0:case 11:case 14:case 15:case 22:Ah(3,b);return;case 1:return;case 5:var c=b.stateNode;if(null!=c){var d=b.memoizedProps,e=null!==a?a.memoizedProps:d;a=b.type;var f=b.updateQueue;b.updateQueue=null;if(null!==f){c[vc]=d;"input"===a&&"radio"===d.type&&null!=d.name&&If(c,d);Vd(a,e);b=Vd(a,d);for(e=0;e<f.length;e+=2){var g=f[e],
+h=f[e+1];"style"===g?gg(c,h):"dangerouslySetInnerHTML"===g?xh(c,h):"children"===g?Wb(c,h):xd(c,g,h,b)}switch(a){case "input":Dd(c,d);break;case "textarea":Lf(c,d);break;case "select":b=c._wrapperState.wasMultiple,c._wrapperState.wasMultiple=!!d.multiple,a=d.value,null!=a?hb(c,!!d.multiple,a,!1):b!==!!d.multiple&&(null!=d.defaultValue?hb(c,!!d.multiple,d.defaultValue,!0):hb(c,!!d.multiple,d.multiple?[]:"",!1))}}}return;case 6:if(null===b.stateNode)throw Error(k(162));b.stateNode.nodeValue=b.memoizedProps;
+return;case 3:b=b.stateNode;b.hydrate&&(b.hydrate=!1,bg(b.containerInfo));return;case 12:return;case 13:c=b;null===b.memoizedState?d=!1:(d=!0,c=b.child,Re=Y());if(null!==c)a:for(a=c;;){if(5===a.tag)f=a.stateNode,d?(f=f.style,"function"===typeof f.setProperty?f.setProperty("display","none","important"):f.display="none"):(f=a.stateNode,e=a.memoizedProps.style,e=void 0!==e&&null!==e&&e.hasOwnProperty("display")?e.display:null,f.style.display=fg("display",e));else if(6===a.tag)a.stateNode.nodeValue=d?
+"":a.memoizedProps;else if(13===a.tag&&null!==a.memoizedState&&null===a.memoizedState.dehydrated){f=a.child.sibling;f.return=a;a=f;continue}else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===c)break;for(;null===a.sibling;){if(null===a.return||a.return===c)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}Hh(b);return;case 19:Hh(b);return;case 17:return}throw Error(k(163));}function Hh(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=
+new pj);b.forEach(function(b){var d=qj.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Ih(a,b,c){c=Ea(c,null);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){cd||(cd=!0,Se=d);Me(a,b)};return c}function Jh(a,b,c){c=Ea(c,null);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){Me(a,b);return d(e)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){"function"!==typeof d&&
+(null===La?La=new Set([this]):La.add(this),Me(a,b));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ka(){return(p&(ca|ma))!==H?1073741821-(Y()/10|0):0!==dd?dd:dd=1073741821-(Y()/10|0)}function Va(a,b,c){b=b.mode;if(0===(b&2))return 1073741823;var d=Cc();if(0===(b&4))return 99===d?1073741823:1073741822;if((p&ca)!==H)return P;if(null!==c)a=Fc(a,c.timeoutMs|0||5E3,250);else switch(d){case 99:a=1073741823;break;case 98:a=Fc(a,150,100);break;case 97:case 96:a=
+Fc(a,5E3,250);break;case 95:a=2;break;default:throw Error(k(326));}null!==U&&a===P&&--a;return a}function ed(a,b){a.expirationTime<b&&(a.expirationTime=b);var c=a.alternate;null!==c&&c.expirationTime<b&&(c.expirationTime=b);var d=a.return,e=null;if(null===d&&3===a.tag)e=a.stateNode;else for(;null!==d;){c=d.alternate;d.childExpirationTime<b&&(d.childExpirationTime=b);null!==c&&c.childExpirationTime<b&&(c.childExpirationTime=b);if(null===d.return&&3===d.tag){e=d.stateNode;break}d=d.return}null!==e&&
+(U===e&&(Kc(b),F===bd&&Ya(e,P)),yh(e,b));return e}function fd(a){var b=a.lastExpiredTime;if(0!==b)return b;b=a.firstPendingTime;if(!Kh(a,b))return b;var c=a.lastPingedTime;a=a.nextKnownPendingLevel;a=c>a?c:a;return 2>=a&&b!==a?0:a}function V(a){if(0!==a.lastExpiredTime)a.callbackExpirationTime=1073741823,a.callbackPriority=99,a.callbackNode=Og(Te.bind(null,a));else{var b=fd(a),c=a.callbackNode;if(0===b)null!==c&&(a.callbackNode=null,a.callbackExpirationTime=0,a.callbackPriority=90);else{var d=ka();
+1073741823===b?d=99:1===b||2===b?d=95:(d=10*(1073741821-b)-10*(1073741821-d),d=0>=d?99:250>=d?98:5250>=d?97:95);if(null!==c){var e=a.callbackPriority;if(a.callbackExpirationTime===b&&e>=d)return;c!==Qg&&Rg(c)}a.callbackExpirationTime=b;a.callbackPriority=d;b=1073741823===b?Og(Te.bind(null,a)):Ng(d,Lh.bind(null,a),{timeout:10*(1073741821-b)-Y()});a.callbackNode=b}}}function Lh(a,b){dd=0;if(b)return b=ka(),Ue(a,b),V(a),null;var c=fd(a);if(0!==c){b=a.callbackNode;if((p&(ca|ma))!==H)throw Error(k(327));
+xb();a===U&&c===P||$a(a,c);if(null!==t){var d=p;p|=ca;var e=Mh();do try{rj();break}catch(h){Nh(a,h)}while(1);le();p=d;gd.current=e;if(F===hd)throw b=id,$a(a,c),Ya(a,c),V(a),b;if(null===t)switch(e=a.finishedWork=a.current.alternate,a.finishedExpirationTime=c,d=F,U=null,d){case Xa:case hd:throw Error(k(345));case Oh:Ue(a,2<c?2:c);break;case ad:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(1073741823===ta&&(e=Re+Ph-Y(),10<e)){if(jd){var f=a.lastPingedTime;if(0===f||f>=c){a.lastPingedTime=
+c;$a(a,c);break}}f=fd(a);if(0!==f&&f!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}a.timeoutHandle=We(ab.bind(null,a),e);break}ab(a);break;case bd:Ya(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ve(e));if(jd&&(e=a.lastPingedTime,0===e||e>=c)){a.lastPingedTime=c;$a(a,c);break}e=fd(a);if(0!==e&&e!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}1073741823!==Yb?d=10*(1073741821-Yb)-Y():1073741823===ta?d=0:(d=10*(1073741821-ta)-5E3,e=Y(),c=10*(1073741821-c)-e,d=e-d,0>d&&(d=0),d=
+(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*sj(d/1960))-d,c<d&&(d=c));if(10<d){a.timeoutHandle=We(ab.bind(null,a),d);break}ab(a);break;case Xe:if(1073741823!==ta&&null!==kd){f=ta;var g=kd;d=g.busyMinDurationMs|0;0>=d?d=0:(e=g.busyDelayMs|0,f=Y()-(10*(1073741821-f)-(g.timeoutMs|0||5E3)),d=f<=e?0:e+d-f);if(10<d){Ya(a,c);a.timeoutHandle=We(ab.bind(null,a),d);break}}ab(a);break;default:throw Error(k(329));}V(a);if(a.callbackNode===b)return Lh.bind(null,a)}}return null}function Te(a){var b=
+a.lastExpiredTime;b=0!==b?b:1073741823;if((p&(ca|ma))!==H)throw Error(k(327));xb();a===U&&b===P||$a(a,b);if(null!==t){var c=p;p|=ca;var d=Mh();do try{tj();break}catch(e){Nh(a,e)}while(1);le();p=c;gd.current=d;if(F===hd)throw c=id,$a(a,b),Ya(a,b),V(a),c;if(null!==t)throw Error(k(261));a.finishedWork=a.current.alternate;a.finishedExpirationTime=b;U=null;ab(a);V(a)}return null}function uj(){if(null!==bb){var a=bb;bb=null;a.forEach(function(a,c){Ue(c,a);V(c)});ha()}}function Qh(a,b){var c=p;p|=1;try{return a(b)}finally{p=
+c,p===H&&ha()}}function Rh(a,b){var c=p;p&=-2;p|=Ye;try{return a(b)}finally{p=c,p===H&&ha()}}function $a(a,b){a.finishedWork=null;a.finishedExpirationTime=0;var c=a.timeoutHandle;-1!==c&&(a.timeoutHandle=-1,vj(c));if(null!==t)for(c=t.return;null!==c;){var d=c;switch(d.tag){case 1:d=d.type.childContextTypes;null!==d&&void 0!==d&&(q(G),q(B));break;case 3:tb();q(G);q(B);break;case 5:te(d);break;case 4:tb();break;case 13:q(D);break;case 19:q(D);break;case 10:me(d)}c=c.return}U=a;t=Sa(a.current,null);
+P=b;F=Xa;id=null;Yb=ta=1073741823;kd=null;Xb=0;jd=!1}function Nh(a,b){do{try{le();Sc.current=Tc;if(Uc)for(var c=z.memoizedState;null!==c;){var d=c.queue;null!==d&&(d.pending=null);c=c.next}Ia=0;J=K=z=null;Uc=!1;if(null===t||null===t.return)return F=hd,id=b,t=null;a:{var e=a,f=t.return,g=t,h=b;b=P;g.effectTag|=2048;g.firstEffect=g.lastEffect=null;if(null!==h&&"object"===typeof h&&"function"===typeof h.then){var m=h;if(0===(g.mode&2)){var n=g.alternate;n?(g.updateQueue=n.updateQueue,g.memoizedState=
+n.memoizedState,g.expirationTime=n.expirationTime):(g.updateQueue=null,g.memoizedState=null)}var l=0!==(D.current&1),k=f;do{var p;if(p=13===k.tag){var q=k.memoizedState;if(null!==q)p=null!==q.dehydrated?!0:!1;else{var w=k.memoizedProps;p=void 0===w.fallback?!1:!0!==w.unstable_avoidThisFallback?!0:l?!1:!0}}if(p){var y=k.updateQueue;if(null===y){var r=new Set;r.add(m);k.updateQueue=r}else y.add(m);if(0===(k.mode&2)){k.effectTag|=64;g.effectTag&=-2981;if(1===g.tag)if(null===g.alternate)g.tag=17;else{var O=
+Ea(1073741823,null);O.tag=Jc;Fa(g,O)}g.expirationTime=1073741823;break a}h=void 0;g=b;var v=e.pingCache;null===v?(v=e.pingCache=new wj,h=new Set,v.set(m,h)):(h=v.get(m),void 0===h&&(h=new Set,v.set(m,h)));if(!h.has(g)){h.add(g);var x=xj.bind(null,e,m,g);m.then(x,x)}k.effectTag|=4096;k.expirationTime=b;break a}k=k.return}while(null!==k);h=Error((na(g.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."+
+Bd(g))}F!==Xe&&(F=Oh);h=Le(h,g);k=f;do{switch(k.tag){case 3:m=h;k.effectTag|=4096;k.expirationTime=b;var A=Ih(k,m,b);Ug(k,A);break a;case 1:m=h;var u=k.type,B=k.stateNode;if(0===(k.effectTag&64)&&("function"===typeof u.getDerivedStateFromError||null!==B&&"function"===typeof B.componentDidCatch&&(null===La||!La.has(B)))){k.effectTag|=4096;k.expirationTime=b;var H=Jh(k,m,b);Ug(k,H);break a}}k=k.return}while(null!==k)}t=Sh(t)}catch(cj){b=cj;continue}break}while(1)}function Mh(a){a=gd.current;gd.current=
+Tc;return null===a?Tc:a}function Vg(a,b){a<ta&&2<a&&(ta=a);null!==b&&a<Yb&&2<a&&(Yb=a,kd=b)}function Kc(a){a>Xb&&(Xb=a)}function tj(){for(;null!==t;)t=Th(t)}function rj(){for(;null!==t&&!yj();)t=Th(t)}function Th(a){var b=zj(a.alternate,a,P);a.memoizedProps=a.pendingProps;null===b&&(b=Sh(a));Uh.current=null;return b}function Sh(a){t=a;do{var b=t.alternate;a=t.return;if(0===(t.effectTag&2048)){b=hj(b,t,P);if(1===P||1!==t.childExpirationTime){for(var c=0,d=t.child;null!==d;){var e=d.expirationTime,
+f=d.childExpirationTime;e>c&&(c=e);f>c&&(c=f);d=d.sibling}t.childExpirationTime=c}if(null!==b)return b;null!==a&&0===(a.effectTag&2048)&&(null===a.firstEffect&&(a.firstEffect=t.firstEffect),null!==t.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=t.firstEffect),a.lastEffect=t.lastEffect),1<t.effectTag&&(null!==a.lastEffect?a.lastEffect.nextEffect=t:a.firstEffect=t,a.lastEffect=t))}else{b=lj(t);if(null!==b)return b.effectTag&=2047,b;null!==a&&(a.firstEffect=a.lastEffect=null,a.effectTag|=
+2048)}b=t.sibling;if(null!==b)return b;t=a}while(null!==t);F===Xa&&(F=Xe);return null}function Ve(a){var b=a.expirationTime;a=a.childExpirationTime;return b>a?b:a}function ab(a){var b=Cc();Da(99,Aj.bind(null,a,b));return null}function Aj(a,b){do xb();while(null!==Zb);if((p&(ca|ma))!==H)throw Error(k(327));var c=a.finishedWork,d=a.finishedExpirationTime;if(null===c)return null;a.finishedWork=null;a.finishedExpirationTime=0;if(c===a.current)throw Error(k(177));a.callbackNode=null;a.callbackExpirationTime=
+0;a.callbackPriority=90;a.nextKnownPendingLevel=0;var e=Ve(c);a.firstPendingTime=e;d<=a.lastSuspendedTime?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:d<=a.firstSuspendedTime&&(a.firstSuspendedTime=d-1);d<=a.lastPingedTime&&(a.lastPingedTime=0);d<=a.lastExpiredTime&&(a.lastExpiredTime=0);a===U&&(t=U=null,P=0);1<c.effectTag?null!==c.lastEffect?(c.lastEffect.nextEffect=c,e=c.firstEffect):e=c:e=c.firstEffect;if(null!==e){var f=p;p|=ma;Uh.current=null;Ze=tc;var g=kg();if(Xd(g)){if("selectionStart"in
+g)var h={start:g.selectionStart,end:g.selectionEnd};else a:{h=(h=g.ownerDocument)&&h.defaultView||window;var m=h.getSelection&&h.getSelection();if(m&&0!==m.rangeCount){h=m.anchorNode;var n=m.anchorOffset,q=m.focusNode;m=m.focusOffset;try{h.nodeType,q.nodeType}catch(sb){h=null;break a}var ba=0,w=-1,y=-1,B=0,D=0,r=g,z=null;b:for(;;){for(var v;;){r!==h||0!==n&&3!==r.nodeType||(w=ba+n);r!==q||0!==m&&3!==r.nodeType||(y=ba+m);3===r.nodeType&&(ba+=r.nodeValue.length);if(null===(v=r.firstChild))break;z=r;
+r=v}for(;;){if(r===g)break b;z===h&&++B===n&&(w=ba);z===q&&++D===m&&(y=ba);if(null!==(v=r.nextSibling))break;r=z;z=r.parentNode}r=v}h=-1===w||-1===y?null:{start:w,end:y}}else h=null}h=h||{start:0,end:0}}else h=null;$e={activeElementDetached:null,focusedElem:g,selectionRange:h};tc=!1;l=e;do try{Bj()}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=e;do try{for(g=a,h=b;null!==l;){var x=l.effectTag;x&16&&Wb(l.stateNode,"");if(x&128){var A=l.alternate;if(null!==A){var u=
+A.ref;null!==u&&("function"===typeof u?u(null):u.current=null)}}switch(x&1038){case 2:Gh(l);l.effectTag&=-3;break;case 6:Gh(l);l.effectTag&=-3;Qe(l.alternate,l);break;case 1024:l.effectTag&=-1025;break;case 1028:l.effectTag&=-1025;Qe(l.alternate,l);break;case 4:Qe(l.alternate,l);break;case 8:n=l,Dh(g,n,h),Eh(n)}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);u=$e;A=kg();x=u.focusedElem;h=u.selectionRange;if(A!==x&&x&&x.ownerDocument&&jg(x.ownerDocument.documentElement,
+x)){null!==h&&Xd(x)&&(A=h.start,u=h.end,void 0===u&&(u=A),"selectionStart"in x?(x.selectionStart=A,x.selectionEnd=Math.min(u,x.value.length)):(u=(A=x.ownerDocument||document)&&A.defaultView||window,u.getSelection&&(u=u.getSelection(),n=x.textContent.length,g=Math.min(h.start,n),h=void 0===h.end?g:Math.min(h.end,n),!u.extend&&g>h&&(n=h,h=g,g=n),n=ig(x,g),q=ig(x,h),n&&q&&(1!==u.rangeCount||u.anchorNode!==n.node||u.anchorOffset!==n.offset||u.focusNode!==q.node||u.focusOffset!==q.offset)&&(A=A.createRange(),
+A.setStart(n.node,n.offset),u.removeAllRanges(),g>h?(u.addRange(A),u.extend(q.node,q.offset)):(A.setEnd(q.node,q.offset),u.addRange(A))))));A=[];for(u=x;u=u.parentNode;)1===u.nodeType&&A.push({element:u,left:u.scrollLeft,top:u.scrollTop});"function"===typeof x.focus&&x.focus();for(x=0;x<A.length;x++)u=A[x],u.element.scrollLeft=u.left,u.element.scrollTop=u.top}tc=!!Ze;$e=Ze=null;a.current=c;l=e;do try{for(x=a;null!==l;){var F=l.effectTag;F&36&&oj(x,l.alternate,l);if(F&128){A=void 0;var E=l.ref;if(null!==
+E){var G=l.stateNode;switch(l.tag){case 5:A=G;break;default:A=G}"function"===typeof E?E(A):E.current=A}}l=l.nextEffect}}catch(sb){if(null===l)throw Error(k(330));Za(l,sb);l=l.nextEffect}while(null!==l);l=null;Cj();p=f}else a.current=c;if(ld)ld=!1,Zb=a,$b=b;else for(l=e;null!==l;)b=l.nextEffect,l.nextEffect=null,l=b;b=a.firstPendingTime;0===b&&(La=null);1073741823===b?a===af?ac++:(ac=0,af=a):ac=0;"function"===typeof bf&&bf(c.stateNode,d);V(a);if(cd)throw cd=!1,a=Se,Se=null,a;if((p&Ye)!==H)return null;
+ha();return null}function Bj(){for(;null!==l;){var a=l.effectTag;0!==(a&256)&&nj(l.alternate,l);0===(a&512)||ld||(ld=!0,Ng(97,function(){xb();return null}));l=l.nextEffect}}function xb(){if(90!==$b){var a=97<$b?97:$b;$b=90;return Da(a,Dj)}}function Dj(){if(null===Zb)return!1;var a=Zb;Zb=null;if((p&(ca|ma))!==H)throw Error(k(331));var b=p;p|=ma;for(a=a.current.firstEffect;null!==a;){try{var c=a;if(0!==(c.effectTag&512))switch(c.tag){case 0:case 11:case 15:case 22:Ah(5,c),Bh(5,c)}}catch(d){if(null===
+a)throw Error(k(330));Za(a,d)}c=a.nextEffect;a.nextEffect=null;a=c}p=b;ha();return!0}function Vh(a,b,c){b=Le(c,b);b=Ih(a,b,1073741823);Fa(a,b);a=ed(a,1073741823);null!==a&&V(a)}function Za(a,b){if(3===a.tag)Vh(a,a,b);else for(var c=a.return;null!==c;){if(3===c.tag){Vh(c,a,b);break}else if(1===c.tag){var d=c.stateNode;if("function"===typeof c.type.getDerivedStateFromError||"function"===typeof d.componentDidCatch&&(null===La||!La.has(d))){a=Le(b,a);a=Jh(c,a,1073741823);Fa(c,a);c=ed(c,1073741823);null!==
+c&&V(c);break}}c=c.return}}function xj(a,b,c){var d=a.pingCache;null!==d&&d.delete(b);U===a&&P===c?F===bd||F===ad&&1073741823===ta&&Y()-Re<Ph?$a(a,P):jd=!0:Kh(a,c)&&(b=a.lastPingedTime,0!==b&&b<c||(a.lastPingedTime=c,V(a)))}function qj(a,b){var c=a.stateNode;null!==c&&c.delete(b);b=0;0===b&&(b=ka(),b=Va(b,a,null));a=ed(a,b);null!==a&&V(a)}function Ej(a){if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)return!0;try{var c=
+b.inject(a);bf=function(a,e){try{b.onCommitFiberRoot(c,a,void 0,64===(a.current.effectTag&64))}catch(f){}};Ne=function(a){try{b.onCommitFiberUnmount(c,a)}catch(e){}}}catch(d){}return!0}function Fj(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.effectTag=0;this.lastEffect=this.firstEffect=this.nextEffect=
+null;this.childExpirationTime=this.expirationTime=0;this.alternate=null}function Ge(a){a=a.prototype;return!(!a||!a.isReactComponent)}function Gj(a){if("function"===typeof a)return Ge(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===zd)return 11;if(a===Ad)return 14}return 2}function Sa(a,b){var c=a.alternate;null===c?(c=la(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.effectTag=0,c.nextEffect=null,c.firstEffect=
+null,c.lastEffect=null);c.childExpirationTime=a.childExpirationTime;c.expirationTime=a.expirationTime;c.child=a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{expirationTime:b.expirationTime,firstContext:b.firstContext,responders:b.responders};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function Oc(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)Ge(a)&&(g=1);else if("string"===typeof a)g=
+5;else a:switch(a){case Ma:return Ha(c.children,e,f,b);case Hj:g=8;e|=7;break;case Af:g=8;e|=1;break;case kc:return a=la(12,c,b,e|8),a.elementType=kc,a.type=kc,a.expirationTime=f,a;case lc:return a=la(13,c,b,e),a.type=lc,a.elementType=lc,a.expirationTime=f,a;case yd:return a=la(19,c,b,e),a.elementType=yd,a.expirationTime=f,a;default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case Cf:g=10;break a;case Bf:g=9;break a;case zd:g=11;break a;case Ad:g=14;break a;case Ef:g=16;d=null;break a;case Df:g=
+22;break a}throw Error(k(130,null==a?a:typeof a,""));}b=la(g,c,b,e);b.elementType=a;b.type=d;b.expirationTime=f;return b}function Ha(a,b,c,d){a=la(7,a,d,b);a.expirationTime=c;return a}function qe(a,b,c){a=la(6,a,null,b);a.expirationTime=c;return a}function re(a,b,c){b=la(4,null!==a.children?a.children:[],a.key,b);b.expirationTime=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Ij(a,b,c){this.tag=b;this.current=null;this.containerInfo=
+a;this.pingCache=this.pendingChildren=null;this.finishedExpirationTime=0;this.finishedWork=null;this.timeoutHandle=-1;this.pendingContext=this.context=null;this.hydrate=c;this.callbackNode=null;this.callbackPriority=90;this.lastExpiredTime=this.lastPingedTime=this.nextKnownPendingLevel=this.lastSuspendedTime=this.firstSuspendedTime=this.firstPendingTime=0}function Kh(a,b){var c=a.firstSuspendedTime;a=a.lastSuspendedTime;return 0!==c&&c>=b&&a<=b}function Ya(a,b){var c=a.firstSuspendedTime,d=a.lastSuspendedTime;
+c<b&&(a.firstSuspendedTime=b);if(d>b||0===c)a.lastSuspendedTime=b;b<=a.lastPingedTime&&(a.lastPingedTime=0);b<=a.lastExpiredTime&&(a.lastExpiredTime=0)}function yh(a,b){b>a.firstPendingTime&&(a.firstPendingTime=b);var c=a.firstSuspendedTime;0!==c&&(b>=c?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:b>=a.lastSuspendedTime&&(a.lastSuspendedTime=b+1),b>a.nextKnownPendingLevel&&(a.nextKnownPendingLevel=b))}function Ue(a,b){var c=a.lastExpiredTime;if(0===c||c>b)a.lastExpiredTime=b}
+function md(a,b,c,d){var e=b.current,f=ka(),g=Vb.suspense;f=Va(f,e,g);a:if(c){c=c._reactInternalFiber;b:{if(Na(c)!==c||1!==c.tag)throw Error(k(170));var h=c;do{switch(h.tag){case 3:h=h.stateNode.context;break b;case 1:if(N(h.type)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}}h=h.return}while(null!==h);throw Error(k(171));}if(1===c.tag){var m=c.type;if(N(m)){c=Gg(c,m,h);break a}}c=h}else c=Ca;null===b.context?b.context=c:b.pendingContext=c;b=Ea(f,g);b.payload={element:a};d=void 0===
+d?null:d;null!==d&&(b.callback=d);Fa(e,b);Ja(e,f);return f}function cf(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function Wh(a,b){a=a.memoizedState;null!==a&&null!==a.dehydrated&&a.retryTime<b&&(a.retryTime=b)}function df(a,b){Wh(a,b);(a=a.alternate)&&Wh(a,b)}function ef(a,b,c){c=null!=c&&!0===c.hydrate;var d=new Ij(a,b,c),e=la(3,null,null,2===b?7:1===b?3:0);d.current=e;e.stateNode=d;ne(e);a[Lb]=d.current;c&&0!==b&&
+xi(a,9===a.nodeType?a:a.ownerDocument);this._internalRoot=d}function bc(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}function Jj(a,b){b||(b=a?9===a.nodeType?a.documentElement:a.firstChild:null,b=!(!b||1!==b.nodeType||!b.hasAttribute("data-reactroot")));if(!b)for(var c;c=a.lastChild;)a.removeChild(c);return new ef(a,0,b?{hydrate:!0}:void 0)}function nd(a,b,c,d,e){var f=c._reactRootContainer;if(f){var g=f._internalRoot;
+if("function"===typeof e){var h=e;e=function(){var a=cf(g);h.call(a)}}md(b,g,a,e)}else{f=c._reactRootContainer=Jj(c,d);g=f._internalRoot;if("function"===typeof e){var m=e;e=function(){var a=cf(g);m.call(a)}}Rh(function(){md(b,g,a,e)})}return cf(g)}function Kj(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:gb,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}function Xh(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;
+if(!bc(b))throw Error(k(200));return Kj(a,b,null,c)}if(!ea)throw Error(k(227));var ki=function(a,b,c,d,e,f,g,h,m){var n=Array.prototype.slice.call(arguments,3);try{b.apply(c,n)}catch(C){this.onError(C)}},yb=!1,gc=null,hc=!1,pd=null,li={onError:function(a){yb=!0;gc=a}},td=null,rf=null,mf=null,ic=null,cb={},jc=[],qd={},db={},rd={},wa=!("undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement),M=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.assign,
+sd=null,eb=null,fb=null,ee=function(a,b){return a(b)},eg=function(a,b,c,d,e){return a(b,c,d,e)},vd=function(){},vf=ee,Oa=!1,wd=!1,Z=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Lj=Z.unstable_cancelCallback,ff=Z.unstable_now,$f=Z.unstable_scheduleCallback,Mj=Z.unstable_shouldYield,Yh=Z.unstable_requestPaint,Pd=Z.unstable_runWithPriority,Nj=Z.unstable_getCurrentPriorityLevel,Oj=Z.unstable_ImmediatePriority,Zh=Z.unstable_UserBlockingPriority,ag=Z.unstable_NormalPriority,Pj=Z.unstable_LowPriority,
+Qj=Z.unstable_IdlePriority,oi=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,wf=Object.prototype.hasOwnProperty,yf={},xf={},E={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){E[a]=
+new L(a,0,!1,a,null,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];E[b]=new L(b,1,!1,a[1],null,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){E[a]=new L(a,2,!1,a.toLowerCase(),null,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){E[a]=new L(a,2,!1,a,null,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){E[a]=
+new L(a,3,!1,a.toLowerCase(),null,!1)});["checked","multiple","muted","selected"].forEach(function(a){E[a]=new L(a,3,!0,a,null,!1)});["capture","download"].forEach(function(a){E[a]=new L(a,4,!1,a,null,!1)});["cols","rows","size","span"].forEach(function(a){E[a]=new L(a,6,!1,a,null,!1)});["rowSpan","start"].forEach(function(a){E[a]=new L(a,5,!1,a.toLowerCase(),null,!1)});var gf=/[\-:]([a-z])/g,hf=function(a){return a[1].toUpperCase()};"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=
+a.replace(gf,hf);E[b]=new L(b,1,!1,a,null,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/1999/xlink",!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(gf,hf);E[b]=new L(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1)});["tabIndex","crossOrigin"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!1)});E.xlinkHref=new L("xlinkHref",1,
+!1,"xlink:href","http://www.w3.org/1999/xlink",!0);["src","href","action","formAction"].forEach(function(a){E[a]=new L(a,1,!1,a.toLowerCase(),null,!0)});var da=ea.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;da.hasOwnProperty("ReactCurrentDispatcher")||(da.ReactCurrentDispatcher={current:null});da.hasOwnProperty("ReactCurrentBatchConfig")||(da.ReactCurrentBatchConfig={suspense:null});var si=/^(.*)[\\\/]/,Q="function"===typeof Symbol&&Symbol.for,Pc=Q?Symbol.for("react.element"):60103,gb=Q?Symbol.for("react.portal"):
+60106,Ma=Q?Symbol.for("react.fragment"):60107,Af=Q?Symbol.for("react.strict_mode"):60108,kc=Q?Symbol.for("react.profiler"):60114,Cf=Q?Symbol.for("react.provider"):60109,Bf=Q?Symbol.for("react.context"):60110,Hj=Q?Symbol.for("react.concurrent_mode"):60111,zd=Q?Symbol.for("react.forward_ref"):60112,lc=Q?Symbol.for("react.suspense"):60113,yd=Q?Symbol.for("react.suspense_list"):60120,Ad=Q?Symbol.for("react.memo"):60115,Ef=Q?Symbol.for("react.lazy"):60116,Df=Q?Symbol.for("react.block"):60121,zf="function"===
+typeof Symbol&&Symbol.iterator,od,xh=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if("http://www.w3.org/2000/svg"!==a.namespaceURI||"innerHTML"in a)a.innerHTML=b;else{od=od||document.createElement("div");od.innerHTML="<svg>"+b.valueOf().toString()+"</svg>";for(b=od.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Wb=function(a,
+b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},ib={animationend:nc("Animation","AnimationEnd"),animationiteration:nc("Animation","AnimationIteration"),animationstart:nc("Animation","AnimationStart"),transitionend:nc("Transition","TransitionEnd")},Id={},Of={};wa&&(Of=document.createElement("div").style,"AnimationEvent"in window||(delete ib.animationend.animation,delete ib.animationiteration.animation,delete ib.animationstart.animation),"TransitionEvent"in
+window||delete ib.transitionend.transition);var $h=oc("animationend"),ai=oc("animationiteration"),bi=oc("animationstart"),ci=oc("transitionend"),Db="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Pf=new ("function"===typeof WeakMap?WeakMap:Map),Ab=null,wi=function(a){if(a){var b=a._dispatchListeners,c=a._dispatchInstances;
+if(Array.isArray(b))for(var d=0;d<b.length&&!a.isPropagationStopped();d++)lf(a,b[d],c[d]);else b&&lf(a,b,c);a._dispatchListeners=null;a._dispatchInstances=null;a.isPersistent()||a.constructor.release(a)}},qc=[],Rd=!1,fa=[],xa=null,ya=null,za=null,Eb=new Map,Fb=new Map,Jb=[],Nd="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),
+yi="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" "),dg={},cg=new Map,Td=new Map,Rj=["abort","abort",$h,"animationEnd",ai,"animationIteration",bi,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata",
+"loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",ci,"transitionEnd","waiting","waiting"];Sd("blur blur cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focus focus input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),
+0);Sd("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1);Sd(Rj,2);(function(a,b){for(var c=0;c<a.length;c++)Td.set(a[c],b)})("change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),0);var Hi=Zh,Gi=Pd,tc=!0,Kb={animationIterationCount:!0,
+borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,
+strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Sj=["Webkit","ms","Moz","O"];Object.keys(Kb).forEach(function(a){Sj.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);Kb[b]=Kb[a]})});var Ii=M({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ng="$",og="/$",$d="$?",Zd="$!",Ze=null,$e=null,We="function"===typeof setTimeout?setTimeout:void 0,vj="function"===
+typeof clearTimeout?clearTimeout:void 0,jf=Math.random().toString(36).slice(2),Aa="__reactInternalInstance$"+jf,vc="__reactEventHandlers$"+jf,Lb="__reactContainere$"+jf,Ba=null,ce=null,wc=null;M(R.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=xc)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():"unknown"!==
+typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=xc)},persist:function(){this.isPersistent=xc},isPersistent:yc,destructor:function(){var a=this.constructor.Interface,b;for(b in a)this[b]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null;this.isPropagationStopped=this.isDefaultPrevented=yc;this._dispatchInstances=this._dispatchListeners=null}});R.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(a){return a.timeStamp||
+Date.now()},defaultPrevented:null,isTrusted:null};R.extend=function(a){function b(){return c.apply(this,arguments)}var c=this,d=function(){};d.prototype=c.prototype;d=new d;M(d,b.prototype);b.prototype=d;b.prototype.constructor=b;b.Interface=M({},c.Interface,a);b.extend=c.extend;sg(b);return b};sg(R);var Tj=R.extend({data:null}),Uj=R.extend({data:null}),Ni=[9,13,27,32],de=wa&&"CompositionEvent"in window,cc=null;wa&&"documentMode"in document&&(cc=document.documentMode);var Vj=wa&&"TextEvent"in window&&
+!cc,xg=wa&&(!de||cc&&8<cc&&11>=cc),wg=String.fromCharCode(32),ua={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},
+dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},vg=!1,mb=!1,Wj={eventTypes:ua,extractEvents:function(a,b,c,d,e){var f;if(de)b:{switch(a){case "compositionstart":var g=ua.compositionStart;break b;case "compositionend":g=ua.compositionEnd;break b;case "compositionupdate":g=
+ua.compositionUpdate;break b}g=void 0}else mb?tg(a,c)&&(g=ua.compositionEnd):"keydown"===a&&229===c.keyCode&&(g=ua.compositionStart);g?(xg&&"ko"!==c.locale&&(mb||g!==ua.compositionStart?g===ua.compositionEnd&&mb&&(f=rg()):(Ba=d,ce="value"in Ba?Ba.value:Ba.textContent,mb=!0)),e=Tj.getPooled(g,b,c,d),f?e.data=f:(f=ug(c),null!==f&&(e.data=f)),lb(e),f=e):f=null;(a=Vj?Oi(a,c):Pi(a,c))?(b=Uj.getPooled(ua.beforeInput,b,c,d),b.data=a,lb(b)):b=null;return null===f?b:null===b?f:[f,b]}},Qi={color:!0,date:!0,
+datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},Ag={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"blur change click focus input keydown keyup selectionchange".split(" ")}},Mb=null,Nb=null,kf=!1;wa&&(kf=Tf("input")&&(!document.documentMode||9<document.documentMode));var Xj={eventTypes:Ag,_isInputEventSupported:kf,extractEvents:function(a,b,c,d,e){e=b?Pa(b):window;var f=
+e.nodeName&&e.nodeName.toLowerCase();if("select"===f||"input"===f&&"file"===e.type)var g=Si;else if(yg(e))if(kf)g=Wi;else{g=Ui;var h=Ti}else(f=e.nodeName)&&"input"===f.toLowerCase()&&("checkbox"===e.type||"radio"===e.type)&&(g=Vi);if(g&&(g=g(a,b)))return zg(g,c,d);h&&h(a,e,b);"blur"===a&&(a=e._wrapperState)&&a.controlled&&"number"===e.type&&Ed(e,"number",e.value)}},dc=R.extend({view:null,detail:null}),Yi={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},di=0,ei=0,fi=!1,gi=!1,ec=dc.extend({screenX:null,
+screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:fe,button:null,buttons:null,relatedTarget:function(a){return a.relatedTarget||(a.fromElement===a.srcElement?a.toElement:a.fromElement)},movementX:function(a){if("movementX"in a)return a.movementX;var b=di;di=a.screenX;return fi?"mousemove"===a.type?a.screenX-b:0:(fi=!0,0)},movementY:function(a){if("movementY"in a)return a.movementY;var b=ei;ei=a.screenY;return gi?"mousemove"===
+a.type?a.screenY-b:0:(gi=!0,0)}}),hi=ec.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),fc={mouseEnter:{registrationName:"onMouseEnter",dependencies:["mouseout","mouseover"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["mouseout","mouseover"]},pointerEnter:{registrationName:"onPointerEnter",dependencies:["pointerout","pointerover"]},pointerLeave:{registrationName:"onPointerLeave",dependencies:["pointerout",
+"pointerover"]}},Yj={eventTypes:fc,extractEvents:function(a,b,c,d,e){var f="mouseover"===a||"pointerover"===a,g="mouseout"===a||"pointerout"===a;if(f&&0===(e&32)&&(c.relatedTarget||c.fromElement)||!g&&!f)return null;f=d.window===d?d:(f=d.ownerDocument)?f.defaultView||f.parentWindow:window;if(g){if(g=b,b=(b=c.relatedTarget||c.toElement)?Bb(b):null,null!==b){var h=Na(b);if(b!==h||5!==b.tag&&6!==b.tag)b=null}}else g=null;if(g===b)return null;if("mouseout"===a||"mouseover"===a){var m=ec;var n=fc.mouseLeave;
+var l=fc.mouseEnter;var k="mouse"}else if("pointerout"===a||"pointerover"===a)m=hi,n=fc.pointerLeave,l=fc.pointerEnter,k="pointer";a=null==g?f:Pa(g);f=null==b?f:Pa(b);n=m.getPooled(n,g,c,d);n.type=k+"leave";n.target=a;n.relatedTarget=f;c=m.getPooled(l,b,c,d);c.type=k+"enter";c.target=f;c.relatedTarget=a;d=g;k=b;if(d&&k)a:{m=d;l=k;g=0;for(a=m;a;a=pa(a))g++;a=0;for(b=l;b;b=pa(b))a++;for(;0<g-a;)m=pa(m),g--;for(;0<a-g;)l=pa(l),a--;for(;g--;){if(m===l||m===l.alternate)break a;m=pa(m);l=pa(l)}m=null}else m=
+null;l=m;for(m=[];d&&d!==l;){g=d.alternate;if(null!==g&&g===l)break;m.push(d);d=pa(d)}for(d=[];k&&k!==l;){g=k.alternate;if(null!==g&&g===l)break;d.push(k);k=pa(k)}for(k=0;k<m.length;k++)be(m[k],"bubbled",n);for(k=d.length;0<k--;)be(d[k],"captured",c);return 0===(e&64)?[n]:[n,c]}},Qa="function"===typeof Object.is?Object.is:Zi,$i=Object.prototype.hasOwnProperty,Zj=wa&&"documentMode"in document&&11>=document.documentMode,Eg={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},
+dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},nb=null,he=null,Pb=null,ge=!1,ak={eventTypes:Eg,extractEvents:function(a,b,c,d,e,f){e=f||(d.window===d?d.document:9===d.nodeType?d:d.ownerDocument);if(!(f=!e)){a:{e=Jd(e);f=rd.onSelect;for(var g=0;g<f.length;g++)if(!e.has(f[g])){e=!1;break a}e=!0}f=!e}if(f)return null;e=b?Pa(b):window;switch(a){case "focus":if(yg(e)||"true"===e.contentEditable)nb=e,he=b,Pb=null;break;case "blur":Pb=he=nb=null;
+break;case "mousedown":ge=!0;break;case "contextmenu":case "mouseup":case "dragend":return ge=!1,Dg(c,d);case "selectionchange":if(Zj)break;case "keydown":case "keyup":return Dg(c,d)}return null}},bk=R.extend({animationName:null,elapsedTime:null,pseudoElement:null}),ck=R.extend({clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),dk=dc.extend({relatedTarget:null}),ek={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",
+Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},fk={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",
+224:"Meta"},gk=dc.extend({key:function(a){if(a.key){var b=ek[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=Ac(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?fk[a.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:fe,charCode:function(a){return"keypress"===a.type?Ac(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===
+a.type?Ac(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),hk=ec.extend({dataTransfer:null}),ik=dc.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:fe}),jk=R.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),kk=ec.extend({deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?
+-a.wheelDelta:0},deltaZ:null,deltaMode:null}),lk={eventTypes:dg,extractEvents:function(a,b,c,d,e){e=cg.get(a);if(!e)return null;switch(a){case "keypress":if(0===Ac(c))return null;case "keydown":case "keyup":a=gk;break;case "blur":case "focus":a=dk;break;case "click":if(2===c.button)return null;case "auxclick":case "dblclick":case "mousedown":case "mousemove":case "mouseup":case "mouseout":case "mouseover":case "contextmenu":a=ec;break;case "drag":case "dragend":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "dragstart":case "drop":a=
+hk;break;case "touchcancel":case "touchend":case "touchmove":case "touchstart":a=ik;break;case $h:case ai:case bi:a=bk;break;case ci:a=jk;break;case "scroll":a=dc;break;case "wheel":a=kk;break;case "copy":case "cut":case "paste":a=ck;break;case "gotpointercapture":case "lostpointercapture":case "pointercancel":case "pointerdown":case "pointermove":case "pointerout":case "pointerover":case "pointerup":a=hi;break;default:a=R}b=a.getPooled(e,b,c,d);lb(b);return b}};(function(a){if(ic)throw Error(k(101));
+ic=Array.prototype.slice.call(a);nf()})("ResponderEventPlugin SimpleEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" "));(function(a,b,c){td=a;rf=b;mf=c})(ae,Hb,Pa);pf({SimpleEventPlugin:lk,EnterLeaveEventPlugin:Yj,ChangeEventPlugin:Xj,SelectEventPlugin:ak,BeforeInputEventPlugin:Wj});var ie=[],ob=-1,Ca={},B={current:Ca},G={current:!1},Ra=Ca,bj=Pd,je=$f,Rg=Lj,aj=Nj,Dc=Oj,Ig=Zh,Jg=ag,Kg=Pj,Lg=Qj,Qg={},yj=Mj,Cj=void 0!==Yh?Yh:function(){},qa=null,
+Ec=null,ke=!1,ii=ff(),Y=1E4>ii?ff:function(){return ff()-ii},Ic={current:null},Hc=null,qb=null,Gc=null,Tg=0,Jc=2,Ga=!1,Vb=da.ReactCurrentBatchConfig,$g=(new ea.Component).refs,Mc={isMounted:function(a){return(a=a._reactInternalFiber)?Na(a)===a:!1},enqueueSetState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;d=Va(d,a,e);e=Ea(d,e);e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueReplaceState:function(a,b,c){a=a._reactInternalFiber;var d=ka(),e=Vb.suspense;
+d=Va(d,a,e);e=Ea(d,e);e.tag=1;e.payload=b;void 0!==c&&null!==c&&(e.callback=c);Fa(a,e);Ja(a,d)},enqueueForceUpdate:function(a,b){a=a._reactInternalFiber;var c=ka(),d=Vb.suspense;c=Va(c,a,d);d=Ea(c,d);d.tag=Jc;void 0!==b&&null!==b&&(d.callback=b);Fa(a,d);Ja(a,c)}},Qc=Array.isArray,wb=ah(!0),Fe=ah(!1),Sb={},ja={current:Sb},Ub={current:Sb},Tb={current:Sb},D={current:0},Sc=da.ReactCurrentDispatcher,X=da.ReactCurrentBatchConfig,Ia=0,z=null,K=null,J=null,Uc=!1,Tc={readContext:W,useCallback:S,useContext:S,
+useEffect:S,useImperativeHandle:S,useLayoutEffect:S,useMemo:S,useReducer:S,useRef:S,useState:S,useDebugValue:S,useResponder:S,useDeferredValue:S,useTransition:S},dj={readContext:W,useCallback:ih,useContext:W,useEffect:eh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return ze(4,2,gh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return ze(4,2,a,b)},useMemo:function(a,b){var c=ub();b=void 0===b?null:b;a=a();c.memoizedState=[a,b];return a},useReducer:function(a,b,c){var d=
+ub();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a=d.queue={pending:null,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};a=a.dispatch=ch.bind(null,z,a);return[d.memoizedState,a]},useRef:function(a){var b=ub();a={current:a};return b.memoizedState=a},useState:xe,useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=xe(a),d=c[0],e=c[1];eh(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=
+xe(!1),c=b[0];b=b[1];return[ih(Ce.bind(null,b,a),[b,a]),c]}},ej={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Vc,useRef:dh,useState:function(a){return Vc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Vc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Vc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,
+b,a),[b,a]),c]}},fj={readContext:W,useCallback:Yc,useContext:W,useEffect:Xc,useImperativeHandle:hh,useLayoutEffect:fh,useMemo:jh,useReducer:Wc,useRef:dh,useState:function(a){return Wc(Ua)},useDebugValue:Be,useResponder:ue,useDeferredValue:function(a,b){var c=Wc(Ua),d=c[0],e=c[1];Xc(function(){var c=X.suspense;X.suspense=void 0===b?null:b;try{e(a)}finally{X.suspense=c}},[a,b]);return d},useTransition:function(a){var b=Wc(Ua),c=b[0];b=b[1];return[Yc(Ce.bind(null,b,a),[b,a]),c]}},ra=null,Ka=null,Wa=
+!1,gj=da.ReactCurrentOwner,ia=!1,Je={dehydrated:null,retryTime:0};var jj=function(a,b,c,d){for(c=b.child;null!==c;){if(5===c.tag||6===c.tag)a.appendChild(c.stateNode);else if(4!==c.tag&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return}c.sibling.return=c.return;c=c.sibling}};var wh=function(a){};var ij=function(a,b,c,d,e){var f=a.memoizedProps;if(f!==d){var g=b.stateNode;Ta(ja.current);a=null;switch(c){case "input":f=
+Cd(g,f);d=Cd(g,d);a=[];break;case "option":f=Fd(g,f);d=Fd(g,d);a=[];break;case "select":f=M({},f,{value:void 0});d=M({},d,{value:void 0});a=[];break;case "textarea":f=Gd(g,f);d=Gd(g,d);a=[];break;default:"function"!==typeof f.onClick&&"function"===typeof d.onClick&&(g.onclick=uc)}Ud(c,d);var h,m;c=null;for(h in f)if(!d.hasOwnProperty(h)&&f.hasOwnProperty(h)&&null!=f[h])if("style"===h)for(m in g=f[h],g)g.hasOwnProperty(m)&&(c||(c={}),c[m]="");else"dangerouslySetInnerHTML"!==h&&"children"!==h&&"suppressContentEditableWarning"!==
+h&&"suppressHydrationWarning"!==h&&"autoFocus"!==h&&(db.hasOwnProperty(h)?a||(a=[]):(a=a||[]).push(h,null));for(h in d){var k=d[h];g=null!=f?f[h]:void 0;if(d.hasOwnProperty(h)&&k!==g&&(null!=k||null!=g))if("style"===h)if(g){for(m in g)!g.hasOwnProperty(m)||k&&k.hasOwnProperty(m)||(c||(c={}),c[m]="");for(m in k)k.hasOwnProperty(m)&&g[m]!==k[m]&&(c||(c={}),c[m]=k[m])}else c||(a||(a=[]),a.push(h,c)),c=k;else"dangerouslySetInnerHTML"===h?(k=k?k.__html:void 0,g=g?g.__html:void 0,null!=k&&g!==k&&(a=a||
+[]).push(h,k)):"children"===h?g===k||"string"!==typeof k&&"number"!==typeof k||(a=a||[]).push(h,""+k):"suppressContentEditableWarning"!==h&&"suppressHydrationWarning"!==h&&(db.hasOwnProperty(h)?(null!=k&&oa(e,h),a||g===k||(a=[])):(a=a||[]).push(h,k))}c&&(a=a||[]).push("style",c);e=a;if(b.updateQueue=e)b.effectTag|=4}};var kj=function(a,b,c,d){c!==d&&(b.effectTag|=4)};var pj="function"===typeof WeakSet?WeakSet:Set,wj="function"===typeof WeakMap?WeakMap:Map,sj=Math.ceil,gd=da.ReactCurrentDispatcher,
+Uh=da.ReactCurrentOwner,H=0,Ye=8,ca=16,ma=32,Xa=0,hd=1,Oh=2,ad=3,bd=4,Xe=5,p=H,U=null,t=null,P=0,F=Xa,id=null,ta=1073741823,Yb=1073741823,kd=null,Xb=0,jd=!1,Re=0,Ph=500,l=null,cd=!1,Se=null,La=null,ld=!1,Zb=null,$b=90,bb=null,ac=0,af=null,dd=0,Ja=function(a,b){if(50<ac)throw ac=0,af=null,Error(k(185));a=ed(a,b);if(null!==a){var c=Cc();1073741823===b?(p&Ye)!==H&&(p&(ca|ma))===H?Te(a):(V(a),p===H&&ha()):V(a);(p&4)===H||98!==c&&99!==c||(null===bb?bb=new Map([[a,b]]):(c=bb.get(a),(void 0===c||c>b)&&bb.set(a,
+b)))}};var zj=function(a,b,c){var d=b.expirationTime;if(null!==a){var e=b.pendingProps;if(a.memoizedProps!==e||G.current)ia=!0;else{if(d<c){ia=!1;switch(b.tag){case 3:sh(b);Ee();break;case 5:bh(b);if(b.mode&4&&1!==c&&e.hidden)return b.expirationTime=b.childExpirationTime=1,null;break;case 1:N(b.type)&&Bc(b);break;case 4:se(b,b.stateNode.containerInfo);break;case 10:d=b.memoizedProps.value;e=b.type._context;y(Ic,e._currentValue);e._currentValue=d;break;case 13:if(null!==b.memoizedState){d=b.child.childExpirationTime;
+if(0!==d&&d>=c)return th(a,b,c);y(D,D.current&1);b=sa(a,b,c);return null!==b?b.sibling:null}y(D,D.current&1);break;case 19:d=b.childExpirationTime>=c;if(0!==(a.effectTag&64)){if(d)return vh(a,b,c);b.effectTag|=64}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null);y(D,D.current);if(!d)return null}return sa(a,b,c)}ia=!1}}else ia=!1;b.expirationTime=0;switch(b.tag){case 2:d=b.type;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;e=pb(b,B.current);rb(b,c);e=we(null,
+b,d,a,e,c);b.effectTag|=1;if("object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof){b.tag=1;b.memoizedState=null;b.updateQueue=null;if(N(d)){var f=!0;Bc(b)}else f=!1;b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null;ne(b);var g=d.getDerivedStateFromProps;"function"===typeof g&&Lc(b,d,g,a);e.updater=Mc;b.stateNode=e;e._reactInternalFiber=b;pe(b,d,a,c);b=Ie(null,b,d,!0,f,c)}else b.tag=0,T(null,b,e,c),b=b.child;return b;case 16:a:{e=b.elementType;null!==a&&(a.alternate=
+null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;ri(e);if(1!==e._status)throw e._result;e=e._result;b.type=e;f=b.tag=Gj(e);a=aa(e,a);switch(f){case 0:b=He(null,b,e,a,c);break a;case 1:b=rh(null,b,e,a,c);break a;case 11:b=nh(null,b,e,a,c);break a;case 14:b=oh(null,b,e,aa(e.type,a),d,c);break a}throw Error(k(306,e,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),He(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),rh(a,b,d,e,c);
+case 3:sh(b);d=b.updateQueue;if(null===a||null===d)throw Error(k(282));d=b.pendingProps;e=b.memoizedState;e=null!==e?e.element:null;oe(a,b);Qb(b,d,null,c);d=b.memoizedState.element;if(d===e)Ee(),b=sa(a,b,c);else{if(e=b.stateNode.hydrate)Ka=kb(b.stateNode.containerInfo.firstChild),ra=b,e=Wa=!0;if(e)for(c=Fe(b,null,d,c),b.child=c;c;)c.effectTag=c.effectTag&-3|1024,c=c.sibling;else T(a,b,d,c),Ee();b=b.child}return b;case 5:return bh(b),null===a&&De(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:
+null,g=e.children,Yd(d,e)?g=null:null!==f&&Yd(d,f)&&(b.effectTag|=16),qh(a,b),b.mode&4&&1!==c&&e.hidden?(b.expirationTime=b.childExpirationTime=1,b=null):(T(a,b,g,c),b=b.child),b;case 6:return null===a&&De(b),null;case 13:return th(a,b,c);case 4:return se(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=wb(b,null,d,c):T(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),nh(a,b,d,e,c);case 7:return T(a,b,b.pendingProps,c),b.child;case 8:return T(a,
+b,b.pendingProps.children,c),b.child;case 12:return T(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;g=b.memoizedProps;f=e.value;var h=b.type._context;y(Ic,h._currentValue);h._currentValue=f;if(null!==g)if(h=g.value,f=Qa(h,f)?0:("function"===typeof d._calculateChangedBits?d._calculateChangedBits(h,f):1073741823)|0,0===f){if(g.children===e.children&&!G.current){b=sa(a,b,c);break a}}else for(h=b.child,null!==h&&(h.return=b);null!==h;){var m=h.dependencies;if(null!==
+m){g=h.child;for(var l=m.firstContext;null!==l;){if(l.context===d&&0!==(l.observedBits&f)){1===h.tag&&(l=Ea(c,null),l.tag=Jc,Fa(h,l));h.expirationTime<c&&(h.expirationTime=c);l=h.alternate;null!==l&&l.expirationTime<c&&(l.expirationTime=c);Sg(h.return,c);m.expirationTime<c&&(m.expirationTime=c);break}l=l.next}}else g=10===h.tag?h.type===b.type?null:h.child:h.child;if(null!==g)g.return=h;else for(g=h;null!==g;){if(g===b){g=null;break}h=g.sibling;if(null!==h){h.return=g.return;g=h;break}g=g.return}h=
+g}T(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,f=b.pendingProps,d=f.children,rb(b,c),e=W(e,f.unstable_observedBits),d=d(e),b.effectTag|=1,T(a,b,d,c),b.child;case 14:return e=b.type,f=aa(e,b.pendingProps),f=aa(e.type,f),oh(a,b,e,f,d,c);case 15:return ph(a,b,b.type,b.pendingProps,d,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:aa(d,e),null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2),b.tag=1,N(d)?(a=!0,Bc(b)):a=!1,rb(b,c),Yg(b,d,e),pe(b,d,e,c),Ie(null,
+b,d,!0,a,c);case 19:return vh(a,b,c)}throw Error(k(156,b.tag));};var bf=null,Ne=null,la=function(a,b,c,d){return new Fj(a,b,c,d)};ef.prototype.render=function(a){md(a,this._internalRoot,null,null)};ef.prototype.unmount=function(){var a=this._internalRoot,b=a.containerInfo;md(null,a,null,function(){b[Lb]=null})};var Di=function(a){if(13===a.tag){var b=Fc(ka(),150,100);Ja(a,b);df(a,b)}};var Yf=function(a){13===a.tag&&(Ja(a,3),df(a,3))};var Bi=function(a){if(13===a.tag){var b=ka();b=Va(b,a,null);Ja(a,
+b);df(a,b)}};sd=function(a,b,c){switch(b){case "input":Dd(a,c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=c.parentNode;c=c.querySelectorAll("input[name="+JSON.stringify(""+b)+'][type="radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=ae(d);if(!e)throw Error(k(90));Gf(d);Dd(d,e)}}}break;case "textarea":Lf(a,c);break;case "select":b=c.value,null!=b&&hb(a,!!c.multiple,b,!1)}};(function(a,b,c,d){ee=a;eg=b;vd=c;vf=d})(Qh,function(a,b,c,d,e){var f=p;p|=4;
+try{return Da(98,a.bind(null,b,c,d,e))}finally{p=f,p===H&&ha()}},function(){(p&(1|ca|ma))===H&&(uj(),xb())},function(a,b){var c=p;p|=2;try{return a(b)}finally{p=c,p===H&&ha()}});var mk={Events:[Hb,Pa,ae,pf,qd,lb,function(a){Kd(a,Ki)},sf,tf,sc,pc,xb,{current:!1}]};(function(a){var b=a.findFiberByHostInstance;return Ej(M({},a,{overrideHookState:null,overrideProps:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:da.ReactCurrentDispatcher,findHostInstanceByFiber:function(a){a=Sf(a);
+return null===a?null:a.stateNode},findFiberByHostInstance:function(a){return b?b(a):null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null}))})({findFiberByHostInstance:Bb,bundleType:0,version:"16.14.0",rendererPackageName:"react-dom"});I.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=mk;I.createPortal=Xh;I.findDOMNode=function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternalFiber;if(void 0===
+b){if("function"===typeof a.render)throw Error(k(188));throw Error(k(268,Object.keys(a)));}a=Sf(b);a=null===a?null:a.stateNode;return a};I.flushSync=function(a,b){if((p&(ca|ma))!==H)throw Error(k(187));var c=p;p|=1;try{return Da(99,a.bind(null,b))}finally{p=c,ha()}};I.hydrate=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!0,c)};I.render=function(a,b,c){if(!bc(b))throw Error(k(200));return nd(null,a,b,!1,c)};I.unmountComponentAtNode=function(a){if(!bc(a))throw Error(k(40));return a._reactRootContainer?
+(Rh(function(){nd(null,null,a,!1,function(){a._reactRootContainer=null;a[Lb]=null})}),!0):!1};I.unstable_batchedUpdates=Qh;I.unstable_createPortal=function(a,b){return Xh(a,b,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)};I.unstable_renderSubtreeIntoContainer=function(a,b,c,d){if(!bc(c))throw Error(k(200));if(null==a||void 0===a._reactInternalFiber)throw Error(k(38));return nd(a,b,c,!1,d)};I.version="16.14.0"});

+ 32 - 0
Info/static/react.production.min.js

@@ -0,0 +1,32 @@
+/** @license React v16.14.0
+ * react.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+'use strict';(function(d,r){"object"===typeof exports&&"undefined"!==typeof module?r(exports):"function"===typeof define&&define.amd?define(["exports"],r):(d=d||self,r(d.React={}))})(this,function(d){function r(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
+function w(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function da(){}function L(a,b,c){this.props=a;this.context=b;this.refs=ba;this.updater=c||ca}function ea(a,b,c){var g,e={},fa=null,d=null;if(null!=b)for(g in void 0!==b.ref&&(d=b.ref),void 0!==b.key&&(fa=""+b.key),b)ha.call(b,g)&&!ia.hasOwnProperty(g)&&(e[g]=b[g]);var h=arguments.length-2;if(1===h)e.children=c;else if(1<h){for(var k=Array(h),f=0;f<h;f++)k[f]=arguments[f+2];e.children=k}if(a&&a.defaultProps)for(g in h=a.defaultProps,
+h)void 0===e[g]&&(e[g]=h[g]);return{$$typeof:x,type:a,key:fa,ref:d,props:e,_owner:M.current}}function va(a,b){return{$$typeof:x,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}function N(a){return"object"===typeof a&&null!==a&&a.$$typeof===x}function wa(a){var b={"=":"=0",":":"=2"};return"$"+(""+a).replace(/[=:]/g,function(a){return b[a]})}function ja(a,b,c,g){if(C.length){var e=C.pop();e.result=a;e.keyPrefix=b;e.func=c;e.context=g;e.count=0;return e}return{result:a,keyPrefix:b,func:c,
+context:g,count:0}}function ka(a){a.result=null;a.keyPrefix=null;a.func=null;a.context=null;a.count=0;10>C.length&&C.push(a)}function O(a,b,c,g){var e=typeof a;if("undefined"===e||"boolean"===e)a=null;var d=!1;if(null===a)d=!0;else switch(e){case "string":case "number":d=!0;break;case "object":switch(a.$$typeof){case x:case xa:d=!0}}if(d)return c(g,a,""===b?"."+P(a,0):b),1;d=0;b=""===b?".":b+":";if(Array.isArray(a))for(var f=0;f<a.length;f++){e=a[f];var h=b+P(e,f);d+=O(e,h,c,g)}else if(null===a||
+"object"!==typeof a?h=null:(h=la&&a[la]||a["@@iterator"],h="function"===typeof h?h:null),"function"===typeof h)for(a=h.call(a),f=0;!(e=a.next()).done;)e=e.value,h=b+P(e,f++),d+=O(e,h,c,g);else if("object"===e)throw c=""+a,Error(r(31,"[object Object]"===c?"object with keys {"+Object.keys(a).join(", ")+"}":c,""));return d}function Q(a,b,c){return null==a?0:O(a,"",b,c)}function P(a,b){return"object"===typeof a&&null!==a&&null!=a.key?wa(a.key):b.toString(36)}function ya(a,b,c){a.func.call(a.context,b,
+a.count++)}function za(a,b,c){var g=a.result,e=a.keyPrefix;a=a.func.call(a.context,b,a.count++);Array.isArray(a)?R(a,g,c,function(a){return a}):null!=a&&(N(a)&&(a=va(a,e+(!a.key||b&&b.key===a.key?"":(""+a.key).replace(ma,"$&/")+"/")+c)),g.push(a))}function R(a,b,c,g,e){var d="";null!=c&&(d=(""+c).replace(ma,"$&/")+"/");b=ja(b,d,g,e);Q(a,za,b);ka(b)}function t(){var a=na.current;if(null===a)throw Error(r(321));return a}function S(a,b){var c=a.length;a.push(b);a:for(;;){var g=c-1>>>1,e=a[g];if(void 0!==
+e&&0<D(e,b))a[g]=b,a[c]=e,c=g;else break a}}function n(a){a=a[0];return void 0===a?null:a}function E(a){var b=a[0];if(void 0!==b){var c=a.pop();if(c!==b){a[0]=c;a:for(var g=0,e=a.length;g<e;){var d=2*(g+1)-1,f=a[d],h=d+1,k=a[h];if(void 0!==f&&0>D(f,c))void 0!==k&&0>D(k,f)?(a[g]=k,a[h]=c,g=h):(a[g]=f,a[d]=c,g=d);else if(void 0!==k&&0>D(k,c))a[g]=k,a[h]=c,g=h;else break a}}return b}return null}function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function F(a){for(var b=n(u);null!==
+b;){if(null===b.callback)E(u);else if(b.startTime<=a)E(u),b.sortIndex=b.expirationTime,S(p,b);else break;b=n(u)}}function T(a){y=!1;F(a);if(!v)if(null!==n(p))v=!0,z(U);else{var b=n(u);null!==b&&G(T,b.startTime-a)}}function U(a,b){v=!1;y&&(y=!1,V());H=!0;var c=m;try{F(b);for(l=n(p);null!==l&&(!(l.expirationTime>b)||a&&!W());){var g=l.callback;if(null!==g){l.callback=null;m=l.priorityLevel;var e=g(l.expirationTime<=b);b=q();"function"===typeof e?l.callback=e:l===n(p)&&E(p);F(b)}else E(p);l=n(p)}if(null!==
+l)var d=!0;else{var f=n(u);null!==f&&G(T,f.startTime-b);d=!1}return d}finally{l=null,m=c,H=!1}}function oa(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var f="function"===typeof Symbol&&Symbol.for,x=f?Symbol.for("react.element"):60103,xa=f?Symbol.for("react.portal"):60106,Aa=f?Symbol.for("react.fragment"):60107,Ba=f?Symbol.for("react.strict_mode"):60108,Ca=f?Symbol.for("react.profiler"):60114,Da=f?Symbol.for("react.provider"):60109,
+Ea=f?Symbol.for("react.context"):60110,Fa=f?Symbol.for("react.forward_ref"):60112,Ga=f?Symbol.for("react.suspense"):60113,Ha=f?Symbol.for("react.memo"):60115,Ia=f?Symbol.for("react.lazy"):60116,la="function"===typeof Symbol&&Symbol.iterator,pa=Object.getOwnPropertySymbols,Ja=Object.prototype.hasOwnProperty,Ka=Object.prototype.propertyIsEnumerable,I=function(){try{if(!Object.assign)return!1;var a=new String("abc");a[5]="de";if("5"===Object.getOwnPropertyNames(a)[0])return!1;var b={};for(a=0;10>a;a++)b["_"+
+String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?!1:!0}catch(g){return!1}}()?Object.assign:function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined");var c=Object(a);for(var g,e=1;e<arguments.length;e++){var d=Object(arguments[e]);
+for(var f in d)Ja.call(d,f)&&(c[f]=d[f]);if(pa){g=pa(d);for(var h=0;h<g.length;h++)Ka.call(d,g[h])&&(c[g[h]]=d[g[h]])}}return c},ca={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,d){},enqueueSetState:function(a,b,c,d){}},ba={};w.prototype.isReactComponent={};w.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error(r(85));this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=
+function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};da.prototype=w.prototype;f=L.prototype=new da;f.constructor=L;I(f,w.prototype);f.isPureReactComponent=!0;var M={current:null},ha=Object.prototype.hasOwnProperty,ia={key:!0,ref:!0,__self:!0,__source:!0},ma=/\/+/g,C=[],na={current:null},X;if("undefined"===typeof window||"function"!==typeof MessageChannel){var A=null,qa=null,ra=function(){if(null!==A)try{var a=q();A(!0,a);A=null}catch(b){throw setTimeout(ra,0),b;}},La=Date.now();var q=
+function(){return Date.now()-La};var z=function(a){null!==A?setTimeout(z,0,a):(A=a,setTimeout(ra,0))};var G=function(a,b){qa=setTimeout(a,b)};var V=function(){clearTimeout(qa)};var W=function(){return!1};f=X=function(){}}else{var Y=window.performance,sa=window.Date,Ma=window.setTimeout,Na=window.clearTimeout;"undefined"!==typeof console&&(f=window.cancelAnimationFrame,"function"!==typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),
+"function"!==typeof f&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"));if("object"===typeof Y&&"function"===typeof Y.now)q=function(){return Y.now()};else{var Oa=sa.now();q=function(){return sa.now()-Oa}}var J=!1,K=null,Z=-1,ta=5,ua=0;W=function(){return q()>=ua};f=function(){};X=function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):
+ta=0<a?Math.floor(1E3/a):5};var B=new MessageChannel,aa=B.port2;B.port1.onmessage=function(){if(null!==K){var a=q();ua=a+ta;try{K(!0,a)?aa.postMessage(null):(J=!1,K=null)}catch(b){throw aa.postMessage(null),b;}}else J=!1};z=function(a){K=a;J||(J=!0,aa.postMessage(null))};G=function(a,b){Z=Ma(function(){a(q())},b)};V=function(){Na(Z);Z=-1}}var p=[],u=[],Pa=1,l=null,m=3,H=!1,v=!1,y=!1,Qa=0;B={ReactCurrentDispatcher:na,ReactCurrentOwner:M,IsSomeRendererActing:{current:!1},assign:I};I(B,{Scheduler:{__proto__:null,
+unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=m;m=a;try{return b()}finally{m=c}},unstable_next:function(a){switch(m){case 1:case 2:case 3:var b=3;break;default:b=m}var c=m;m=b;try{return a()}finally{m=c}},unstable_scheduleCallback:function(a,b,c){var d=q();if("object"===typeof c&&null!==c){var e=c.delay;
+e="number"===typeof e&&0<e?d+e:d;c="number"===typeof c.timeout?c.timeout:oa(a)}else c=oa(a),e=d;c=e+c;a={id:Pa++,callback:b,priorityLevel:a,startTime:e,expirationTime:c,sortIndex:-1};e>d?(a.sortIndex=e,S(u,a),null===n(p)&&a===n(u)&&(y?V():y=!0,G(T,e-d))):(a.sortIndex=c,S(p,a),v||H||(v=!0,z(U)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=m;return function(){var c=m;m=b;try{return a.apply(this,arguments)}finally{m=c}}},unstable_getCurrentPriorityLevel:function(){return m},
+unstable_shouldYield:function(){var a=q();F(a);var b=n(p);return b!==l&&null!==l&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTime<l.expirationTime||W()},unstable_requestPaint:f,unstable_continueExecution:function(){v||H||(v=!0,z(U))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return n(p)},get unstable_now(){return q},get unstable_forceFrameRate(){return X},unstable_Profiling:null},SchedulerTracing:{__proto__:null,__interactionsRef:null,__subscriberRef:null,
+unstable_clear:function(a){return a()},unstable_getCurrent:function(){return null},unstable_getThreadID:function(){return++Qa},unstable_trace:function(a,b,c){return c()},unstable_wrap:function(a){return a},unstable_subscribe:function(a){},unstable_unsubscribe:function(a){}}});d.Children={map:function(a,b,c){if(null==a)return a;var d=[];R(a,d,null,b,c);return d},forEach:function(a,b,c){if(null==a)return a;b=ja(null,null,b,c);Q(a,ya,b);ka(b)},count:function(a){return Q(a,function(){return null},null)},
+toArray:function(a){var b=[];R(a,b,null,function(a){return a});return b},only:function(a){if(!N(a))throw Error(r(143));return a}};d.Component=w;d.Fragment=Aa;d.Profiler=Ca;d.PureComponent=L;d.StrictMode=Ba;d.Suspense=Ga;d.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=B;d.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(r(267,a));var d=I({},a.props),e=a.key,f=a.ref,m=a._owner;if(null!=b){void 0!==b.ref&&(f=b.ref,m=M.current);void 0!==b.key&&(e=""+b.key);if(a.type&&a.type.defaultProps)var h=
+a.type.defaultProps;for(k in b)ha.call(b,k)&&!ia.hasOwnProperty(k)&&(d[k]=void 0===b[k]&&void 0!==h?h[k]:b[k])}var k=arguments.length-2;if(1===k)d.children=c;else if(1<k){h=Array(k);for(var l=0;l<k;l++)h[l]=arguments[l+2];d.children=h}return{$$typeof:x,type:a.type,key:e,ref:f,props:d,_owner:m}};d.createContext=function(a,b){void 0===b&&(b=null);a={$$typeof:Ea,_calculateChangedBits:b,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null};a.Provider={$$typeof:Da,_context:a};return a.Consumer=
+a};d.createElement=ea;d.createFactory=function(a){var b=ea.bind(null,a);b.type=a;return b};d.createRef=function(){return{current:null}};d.forwardRef=function(a){return{$$typeof:Fa,render:a}};d.isValidElement=N;d.lazy=function(a){return{$$typeof:Ia,_ctor:a,_status:-1,_result:null}};d.memo=function(a,b){return{$$typeof:Ha,type:a,compare:void 0===b?null:b}};d.useCallback=function(a,b){return t().useCallback(a,b)};d.useContext=function(a,b){return t().useContext(a,b)};d.useDebugValue=function(a,b){};
+d.useEffect=function(a,b){return t().useEffect(a,b)};d.useImperativeHandle=function(a,b,c){return t().useImperativeHandle(a,b,c)};d.useLayoutEffect=function(a,b){return t().useLayoutEffect(a,b)};d.useMemo=function(a,b){return t().useMemo(a,b)};d.useReducer=function(a,b,c){return t().useReducer(a,b,c)};d.useRef=function(a){return t().useRef(a)};d.useState=function(a){return t().useState(a)};d.version="16.14.0"});

File diff suppressed because it is too large
+ 0 - 0
Info/static/vue.global.prod.js


+ 6 - 0
Info/templates/info/bottom.html

@@ -0,0 +1,6 @@
+{% load static %}
+<div id="bottom" style="display:flex; justify-content:center">
+    <a href="https://beian.miit.gov.cn/">
+        沪ICP备2021001512号
+    </a>
+</div>

+ 18 - 0
Info/templates/info/choicePage.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="utf-8" />
+    <title>choicePage</title>
+</head>
+<body>
+    <script>
+        if(window.confirm("{{msg}}")){
+            window.location.href="{{yes_url}}";
+        }
+        else{
+            window.location.href="{{no_url}}";
+        }
+    </script>
+</body>
+</html>

+ 70 - 0
Info/templates/info/company/profile.html

@@ -0,0 +1,70 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="company_name" class="form-label">公司/组织名:</label>
+            <input type="text" class="form-control" id="company_name" placeholder="输入公司/组织名" name="company_name"
+                required maxlength="64" value="{{ company.name }}" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="phone" class="form-label">公司电话号码:</label>
+            <input type="number" class="form-control" id="phone" placeholder="输入公司电话号码"
+                name="phone" required  value="{{ company.phone }}" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="company_address" class="form-label">公司/组织地址:</label>
+            <input type="text" class="form-control" id="company_address" placeholder="输入公司/组织地址" name="company_address"
+                required maxlength="128" value="{{ company.address }}" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="company_license" class="form-label">公司组织机构代码:</label>
+            <input type="text" class="form-control" id="company_license" placeholder="输入公司组织机构代码" name="company_license"
+                maxlength="64" value="{{ company.license_id }}" />
+        </div>
+        <hr />
+        <div class="mb-3 mt-3">
+            <label for="primary_contact_name" class="form-label">主要联系人:</label>
+            <input type="text" class="form-control" id="primary_contact_name" placeholder="输入主要联系人姓名"
+                name="primary_contact_name" required maxlength="16" value="{{ company.primary_contact_name }}" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="primary_contact_email" class="form-label">主要联系人邮箱地址:</label>
+            <input type="email" class="form-control" id="primary_contact_email" placeholder="输入主要联系人邮箱地址"
+                name="primary_contact_email" required value="{{ company.primary_contact_email }}" maxlength="128" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="primary_contact_mobile" class="form-label">主要联系人手机号码:</label>
+            <input type="number" class="form-control" id="primary_contact_mobile" placeholder="输入主要联系人手机号码"
+                name="primary_contact_mobile" required value="{{ company.primary_contact_mobile }}" min="13000000000"
+                max="19999999999" />
+        </div>
+        <div id="form-button-area">
+            <button type="submit" class="btn btn-primary">修改</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 20px;
+        width: 500px;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 51 - 0
Info/templates/info/company/user_create.html

@@ -0,0 +1,51 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}{% endblock script %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="name" class="form-label">姓名:</label>
+            <input type="text" class="form-control" id="name" placeholder="输入姓名" name="name" required maxlength="64" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="email" class="form-label">邮箱地址:</label>
+            <input type="email" class="form-control" id="email" placeholder="输入邮箱地址" name="email" required
+                maxlength="128" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="mobile" class="form-label">手机号码:</label>
+            <input type="number" class="form-control" id="mobile" placeholder="输入手机号码" name="mobile" required
+                min="13000000000" max="19999999999" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="position" class="form-label">职位:</label>
+            <input type="text" class="form-control" id="position" placeholder="输入职位" name="position" maxlength="64" />
+        </div>
+        
+        <div id="form-button-area">
+            <button type="submit" class="btn btn-primary">添加</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 20px;
+        width: 500px;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 189 - 0
Info/templates/info/company/user_manager.html

@@ -0,0 +1,189 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+{% comment %}
+<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
+<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
+<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
+<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
+<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
+<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
+<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script> {% endcomment %}
+<script src="{% static 'axios.min.js' %}"></script>
+<script src="{% static 'qs.js' %}"></script>
+<script src="{% static 'react.production.min.js' %}"></script>
+<script src="{% static 'react-dom.production.min.js' %}"></script>
+<script src="{% static 'babel.min.js' %}"></script>
+{% endblock script %}
+{% block body %}
+{% verbatim %}
+<div id="main" class="container-fluid">
+    <div id="message"></div>
+    <div id="userTable"></div>
+    <div id="button-area">
+        <a href="/home/">返回</a>
+    </div>
+</div>
+<script type="text/babel">
+    var users = []
+
+    class UserTable2 extends React.Component {
+        constructor(props) {
+            super(props);
+            this.message = props.message
+            this.state = { users: [] }
+            // this.getUsers()
+        }
+        componentDidMount() {
+            this.getUsers()
+        }
+        getUsers() {
+            // var that = this
+            axios
+                .get("/home/company/api/user/list/")
+                .then(function (response) {
+                    if (response.data.code == 0) {
+                        this.setState({ users: response.data.content })
+                    } else {
+                        alert("服务器连接错误")
+                    }
+                }.bind(this))
+                .catch(function (error) {
+                    console.log(error)
+                })
+        }
+        isAdmin(value) {
+            if (value == 1) {
+                return true
+                console.log(1)
+            } else {
+                return false
+                console.log(0)
+            }
+        }
+        adminChange(e, user_id, user_name) {
+            console.log("checked status", e.currentTarget.getAttribute("checked"))
+            if (e.currentTarget.getAttribute("checked") == null) {
+                var admin_status_target = true
+            }
+            if (e.currentTarget.getAttribute("checked") == "") {
+                var admin_status_target = false
+            }
+            var that = this
+            var result = confirm("确认修改" + user_name + "管理员权限?")
+            if (result == true) {
+                axios({
+                    method: "POST",
+                    headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                    data: Qs.stringify({
+                        target_user_id: user_id,
+                        new_admin_status: admin_status_target
+                    }),
+                    url: "/home/company/api/user/admin/change/"
+                })
+                    .then(function (response) {
+                        if (response.data.code == 0) {
+                            alert("修改成功")
+                        } else {
+                            alert(response.data.content)
+                            console.log(response.data.code)
+                        }
+                        that.getUsers()
+                    })
+                    .catch(function (error) {
+                        console.log(error)
+                    })
+            }
+        }
+        deleteUser(user_id, user_name) {
+            var that = this
+            var result = confirm("确认删除用户" + user_name + "?")
+            if (result == true) {
+
+                // $.post("/home/company/api/user/delete/",
+                //     {
+                //         delete_user_id: user_id
+                //     },
+                //     function (response) {
+                //         console.log(response)
+                //         if (response.code == 0) {
+                //             alert("删除成功")
+                //             that.getUsers()
+                //         } else {
+                //             alert("服务器连接错误")
+                //         }
+                //     }
+                // )
+                axios({
+                    method: "POST",
+                    headers: { 'content-type': 'application/x-www-form-urlencoded' },
+                    data: Qs.stringify({ delete_user_id: user_id }),
+                    url: "/home/company/api/user/delete/"
+                }).then(function (response) {
+                    if (response.data.code == 0) {
+                        alert("删除成功")
+                        that.getUsers()
+                    } else {
+                        alert("服务器连接错误")
+                    }
+                })
+                    .catch(function (error) {
+                        console.log(error)
+                    })
+            }
+
+        }
+        render() {
+            return (
+                <div>
+                    <table className="table table-striped">
+                        <thead>
+                            <tr>
+                                <th>姓名</th>
+                                <th>手机号码</th>
+                                <th>电子邮件</th>
+                                <th>职位</th>
+                                <th>是否管理员</th>
+                                <th>删除</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {this.state.users.map((row, index) =>
+                                <tr key={index}>
+                                    <td>{row.name}</td>
+                                    <td>{row.mobile}</td>
+                                    <td>{row.email}</td>
+                                    <td>{row.position}</td>
+                                    <td>
+                                        <div className="form-check form-switch">
+                                            <input name="isAdmin" className="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckDefault" checked={this.isAdmin(row.admin)} onChange={(e) => this.adminChange(e, row.id, row.name)} />
+                                            <label className="form-check-label" htmlFor="flexSwitchCheckDefault"></label>
+                                        </div>
+                                    </td>
+                                    <td>{this.isAdmin(row.admin) ? "" : <div onClick={() => this.deleteUser(row.id, row.name)}>删除</div>}</td>
+                                </tr>
+                            )}
+                        </tbody>
+                    </table>
+                </div>
+            )
+
+        }
+    }
+    // ReactDOM.render(
+    //     <h1>Hello, world!</h1>,
+    //     document.getElementById('message'));
+    ReactDOM.render(
+        <UserTable2 message={message} />,
+        document.getElementById('userTable'))
+</script>
+<style type="text/css">
+    #button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endverbatim %}
+{% endblock body %}

+ 22 - 0
Info/templates/info/directPage.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <meta charset="utf-8" />
+    <title>directPage</title>
+</head>
+<body>
+    {% if alertMsg != None %}
+        <script type="text/javascript">
+            {% autoescape off %}
+            alert ('{{alertMsg}}');
+            {% endautoescape %}
+        </script>
+    {% endif %}
+    {% if dirLink %}
+        <script type="text/javascript">
+            window.location.href = '{{dirLink}}';
+        </script>
+    {% endif %}
+</body>
+</html>

+ 164 - 0
Info/templates/info/home.html

@@ -0,0 +1,164 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+{% endblock script %}
+{% block body %}
+    <div id="main" class="container-fluid">
+        <div id="panel" class="row">
+            <div class="col-md-12">
+                <div class="card">
+                    <div class="card-header">个人信息</div>
+                    <div class="card-body">
+                        <div class="function-group">
+                            <div class="item">
+                                <a href="user/profile/">修改资料</a>
+                            </div>
+                            <div class="item">
+                                <a href="user/password/">修改密码</a>
+                            </div>
+                            <div class="item">
+                                <a href="user/message/">消息</a>
+                                {% if info.unread_message_qty > 0 %}
+                                    <span class="badge rounded-pill bg-info">{{ info.unread_message_qty }}</span>
+                                {% endif %}
+                            </div>
+                            <div class="item">
+                                <a href="/logout/">退出</a>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            {% if user.admin %}
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">公司信息管理</div>
+                        <div class="card-body">
+                            <div class="function-group">
+                                <div class="item">
+                                    <a href="company/user/create/">添加用户</a>
+                                </div>
+                                {% comment %} <div class="item"><a href="company/admin/">管理员设定</a></div> {% endcomment %}
+                                <div class="item">
+                                    <a href="company/user/manager/">管理用户</a>
+                                </div>
+                                <div class="item">
+                                    <a href="company/profile/">修改公司信息</a>
+                                </div>
+                                <div class="item">
+                                    <a href="company/dismiss/">解散公司</a>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            {% endif %}
+            {% if 'BOM_management' in modular_list %}
+                {% comment %} BOM管理功能 {% endcomment %}
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">BOM管理</div>
+                        <div class="card-body">
+                            <div class="function-group">
+                                <div class="item">
+                                    <a href="/BOM/part/create/">新建料号</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/BOM/part/edit/list/">编辑料号</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/BOM/part/my_list/">我管理的料号</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/BOM/part/search/">BOM查询</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/BOM/part/where_used/">使用查询(where-used)</a>
+                                </div>
+                            </div>
+                            <div class="function-group">
+                                <div class="item">
+                                    <a href="/BOM/ECN/my_list/">我管理的ECN</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/BOM/ECN/my_signoff/">等待签核ECN</a>
+                                    {% if BOM.pending_ecn_qty > 0 %}
+                                        <span class="badge rounded-pill bg-info">{{
+                                        BOM.pending_ecn_qty }}</span>
+                                    {% endif %}
+                                </div>
+                            </div>
+                            <div class="function-group">
+                                <div class="item">
+                                    <a href="/BOM/vendor/create/">创建供应商</a>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            {% endif %}
+            {% if 'IT_service' in modular_list %}
+                {% comment %} IT ticket功能 {% endcomment %}
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">IT服务申请管理</div>
+                        <div class="card-body">
+                            <div class="function-group">
+                                <div class="item">
+                                    <a href="/IT_service/user/request/create/">提交请求</a>
+                                </div>
+                                <div class="item">
+                                    <a href="/IT_service/user/request/list/">我的请求</a>
+                                </div>
+                            </div>
+                            <div class="function-group">
+                                <div class="item">
+                                <a href="/IT_service/backend/open_list/" 待处理请求</a>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        {% endif %}
+    </div>
+</div>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    #panel {
+        width: 100%;
+    }
+
+    .card {
+        margin-top: 5px;
+        margin-bottom: 5px;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+
+    .card-body {
+        display: flex;
+        justify-content: flex-start;
+        flex-wrap: wrap;
+        flex-direction: column;
+    }
+
+    .function-group {
+        display: flex;
+        justify-content: flex-start;
+        flex-wrap: wrap;
+    }
+
+    .item {
+        margin-left: 5px;
+        margin-right: 10px;
+    }
+</style>
+{% endblock body %}

+ 47 - 0
Info/templates/info/internal/login.html

@@ -0,0 +1,47 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}{% endblock %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="name" class="form-label">姓名:</label>
+            <input type="name" class="form-control" id="name" placeholder="输入姓名" name="name" required maxlength="64" />
+        </div>
+        <div class="mb-3">
+            <label for="pwd" class="form-label">密码:</label>
+            <input type="password" class="form-control" id="pwd" placeholder="输入密码" name="password" minlength="6"
+                maxlength="32" required />
+        </div>
+        <!-- <div class="form-check mb-3">
+            <label class="form-check-label">
+                <input class="form-check-input" type="checkbox" name="remember"> Remember me
+            </label>
+</div> -->
+        <button type="submit" class="btn btn-primary">登录</button>
+        <!-- <div id="form-button-area">
+            <a href="/register">注册</a>
+            <a href="/forgotPassword">忘记密码</a>
+        </div> -->
+    </form>
+</div>
+<style type="text/css">
+    form {
+        justify-content: center;
+        margin: 20px;
+        width: 500px;
+    }
+
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 24 - 0
Info/templates/info/layout.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta charset="utf-8" />
+        <meta http-equiv="X-UA-Compatible" content="chrome=1" />
+        <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
+        {% load static %}
+        <title>{% if title %} {{title}} {% else %} Nimble System {% endif %}</title>
+        <!-- 新 Bootstrap5 核心 CSS 文件 -->
+        {% comment %} <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css" /> {% endcomment %}
+        <link rel="stylesheet" href="{% static 'bootstrap.min.css' %}"/>
+        <!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
+        {% comment %} <script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script> {% endcomment %}
+        <script src="{% static 'bootstrap.bundle.min.js' %}"></script>
+        {% comment %} <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> {% endcomment %}
+        {% block head %}{% endblock %}
+        {% block script %}{% endblock %}
+    </head>
+    {% load static %}
+    <body>
+        {% block body %}{% endblock %}
+        {% include 'info/bottom.html' %}
+    </body>
+</html>

+ 48 - 0
Info/templates/info/login.html

@@ -0,0 +1,48 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}{% endblock %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="email" class="form-label">邮箱地址:</label>
+            <input type="email" class="form-control" id="email" placeholder="输入邮箱地址" name="email" required
+                maxlength="64" />
+        </div>
+        <div class="mb-3">
+            <label for="pwd" class="form-label">密码:</label>
+            <input type="password" class="form-control" id="pwd" placeholder="输入密码" name="password" minlength="6"
+                maxlength="32" required />
+        </div>
+        <!-- <div class="form-check mb-3">
+            <label class="form-check-label">
+                <input class="form-check-input" type="checkbox" name="remember"> Remember me
+            </label>
+</div> -->
+        <button type="submit" class="btn btn-primary">登录</button>
+        <div id="form-button-area">
+            <a href="/register">注册</a>
+            <a href="/forgotPassword">忘记密码</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    form {
+        justify-content: center;
+        margin: 20px;
+        width: 500px;
+    }
+
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 80 - 0
Info/templates/info/register.html

@@ -0,0 +1,80 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+<script type="text/javascript" src="{% static 'info/javascript/register.js' %}"></script>
+{% endblock %}
+{% block body %}
+<div id="main" class="container-fluid">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="name" class="form-label">姓名:</label>
+            <input type="text" class="form-control" id="name" placeholder="输入姓名" name="name" required maxlength="64" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="email" class="form-label">邮箱地址:</label>
+            <input type="email" class="form-control" id="email" placeholder="输入邮箱地址" name="email" required
+                maxlength="128" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="mobile" class="form-label">手机号码:</label>
+            <input type="number" class="form-control" id="mobile" placeholder="输入手机号码" name="mobile" required
+                min="13000000000" max="19999999999" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="position" class="form-label">职位:</label>
+            <input type="text" class="form-control" id="position" placeholder="输入职位" name="position" maxlength="64" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="pwd" class="form-label">密码:</label>
+            <input type="password" id="psw" class="form-control" id="pwd" placeholder="输入密码" name="password" required
+                minlength="6" maxlength="32" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="pwd" class="form-label">再次输入密码:</label>
+            <input type="password" id="psw2" class="form-control" id="pwd2" placeholder="再次输入密码" name="paasword2"
+                required minlength="6" maxlength="64" onblur="IsSamePassword()" />
+            <span id="password_alert"></span>
+        </div>
+        <hr />
+        <div class="mb-3 mt-3">
+            <label for="company_name" class="form-label">公司/组织名:</label>
+            <input type="text" class="form-control" id="company_name" placeholder="输入公司/组织名" name="company_name"
+                required maxlength="64" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="company_address" class="form-label">公司/组织地址:</label>
+            <input type="text" class="form-control" id="company_address" placeholder="输入公司/组织地址" name="company_address"
+                required maxlength="128" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="company_license" class="form-label">公司组织机构代码:</label>
+            <input type="text" class="form-control" id="company_license" placeholder="输入公司组织机构代码" name="company_license"
+                maxlength="64" />
+        </div>
+        <button id="submit_btn" type="submit" class="btn btn-primary">注册</button>
+        <div id="form-button-area">
+            <a href="/">登录</a>
+            <a href="/forgotPassword">忘记密码</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    #main {
+        display: inline-flex;
+        justify-content: center;
+    }
+
+    form {
+        justify-content: center;
+        margin: 20px;
+        width: 500px;
+    }
+
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 40 - 0
Info/templates/info/user_password.html

@@ -0,0 +1,40 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}
+<script type="text/javascript" src="{% static 'info/javascript/register.js' %}"></script>
+{% endblock script %}
+{% block body %}
+<div id="main" class="container">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="current_pwd" class="form-label">当前密码:</label>
+            <input type="password" id="current_pwd" class="form-control" id="pwd" placeholder="输入当前密码" name="current_pwd" required
+                minlength="6" maxlength="32" />
+        </div>
+        <hr/>
+        <div class="mb-3 mt-3">
+            <label for="pwd" class="form-label">新密码:</label>
+            <input type="password" id="psw" class="form-control" id="pwd" placeholder="输入新密码" name="password" required
+                minlength="6" maxlength="32" />
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="pwd" class="form-label">再次输入新密码:</label>
+            <input type="password" id="psw2" class="form-control" id="pwd2" placeholder="再次输入新密码" name="paasword2"
+                required minlength="6" maxlength="64" onblur="IsSamePassword()" />
+            <span id="password_alert"></span>
+        </div>
+        <div id="form-button-area">
+            <button type="submit" class="btn btn-primary">修改</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 39 - 0
Info/templates/info/user_profile.html

@@ -0,0 +1,39 @@
+{% extends "info/layout.html" %}
+{% load static %}
+{% block script %}{% endblock script %}
+{% block body %}
+<div id="main" class="container">
+    <form method="post">
+        {% csrf_token %}
+        <div class="mb-3 mt-3">
+            <label for="name" class="form-label">姓名:</label>
+            <input type="text" class="form-control" id="name" placeholder="输入姓名" name="name" required maxlength="16" value="{{ user.name }}"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="email" class="form-label">邮箱地址:</label>
+            <input type="email" class="form-control" id="email" placeholder="输入邮箱地址" name="email" required
+                maxlength="64" value="{{ user.email }}"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="mobile" class="form-label">手机号码:</label>
+            <input type="number" class="form-control" id="mobile" placeholder="输入手机号码" name="mobile" required
+                min="13000000000" max="19999999999" value="{{ user.mobile }}"/>
+        </div>
+        <div class="mb-3 mt-3">
+            <label for="position" class="form-label">职位:</label>
+            <input type="text" class="form-control" id="position" placeholder="输入职位" name="position" maxlength="64" value="{{ user.position }}"/>
+        </div>
+        <div id="form-button-area">
+            <button type="submit" class="btn btn-primary">修改</button>
+            <a href="/home/">返回</a>
+        </div>
+    </form>
+</div>
+<style type="text/css">
+    #form-button-area {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 10px;
+    }
+</style>
+{% endblock body %}

+ 3 - 0
Info/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 30 - 0
Info/urls.py

@@ -0,0 +1,30 @@
+from django.urls import path, re_path
+from . import views, views_company, views_company_api, view_internal
+
+urlpatterns = [
+    # path('api/<str:role>/<str:action>/', views.api),
+    path('', views.login),
+    path('register/', views.register),
+    path('logout/', views.logout),
+    path('home/', views.home),
+    path('home/user/profile/', views.user_profile),
+    path('home/user/password/', views.user_password),
+    path('home/company/user/create/', views_company.new_user),
+    path('home/company/profile/', views_company.profile),
+    path('home/company/user/manager/', views_company.user_manager),
+    re_path(r'upload/*', views.get_upload_file)
+]
+
+# API urls
+urlpatterns += [
+    path('home/company/api/user/list/', views_company_api.user_list),
+    path('home/company/api/user/delete/', views_company_api.user_delete),
+    path('home/company/api/user/admin/change/', views_company_api.user_admin_change),
+    path('home/company/api/user/search/', views_company_api.user_search)
+]
+
+# internal management urls
+
+urlpatterns += [
+    path('internal/', view_internal.login)
+]

Some files were not shown because too many files changed in this diff