Compare commits
No commits in common. "b88510ca0a3a660f52e8c276afef109812bf0982" and "d873f3e0163e9bcb7f057781cc17f8a46a49f8d5" have entirely different histories.
b88510ca0a
...
d873f3e016
|
|
@ -118,7 +118,7 @@ mat/
|
|||
- dealer_name: 经销商名称
|
||||
- product_category: 产品分类
|
||||
- factory_name: 生产工厂全称
|
||||
- brand: 品牌(唯一)
|
||||
- factory_short_name: 工厂简称
|
||||
- province: 省
|
||||
- city: 市
|
||||
- district: 区
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/media/*
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
import re
|
||||
import secrets
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from apps.factory.models import Factory
|
||||
|
||||
|
||||
_DEFAULT_INPUT = """生产工厂全称\t品牌\t省市\t详细地址\t官网链接
|
||||
立邦涂料(中国)有限公司\t立邦中国\t上海市\t上海市浦东新区金桥出口加工区南区创业路287号\thttps://www.nipponpaint.com.cn/
|
||||
广州立邦涂料有限公司\t广州立邦\t广东省广州市\t广州经济技术开发区风华二路1号\thttp://www.nipponpaint.com.cn
|
||||
黑龙江嘉达建材科技发展有限公司\t黑龙江嘉达\t黑龙江省齐齐哈尔市\t黑龙江省齐齐哈尔市铁锋区北疆中小企业园孵化基地B区\t
|
||||
吉林市嘉达慧宇建筑节能材料有限责任公司\t吉林嘉达\t吉林省吉林市\t吉林市吉林经济技术开发区政达街139号\t
|
||||
深圳市荣耀达新材料科技有限公司 (隶属北京荣耀达科技控股有限公司)\t深圳荣耀达\t广东省深圳市\t深圳市福田区深业泰然大厦\t
|
||||
上海典跃建材科技有限公司\t上海典跃\t上海市\t上海市闵行区万康路328号环普云创4号楼\twww.chinasrs.com.cn
|
||||
深圳市百欧森环保科技股份有限公司\t百欧森\t广东省深圳市\t广东省深圳市南山区桃源街道塘朗工业区B区44栋厂房第二层B区\twww.baiousen.com
|
||||
广东马可波罗陶瓷有限公司\t马可波罗\t广东省东莞市\t广东省东莞市沙田镇立沙东路66号502室\thttp://www.marcopolo.com.cn/
|
||||
佛山市东鹏陶瓷发展有限公司\t东鹏发展\t广东省佛山市\t佛山市禅城区季华西路127号首层\thttp://www.dongpeng.net
|
||||
广东新明珠陶瓷集团有限公司\t新明珠集团\t广东省佛山市\t佛山市禅城区南庄镇华夏陶瓷博览城\thttp://slab.newpearl.com/
|
||||
蒙娜丽莎集团股份有限公司\t蒙娜丽莎\t广东省佛山市\t佛山市 (总部)\thttp://www.monalisa.com.cn/
|
||||
佛山新中源陶瓷建材有限公司\t新中源\t广东省佛山市\t佛山市禅城区南庄镇石南大道190号中源企业大厦\thttps://www.newzhongyuan.com/
|
||||
上海斯米克健康环境科技有限公司\t斯米克\t上海市\t未找到公开信息\thttp://www.cimictiles.com/
|
||||
普纳尼亚集团陶瓷股份有限公司\t帕纳尼亚\t意大利\t意大利,蒙蒂那,41034芬那艾米丽,帕纳瑞巴萨街22/A\t
|
||||
阿鲁克邦复合材料(江苏)有限公司\t阿鲁克邦\t江苏省常州市\t中国江苏常州钟楼区合欢南路10号\thttps://www.alucobond.com.cn
|
||||
张家港飞腾复合新材料股份有限公司 (曾用名:张家港飞腾铝塑板股份有限公司)\t张家港飞腾\t江苏省苏州市\t张家港保税区环保新材料产业园华达路77号\twww.feiteng.cn
|
||||
江阴天虹板业有限公司 (“虹铠甲”为其产品系列名称)\t江阴天虹\t江苏省无锡市\t未找到公开信息\twww.sky-rainbow.com
|
||||
佛山市铸辉金属装饰材料有限公司\t佛山铸辉\t广东省佛山市\t未找到公开信息\thttp://zhuhui.cn.makepolo.com
|
||||
王力安防科技股份有限公司\t王力\t浙江省金华市\t未找到公开信息\thttp://www.wanglianfang.com
|
||||
温州麦克辛石业有限公司\t温州麦克辛\t浙江省温州市\t(公开信息未找到详细地址)\t
|
||||
青岛英博建筑装饰有限公司 (曾用名:青岛英博石业有限公司)\t青岛英博\t山东省青岛市\t未找到公开信息\twww.yingbozhuangshi.com
|
||||
广西碳歌环保新材料股份有限公司\t碳歌\t广西壮族自治区梧州市\t广西壮族自治区藤县中和陶瓷园区B01-02-01地块\twww.toco.cc
|
||||
大巨龙集团 (品牌归属:典跃集团上海梦想空间文化科技有限公司)\t大巨龙集团\t上海市\t上海市金山工业区金腾璐1106\twww.da-ju-long.com
|
||||
深圳洛赛声学技术有限公司\t洛赛声学\t广东省深圳市\t未找到公开信息\thttp://www.clocell.com
|
||||
甘肃世宁新型材料有限公司\t世宁新材\t甘肃省庆阳市\t甘肃省庆阳市宁县和盛镇工业集中区\t
|
||||
罗姆(上海)有限公司 (Röhm GmbH 中国全资子公司)\t罗姆\t上海市\t上海市闵行区春东路55号1号楼\twww.roehm.com
|
||||
韩国奥姆勒自动机械有限公司北京代表处\t奥姆勒\t北京市\t(仅为代表处,无生产工厂)\t
|
||||
北京博联建材机械厂\t博联\t北京市\t北京市昌平区南口镇南大街21号\t
|
||||
广东新星联合科技有限公司\t广东新星联合\t广东省深圳市\t深圳市宝安区西乡街道共乐社区铁仔路50号凤凰智谷A栋1406\t
|
||||
"""
|
||||
|
||||
|
||||
_WS_RE = re.compile(r"\s+")
|
||||
_TAB_SPLIT_RE = re.compile(r"\t+")
|
||||
|
||||
# Close to Django's allowed username chars: letters/digits/underscore and @.+-.
|
||||
_USERNAME_ALLOWED_RE = re.compile(r"[^\w@.+-]+", flags=re.UNICODE)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Row:
|
||||
factory_name: str
|
||||
brand: str
|
||||
province_raw: str
|
||||
address: str
|
||||
website_raw: str
|
||||
|
||||
|
||||
def _norm(s: str) -> str:
|
||||
return _WS_RE.sub(" ", (s or "").strip())
|
||||
|
||||
|
||||
def _parse_province_city(s: str) -> Tuple[str, str]:
|
||||
s = _norm(s)
|
||||
if not s:
|
||||
return "", ""
|
||||
|
||||
# e.g. "广东省广州市" -> ("广东省", "广州市")
|
||||
if "省" in s and s.endswith("市"):
|
||||
idx = s.find("省")
|
||||
if idx != -1 and idx + 1 < len(s):
|
||||
return s[: idx + 1], s[idx + 1 :]
|
||||
|
||||
# e.g. "广西壮族自治区梧州市"
|
||||
if "自治区" in s and s.endswith("市"):
|
||||
idx = s.find("自治区")
|
||||
if idx != -1 and idx + 3 < len(s):
|
||||
return s[: idx + 3], s[idx + 3 :]
|
||||
|
||||
# e.g. "北京市" / "上海市"
|
||||
if s.endswith("市"):
|
||||
return s, s
|
||||
|
||||
# Other (e.g. "意大利")
|
||||
return s, s
|
||||
|
||||
|
||||
def _normalize_website(s: str) -> Optional[str]:
|
||||
s = _norm(s)
|
||||
if not s:
|
||||
return None
|
||||
if s.startswith(("http://", "https://")):
|
||||
return s
|
||||
return f"http://{s}"
|
||||
|
||||
|
||||
def _parse_line(line: str) -> Optional[Row]:
|
||||
raw = line.strip()
|
||||
if not raw:
|
||||
return None
|
||||
if raw.startswith("生产工厂全称"):
|
||||
return None
|
||||
|
||||
parts = [p for p in _TAB_SPLIT_RE.split(raw) if p is not None]
|
||||
# tolerate missing trailing columns
|
||||
while len(parts) < 5:
|
||||
parts.append("")
|
||||
|
||||
factory_name = _norm(parts[0])
|
||||
short_name = _norm(parts[1])
|
||||
province_raw = _norm(parts[2])
|
||||
address = _norm(parts[3])
|
||||
website_raw = _norm(parts[4])
|
||||
|
||||
if not factory_name or not short_name:
|
||||
return None
|
||||
|
||||
return Row(
|
||||
factory_name=factory_name,
|
||||
brand=short_name,
|
||||
province_raw=province_raw,
|
||||
address=address,
|
||||
website_raw=website_raw,
|
||||
)
|
||||
|
||||
|
||||
def parse_text(text: str) -> List[Row]:
|
||||
rows: List[Row] = []
|
||||
for line in text.splitlines():
|
||||
row = _parse_line(line)
|
||||
if row:
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
def _make_username_base(brand: str) -> str:
|
||||
base = _norm(brand)
|
||||
base = base.replace(" ", "")
|
||||
base = _USERNAME_ALLOWED_RE.sub("", base)
|
||||
base = base.strip("._-@+")
|
||||
if not base:
|
||||
base = "user"
|
||||
return base.lower()
|
||||
|
||||
|
||||
def _unique_username(UserModel, base: str) -> str:
|
||||
if not UserModel.objects.filter(username=base).exists():
|
||||
return base
|
||||
for i in range(2, 10000):
|
||||
candidate = f"{base}{i}"
|
||||
if not UserModel.objects.filter(username=candidate).exists():
|
||||
return candidate
|
||||
raise RuntimeError(f"Unable to allocate unique username for base={base!r}")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Import factories and create one normal user per factory."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--file", dest="file", default=None, help="Input TSV text file path.")
|
||||
parser.add_argument("--stdin", action="store_true", help="Read TSV from stdin.")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Parse and report only; no DB writes.")
|
||||
parser.add_argument(
|
||||
"--password",
|
||||
dest="password",
|
||||
default=None,
|
||||
help="Set the same initial password for all created users. If omitted, random passwords are generated.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--print-credentials",
|
||||
action="store_true",
|
||||
help="Print created/ensured usernames and passwords as TSV to stdout.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
file_path = options.get("file")
|
||||
use_stdin: bool = bool(options.get("stdin"))
|
||||
dry_run: bool = bool(options.get("dry_run"))
|
||||
fixed_password: Optional[str] = options.get("password") or None
|
||||
print_credentials: bool = bool(options.get("print_credentials"))
|
||||
|
||||
if file_path:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
elif use_stdin:
|
||||
text = sys.stdin.read()
|
||||
else:
|
||||
text = _DEFAULT_INPUT
|
||||
|
||||
rows = parse_text(text)
|
||||
if not rows:
|
||||
self.stdout.write(self.style.WARNING("No valid rows parsed; nothing to import."))
|
||||
return
|
||||
|
||||
unique_factories = {(r.factory_name, r.brand) for r in rows}
|
||||
self.stdout.write(f"Parsed rows: {len(rows)}")
|
||||
self.stdout.write(f"Unique factories: {len(unique_factories)}")
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.SUCCESS("Dry run complete (no DB changes)."))
|
||||
return
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
created_factories = 0
|
||||
created_users = 0
|
||||
ensured_users = 0
|
||||
|
||||
# header for credential output
|
||||
creds: List[Tuple[str, str, str]] = [] # (brand, username, password_or_blank)
|
||||
|
||||
for r in rows:
|
||||
province, city = _parse_province_city(r.province_raw)
|
||||
website = _normalize_website(r.website_raw)
|
||||
|
||||
factory, f_created = Factory.objects.get_or_create(
|
||||
brand=r.brand,
|
||||
defaults={
|
||||
"factory_name": r.factory_name,
|
||||
"province": province or "未知",
|
||||
"city": city or "未知",
|
||||
"address": r.address or None,
|
||||
"website": website,
|
||||
"dealer_name": None,
|
||||
"product_category": None,
|
||||
},
|
||||
)
|
||||
if f_created:
|
||||
created_factories += 1
|
||||
else:
|
||||
changed = False
|
||||
if factory.province != (province or factory.province):
|
||||
factory.province = province or factory.province
|
||||
changed = True
|
||||
if factory.city != (city or factory.city):
|
||||
factory.city = city or factory.city
|
||||
changed = True
|
||||
if r.address and factory.address != r.address:
|
||||
factory.address = r.address
|
||||
changed = True
|
||||
if website and factory.website != website:
|
||||
factory.website = website
|
||||
changed = True
|
||||
if changed:
|
||||
factory.save()
|
||||
|
||||
# One normal user per factory. If exists, keep it; otherwise create a new one.
|
||||
existing_user = (
|
||||
UserModel.objects.filter(role="user", factory_id=factory.id).order_by("id").first()
|
||||
)
|
||||
if existing_user:
|
||||
ensured_users += 1
|
||||
creds.append((factory.brand, existing_user.username, ""))
|
||||
continue
|
||||
|
||||
base = _make_username_base(factory.brand)
|
||||
username = _unique_username(UserModel, base)
|
||||
password = fixed_password or secrets.token_urlsafe(12)
|
||||
|
||||
user = UserModel.objects.create_user(
|
||||
username=username,
|
||||
password=password,
|
||||
role="user",
|
||||
factory=factory,
|
||||
)
|
||||
created_users += 1
|
||||
creds.append((factory.brand, user.username, password))
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Import complete. Created factories: {created_factories}, created users: {created_users}, ensured existing users: {ensured_users}."
|
||||
)
|
||||
)
|
||||
|
||||
if print_credentials:
|
||||
self.stdout.write("品牌\t用户名\t初始密码(仅新建时输出)")
|
||||
for short_name, username, password in creds:
|
||||
self.stdout.write(f"{short_name}\t{username}\t{password}")
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Generated by Django 4.2.7
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('factory', '0002_make_dealer_name_optional'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='factory',
|
||||
old_name='factory_short_name',
|
||||
new_name='brand',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='factory',
|
||||
name='brand',
|
||||
field=models.CharField(max_length=100, unique=True, verbose_name='品牌'),
|
||||
),
|
||||
]
|
||||
|
|
@ -7,7 +7,7 @@ class Factory(models.Model):
|
|||
dealer_name = models.CharField(max_length=255, blank=True, null=True, verbose_name='经销商名称')
|
||||
product_category = models.CharField(max_length=255, blank=True, null=True, verbose_name='产品分类')
|
||||
factory_name = models.CharField(max_length=255, verbose_name='生产工厂全称')
|
||||
brand = models.CharField(max_length=100, unique=True, verbose_name='品牌')
|
||||
factory_short_name = models.CharField(max_length=100, verbose_name='工厂简称')
|
||||
province = models.CharField(max_length=50, verbose_name='省')
|
||||
city = models.CharField(max_length=50, verbose_name='市')
|
||||
district = models.CharField(max_length=50, blank=True, null=True, verbose_name='区')
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class FactorySerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Factory
|
||||
fields = ['id', 'dealer_name', 'product_category', 'factory_name',
|
||||
'brand', 'province', 'city', 'district',
|
||||
'factory_short_name', 'province', 'city', 'district',
|
||||
'address', 'website', 'created_at', 'updated_at', 'material_count']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'material_count']
|
||||
|
||||
|
|
@ -28,4 +28,4 @@ class FactoryListSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
class Meta:
|
||||
model = Factory
|
||||
fields = ['id', 'factory_name', 'brand', 'province', 'city', 'dealer_name']
|
||||
fields = ['id', 'factory_name', 'factory_short_name', 'province', 'city', 'dealer_name']
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from apps.material.models import MaterialCategory, MaterialSubcategory
|
||||
|
||||
|
||||
_DEFAULT_INPUT = """
|
||||
门窗\t智能门窗、智能升降护栏
|
||||
机电产品\t机电产品
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
内墙\t涂料
|
||||
外墙\t涂料
|
||||
外墙\t涂料
|
||||
地坪\t地坪
|
||||
地坪\t地坪
|
||||
地坪\t水磨石
|
||||
墙材\t墙板
|
||||
电工\t电工类
|
||||
瓷砖\t岩板
|
||||
瓷砖\t大理石
|
||||
瓷砖\t玻化砖
|
||||
瓷砖\t云石代
|
||||
石膏板\t脱硫石膏板
|
||||
石膏板\t脱硫石膏板
|
||||
石膏板\t脱硫石膏板
|
||||
石膏板\t脱硫石膏板
|
||||
石膏板\t石膏基纤维板
|
||||
石膏板\t脱硫石膏板
|
||||
龙骨\t轻钢龙骨
|
||||
腻子\t腻子粉
|
||||
腻子\t腻子粉
|
||||
石膏板\t抹灰石膏
|
||||
石膏板\t抹灰石膏
|
||||
石膏板\t嵌缝石膏
|
||||
石膏板\t石膏基自流平
|
||||
石膏板\t石膏基自流平
|
||||
胶\t瓷砖胶
|
||||
胶\t瓷砖胶
|
||||
胶\t瓷砖胶
|
||||
固剂\t界面剂
|
||||
固剂\t界面剂
|
||||
腻子\t腻子
|
||||
石膏板\t找平石膏
|
||||
石膏板\t嵌缝石膏
|
||||
涂料\t节能材料
|
||||
涂料\t平面金属漆
|
||||
涂料\t质感金属漆
|
||||
门窗\t入户门
|
||||
墙体\t保温板(无机材料)
|
||||
墙体\t预制墙板(无机材料)
|
||||
涂料\t涂料
|
||||
板材\t人造板
|
||||
装配式外墙 高分子材料保温类
|
||||
板材\t人造板
|
||||
板材\t人造板
|
||||
涂料\t地下工程\t防水涂料
|
||||
胶\t瓷砖胶
|
||||
地毯\t商用地垫、S垫疏水垫、方块毯
|
||||
"""
|
||||
|
||||
|
||||
_LINE_SPLIT_RE = re.compile(r"\t+| {2,}")
|
||||
_SUB_SPLIT_RE = re.compile(r"[、,,;;]+")
|
||||
_WS_RE = re.compile(r"\s+")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Row:
|
||||
category: str
|
||||
subcategories: Tuple[str, ...]
|
||||
|
||||
|
||||
def _normalize_cell(s: str) -> str:
|
||||
s = s.strip()
|
||||
s = _WS_RE.sub(" ", s)
|
||||
return s
|
||||
|
||||
|
||||
def _parse_line(line: str) -> Optional[Row]:
|
||||
raw = line.strip()
|
||||
if not raw:
|
||||
return None
|
||||
if raw.startswith("材料分类"):
|
||||
return None
|
||||
|
||||
parts = [p for p in _LINE_SPLIT_RE.split(raw) if p.strip()]
|
||||
if len(parts) >= 2:
|
||||
category = _normalize_cell(parts[0])
|
||||
sub_raw = _normalize_cell(" ".join(parts[1:]))
|
||||
else:
|
||||
# Fallback: split by whitespace into 2 chunks
|
||||
chunks = [c for c in _WS_RE.split(raw) if c.strip()]
|
||||
if len(chunks) < 2:
|
||||
return None
|
||||
category = _normalize_cell(chunks[0])
|
||||
sub_raw = _normalize_cell(" ".join(chunks[1:]))
|
||||
|
||||
subs: List[str] = []
|
||||
for token in _SUB_SPLIT_RE.split(sub_raw):
|
||||
token = _normalize_cell(token)
|
||||
if token:
|
||||
subs.append(token)
|
||||
|
||||
if not category or not subs:
|
||||
return None
|
||||
return Row(category=category, subcategories=tuple(subs))
|
||||
|
||||
|
||||
def parse_text(text: str) -> List[Row]:
|
||||
rows: List[Row] = []
|
||||
for line in text.splitlines():
|
||||
row = _parse_line(line)
|
||||
if row:
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
def _category_value(category_name: str) -> str:
|
||||
# Keep it human-readable; unique constraint is enforced at DB level.
|
||||
return category_name
|
||||
|
||||
|
||||
def _subcategory_value(category_name: str, subcategory_name: str) -> str:
|
||||
# MaterialSubcategory.value is globally unique, so include category to avoid collisions.
|
||||
value = f"{category_name}/{subcategory_name}"
|
||||
return value[:255]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Import MaterialCategory & MaterialSubcategory from text/tsv."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--file",
|
||||
dest="file",
|
||||
default=None,
|
||||
help="Input text file path. If omitted, uses built-in default dataset (unless --stdin is specified).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stdin",
|
||||
action="store_true",
|
||||
help="Read import text from stdin (e.g. pipe a file or paste).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Parse and report counts only; do not write to DB.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
dry_run: bool = bool(options["dry_run"])
|
||||
file_path = options.get("file")
|
||||
use_stdin: bool = bool(options.get("stdin"))
|
||||
|
||||
if file_path:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
elif use_stdin:
|
||||
text = sys.stdin.read()
|
||||
else:
|
||||
text = _DEFAULT_INPUT
|
||||
|
||||
rows = parse_text(text)
|
||||
if not rows:
|
||||
self.stdout.write(self.style.WARNING("No valid rows parsed; nothing to import."))
|
||||
return
|
||||
|
||||
unique_categories = {r.category for r in rows}
|
||||
unique_subs = {(r.category, s) for r in rows for s in r.subcategories}
|
||||
|
||||
self.stdout.write(f"Parsed rows: {len(rows)}")
|
||||
self.stdout.write(f"Unique categories: {len(unique_categories)}")
|
||||
self.stdout.write(f"Unique subcategories (category-scoped): {len(unique_subs)}")
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.SUCCESS("Dry run complete (no DB changes)."))
|
||||
return
|
||||
|
||||
created_categories = 0
|
||||
created_subcategories = 0
|
||||
|
||||
category_by_value = {}
|
||||
for category_name in sorted(unique_categories):
|
||||
value = _category_value(category_name)
|
||||
obj, created = MaterialCategory.objects.get_or_create(
|
||||
value=value, defaults={"name": category_name}
|
||||
)
|
||||
if not created and obj.name != category_name:
|
||||
obj.name = category_name
|
||||
obj.save(update_fields=["name"])
|
||||
if created:
|
||||
created_categories += 1
|
||||
category_by_value[value] = obj
|
||||
|
||||
for category_name, sub_name in sorted(unique_subs):
|
||||
cat_value = _category_value(category_name)
|
||||
category = category_by_value[cat_value]
|
||||
sub_value = _subcategory_value(category_name, sub_name)
|
||||
obj, created = MaterialSubcategory.objects.get_or_create(
|
||||
value=sub_value,
|
||||
defaults={"category": category, "name": sub_name},
|
||||
)
|
||||
changed = False
|
||||
if obj.category_id != category.id:
|
||||
obj.category = category
|
||||
changed = True
|
||||
if obj.name != sub_name:
|
||||
obj.name = sub_name
|
||||
changed = True
|
||||
if changed:
|
||||
obj.save(update_fields=["category", "name"])
|
||||
if created:
|
||||
created_subcategories += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Import complete. Created categories: {created_categories}, created subcategories: {created_subcategories}."
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
从 Excel「材料库」sheet 导入材料数据。
|
||||
表头行:材料ID, 材料名称, 专业类别, 材料分类, 材料子类, 品牌, 规格型号, 符合标准, ...
|
||||
工厂解析:依据「品牌」列与 Factory.brand 模糊匹配(取首行);未匹配时关联到「未识别的品牌」工厂(不存在则自动创建)。
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from apps.factory.models import Factory
|
||||
from apps.material.models import Material
|
||||
|
||||
|
||||
# 专业类别:中文 -> 模型 choice value
|
||||
MAJOR_CATEGORY_MAP = {
|
||||
"建筑": "architecture",
|
||||
"景观": "landscape",
|
||||
"设备": "equipment",
|
||||
"装修": "decoration",
|
||||
}
|
||||
# 应用场景:中文 -> 模型 choice value
|
||||
APPLICATION_SCENE_MAP = {
|
||||
"府系": "fu",
|
||||
"境系": "jing",
|
||||
"城系": "cheng",
|
||||
"住系": "zhu",
|
||||
"保障房": "affordable",
|
||||
}
|
||||
# 替代类型
|
||||
REPLACE_TYPE_MAP = {"平替": "alternative", "新研发": "new_development"}
|
||||
# 竞争优势
|
||||
ADVANTAGE_MAP = {"品质": "quality", "成本": "cost"}
|
||||
# 星级:中文 -> 1/2/3
|
||||
STAR_LEVEL_MAP = {"一": 1, "二": 2, "三": 3, "1": 1, "2": 2, "3": 3}
|
||||
|
||||
|
||||
def _cell(v: Any) -> str:
|
||||
if v is None:
|
||||
return ""
|
||||
s = str(v).strip()
|
||||
return s
|
||||
|
||||
|
||||
def _norm_header(h: Any) -> str:
|
||||
return _cell(h).replace("\n", " ").strip()
|
||||
|
||||
|
||||
def _parse_application_scene(s: str) -> List[str]:
|
||||
if not s:
|
||||
return []
|
||||
out = []
|
||||
for part in re.split(r"[\s,,、;;]+", s):
|
||||
key = part.strip()
|
||||
if key in APPLICATION_SCENE_MAP:
|
||||
out.append(APPLICATION_SCENE_MAP[key])
|
||||
return list(dict.fromkeys(out)) # preserve order, dedup
|
||||
|
||||
|
||||
def _parse_advantage(s: str) -> List[str]:
|
||||
if not s:
|
||||
return []
|
||||
out = []
|
||||
for part in re.split(r"[\s,,、;;]+", s):
|
||||
key = part.strip()
|
||||
if key in ADVANTAGE_MAP:
|
||||
out.append(ADVANTAGE_MAP[key])
|
||||
return list(dict.fromkeys(out))
|
||||
|
||||
|
||||
def _parse_star(s: str) -> Optional[int]:
|
||||
if not s:
|
||||
return None
|
||||
s = _cell(s)
|
||||
return STAR_LEVEL_MAP.get(s)
|
||||
|
||||
|
||||
def _parse_cost_compare(s: str) -> Optional[Decimal]:
|
||||
if not s:
|
||||
return None
|
||||
s = _cell(s)
|
||||
try:
|
||||
return Decimal(s)
|
||||
except (InvalidOperation, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _first_line(s: str) -> str:
|
||||
if not s:
|
||||
return ""
|
||||
return (s.split("\n")[0] or "").strip()
|
||||
|
||||
|
||||
def _single_line(s: str, max_len: int = 255) -> str:
|
||||
if not s:
|
||||
return ""
|
||||
s = re.sub(r"\s+", " ", _cell(s))[:max_len]
|
||||
return s.strip() or ""
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Import materials from Excel file, sheet name '材料库'."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("excel_path", nargs="?", default=None, help="Path to the .xlsx file.")
|
||||
parser.add_argument(
|
||||
"--sheet",
|
||||
default="材料库",
|
||||
help="Sheet name to read (default: 材料库).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Only parse and report row count; do not write to DB.",
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, *args, **options):
|
||||
import openpyxl
|
||||
|
||||
excel_path = options.get("excel_path")
|
||||
if not excel_path:
|
||||
self.stdout.write(self.style.ERROR("Please provide excel_path (path to .xlsx)."))
|
||||
return
|
||||
|
||||
sheet_name = options.get("sheet") or "材料库"
|
||||
dry_run = bool(options.get("dry_run"))
|
||||
|
||||
try:
|
||||
wb = openpyxl.load_workbook(excel_path, read_only=True, data_only=True)
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR("Failed to open Excel: %s" % e))
|
||||
return
|
||||
|
||||
if sheet_name not in wb.sheetnames:
|
||||
self.stdout.write(self.style.ERROR("Sheet '%s' not found. Available: %s" % (sheet_name, wb.sheetnames)))
|
||||
wb.close()
|
||||
return
|
||||
|
||||
ws = wb[sheet_name]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
wb.close()
|
||||
|
||||
if len(rows) < 2:
|
||||
self.stdout.write(self.style.WARNING("No header or data rows."))
|
||||
return
|
||||
|
||||
# Build column index by header (first row that looks like header)
|
||||
header_row = None
|
||||
header_idx = {}
|
||||
for i, row in enumerate(rows):
|
||||
if not row:
|
||||
continue
|
||||
headers = [_norm_header(c) for c in row]
|
||||
if "材料名称" in headers:
|
||||
header_row = row
|
||||
for j, h in enumerate(headers):
|
||||
if h:
|
||||
header_idx[h] = j
|
||||
break
|
||||
|
||||
if not header_idx or "材料名称" not in header_idx:
|
||||
self.stdout.write(self.style.ERROR("Header row with '材料名称' not found."))
|
||||
return
|
||||
|
||||
data_start = i + 1 # first data row after header
|
||||
|
||||
def get(row: Tuple, key: str, default: str = "") -> str:
|
||||
j = header_idx.get(key, -1)
|
||||
if j < 0 or j >= len(row):
|
||||
return default
|
||||
return _cell(row[j])
|
||||
|
||||
# Resolve factory by 品牌:精确匹配 -> 模糊匹配(品牌包含 Excel 值 或 Excel 值包含品牌)
|
||||
factory_cache: Dict[str, Optional[Factory]] = {}
|
||||
|
||||
def get_factory(brand_name: str) -> Optional[Factory]:
|
||||
if not brand_name:
|
||||
return None
|
||||
if brand_name not in factory_cache:
|
||||
# 1) 精确匹配
|
||||
f = Factory.objects.filter(brand=brand_name).first()
|
||||
if f:
|
||||
factory_cache[brand_name] = f
|
||||
return f
|
||||
# 2) 模糊:工厂品牌包含 Excel 值(如 Excel「立邦」匹配「立邦中国」)
|
||||
f = Factory.objects.filter(brand__icontains=brand_name).first()
|
||||
if f:
|
||||
factory_cache[brand_name] = f
|
||||
return f
|
||||
# 3) 模糊:Excel 值包含某工厂品牌(如 Excel「立邦中国\n广州立邦」取首行后仍可匹配)
|
||||
brand_lower = brand_name.lower()
|
||||
for factory in Factory.objects.all():
|
||||
if factory.brand and factory.brand.lower() in brand_lower:
|
||||
factory_cache[brand_name] = factory
|
||||
return factory
|
||||
factory_cache[brand_name] = None
|
||||
return factory_cache[brand_name]
|
||||
|
||||
# 未识别品牌工厂:品牌列无法匹配到任一工厂时,统一关联到此工厂(不存在则创建)
|
||||
UNRECOGNIZED_BRAND = "未识别的品牌"
|
||||
unrecognized_factory, _ = Factory.objects.get_or_create(
|
||||
brand=UNRECOGNIZED_BRAND,
|
||||
defaults={
|
||||
"factory_name": "未识别的品牌工厂",
|
||||
"province": "-",
|
||||
"city": "-",
|
||||
},
|
||||
)
|
||||
|
||||
created = 0
|
||||
updated = 0
|
||||
skipped = 0
|
||||
errors: List[str] = []
|
||||
|
||||
for i in range(data_start, len(rows)):
|
||||
row = rows[i]
|
||||
if not row:
|
||||
continue
|
||||
name = get(row, "材料名称")
|
||||
if not name:
|
||||
continue
|
||||
|
||||
# Factory: 依据「品牌」列模糊匹配 Factory.brand(取首行),未匹配则关联到「未识别的品牌」工厂
|
||||
brand_val = _first_line(get(row, "品牌"))
|
||||
factory = get_factory(brand_val) if brand_val else None
|
||||
if not factory:
|
||||
factory = unrecognized_factory
|
||||
|
||||
major_cn = get(row, "专业类别")
|
||||
major_category = MAJOR_CATEGORY_MAP.get(major_cn, "architecture")
|
||||
material_category = _single_line(get(row, "材料分类"))
|
||||
material_subcategory = _single_line(get(row, "材料子类"))
|
||||
if not material_category:
|
||||
material_category = "-"
|
||||
if not material_subcategory:
|
||||
material_subcategory = "-"
|
||||
|
||||
application_scene = _parse_application_scene(get(row, "应用场景"))
|
||||
replace_type = REPLACE_TYPE_MAP.get(_cell(get(row, "替代材料")))
|
||||
advantage = _parse_advantage(get(row, "竞争优势"))
|
||||
|
||||
data = {
|
||||
"name": name[:255],
|
||||
"major_category": major_category,
|
||||
"material_category": material_category,
|
||||
"material_subcategory": material_subcategory,
|
||||
"spec": _single_line(get(row, "规格型号")) or None,
|
||||
"standard": _single_line(get(row, "符合标准")) or None,
|
||||
"application_scene": application_scene or None,
|
||||
"application_desc": _cell(get(row, "应用场景说明")) or None,
|
||||
"replace_type": replace_type,
|
||||
"advantage_desc": _cell(get(row, "竞争优势说明")) or None,
|
||||
"advantage": advantage or None,
|
||||
"cost_compare": _parse_cost_compare(get(row, "成本")),
|
||||
"cost_desc": _cell(get(row, "成本")) or None,
|
||||
"cases": _cell(get(row, "应用案例")) or None,
|
||||
"quality_level": _parse_star(get(row, "质量提升")),
|
||||
"durability_level": _parse_star(get(row, "耐久可靠")),
|
||||
"eco_level": _parse_star(get(row, "环保健康")),
|
||||
"carbon_level": _parse_star(get(row, "循环低碳")),
|
||||
"score_level": _parse_star(get(row, "评分等级")),
|
||||
"connection_method": _single_line(get(row, "连接方式")) or None,
|
||||
"construction_method": _single_line(get(row, "施工工艺")) or None,
|
||||
"limit_condition": _cell(get(row, "限制条件")) or None,
|
||||
"factory": factory,
|
||||
"status": "draft",
|
||||
}
|
||||
|
||||
if dry_run:
|
||||
created += 1
|
||||
continue
|
||||
|
||||
material_id = None
|
||||
if header_idx.get("材料ID") is not None and row[header_idx["材料ID"]] is not None:
|
||||
try:
|
||||
material_id = int(row[header_idx["材料ID"]])
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
if material_id and Material.objects.filter(id=material_id).exists():
|
||||
m = Material.objects.get(id=material_id)
|
||||
for k, v in data.items():
|
||||
setattr(m, k, v)
|
||||
m.save()
|
||||
updated += 1
|
||||
else:
|
||||
Material.objects.create(**data)
|
||||
created += 1
|
||||
|
||||
if dry_run:
|
||||
self.stdout.write(self.style.SUCCESS("Dry run: would process %s rows." % created))
|
||||
return
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Import complete. Created: %s, Updated: %s, Skipped: %s." % (created, updated, skipped))
|
||||
)
|
||||
if errors:
|
||||
for msg in errors[:20]:
|
||||
self.stdout.write(self.style.WARNING(msg))
|
||||
if len(errors) > 20:
|
||||
self.stdout.write(self.style.WARNING("... and %s more." % (len(errors) - 20)))
|
||||
|
|
@ -18,7 +18,7 @@ class MaterialSerializer(serializers.ModelSerializer):
|
|||
材料序列化器
|
||||
"""
|
||||
factory_name = serializers.CharField(source='factory.factory_name', read_only=True)
|
||||
brand = serializers.CharField(source='factory.brand', read_only=True)
|
||||
factory_short_name = serializers.CharField(source='factory.factory_short_name', read_only=True)
|
||||
major_category_display = serializers.CharField(source='get_major_category_display', read_only=True)
|
||||
replace_type_display = serializers.CharField(source='get_replace_type_display', read_only=True)
|
||||
application_scene = JSONListField(
|
||||
|
|
@ -33,9 +33,6 @@ class MaterialSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
advantage_display = serializers.SerializerMethodField()
|
||||
application_scene_display = serializers.SerializerMethodField()
|
||||
brochure = serializers.CharField(
|
||||
required=False, allow_blank=True, allow_null=True, default='',
|
||||
)
|
||||
brochure_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
|
|
@ -47,11 +44,14 @@ class MaterialSerializer(serializers.ModelSerializer):
|
|||
'advantage_desc', 'cost_compare', 'cost_desc', 'cases', 'brochure',
|
||||
'brochure_url', 'quality_level', 'durability_level', 'eco_level',
|
||||
'carbon_level', 'score_level', 'connection_method', 'construction_method',
|
||||
'limit_condition', 'factory', 'factory_name', 'brand',
|
||||
'limit_condition', 'factory', 'factory_name', 'factory_short_name',
|
||||
'status', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
def get_brochure_url(self, obj):
|
||||
"""
|
||||
获取宣传页图片URL
|
||||
"""
|
||||
if obj.brochure:
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
|
|
@ -78,7 +78,7 @@ class MaterialListSerializer(serializers.ModelSerializer):
|
|||
材料列表序列化器(简化版)
|
||||
"""
|
||||
factory_name = serializers.CharField(source='factory.factory_name', read_only=True)
|
||||
brand = serializers.CharField(source='factory.brand', read_only=True)
|
||||
factory_short_name = serializers.CharField(source='factory.factory_short_name', read_only=True)
|
||||
major_category_display = serializers.CharField(source='get_major_category_display', read_only=True)
|
||||
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ class MaterialListSerializer(serializers.ModelSerializer):
|
|||
model = Material
|
||||
fields = ['id', 'name', 'major_category', 'major_category_display',
|
||||
'material_category', 'material_subcategory', 'factory',
|
||||
'factory_name', 'brand', 'status', 'status_display']
|
||||
'factory_name', 'factory_short_name', 'status', 'status_display']
|
||||
|
||||
|
||||
class MaterialCategorySerializer(serializers.ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ def factory_statistics(request):
|
|||
})
|
||||
|
||||
factories_list = list(Factory.objects.values(
|
||||
'id', 'factory_name', 'brand', 'province', 'city', 'website'
|
||||
'id', 'factory_name', 'factory_short_name', 'province', 'city', 'website'
|
||||
))
|
||||
|
||||
return Response({
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ from django.conf import settings
|
|||
from django.conf.urls.static import static
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from .views import upload_image
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/auth/', include('apps.authentication.urls')),
|
||||
|
|
@ -16,7 +14,6 @@ urlpatterns = [
|
|||
path('api/material/', include('apps.material.urls')),
|
||||
path('api/dictionary/', include('apps.dictionary.urls')),
|
||||
path('api/statistics/', include('apps.statistics.urls')),
|
||||
path('api/upload/', upload_image, name='upload-image'),
|
||||
]
|
||||
|
||||
# 开发环境下提供媒体文件服务
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from rest_framework.decorators import api_view, permission_classes, parser_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
ALLOWED_IMAGE_TYPES = {
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp',
|
||||
}
|
||||
|
||||
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@parser_classes([MultiPartParser])
|
||||
def upload_image(request):
|
||||
file = request.FILES.get('file')
|
||||
if not file:
|
||||
return Response({'detail': '未提供文件'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if file.content_type not in ALLOWED_IMAGE_TYPES:
|
||||
return Response(
|
||||
{'detail': '仅支持 JPG/PNG/GIF/WEBP/BMP 格式的图片'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if file.size > MAX_FILE_SIZE:
|
||||
return Response(
|
||||
{'detail': '文件大小不能超过 10MB'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
ext = os.path.splitext(file.name)[1].lower()
|
||||
filename = f"{uuid.uuid4().hex}{ext}"
|
||||
relative_path = f"uploads/{filename}"
|
||||
|
||||
full_path = os.path.join(settings.MEDIA_ROOT, relative_path)
|
||||
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
||||
|
||||
with open(full_path, 'wb+') as dest:
|
||||
for chunk in file.chunks():
|
||||
dest.write(chunk)
|
||||
|
||||
url = request.build_absolute_uri(f"/{settings.MEDIA_URL}{relative_path}")
|
||||
|
||||
return Response({'url': url, 'path': relative_path})
|
||||
|
|
@ -5,4 +5,3 @@ psycopg2-binary==2.9.9
|
|||
django-cors-headers==4.3.0
|
||||
Pillow==10.1.0
|
||||
python-decouple==3.8
|
||||
openpyxl==3.1.2
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"sass": "^1.98.0",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vite": "^5.2.10"
|
||||
}
|
||||
},
|
||||
|
|
@ -503,365 +500,12 @@
|
|||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"node-addon-api": "^7.0.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||
"@parcel/watcher-win32-x64": "2.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"name": "@sxzz/popperjs-es",
|
||||
"version": "2.11.8",
|
||||
|
|
@ -1407,19 +1051,6 @@
|
|||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||
|
|
@ -1462,22 +1093,6 @@
|
|||
"integrity": "sha512-BQDPpiv5Nn+018ekcJK2oSD9PAD+E1bvXB0wgabc//dFVS/KvRqCgg0QOEUt3vBkx9XzB5a9BmkJCEZDBxVjVw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
|
@ -1490,13 +1105,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz",
|
||||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||
|
|
@ -1518,17 +1126,6 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
|
@ -1684,50 +1281,12 @@
|
|||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
|
||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
|
|
@ -1876,63 +1435,6 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz",
|
||||
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mlly": "^1.7.4",
|
||||
"pkg-types": "^2.3.0",
|
||||
"quansync": "^0.2.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
|
||||
|
|
@ -2001,38 +1503,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.1.tgz",
|
||||
"integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
|
||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly/node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.8",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
|
||||
|
|
@ -2051,69 +1521,18 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/normalize-wheel-es": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.2",
|
||||
"exsolve": "^1.0.7",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.8",
|
||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz",
|
||||
|
|
@ -2148,37 +1567,6 @@
|
|||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
|
||||
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz",
|
||||
|
|
@ -2224,64 +1612,6 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.98.0",
|
||||
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.98.0.tgz",
|
||||
"integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.1.5",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
|
|
@ -2291,184 +1621,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-literal": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz",
|
||||
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^9.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz",
|
||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.7.0.tgz",
|
||||
"integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.0",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"strip-literal": "^3.1.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz",
|
||||
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"acorn": "^8.15.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"webpack-virtual-modules": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-auto-import": {
|
||||
"version": "21.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
|
||||
"integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"picomatch": "^4.0.3",
|
||||
"unimport": "^5.6.0",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^4.0.0",
|
||||
"@vueuse/core": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
||||
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components": {
|
||||
"version": "31.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-31.0.0.tgz",
|
||||
"integrity": "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.0",
|
||||
"obug": "^2.1.1",
|
||||
"picomatch": "^4.0.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^3.2.2 || ^4.0.0",
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",
|
||||
|
|
@ -2565,13 +1723,6 @@
|
|||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"sass": "^1.98.0",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"vite": "^5.2.10"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
<template>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<template>
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
import api from './client'
|
||||
import api from './client'
|
||||
|
||||
const toFormData = (payload) => {
|
||||
const form = new FormData()
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null) return
|
||||
if (Array.isArray(value)) {
|
||||
form.append(key, JSON.stringify(value))
|
||||
return
|
||||
}
|
||||
form.append(key, value)
|
||||
})
|
||||
return form
|
||||
}
|
||||
|
||||
export const fetchMaterials = async (params) => {
|
||||
const { data } = await api.get('/material/', { params })
|
||||
|
|
@ -10,21 +23,18 @@ export const fetchMaterialDetail = async (id) => {
|
|||
return data
|
||||
}
|
||||
|
||||
export const createMaterial = async (payload) => {
|
||||
const { data } = await api.post('/material/', payload)
|
||||
export const createMaterial = async (payload, withFile = false) => {
|
||||
const dataPayload = withFile ? toFormData(payload) : payload
|
||||
const { data } = await api.post('/material/', dataPayload, {
|
||||
headers: withFile ? { 'Content-Type': 'multipart/form-data' } : undefined
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export const updateMaterial = async (id, payload) => {
|
||||
const { data } = await api.put(`/material/${id}/`, payload)
|
||||
return data
|
||||
}
|
||||
|
||||
export const uploadImage = async (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const { data } = await api.post('/upload/', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
export const updateMaterial = async (id, payload, withFile = false) => {
|
||||
const dataPayload = withFile ? toFormData(payload) : payload
|
||||
const { data } = await api.put(`/material/${id}/`, dataPayload, {
|
||||
headers: withFile ? { 'Content-Type': 'multipart/form-data' } : undefined
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
|
|
@ -108,7 +108,7 @@ const onLogout = () => {
|
|||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
|
|
@ -160,7 +160,6 @@ const onLogout = () => {
|
|||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
|
|
@ -186,8 +185,6 @@ const onLogout = () => {
|
|||
.content {
|
||||
flex: 1;
|
||||
background: var(--bg);
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { ElLoading } from 'element-plus'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
import './styles/base.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(ElLoading)
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
:root {
|
||||
:root {
|
||||
--brand-950: #0e1a2a;
|
||||
--brand-900: #16263c;
|
||||
--brand-800: #203754;
|
||||
|
|
@ -88,18 +88,7 @@ a {
|
|||
}
|
||||
|
||||
.table-maxheight {
|
||||
/* Element Plus el-table 的滚动容器在内部(wrapper),max-height 加在根节点不会触发表体滚动 */
|
||||
--table-maxheight: 560px;
|
||||
}
|
||||
|
||||
.table-maxheight .el-table__body-wrapper {
|
||||
max-height: var(--table-maxheight);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 部分版本/样式下表体通过 el-scrollbar 承载 */
|
||||
.table-maxheight .el-scrollbar__wrap {
|
||||
max-height: var(--table-maxheight);
|
||||
max-height: 560px;
|
||||
}
|
||||
|
||||
.el-table th.el-table__cell {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="page-title">材料分类管理</div>
|
||||
<el-tabs v-model="activeTab">
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openCategoryCreate">新增分类</el-button>
|
||||
</div>
|
||||
<el-table v-loading="categoryLoading" :data="categories" border>
|
||||
<el-table :data="categories" border>
|
||||
<el-table-column prop="name" label="分类名称" />
|
||||
<el-table-column prop="value" label="分类值" />
|
||||
<el-table-column prop="subcategory_count" label="子类数量" width="120" />
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</el-select>
|
||||
<el-button type="primary" @click="openSubcategoryCreate">新增子分类</el-button>
|
||||
</div>
|
||||
<el-table v-loading="subcategoryLoading" :data="subcategories" border>
|
||||
<el-table :data="subcategories" border>
|
||||
<el-table-column prop="category_name" label="所属分类" />
|
||||
<el-table-column prop="name" label="子分类名称" />
|
||||
<el-table-column prop="value" label="子分类值" />
|
||||
|
|
@ -97,8 +97,6 @@ import {
|
|||
const activeTab = ref('category')
|
||||
const categories = ref([])
|
||||
const subcategories = ref([])
|
||||
const categoryLoading = ref(false)
|
||||
const subcategoryLoading = ref(false)
|
||||
|
||||
const filters = reactive({
|
||||
category_id: ''
|
||||
|
|
@ -124,23 +122,13 @@ const subcategoryForm = reactive({
|
|||
})
|
||||
|
||||
const loadCategories = async () => {
|
||||
categoryLoading.value = true
|
||||
try {
|
||||
const data = await fetchCategories()
|
||||
categories.value = data.results || data
|
||||
} finally {
|
||||
categoryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSubcategories = async () => {
|
||||
subcategoryLoading.value = true
|
||||
try {
|
||||
const data = await fetchSubcategories(filters.category_id ? { category_id: filters.category_id } : {})
|
||||
subcategories.value = data.results || data
|
||||
} finally {
|
||||
subcategoryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetCategoryForm = () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="page-title">
|
||||
工厂详情
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<div class="card" v-if="factory">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="工厂全称">{{ factory.factory_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌">{{ factory.brand }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工厂简称">{{ factory.factory_short_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="经销商">{{ factory.dealer_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品分类">{{ factory.product_category }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地区">{{ formatRegion(factory.province, factory.city, factory.district) }}</el-descriptions-item>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
<div class="toolbar">
|
||||
<el-button v-if="isAdmin" type="primary" @click="openCreate">新增工厂</el-button>
|
||||
</div>
|
||||
<el-table v-loading="tableLoading" :data="factories" border :max-height="560">
|
||||
<el-table :data="factories" border class="table-maxheight">
|
||||
<el-table-column prop="factory_name" label="工厂全称" />
|
||||
<el-table-column prop="brand" label="品牌" />
|
||||
<el-table-column prop="factory_short_name" label="工厂简称" />
|
||||
<el-table-column prop="dealer_name" label="经销商" />
|
||||
<el-table-column label="地区">
|
||||
<template #default="scope">
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
<el-form-item label="工厂全称" required>
|
||||
<el-input v-model="form.factory_name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌" required>
|
||||
<el-input v-model="form.brand" />
|
||||
<el-form-item label="工厂简称" required>
|
||||
<el-input v-model="form.factory_short_name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="省市区" required>
|
||||
<el-cascader
|
||||
|
|
@ -85,7 +85,6 @@ import { fetchFactories, fetchFactoryDetail, createFactory, updateFactory, delet
|
|||
const router = useRouter()
|
||||
const { isAdmin } = useAuth()
|
||||
const factories = ref([])
|
||||
const tableLoading = ref(false)
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
|
|
@ -103,7 +102,7 @@ const form = reactive({
|
|||
dealer_name: '',
|
||||
product_category: '',
|
||||
factory_name: '',
|
||||
brand: '',
|
||||
factory_short_name: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
|
|
@ -112,21 +111,16 @@ const form = reactive({
|
|||
})
|
||||
|
||||
const loadFactories = async () => {
|
||||
tableLoading.value = true
|
||||
try {
|
||||
const data = await fetchFactories({ page: pagination.page, page_size: pagination.pageSize })
|
||||
factories.value = data.results || data
|
||||
pagination.total = data.count || factories.value.length
|
||||
} finally {
|
||||
tableLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.dealer_name = ''
|
||||
form.product_category = ''
|
||||
form.factory_name = ''
|
||||
form.brand = ''
|
||||
form.factory_short_name = ''
|
||||
form.province = ''
|
||||
form.city = ''
|
||||
form.district = ''
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="page-title">材料管理</div>
|
||||
<div class="toolbar">
|
||||
|
|
@ -13,12 +13,12 @@
|
|||
<el-button type="primary" @click="openCreate">新增材料</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="tableLoading" :data="materials" border :max-height="560">
|
||||
<el-table :data="materials" border class="table-maxheight">
|
||||
<el-table-column prop="name" label="材料名称" />
|
||||
<el-table-column prop="major_category_display" label="专业类别" />
|
||||
<el-table-column prop="material_category" label="材料分类" />
|
||||
<el-table-column prop="material_subcategory" label="材料子类" />
|
||||
<el-table-column prop="brand" label="所属工厂" />
|
||||
<el-table-column prop="factory_short_name" label="所属工厂" />
|
||||
<el-table-column prop="status_display" label="状态" width="120" />
|
||||
<el-table-column label="操作" width="320">
|
||||
<template #default="scope">
|
||||
|
|
@ -105,12 +105,11 @@
|
|||
<el-form-item label="宣传页">
|
||||
<el-upload
|
||||
class="upload"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="handleUpload"
|
||||
accept="image/*"
|
||||
:auto-upload="false"
|
||||
:show-file-list="true"
|
||||
:on-change="onFileChange"
|
||||
>
|
||||
<el-button :loading="uploading">{{ uploading ? '上传中...' : '选择图片' }}</el-button>
|
||||
<el-button>选择图片</el-button>
|
||||
</el-upload>
|
||||
<div v-if="form.brochure_url" class="preview">
|
||||
<img :src="form.brochure_url" alt="预览" />
|
||||
|
|
@ -152,7 +151,7 @@
|
|||
</el-form-item>
|
||||
<el-form-item label="所属工厂" v-if="isAdmin">
|
||||
<el-select v-model="form.factory">
|
||||
<el-option v-for="item in factories" :key="item.id" :label="item.brand" :value="item.id" />
|
||||
<el-option v-for="item in factories" :key="item.id" :label="item.factory_name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
|
@ -169,14 +168,13 @@ import { ref, reactive, onMounted } from 'vue'
|
|||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useAuth } from '@/store/auth'
|
||||
import { fetchMaterials, fetchMaterialDetail, createMaterial, updateMaterial, deleteMaterial, submitMaterial, approveMaterial, rejectMaterial, fetchMaterialChoices, uploadImage } from '@/api/material'
|
||||
import { fetchMaterials, fetchMaterialDetail, createMaterial, updateMaterial, deleteMaterial, submitMaterial, approveMaterial, rejectMaterial, fetchMaterialChoices } from '@/api/material'
|
||||
import { fetchCategories, fetchSubcategories } from '@/api/category'
|
||||
import { fetchFactorySimple } from '@/api/factory'
|
||||
|
||||
const router = useRouter()
|
||||
const { isAdmin } = useAuth()
|
||||
const materials = ref([])
|
||||
const tableLoading = ref(false)
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
|
|
@ -187,7 +185,7 @@ const dialogVisible = ref(false)
|
|||
const dialogTitle = ref('')
|
||||
const isEdit = ref(false)
|
||||
const currentId = ref(null)
|
||||
const uploading = ref(false)
|
||||
const fileRef = ref(null)
|
||||
|
||||
const filters = reactive({
|
||||
name: '',
|
||||
|
|
@ -235,8 +233,6 @@ const filterSubcategoryOptions = ref([])
|
|||
const allSubcategories = ref([])
|
||||
|
||||
const loadMaterials = async () => {
|
||||
tableLoading.value = true
|
||||
try {
|
||||
const data = await fetchMaterials({
|
||||
...filters,
|
||||
page: pagination.page,
|
||||
|
|
@ -244,9 +240,6 @@ const loadMaterials = async () => {
|
|||
})
|
||||
materials.value = data.results || data
|
||||
pagination.total = data.count || materials.value.length
|
||||
} finally {
|
||||
tableLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadChoices = async () => {
|
||||
|
|
@ -313,6 +306,7 @@ const resetForm = () => {
|
|||
limit_condition: '',
|
||||
factory: null
|
||||
})
|
||||
fileRef.value = null
|
||||
}
|
||||
|
||||
const onCategoryChange = async (val, resetSub = true) => {
|
||||
|
|
@ -352,31 +346,22 @@ const openEdit = async (row) => {
|
|||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleUpload = async (options) => {
|
||||
uploading.value = true
|
||||
try {
|
||||
const result = await uploadImage(options.file)
|
||||
form.brochure = result.path
|
||||
form.brochure_url = result.url
|
||||
ElMessage.success('图片上传成功')
|
||||
} catch {
|
||||
ElMessage.error('图片上传失败')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
const onFileChange = (file) => {
|
||||
fileRef.value = file.raw
|
||||
form.brochure = file.raw
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
const payload = { ...form }
|
||||
delete payload.brochure_url
|
||||
const withFile = !!fileRef.value
|
||||
if (!isAdmin.value) {
|
||||
delete payload.factory
|
||||
}
|
||||
if (isEdit.value) {
|
||||
await updateMaterial(currentId.value, payload)
|
||||
await updateMaterial(currentId.value, payload, withFile)
|
||||
} else {
|
||||
await createMaterial(payload)
|
||||
await createMaterial(payload, withFile)
|
||||
}
|
||||
ElMessage.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="page-title">用户管理</div>
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openCreate">新增用户</el-button>
|
||||
</div>
|
||||
<el-table v-loading="tableLoading" :data="users" border :max-height="560">
|
||||
<el-table :data="users" border class="table-maxheight">
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column prop="role" label="角色">
|
||||
<template #default="scope">
|
||||
|
|
@ -81,7 +81,6 @@ import { fetchUsers, createUser, updateUser, deleteUser, resetUserPassword } fro
|
|||
import { fetchFactorySimple } from '@/api/factory'
|
||||
|
||||
const users = ref([])
|
||||
const tableLoading = ref(false)
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
|
|
@ -104,14 +103,9 @@ const form = reactive({
|
|||
})
|
||||
|
||||
const loadUsers = async () => {
|
||||
tableLoading.value = true
|
||||
try {
|
||||
const data = await fetchUsers({ page: pagination.page, page_size: pagination.pageSize })
|
||||
users.value = data.results || data
|
||||
pagination.total = data.count || users.value.length
|
||||
} finally {
|
||||
tableLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadFactories = async () => {
|
||||
|
|
|
|||
|
|
@ -1,54 +1,15 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver({ importStyle: 'sass' })],
|
||||
dts: false
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver({ importStyle: 'sass' })],
|
||||
dts: false
|
||||
})
|
||||
],
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src')
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
silenceDeprecations: ['legacy-js-api']
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (!id.includes('node_modules')) return
|
||||
|
||||
if (id.includes('element-plus')) return 'element-plus'
|
||||
if (id.includes('echarts')) return 'echarts'
|
||||
|
||||
if (id.includes('axios')) return 'axios'
|
||||
if (id.includes('element-china-area-data')) return 'china-area'
|
||||
if (id.includes('highlight.js')) return 'highlight'
|
||||
|
||||
// other deps: let Rollup decide to avoid circular chunks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue