Import local app source

This commit is contained in:
Codex committed 2026-04-23 10:43:49 +00:00
commit 283972860c
65 files changed
+7942

No files matched your search

+1
View File
@@ -0,0 +1 @@
__version__ = "0.0.1"
+329
View File
@@ -0,0 +1,329 @@
# sunderer_app/api/custom_user_signup.py
import frappe
from frappe import _ # 虽然这里不直接用 _(),但保持导入
from frappe.utils import validate_email_address, random_string, get_url, generate_hash
import json # 导入 json 模块
@frappe.whitelist(allow_guest=True)
def custom_sign_up(email, full_name, password=None, send_email=True, **kwargs):
"""
自定义用户注册 API,模仿 Frappe 的 sign_up 但发送自定义邮件链接
Args:
email (str): 用户邮箱
full_name (str): 用户全名
password (str, optional): 用户密码 (如果需要立即设置)
send_email (bool, optional): 是否发送激活邮件. Defaults to True.
**kwargs: 其他可能传递给 User DocType 的字段
Returns:
dict: 包含操作结果的字典
"""
try:
# --- 1. 验证输入 ---
email = validate_email_address(email, throw=True) # 验证邮箱格式
if frappe.db.exists("User", {"email": email}):
# 不再使用 frappe.throw,而是直接返回错误信息
return {"status": "error", "message": _("该邮箱地址已被注册。")}
if not full_name:
return {"status": "error", "message": _("请输入全名。")}
# --- 2. 创建 User 文档 ---
user = frappe.get_doc({
"doctype": "User",
"email": email,
"first_name": full_name.split(" ")[0], # 简单拆分姓
"last_name": " ".join(full_name.split(" ")[1:]) if len(full_name.split(" ")) > 1 else "", # 其余部分为名
"enabled": 1 if password else 0, # 如果提供了密码,可以立即启用;否则等待激活
"new_password": password, # 如果提供了密码,设置它
"send_welcome_email": 0, # 我们自己发送邮件
"user_type": "Website User", # 通常注册的是网站用户
# **kwargs: 添加其他通过 kwargs 传递的字段
**kwargs
})
# --- 3. 插入用户文档 ---
user.insert(ignore_permissions=True)
# --- 4. 生成激活密钥 ---
# 如果没有提供密码,或者总是需要激活邮件,则生成密钥
if not password:
# Use frappe.generate_hash instead of get_password_reset_key
reset_key = frappe.generate_hash(length=100) # Generate a secure key
else:
reset_key = None # 如果密码已设置,可能不需要密钥,或用于其他目的
# --- 5. 保存用户文档 (如果需要更新密钥) ---
if reset_key:
user.reload() # 重新加载文档以确保它是最新的
user.reset_password_key = reset_key
user.save(ignore_permissions=True)
# --- 6. 发送自定义激活邮件 ---
if send_email and reset_key:
send_custom_activation_email(user.email, reset_key)
# --- 7. 返回结果 ---
if password:
return {"status": "success", "message": _("用户注册成功,您现在可以登录。")}
else:
return {"status": "success", "message": _("激活邮件已发送,请查收邮箱。")}
except frappe.DuplicateEntryError:
# 这个异常可能在 user.insert() 时由数据库唯一性约束触发
return {"status": "error", "message": _("该邮箱地址已被注册。")}
except Exception as e:
# 捕获所有其他意外错误
frappe.log_error(f"注册过程中发生未知错误,邮箱: {email}, 错误: {str(e)}")
return {"status": "error", "message": _("注册过程中发生未知错误,请稍后再试。")}
@frappe.whitelist(allow_guest=True)
def send_custom_activation_email(user_email, reset_key):
"""
发送包含自定义激活链接的邮件
"""
# 构造自定义激活 URL
activation_url = f"{get_url()}/activate-account?key={reset_key}" # Use get_url helper
# 使用中文主题和内容
subject = _("决裂者终端 - 激活您的账户") # 或者 _("决裂者终端 - 设置密码")
message = f"""
<p>您好!</p>
<p>请点击下方链接设置密码并激活您的账户:</p>
<p><a href="{activation_url}">{activation_url}</a></p>
<p>如果您未发起此请求,请忽略此邮件。</p>
"""
frappe.sendmail(
recipients=[user_email],
subject=subject,
message=message,
delayed=False # Send immediately
)
@frappe.whitelist(allow_guest=True)
def trigger_account_setup_email(email):
try:
# --- 1. 验证邮箱格式 (可选,但推荐) ---
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return {"exc": "请输入有效的邮箱地址!"}
# --- 2. 检查邮箱是否已存在于系统中 ---
if frappe.db.exists("User", {"email": email}):
return {"exc": "该邮箱地址已被注册。"}
# --- 3. 生成唯一的、有时效性的设置密钥 ---
setup_key = frappe.generate_hash(length=100) # 生成一个足够长的随机密钥
# --- 4. 创建初始的 User 记录 (禁用) ---
# 由于没有全名,我们暂时使用邮箱前缀作为名字,或者可以要求前端提供
username_parts = email.split('@')
first_name = username_parts[0] # 使用邮箱前缀作为名字
user_doc = frappe.get_doc({
"doctype": "User",
"email": email,
"first_name": first_name,
"last_name": "", # 可以留空或根据需要填充
"enabled": 0, # 初始状态为禁用
"send_welcome_email": 0, # 我们自己发送邮件
"user_type": "Website User",
# 可以根据需要添加其他默认字段
})
user_doc.insert(ignore_permissions=True) # 插入用户,此时是禁用的
print(f"Created initial disabled User: {user_doc.name}") # 调试日志
# --- 5. 创建 Signup Key DocType 记录 ---
signup_key_doc = frappe.new_doc("Signup Key")
signup_key_doc.key = setup_key # 使用生成的 setup_key 作为 key 字段
signup_key_doc.email = email # 存储关联的邮箱
signup_key_doc.used = 0 # 标记为未使用
# 如果 Signup Key DocType 有 expires_on 字段,可以设置过期时间
# signup_key_doc.expires_on = add_to_date(now_datetime(), hours=1) # 1小时后过期
signup_key_doc.insert(ignore_permissions=True)
print(f"Created Signup Key: {signup_key_doc.name} for email: {email}") # 调试日志
# --- 6. 构造 IncreaseAcc 链接 ---
site_url = get_url()
setup_url = f"{site_url}/IncreaseAcc?key={setup_key}"
# --- 7. 发送包含链接的邮件 ---
send_setup_link_email(email, setup_url)
# --- 8. 返回成功信息 ---
return {"message": "注册链接已发送至您的邮箱,请查收。"}
except frappe.DuplicateEntryError as e:
# 如果 User 或 Signup Key 插入时违反唯一性约束
frappe.log_error(f"Trigger account setup failed due to duplicate entry, email: {email}, error: {str(e)}")
return {"exc": "用户或密钥已存在,请稍后再试。"}
except Exception as e:
# 捕获所有其他意外错误
frappe.log_error(f"触发账户设置邮件失败,邮箱: {email}, 错误: {str(e)}")
import traceback
frappe.log_error(f"触发账户设置邮件错误堆栈:\n{traceback.format_exc()}")
# 如果 User 创建失败,尝试回滚 Signup Key (可选)
# frappe.delete_doc_if_exists("Signup Key", setup_key_name, ignore_permissions=True)
return {"exc": f"发送失败: {str(e)}"}
@frappe.whitelist(allow_guest=True)
def send_setup_link_email(user_email, setup_url):
"""
发送包含账户设置链接的邮件
"""
# 使用中文主题和内容
subject = "决裂者终端 - 完成账户注册"
message = f"""
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #00AFFF;">决裂者终端</h2>
<p>您好!</p>
<p>请点击下方链接完成您的账户注册:</p>
<p><a href="{setup_url}" style="display: inline-block; padding: 15px 30px; background-color: #00AFFF; color: white; text-decoration: none; border-radius: 5px; font-weight: bold;">完成注册</a></p>
<p><em>此链接将在 1 小时内有效。</em></p>
<p>如果您未发起此请求,请忽略此邮件。</p>
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ccc;">
<p style="color: #666; font-size: 12px;">© 2026 Sunderer Games. All rights reserved.</p>
</div>
"""
frappe.sendmail(
recipients=[user_email],
subject=subject,
message=message,
delayed=False # 立即发送
)
@frappe.whitelist(allow_guest=True)
@frappe.whitelist(allow_guest=True)
def complete_account(key, password):
print(f"[DEBUG] 开始执行 complete_account,传入的 key: {key[:10]}...")
try:
signup_key_doc_list = frappe.get_all("Signup Key", fields=["name", "email"], filters={"key": key, "used": 0})
print(f"[DEBUG] 查询到的 signup_key_doc_list: {signup_key_doc_list}")
if not signup_key_doc_list:
print("[DEBUG] 未找到有效的未使用密钥,抛出错误。")
frappe.throw("无效或已过期的密钥。", frappe.ValidationError)
return
signup_key_name = signup_key_doc_list[0].name
email = signup_key_doc_list[0].email
print(f"[DEBUG] 获取到的邮箱: {email}")
# --- 获取用户文档 ---
try:
user_doc = frappe.get_doc("User", email)
print(f"[DEBUG] 找到用户文档: {user_doc.name}, 启用状态: {user_doc.enabled}")
except frappe.DoesNotExistError:
print(f"[ERROR] 用户 {email} 不存在。")
frappe.throw(f"用户 {email} 不存在。")
return
# --- 新增:手动进行密码强度校验 ---
# 为密码强度检查准备用户信息
user_info = {
"email": user_doc.email or "",
"first_name": user_doc.first_name or "",
"last_name": user_doc.last_name or "",
"username": user_doc.name or "" # 如果 User 文档有 username 字段,也可以包含
}
# 调用 Frappe 的密码强度检查函数
# 注意:这个函数名可能因版本而异,但通常在 frappe.utils.password_strength 中
# 我们会尝试不同的函数名
try:
from frappe.utils.password_strength import get_password_strength
strength_result = get_password_strength(password, user_info)
except ImportError:
# 如果无法导入 get_password_strength,尝试其他方式
# 这里我们假设它在 frappe.utils 中,或者使用一个简单的自定义检查
strength_result = {"score": 0, "feedback": {"warning": "", "suggestions": ["密码至少需要8位字符,包含大小写字母和数字"]}}
# 或者,您可以在这里实现更复杂的自定义强度检查逻辑
score = strength_result.get('score', 0)
feedback = strength_result.get('feedback', {})
# 定义最低分数要求(Frappe 默认是2)
min_score = 2
if score < min_score:
suggestions = feedback.get('suggestions', [])
warning = feedback.get('warning', "")
error_message_parts = ["密码强度不足,请设置一个更强的密码。"]
if warning:
error_message_parts.append(f"警告: {warning}")
if suggestions:
error_message_parts.append(f"建议: {', '.join(suggestions)}")
# 使用 frappe.throw 返回结构化的错误信息,便于前端解析
frappe.throw({
"message": " ".join(error_message_parts),
"type": "password_strength",
"details": {
"score": score,
"min_score": min_score,
"suggestions": suggestions,
"warning": warning
}
})
# --- 新增结束:手动进行密码强度校验 ---
# --- 关键部分:更新密码和角色 ---
try:
user_doc.new_password = password
user_doc.enabled = 1 # 确保用户处于启用状态
# --- 添加或修改角色 ---
default_roles = ["Sunderer User"] # 根据你的需求调整
for role_name in default_roles:
if frappe.db.exists("Role", role_name):
if not any(d.role == role_name for d in user_doc.roles):
user_doc.append("roles", {"role": role_name})
print(f"[DEBUG] 已将角色 '{role_name}' 添加到用户 {email}。")
else:
print(f"[WARNING] 角色 '{role_name}' 不存在。跳过。")
# --- 角色添加结束 ---
# 在手动检查通过后,再执行 save(),此时应该不会再因为密码强度问题失败
user_doc.save(ignore_permissions=True)
print(f"[DEBUG] 成功更新用户 {email} 的密码、角色和启用状态。")
except Exception as pwd_error:
frappe.log_error(f"在完成账户 {email} 时更新用户详细信息(密码/角色)失败: {str(pwd_error)}")
print(f"[ERROR] 更新用户 {email} 详细信息失败: {str(pwd_error)}")
frappe.throw(f"设置用户账户失败: {str(pwd_error)}")
return
# --- 仅当用户信息更新成功后,才将密钥标记为已使用 ---
try:
frappe.db.set_value("Signup Key", signup_key_name, "used", 1)
print(f"[DEBUG] 已将 Signup Key {signup_key_name} 标记为已使用。")
except Exception as db_error:
frappe.log_error(f"用户更新后,将 Signup Key {signup_key_name} 标记为已使用失败: {str(db_error)}")
print(f"[ERROR] 将 Signup Key 标记为已使用失败: {str(db_error)}")
frappe.msgprint("密码已设置,但请就确认链接状态联系技术支持。")
frappe.db.commit()
print(f"[DEBUG] 已提交 key: {key[:10]}... 的事务。")
return {"message": "账户已激活,密码设置成功!"}
except frappe.AuthenticationError as e:
frappe.log_error(f"complete_account 中发生身份验证错误: {str(e)}")
print(f"[ERROR] 身份验证错误: {str(e)}")
frappe.throw(f"身份验证错误: {str(e)}")
except Exception as e:
frappe.log_error(f"激活账户时发生错误,key {key[:10]}...: {str(e)}")
print(f"[ERROR] 激活账户时发生错误,key {key[:10]}...: {str(e)}")
frappe.throw("激活您的账户时发生错误。请稍后重试或联系技术支持。")
+106
View File
@@ -0,0 +1,106 @@
# sunderer_app/sunderer_app/api.py
import frappe
from frappe.utils import random_string, now_datetime, add_to_date
import secrets
import string
@frappe.whitelist(allow_guest=True)
def generate_verification_code(length=6):
"""生成指定长度的数字验证码"""
code = ''.join([str(secrets.randbelow(10)) for _ in range(length)])
return code
@frappe.whitelist(allow_guest=True)
def send_verification_email(email, verification_code):
"""发送验证码邮件"""
try:
# 邮件标题
subject = "决裂者终端 - 验证码"
# 邮件内容
message = f"""
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #00AFFF;">决裂者终端</h2>
<p>您好!</p>
<p>您的验证码是:</p>
<div style="background: linear-gradient(135deg, #00AFFF, #0077CC);
padding: 20px;
border-radius: 10px;
text-align: center;
color: white;
font-size: 24px;
font-weight: bold;
letter-spacing: 5px;
margin: 20px 0;">
{verification_code}
</div>
<p>此验证码将在 10 分钟内有效。</p>
<p>如果您没有请求此验证码,请忽略此邮件。</p>
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ccc;">
<p style="color: #666; font-size: 12px;">© 2026 Sunderer Games. All rights reserved.</p>
</div>
"""
# 发送邮件
frappe.sendmail(
recipients=[email],
subject=subject,
message=message,
delayed=False # 立即发送
)
return {"success": True, "message": "验证码已发送到您的邮箱"}
except Exception as e:
frappe.log_error(f"发送验证码邮件失败: {str(e)}")
return {"success": False, "message": str(e)}
@frappe.whitelist(allow_guest=True)
def send_signup_code(email):
"""发送注册验证码的主函数"""
try:
# 验证邮箱格式
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return {"exc": "请输入有效的邮箱地址!"}
# 生成验证码
verification_code = generate_verification_code(6)
# 将验证码存储到缓存中(有效期10分钟)
cache_key = f"signup_code_{email}"
frappe.cache().set_value(cache_key, verification_code, expires_in_sec=600) # 10分钟
# 发送邮件
result = send_verification_email(email, verification_code)
if result["success"]:
return {"message": "验证码已发送到您的邮箱"}
else:
return {"exc": f"发送失败: {result['message']}"}
except Exception as e:
frappe.log_error(f"发送注册验证码失败: {str(e)}")
return {"exc": f"发送失败: {str(e)}"}
@frappe.whitelist(allow_guest=True)
def verify_signup_code(email, code):
"""验证注册验证码"""
try:
cache_key = f"signup_code_{email}"
stored_code = frappe.cache().get_value(cache_key)
if not stored_code:
return {"exc": "验证码已过期,请重新获取"}
if str(stored_code) != str(code):
return {"exc": "验证码错误"}
# 验证成功,清除缓存
frappe.cache().delete_value(cache_key)
return {"success": True}
except Exception as e:
frappe.log_error(f"验证注册验证码失败: {str(e)}")
return {"exc": f"验证失败: {str(e)}"}
View File
Whitespace-only changes.
+19
View File
@@ -0,0 +1,19 @@
# sunderer_app/sunderer_app/custom_user.py
import frappe
from frappe.core.doctype.user.user import User as CoreUser
class CustomUser(CoreUser):
def get_redirect_url(self):
"""自定义用户创建后的重定向URL"""
# 检查是否是新用户注册
if self.flags.in_insert and self.new_password:
# 新用户设置密码后,跳转到自定义页面
return "/custom-update-password"
# 默认行为
return super().get_redirect_url()
def after_user_creation(doc, method):
"""用户创建后触发的钩子"""
# 这里可以添加额外的用户初始化逻辑
pass
+12
View File
@@ -0,0 +1,12 @@
# delete_signup_key.py
import frappe
def delete_signup_key_doctype():
frappe.delete_doc("DocType", "Signup Key", force=True)
print("Signup Key DocType deleted.")
if __name__ == "__main__":
frappe.connect()
delete_signup_key_doctype()
frappe.db.commit()
frappe.destroy()
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
+5
View File
@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon3.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>决裂者终端用户中心</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
File diff suppressed because it is too large. Load diff
+23
View File
@@ -0,0 +1,23 @@
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --config vite.config.js",
"build": "vite build --config vite.config.js",
"preview": "vite preview --config vite.config.js"
},
"dependencies": {
"@vicons/ionicons5": "^0.13.0",
"naive-ui": "^2.43.2",
"vue": "^3.4.0",
"vue-router": "^4.2.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"unplugin-auto-import": "^21.0.0",
"unplugin-vue-components": "^31.0.0",
"vite": "^5.0.0"
}
}
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3200 3200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><circle cx="1600" cy="1600" r="1599.79" style="fill:#fff;"/><g><g><clipPath id="_clip1"><path d="M1599.85,0l0.821,0l3.037,0.004c214.309,0.546 428.517,43.913 628.742,130.046c140.346,60.525 270.329,140.571 386.483,236.683l-2100.85,773.042l87.496,94.633l72.692,78.654l42.42,45.867l62.075,67.163l62.759,67.908l42.208,45.65l77.242,83.571l26.233,28.346l149.121,161.308l-181.292,349.696l261.65,-273.775l314.029,-332.013l-118.02,-87.262l-4.934,-3.634l-3.608,-2.691l-6.408,-4.721l-14.334,-10.617l695.725,-470.883l558.85,-590.804c332.971,286.137 547.075,706.833 557.613,1177.41l0.012,0.463l0.092,4.433l0.017,1.008l0.07,4.075l0.025,1.392l0.055,3.792l0.02,1.712l0.038,3.538l0.017,2.037l0.025,3.254l0.016,2.534l0.013,2.791l0.008,4.446l0,1.696l-0.008,4.042l-0.009,2.5l-0.012,2.304l-0.021,2.925l-0.017,1.85l-0.029,3.187l-0.016,1.534l-0.046,3.441l-0.017,1.192l-0.058,3.746l-0.013,0.733c-13.146,757.438 -553.712,1387.3 -1269.93,1537.7l-0.021,0.004c-10.2,2.142 -20.442,4.188 -30.721,6.134l-0.012,0.004l-4.025,0.754l-0.584,0.108l-3.762,0.7l-0.829,0.15l-3.65,0.667l-0.904,0.162l-3.642,0.65l-0.838,0.15l-3.745,0.663l-0.521,0.087c-4.254,0.746 -8.567,1.48 -12.888,2.196l-6,0.988l-5.662,0.904c-23.659,3.746 -47.384,6.958 -71.192,9.65l-0.146,0.017l-5.091,0.562l-0.175,0.021l-7.363,0.787l-4.558,0.471c-1.679,0.167 -3.359,0.338 -5.042,0.504l-4.958,0.48l-2.763,0.262l-2.133,0.2l-3.567,0.321l-1.346,0.121l-3.712,0.329l-1.217,0.104l-3.712,0.313l-1.234,0.104l-3.65,0.3l-1.308,0.104l-3.567,0.279l-1.404,0.113l-3.475,0.258l-1.512,0.113l-3.371,0.245l-1.629,0.117l-3.263,0.225l-1.75,0.121l-3.154,0.208l-1.867,0.121l-3.045,0.192l-1.992,0.125l-2.938,0.175l-2.112,0.125l-2.829,0.158l-2.229,0.125l-2.721,0.146l-2.35,0.125l-2.617,0.133l-2.462,0.121l-2.517,0.121l-2.575,0.116l-2.413,0.109l-2.687,0.116l-2.313,0.096l-2.804,0.109l-2.208,0.087l-2.917,0.104l-2.104,0.075l-3.033,0.104l-2,0.063l-3.159,0.096l-1.883,0.054l-3.292,0.087l-1.758,0.05l-3.446,0.08l-1.616,0.037l-3.638,0.075l-1.429,0.029l-3.913,0.071l-1.162,0.017l-4.379,0.062l-0.704,0.013l-5.109,0.054c-1.721,0.017 -3.441,0.029 -5.162,0.042l-5.092,0.025l-1.312,0.004l-3.755,0.004l-1.725,-0.004l-2.195,-0c-249.413,-0.692 -498.642,-59.346 -725.784,-175.863l-3.054,-1.57c-100.379,-51.846 -194.579,-114.005 -281.167,-185.038l-5.125,-4.225l2100.96,-773.075l-87.475,-94.654l-72.713,-78.658l-42.4,-45.863l-62.075,-67.162l-62.779,-67.888l-42.192,-45.654l-77.237,-83.567l-26.233,-28.366l-149.121,-161.309l181.291,-349.696l-261.65,273.821l-319.754,338.013l125.454,93.779l-8.395,6.662l-635.53,447.688l-583.158,616.529c-229.687,-197.396 -402.804,-458.742 -491.604,-756.308c-42.083,-141.417 -64.196,-287.492 -66.346,-433.821l-0.092,-7.542l-0.054,-6.454l-0.021,-4.854l0,-0.071l-0.008,-5.783l0,-0.313l0.025,-7.458l0.025,-3.671l0.008,-1.375l0.034,-3.25l0.004,-0.625c1.721,-157.862 26.687,-315.55 74.883,-467.458c167.892,-527.492 602.034,-936.642 1144.17,-1069.23l1.533,-0.375l1.854,-0.45c97.83,-23.642 197.455,-37.938 297.471,-42.892l1.571,-0.075l2.467,-0.121l3.016,-0.137l1.367,-0.063c23.608,-1.042 47.454,-1.571 71.421,-1.575Z"/></clipPath><g clip-path="url(#_clip1)"><use id="_Image3_" serif:id="_Image3" xlink:href="#_Image2" x="0" y="0" width="768px" height="768px" transform="matrix(4.16667,0,0,4.16667,0,0)"/></g></g></g></g><defs><image id="_Image2" width="768px" height="768px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAMACAYAAACTgQCOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOy9abc1OXKdl71WfdJkS2R3Te2JP9Br2cuyRsuSLcuzLcsyNVEiRXEemxL1pyxroMhmd1dX9fWH99775gBE7AhMAWDvD1X3IIEdyHPyAA/yIPL91kFRFEWF0bd/7gcvH1+9XP73Xnp7fa+uVrzb5vxSHjlLJfa/+c//9LeEKBRFUVRHcUCmKIpqpO/8/A9eMgyfgPVMhVHwD4J/MpQSW/L82K+X49/+F3+GcxRFUVQDcXClKIpy6ju/8MMrwt6pmvCf91DgH4n/b/9LLhAoiqI84uBJURSV0Xd+8YcvCLhey/eA/2QYJ/xfXqKLj2T8t/99+OPf/Vf/Aec4iqKohDg4UhS1vT79xR9dGTRH703hvxH4p6xrwz8QO/c6e9dfiI/Av1T33/15LgwoitpbHAQpitpGn/7Sj9Q7+jXgf7q7/imPkfAv9jP/WoV/xfff/QUuDCiK2kMc7CiKWlKf/tKPnrhXE/5X3fIj1HXv90+U1YT/56ouX1f2fXl06Q/+4n/IeZKiqOXEgY2iqKn16a989cp/OQI/0uUI/Dfd8pNvGG2/fzJUx2Rfybcl/Eu+f/CXuDCgKGpecQCjKGoqffYrX33E/QyIP4RsGynY8pOsvgj8z5Dsi9SVy23wn2r/B3/5z3I+pShqGnHAoigqtD771a8unPcEWwP8l2z5EWJuA/+TJfu2h//8ufzBX+GCgKKouOIARVFUGH32a1fYz0Pk7Q8E/nrA/2Dwv/y5GvynfrUB42c687HIsQCR4D93Tfz7v8pFAUVRMcTBiKKoYfrs1746UdjtYDj4n/yuf8qjFP6B9z4L/4477qP2++fap45hn8OH//37/4YLAoqixoiDD0VR3fTZr//4JUlIHvh33Dn+8NIA/8hdf8hrMPwz2Tcc/Kca/vu/9uc4J1MU1UUcbCiKaqrPfv3Ht7v8tu0b1ybCXX/IK7FwUL0I/7nYkmdT+Ad+ech0JtklpA/Jg6k1rKEfUn0uBiiKaikOMBRFVdXnv/EB+C+QlaMjAbaSd/1TbSAvw11/Ieaq8M8n/WR8APiXqqS9dPhP+f/hf8sFAUVR9cQBhaKoYr1B/3Gk7iI//ni+zB3ywH/qrn+qbU347wX+Keva8D9Tsi/k+7FC7Sf9uOFfqpuqdqv/h3Line truncated

After

Width:  |  Height:  |  Size: 60 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+21
View File
@@ -0,0 +1,21 @@
<!-- src/App.vue -->
<script setup>
import { RouterView } from 'vue-router'
</script>
<template>
<div id="app">
<RouterView />
</div>
</template>
<style>
/* 确保全屏撑开 */
html, body, #app {
height: 100%;
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
}
</style>
+69
View File
@@ -0,0 +1,69 @@
<script setup>
import LoginView from './views/LoginView.vue'
import { ref } from 'vue'
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
async function handleLogin() {
loading.value = true
error.value = ''
try {
const res = await fetch('/api/method/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ usr: email.value, pwd: password.value })
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.message || 'Invalid credentials')
}
// 登录成功!跳转到 Frappe 应用
window.location.href = 'http://172.25.162.172:8000/app' // 或你的目标页面
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
</script>
<template>
<div style="display: flex; justify-content: center; align-items: center; height: 100vh; background: #0f0f1b; color: white;">
<div style="background: #1a1a2e; padding: 2rem; border-radius: 10px; width: 300px; box-shadow: 0 4px 20px rgba(0,0,0,0.5);">
<h2 style="text-align: center; margin-bottom: 1.5rem;">Sunderer Login</h2>
<form @submit.prevent="handleLogin">
<input
v-model="email"
type="text"
placeholder="Email or Username"
style="width: 100%; padding: 0.5rem; margin: 0.5rem 0; border: 1px solid #444; background: #0f0f1b; color: white; border-radius: 4px;"
required
/>
<input
v-model="password"
type="password"
placeholder="Password"
style="width: 100%; padding: 0.5rem; margin: 0.5rem 0; border: 1px solid #444; background: #0f0f1b; color: white; border-radius: 4px;"
required
/>
<div v-if="error" style="color: #ff6b6b; margin-top: 0.5rem; font-size: 0.9rem;">
{{ error }}
</div>
<button
type="submit"
:disabled="loading"
style="width: 100%; padding: 0.5rem; background: #4e54c8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem;"
>
{{ loading ? 'Signing in...' : 'Sign In' }}
</button>
</form>
</div>
</div>
</template>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

@@ -0,0 +1,43 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+12
View File
@@ -0,0 +1,12 @@
// src/increaseacc.js
import { createApp } from 'vue'
import IncreaseAccView from './views/IncreaseAccView.vue'
const app = createApp(IncreaseAccView)
// 挂载到 DOM
app.mount('#app')
// 导出 app 实例,以便 UMD 格式可以访问
// 如果指定了 name: 'IncreaseAccApp',这个 app 实例将可通过 window.IncreaseAccApp 访问
export default app
@@ -0,0 +1,141 @@
<!-- src/layouts/AppLayout.vue -->
<template>
<n-layout position="absolute">
<!-- 顶部栏 -->
<n-layout-header class="header" bordered>
<div class="header-content">
<h1>My Admin Panel</h1>
<div class="header-actions">
<n-dropdown trigger="click" :options="profileOptions" @select="handleProfileSelect">
<n-button text>
<n-avatar round size="small" :src="userAvatar" />
{{ userName }}
</n-button>
</n-dropdown>
</div>
</div>
</n-layout-header>
<n-layout has-sider position="absolute" style="top: 64px;">
<!-- 侧边栏 -->
<n-layout-sider
bordered
collapse-mode="width"
:collapsed-width="64"
:width="240"
:native-scrollbar="false"
show-trigger
:collapsed="sCollapsed"
@collapse="sCollapsed = !sCollapsed"
@expand="sCollapsed = !sCollapsed"
>
<n-menu
v-model:value="activeKey"
:collapsed="sCollapsed"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
:indent="18"
/>
</n-layout-sider>
<!-- 主内容区 -->
<n-layout content-style="padding: 24px;">
<router-view /> <!-- 这里是后台各页面的渲染位置 -->
</n-layout>
</n-layout>
</n-layout>
</template>
<script setup>
import { ref, h } from 'vue'
import { useRouter } from 'vue-router'
import { NIcon, NText } from 'naive-ui'
import { BookOutline as BookIcon, PersonOutline as PersonIcon, LogOutOutline as LogoutIcon } from '@vicons/ionicons5'
const router = useRouter()
const sCollapsed = ref(false)
const activeKey = ref('dashboard')
// 示例用户信息
const userName = ref('Admin User')
const userAvatar = ref('https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg')
function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) })
}
function renderMenuLabel(option) {
if (sCollapsed.value) {
return h(NIcon, null, { default: () => h(option.icon) });
} else {
return h('span', null, option.label);
}
}
// 侧边栏菜单
const menuOptions = [
{
label: 'Dashboard',
key: 'dashboard',
icon: renderIcon(BookIcon),
props: { onClick: () => router.push('/app/dashboard') }
},
{
label: 'Users',
key: 'users',
icon: renderIcon(PersonIcon),
props: { onClick: () => router.push('/app/users') }
},
// 添加更多菜单项...
]
// 顶栏用户菜单
const profileOptions = [
{ label: 'Profile', key: 'profile' },
{ label: 'Settings', key: 'settings' },
{ type: 'divider', key: 'd1' },
{ label: 'Logout', key: 'logout', icon: renderIcon(LogoutIcon) }
]
// 顶栏菜单选择处理
const handleProfileSelect = (key) => {
switch (key) {
case 'profile':
console.log('Go to Profile');
break;
case 'settings':
console.log('Go to Settings');
break;
case 'logout':
// 执行登出逻辑,例如清除 token
localStorage.removeItem('authToken'); // 示例
router.push('/login'); // 跳转回登录页
break;
default:
break;
}
}
</script>
<style scoped>
.header {
height: 64px;
display: flex;
align-items: center;
padding: 0 20px;
background-color: #fff;
}
.header-content {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-actions .n-button .n-avatar {
margin-right: 8px;
}
</style>
+10
View File
@@ -0,0 +1,10 @@
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 确保这个导入是正确的
const app = createApp(App)
app.use(router) // 使用路由器插件
app.mount('#app')
+80
View File
@@ -0,0 +1,80 @@
// C:\vueprj\my-vue-app\src\router.js
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from './views/LoginView.vue'
import RegisterView from './views/RegisterView.vue'
import CustomPasswordSetup from './views/CustomPasswordSetup.vue'
// --- 新增:导入后台布局和页面 ---
import AppLayout from './layouts/Applayout.vue' // 导入你创建的后台布局
const DashboardView = () => import('./views/sundererapp/DashboardView.vue') // 动态导入后台页面
const UsersView = () => import('./views/sundererapp/UsersView.vue') // 动态导入后台页面
// --- 新增结束 ---
const routes = [
{
path: '/',
name: 'Home',
component: LoginView
},
{
path: '/login',
name: 'Login',
component: LoginView
},
{
path: '/register',
name: 'Register',
component: RegisterView
},
{
path: '/update-password',
name: 'CustomPasswordSetup',
component: CustomPasswordSetup
},
{
path: '/custom-update-password',
name: 'CustomPasswordSetupAlt',
component: CustomPasswordSetup
},
// --- 新增:后台路由组 ---
{
path: '/sundererapp', // 后台页面的基路径
component: AppLayout, // 使用 AppLayout 作为父组件
meta: { requiresAuth: true }, // 可选:标记此路由组需要认证
children: [ // 定义后台页面的子路由
{
path: '', // /sundererapp 访问时,默认重定向到 dashboard
redirect: '/sundererapp/dashboard'
},
{
path: 'dashboard', // 最终路径为 /sundererapp/dashboard
name: 'AppDashboard',
component: DashboardView // 渲染 DashboardView 组件
},
{
path: 'users', // 最终路径为 /sundererapp/users
name: 'AppUsers',
component: UsersView // 渲染 UsersView 组件
},
// 你可以在这里添加更多后台页面,例如:
// {
// path: 'settings',
// name: 'AppSettings',
// component: () => import('./views/sundererapp/SettingsView.vue')
// }
]
},
// --- 新增结束 ---
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: LoginView
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
+79
View File
@@ -0,0 +1,79 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
+21
View File
@@ -0,0 +1,21 @@
// src/main.js (或 src/sundererapp.js)
import { createApp } from 'vue'
import App from './App.vue' // 你可能有一个顶层的 App.vue,或者可以直接创建一个简单的根组件
import router from './router.js' // 导入你配置好的路由
// 如果你的 App.vue 是一个空壳,只是为了挂载 router-view
// 你也可以不导入 App.vue,直接创建一个根组件
// const RootComponent = {
// template: '<router-view />'
// }
// const app = createApp(RootComponent);
const app = createApp(App); // 创建 Vue 应用实例
app.use(router) // 安装 Vue Router 插件
app.mount('#app') // 挂载到 DOM
// 导出 app 实例,以便 UMD 格式可以访问
// 如果 vite.config.js 中指定了 name: 'SundererApp',这个 app 实例将可通过 window.SundererApp 访问
export default app
@@ -0,0 +1,166 @@
<template>
<div class="password-setup-container">
<div class="setup-card">
<h2>设置密码</h2>
<form @submit.prevent="setupPassword">
<div class="input-group">
<label>新密码</label>
<input
v-model="newPassword"
type="password"
placeholder="请输入新密码"
class="input-field"
required
/>
</div>
<div class="input-group">
<label>确认密码</label>
<input
v-model="confirmPassword"
type="password"
placeholder="请再次输入密码"
class="input-field"
required
/>
</div>
<div v-if="error" class="error">{{ error }}</div>
<button
type="submit"
:disabled="loading"
class="btn-primary"
>
{{ loading ? '设置中...' : '设置密码' }}
</button>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const newPassword = ref('')
const confirmPassword = ref('')
const error = ref('')
const loading = ref(false)
const setupPassword = async () => {
if (newPassword.value !== confirmPassword.value) {
error.value = '两次输入的密码不一致'
return
}
if (newPassword.value.length < 6) {
error.value = '密码长度至少为6位'
return
}
loading.value = true
error.value = ''
try {
const actualKey = route.query.key
if (!actualKey) {
error.value = '无效的密码重置链接'
return
}
const response = await fetch('http://172.25.162.172:8000/api/method/frappe.core.doctype.user.user.update_password', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: actualKey,
new_password: newPassword.value
})
})
const result = await response.json()
if (result.exc) {
error.value = result.exc || '设置密码失败'
} else {
alert('密码设置成功!')
router.push('/login')
}
} catch (err) {
error.value = '网络错误,请稍后再试'
console.error(err)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.password-setup-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #00AFFF, #0077CC);
color: white;
}
.setup-card {
width: 400px;
padding: 30px;
background: rgba(0, 0, 0, 0.295);
backdrop-filter: blur(12px);
border-radius: 20px;
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
border: 1px solid rgba(0, 175, 255, 0.3);
text-align: center;
}
.input-group {
margin-bottom: 15px;
text-align: left;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.input-field {
width: 94%;
padding: 12px;
border: none;
border-radius: 10px;
background: rgb(255, 255, 255);
color: rgb(0, 0, 0);
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
}
.btn-primary {
width: 100%;
padding: 12px;
background: linear-gradient(90deg, #4CAF50, #45a049);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin-top: 20px;
font-size: 1rem;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
.error {
color: #ff6b6b;
margin: 10px 0;
font-size: 14px;
}
</style>
@@ -0,0 +1,283 @@
<template>
<div class="container">
<h2>设置您的密码</h2>
<form @submit.prevent="setupPassword" class="register-view">
<div class="form-group">
<input type="password" id="password" v-model="password" required placeholder="请输入新密码" class="input-field"/>
</div>
<div class="form-group">
<input type="password" id="confirmPassword" v-model="confirmPassword" required placeholder="请确认新密码" class="input-field"/>
</div>
<button type="submit" :disabled="loading" class="btn-primary">{{ loading ? '正在设置...' : '设置密码' }}</button>
<!-- 用于显示普通错误信息 -->
<div v-if="message" :class="{ 'success-message': success, 'error-message': !success }">{{ message }}</div>
<!-- 专门用于显示密码强度错误信息 -->
<div v-if="passwordStrengthMessage" class="password-strength-error">
{{ passwordStrengthMessage }}
</div>
</form>
<div class="back-to-login">
<button @click="goBackToLogin" class="btn-secondary">← 返回登录</button>
</div>
</div>
</template>
<script>
export default {
name: 'IncreaseAcc',
data() {
return {
password: '',
confirmPassword: '',
message: '', // 用于存储一般性错误或成功信息
passwordStrengthMessage: '', // 专门用于存储密码强度错误信息
success: false,
loading: false,
key: null // 存储从 URL 获取的 key
}
},
mounted() {
const urlParams = new URLSearchParams(window.location.search);
this.key = urlParams.get('key');
console.log("从 URL 获取的 key:", this.key);
if (!this.key) {
this.message = '未找到密钥。';
this.success = false;
}
},
methods: {
async setupPassword() {
// 清空之前的消息
this.message = '';
this.passwordStrengthMessage = '';
this.success = false;
if (this.password !== this.confirmPassword) {
this.message = '两次输入的密码不匹配。';
this.success = false;
return;
}
if (!this.key) {
this.message = '缺少密钥参数。';
this.success = false;
return;
}
this.loading = true;
try {
const response = await fetch('/api/method/sunderer_app.api.custom_user_signup.complete_account', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({key: this.key, password: this.password})
});
const data = await response.json();
console.log("API 响应数据:", data);
if (response.ok) {
// 成功情况
this.message = "账户已激活,密码设置成功!";
this.success = true;
setTimeout(() => {
window.location.href = 'http://172.25.162.172:8000/';
}, 5000); // 5秒后跳转
} else {
// --- 修改错误处理逻辑 ---
let errorMessage = '操作失败,请稍后再试。';
let isPasswordStrengthError = false; // 标记是否是密码强度错误
if (data && data.exc) {
// 尝试解析 frappe.throw 返回的 exc 信息
try {
// Frappe 可能会将多个错误信息包装在一个字符串中,或者直接返回一个对象
let parsedExc = data.exc;
if (typeof data.exc === 'string') {
// 尝试解析为 JSON 对象(如果后端返回了 JSON 结构的 exc)
parsedExc = JSON.parse(data.exc);
}
// 检查 parsedExc 是否是我们之前在后端构造的密码强度错误对象
if (parsedExc && parsedExc.type === 'password_strength') {
// 是密码强度错误
isPasswordStrengthError = true;
// 构建更友好的提示信息
const details = parsedExc.details;
const warning = details.warning || "";
const suggestions = details.suggestions || [];
const parts = [];
if (warning) parts.push(warning);
if (suggestions.length > 0) parts.push(`建议: ${suggestions.join(', ')}`);
this.passwordStrengthMessage = parts.join(' ');
} else {
// 不是密码强度错误,处理其他 exc
errorMessage = parsedExc._server_messages || parsedExc.message || data._server_messages || '未知错误';
this.message = errorMessage;
}
} catch (parseError) {
// 如果 exc 不是 JSON 格式,或者解析失败,则直接显示 exc 内容
console.error("解析后端错误信息失败:", parseError);
// 检查 exc 字符串是否包含密码强度相关的关键词
if (typeof data.exc === 'string' &&
(data.exc.includes("密码强度") || data.exc.includes("password") || data.exc.includes("strength") || data.exc.includes("重复"))) {
isPasswordStrengthError = true;
// 尝试从原始字符串中提取一些信息,或者给出通用提示
// 这里可以根据实际返回的字符串进行更精细的解析
this.passwordStrengthMessage = "密码强度不足,请设置一个更强的密码。";
} else {
this.message = data.exc || errorMessage;
}
}
} else if (data && data.message) {
// 检查 data.message 是否是密码强度错误对象
if (typeof data.message === 'object' && data.message.type === 'password_strength') {
isPasswordStrengthError = true;
const details = data.message.details;
const warning = details.warning || "";
const suggestions = details.suggestions || [];
const parts = [];
if (warning) parts.push(warning);
if (suggestions.length > 0) parts.push(`建议: ${suggestions.join(', ')}`);
this.passwordStrengthMessage = parts.join(' ');
} else {
// 不是密码强度错误,处理其他 message
errorMessage = typeof data.message === 'object' ? JSON.stringify(data.message) : data.message;
this.message = errorMessage;
}
} else {
// 如果 data 既没有 exc 也没有 message,可能是网络错误或其他问题
this.message = '服务器返回了错误,但没有详细信息。';
}
// 如果不是密码强度错误,则设置 general message
if (!isPasswordStrengthError && !this.passwordStrengthMessage) {
this.message = errorMessage; // 这行其实可能已经在上面的条件分支中设置了
}
// --- 修改结束 ---
}
} catch (error) {
console.error("网络错误或请求失败:", error);
this.message = '网络错误或请求失败。';
this.success = false;
} finally {
this.loading = false;
}
},
goBackToLogin() {
window.location.href = '/login'; // 跳转到登录页
}
}
}
</script>
<style scoped>
/* ... (保持原有的 CSS 不变) ... */
.register-view {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #00AFFF, #0077CC);
color: white;
}
.container {
width: 400px;
padding: 30px;
background: rgba(0, 0, 0, 0.295);
backdrop-filter: blur(12px);
border-radius: 20px;
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
border: 1px solid rgba(0, 175, 255, 0.3);
text-align: center;
}
h2 {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.input-field {
width: 100%;
padding: 20px;
margin: 10px 0;
border: none;
border-radius: 10px;
background: rgb(255, 255, 255);
color: rgb(0, 0, 0);
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
}
.btn-primary {
width: 100%;
padding: 12px;
background: linear-gradient(90deg, #4CAF50, #45a049);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin: 10px 0;
font-size: 1rem;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-primary:disabled {
background: #666;
cursor: not-allowed;
}
.success-message {
color: green;
margin-top: 15px;
text-align: center;
}
.error-message {
color: red;
margin-top: 15px;
text-align: center;
}
/* 新增:专门用于显示密码强度错误的样式 */
.password-strength-error {
color: orange; /* 使用橙色区分于红色的一般错误 */
margin-top: 15px;
text-align: center;
font-size: 0.9em; /* 字体稍小一点 */
font-style: italic; /* 斜体强调 */
}
.back-to-login {
margin-top: 20px;
}
.btn-secondary {
width: 100%;
padding: 10px;
background: linear-gradient(90deg, #6bffce, #c74dff);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin-top: 20px;
font-size: 1rem;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
</style>
@@ -0,0 +1,258 @@
<template>
<div class="login-container">
<!-- 背景几何图案 -->
<div class="bg-pattern"></div>
<!-- 登录卡 -->
<div class="login-card">
<div class="logo">
<span class="brand">决裂者终端</span>
</div>
<form @submit.prevent="handleLogin">
<input
v-model="email"
type="text"
placeholder="请输入账号"
required
class="input-field"
/>
<input
v-model="password"
type="password"
placeholder="请输入密码"
required
class="input-field"
/>
<div v-if="error" class="error">{{ error }}</div>
<div class="auth-buttons">
<button type="submit" :disabled="loginLoading" class="login-btn">
{{ loginLoading ? '正在登录. . .' : '登录' }}
</button>
<button type="button" @click="goToRegister" :disabled="registerLoading" class="register-btn">
{{ registerLoading ? '注册中...' : '注册' }}
</button>
</div>
</form>
<p class="footer">© 2026 Sunderer games. </p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router' // ✅ 确保导入
const router = useRouter()
const email = ref('')
const password = ref('')
const error = ref('')
const loginLoading = ref(false)
const registerLoading = ref(false)
async function handleLogin() {
loginLoading.value = true
error.value = ''
try {
const res = await fetch('/api/method/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ usr: email.value, pwd: password.value })
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.message || '账号或密码错误')
}
// 跳转到 Frappe 后台
window.location.href = 'http://172.25.162.172:8000/app'
} catch (err) {
error.value = err.message
} finally {
loginLoading.value = false
}
}
async function handleRegister() {
registerLoading.value = true
error.value = ''
try {
const res = await fetch('/api/method/frappe.core.doctype.user.user.sign_up', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email.value,
full_name: email.value.split('@')[0], // 简单处理,可用其他字段
redirect_to: '/app'
})
})
if (!res.ok) {
const data = await res.json()
throw new Error(data.message || '注册失败')
}
// 注册成功后,Frappe 会发送验证邮件
alert('注册成功!请查收邮箱验证链接。')
// 或跳转到提示页
} catch (err) {
error.value = err.message
} finally {
registerLoading.value = false
}
}
function goToRegister() {
router.push('/register')
}
</script>
<style scoped>
.login-container {
position: relative;
width: 100vw;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background: linear-gradient(135deg, #00AFFF, #0077CC);
}
/* 登录卡片保持原样 */
.login-card {
background: rgba(0, 0, 0, 0.295);
backdrop-filter: blur(12px);
border-radius: 20px;
padding: 2.5rem;
width: 360px;
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
border: 1px solid rgba(0, 175, 255, 0.3);
position: relative;
z-index: 1;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center; /* 水平居中内容 */
align-items: center; /* 垂直居中内容 */
min-height: 300px; /* 防止高度塌陷 */
}
/* 其他样式(logo、输入框、按钮等)保持不变... */
.logo {
font-family: 'Arial', sans-serif;
font-weight: bold;
margin-bottom: 1.5rem;
letter-spacing: 1px;
color: white;
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.brand {
color: white;
font-size: 2.2rem;
text-shadow: 0 0 10px rgba(0, 175, 255, 0.8), 0 0 20px rgba(0, 175, 255, 0.6);
}
.icon {
color: #00FFFF;
font-size: 1.8rem;
margin: 0 0.5rem;
text-shadow: 0 0 15px rgba(0, 255, 255, 0.7);
}
.input-field {
width: 80%;
padding: 0.8rem 1rem;
margin-bottom: 1rem;
border: none;
border-radius: 10px;
background: rgb(255, 255, 255);
color: rgb(0, 0, 0);
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
}
.input-field:focus {
box-shadow: 0 0 0 2px rgba(0, 175, 255, 0.7), 0 0 10px rgba(0, 175, 255, 0.4);
}
.error {
color: #ff6b6b;
font-size: 0.85rem;
margin: -0.5rem 0 1rem;
}
.login-btn {
width: 70%;
padding: 0.8rem;
background: linear-gradient(90deg, #00AFFF, #0077CC);
color: white;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 15px rgba(0, 175, 255, 0.5);
}
.register-btn {
width: 70%;
padding: 0.8rem;
background: linear-gradient(90deg, #6bffce, #c74dff);
color: white;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 15px rgba(255, 107, 107, 0.5);
}
.login-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 0 20px rgba(0, 175, 255, 0.7);
}
.login-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.auth-buttons {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1.5rem;
}
.footer {
margin-top: 1.5rem;
color: rgba(255, 255, 255, 0.6);
font-size: 0.8rem;
text-align: center;
}
.register-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 0 20px rgba(255, 107, 107, 0.7);
}
.register-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
</style>
@@ -0,0 +1,238 @@
<template>
<div class="register-view">
<div class="container">
<h2>用户注册</h2>
<div v-if="step === 'email'" class="step-email">
<input v-model="email" type="email" placeholder="请输入邮箱地址" class="input-field" />
<button @click="sendCode" :disabled="sending" class="btn-primary">
{{ sending ? '发送中...' : '发送验证码' }}
</button>
<p v-if="error" class="error-msg">{{ error }}</p>
</div>
<div v-else-if="step === 'verify'" class="step-verify">
<input v-model="code" type="text" placeholder="请输入6位验证码" maxlength="6" class="input-field" />
<button @click="verifyAndSignup" class="btn-primary">
完成注册
</button>
<p v-if="error" class="error-msg">{{ error }}</p>
</div>
<!-- 添加返回登录按钮 -->
<div class="back-to-login">
<button @click="goBackToLogin" class="btn-secondary">
← 返回登录
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router' // 引入路由
const router = useRouter() // 获取路由实例
const email = ref('')
const code = ref('')
const step = ref('email')
const error = ref('')
const sending = ref(false)
const validateEmail = (email) => {
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return regex.test(email)
}
const sendCode = async () => {
if (!validateEmail(email.value)) {
error.value = '请输入有效的邮箱地址!'
return
}
sending.value = true
error.value = ''
try {
const response = await fetch('/api/method/sunderer_app.api.verification.send_signup_code', { // 使用相对路径
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email.value
})
})
const result = await response.json()
if (result.exc) {
error.value = result.exc
} else {
step.value = 'verify'
console.log('验证码发送成功')
}
} catch (err) {
error.value = '网络错误,请稍后再试'
console.error(err)
} finally {
sending.value = false
}
}
const verifyAndSignup = async () => {
console.log("DEBUG: verifyAndSignup function called");
console.log('点击完成注册')
console.log('email:', email.value)
console.log('code:', code.value)
if (code.value.length !== 6) {
error.value = '请输入6位验证码!'
console.log("DEBUG: Code length check failed");
return
}
console.log("DEBUG: Code length check passed");
try {
// 1. 验证验证码
const verifyResponse = await fetch('/api/method/sunderer_app.api.verification.verify_signup_code', { // 使用相对路径
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email.value,
code: code.value
})
})
const verifyResult = await verifyResponse.json()
console.log("DEBUG: Verify API Response:", verifyResult);
if (verifyResult.exc) {
error.value = verifyResult.exc
console.log("DEBUG: Verify API returned error:", verifyResult.exc);
return
}
console.log("DEBUG: Verify API succeeded, proceeding to trigger email...");
// 2. 验证成功后,调用新API发送包含 IncreaseAcc 链接的邮件
// 注意:你需要在后端创建一个新API来处理这个逻辑
const triggerEmailResponse = await fetch('/api/method/sunderer_app.api.custom_user_signup.trigger_account_setup_email', { // 新API路径
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email.value
})
});
const triggerEmailResult = await triggerEmailResponse.json();
console.log("DEBUG: Trigger Email API Response:", triggerEmailResult);
if (triggerEmailResult.exc) {
error.value = triggerEmailResult.exc;
console.log("DEBUG: Trigger Email API returned error:", triggerEmailResult.exc);
return;
}
// 3. 提示用户并结束当前流程
alert('验证成功!请查收邮箱中的注册链接,点击链接完成账户设置。');
console.log('Verification successful, email sent with link.');
// 可以选择清空输入框或跳转到等待页面,这里简单提示后清空
email.value = '';
code.value = '';
step.value = 'email'; // 回到初始状态,或者可以设计一个等待页面
error.value = ''; // 清除可能存在的旧错误
} catch (err) {
error.value = '网络异常,请稍后再试';
console.error('注册异常:', err);
}
}
const goBackToLogin = () => {
router.push('/login')
}
</script>
<style scoped>
/* ... CSS 样式保持不变 ... */
.register-view {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #00AFFF, #0077CC);
color: white;
}
.container {
width: 400px;
padding: 30px;
background: rgba(0, 0, 0, 0.295);
backdrop-filter: blur(12px);
border-radius: 20px;
box-shadow: 0 0 30px rgba(0, 175, 255, 0.4);
border: 1px solid rgba(0, 175, 255, 0.3);
text-align: center;
}
.input-field {
width: 90%;
padding: 20px;
margin: 10px 0;
border: none;
border-radius: 10px;
background: rgb(255, 255, 255);
color: rgb(0, 0, 0);
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
box-shadow: 0 0 0 1px rgba(0, 175, 255, 0.3);
}
.btn-primary {
width: 100%;
padding: 12px;
background: linear-gradient(90deg, #4CAF50, #45a049);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin: 10px 0;
font-size: 1rem;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-primary:disabled {
background: #666;
cursor: not-allowed;
}
.btn-secondary {
width: 100%;
padding: 10px;
background: linear-gradient(90deg, #6bffce, #c74dff);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin-top: 20px;
font-size: 1rem;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
}
.error-msg {
color: #ff6b6b;
margin: 10px 0;
font-size: 14px;
}
.back-to-login {
margin-top: 20px;
}
</style>
@@ -0,0 +1,14 @@
<!-- src/views/app/DashboardView.vue -->
<template>
<div>
<n-h2>Dashboard</n-h2>
<p>Welcome to your admin dashboard!</p>
<n-card title="Quick Stats" style="margin-top: 20px;">
<n-statistic label="Total Users" value="12345" />
</n-card>
</div>
</template>
<script setup>
// Dashboard 相关逻辑
</script>
@@ -0,0 +1,26 @@
<!-- src/views/app/UsersView.vue -->
<template>
<div>
<n-h2>Users Management</n-h2>
<p>This is the users management page.</p>
<n-data-table :columns="columns" :data="data" />
</div>
</template>
<script setup>
import { h } from 'vue'
import { NTag, NButton } from 'naive-ui'
// 示例数据
const columns = [
{ title: 'Name', key: 'name' },
{ title: 'Age', key: 'age' },
{ title: 'Tags', key: 'tags', render: (row) => row.tags.map(tag => h(NTag, { type: 'info' }, { default: () => tag })) },
{ title: 'Actions', key: 'actions', render: () => h(NButton, { size: 'small', type: 'primary' }, { default: () => 'Edit' }) }
]
const data = [
{ name: 'John Doe', age: 32, tags: ['admin', 'dev'] },
{ name: 'Jane Smith', age: 28, tags: ['user'] }
]
</script>
+38
View File
@@ -0,0 +1,38 @@
// vite.dev.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
base: '/assets/sunderer_app/dist/', // 开发环境使用根路径
server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
secure: false
}
}
},
build: {
outDir: '../public/dist',
emptyOutDir: true,
assetsDir: '',
manifest: true,
rollupOptions: {
output: {
entryFileNames: 'js/[name].[hash].js',
chunkFileNames: 'js/[name].[hash].js',
assetFileNames: (assetInfo) => {
if (assetInfo.name.endsWith('.css')) {
return 'css/[name].[hash].[ext]';
}
return 'assets/[name].[hash].[ext]';
}
}
}
}
})
+56
View File
@@ -0,0 +1,56 @@
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
// 导入 Naive UI 的解析器,告诉插件哪些组件属于 Naive UI
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: [
'vue',
'vue-router',
{
// 配置自动导入 Naive UI 的特定 API
'naive-ui': [
'useMessage', // 用于显示消息提示
'useDialog', // 用于显示对话框
'useNotification', // 用于显示通知
'useLoadingBar' // 用于显示顶部加载条
// 你可以根据需要添加更多,例如 'NButton', 'NCard' 等,
// 但通常推荐让 Components 插件处理组件导入,这里只放 API。
]
}
]
}),
Components({
resolvers: [NaiveUiResolver()]
})
],
build: {
outDir: '../public/SundererPage',
emptyOutDir: false,
rollupOptions: {
input: {
sundererapp: 'src/sundererapp.js' // 入口文件
},
output: {
format: 'umd', // 设置输出格式为 UMD
name: 'SundererApp', // 指定全局变量名,构建后应用会挂载到 window.IncreaseAccApp
// 可以保留 hash 以利用缓存,但 UMD 通常用于简单场景,可选
entryFileNames: 'js/sundererapp.[hash].js', // 例如: increaseacc.js (或 [name].[hash].js)
chunkFileNames: 'js/sundererapp.[hash].js',
assetFileNames: 'js/sundererapp.[hash].[ext]', // 例如: increaseacc.css (或 [name].[hash].[ext])
// UMD 格式可能需要外部依赖,如果 Vue 是全局引入的,需要声明
// globals: {
// vue: 'Vue' // 假设 Vue 已通过 script 标签全局引入
// }
}
}
}
})
+271
View File
@@ -0,0 +1,271 @@
app_name = "sunderer_app"
app_title = "Sunderer"
app_publisher = "test"
app_description = "test"
app_email = "898879719@qq.com"
app_license = "agpl-3.0"
# sunderer_app/hooks.py
website_context = {
"favicon": "/assets/sunderer_app/icon3.svg",
"splash_image": "/assets/sunderer_app/images/icon3.svg"
}
website_route_rules = [
{"from_route": "/sundererapp/<path:app_path>", "to_route": "sundererapp/index"},
]
# 禁用 Frappe 默认的登录重定向(非常重要!)
disable_website_login_redirect = True
# Apps
# ------------------
# required_apps = []
# Each item in the list will be shown as an app in the apps page
# add_to_apps_screen = [
# {
# "name": "sunderer_app",
# "logo": "/assets/sunderer_app/logo.png",
# "title": "Sunderer",
# "route": "/sunderer_app",
# "has_permission": "sunderer_app.api.permission.has_app_permission"
# }
# ]
# Includes in <head>
# ------------------
# include js, css files in header of desk.html
# app_include_css = "/assets/sunderer_app/css/sunderer_app.css"
# app_include_js = "/assets/sunderer_app/js/sunderer_app.js"
# include js, css files in header of web template
# web_include_css = "/assets/sunderer_app/css/sunderer_app.css"
# web_include_js = "/assets/sunderer_app/js/sunderer_app.js"
# include custom scss in every website theme (without file extension ".scss")
# website_theme_scss = "sunderer_app/public/scss/website"
# include js, css files in header of web form
# webform_include_js = {"doctype": "public/js/doctype.js"}
# webform_include_css = {"doctype": "public/css/doctype.css"}
# include js in page
# page_js = {"page" : "public/js/file.js"}
# include js in doctype views
# doctype_js = {"doctype" : "public/js/doctype.js"}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
# Svg Icons
# ------------------
# include app icons in desk
# app_include_icons = "sunderer_app/public/icons.svg"
# Home Pages
# ----------
# application home page (will override Website Settings)
# home_page = "login"
# website user home page (by Role)
# role_home_page = {
# "Role": "home_page"
# }
# Generators
# ----------
# automatically create page for each record of this doctype
# website_generators = ["Web Page"]
# Jinja
# ----------
# add methods and filters to jinja environment
# jinja = {
# "methods": "sunderer_app.utils.jinja_methods",
# "filters": "sunderer_app.utils.jinja_filters"
# }
# Installation
# ------------
# before_install = "sunderer_app.install.before_install"
# after_install = "sunderer_app.install.after_install"
# Uninstallation
# ------------
# before_uninstall = "sunderer_app.uninstall.before_uninstall"
# after_uninstall = "sunderer_app.uninstall.after_uninstall"
# Integration Setup
# ------------------
# To set up dependencies/integrations with other apps
# Name of the app being installed is passed as an argument
# before_app_install = "sunderer_app.utils.before_app_install"
# after_app_install = "sunderer_app.utils.after_app_install"
# Integration Cleanup
# -------------------
# To clean up dependencies/integrations with other apps
# Name of the app being uninstalled is passed as an argument
# before_app_uninstall = "sunderer_app.utils.before_app_uninstall"
# after_app_uninstall = "sunderer_app.utils.after_app_uninstall"
# Desk Notifications
# ------------------
# See frappe.core.notifications.get_notification_config
# notification_config = "sunderer_app.notifications.get_notification_config"
# Permissions
# -----------
# Permissions evaluated in scripted ways
# permission_query_conditions = {
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
# }
#
# has_permission = {
# "Event": "frappe.desk.doctype.event.event.has_permission",
# }
# DocType Class
# ---------------
# Override standard doctype classes
# override_doctype_class = {
# "ToDo": "custom_app.overrides.CustomToDo"
# }
# Document Events
# ---------------
# Hook on document methods and events
# doc_events = {
# "*": {
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }
# }
# Scheduled Tasks
# ---------------
# scheduler_events = {
# "all": [
# "sunderer_app.tasks.all"
# ],
# "daily": [
# "sunderer_app.tasks.daily"
# ],
# "hourly": [
# "sunderer_app.tasks.hourly"
# ],
# "weekly": [
# "sunderer_app.tasks.weekly"
# ],
# "monthly": [
# "sunderer_app.tasks.monthly"
# ],
# }
# Testing
# -------
# before_tests = "sunderer_app.install.before_tests"
# Overriding Methods
# ------------------------------
#
# override_whitelisted_methods = {
# "frappe.desk.doctype.event.event.get_events": "sunderer_app.event.get_events"
# }
#
# each overriding function accepts a `data` argument;
# generated from the base implementation of the doctype dashboard,
# along with any modifications made in other Frappe apps
# override_doctype_dashboards = {
# "Task": "sunderer_app.task.get_dashboard_data"
# }
# exempt linked doctypes from being automatically cancelled
#
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
# Ignore links to specified DocTypes when deleting documents
# -----------------------------------------------------------
# ignore_links_on_delete = ["Communication", "ToDo"]
# Request Events
# ----------------
# before_request = ["sunderer_app.utils.before_request"]
# after_request = ["sunderer_app.utils.after_request"]
# Job Events
# ----------
# before_job = ["sunderer_app.utils.before_job"]
# after_job = ["sunderer_app.utils.after_job"]
# User Data Protection
# --------------------
# user_data_fields = [
# {
# "doctype": "{doctype_1}",
# "filter_by": "{filter_by}",
# "redact_fields": ["{field_1}", "{field_2}"],
# "partial": 1,
# },
# {
# "doctype": "{doctype_2}",
# "filter_by": "{filter_by}",
# "partial": 1,
# },
# {
# "doctype": "{doctype_3}",
# "strict": False,
# },
# {
# "doctype": "{doctype_4}"
# }
# ]
# Authentication and authorization
# --------------------------------
# auth_hooks = [
# "sunderer_app.auth.validate"
# ]
# Automatically update python controller files with type annotations for this app.
# export_python_type_annotations = True
# default_log_clearing_doctypes = {
# "Logging DocType Name": 30 # days to retain logs
# }
# Translation
# ------------
# List of apps whose translatable strings should be excluded from this app's translations.
# ignore_translatable_strings_from = []
+1
View File
@@ -0,0 +1 @@
Sunderer
+6
View File
@@ -0,0 +1,6 @@
[pre_model_sync]
# Patches added in this section will be executed before doctypes are migrated
# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3200 3200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><circle cx="1600" cy="1600" r="1599.79" style="fill:#fff;"/><g><g><clipPath id="_clip1"><path d="M1599.85,0l0.821,0l3.037,0.004c214.309,0.546 428.517,43.913 628.742,130.046c140.346,60.525 270.329,140.571 386.483,236.683l-2100.85,773.042l87.496,94.633l72.692,78.654l42.42,45.867l62.075,67.163l62.759,67.908l42.208,45.65l77.242,83.571l26.233,28.346l149.121,161.308l-181.292,349.696l261.65,-273.775l314.029,-332.013l-118.02,-87.262l-4.934,-3.634l-3.608,-2.691l-6.408,-4.721l-14.334,-10.617l695.725,-470.883l558.85,-590.804c332.971,286.137 547.075,706.833 557.613,1177.41l0.012,0.463l0.092,4.433l0.017,1.008l0.07,4.075l0.025,1.392l0.055,3.792l0.02,1.712l0.038,3.538l0.017,2.037l0.025,3.254l0.016,2.534l0.013,2.791l0.008,4.446l0,1.696l-0.008,4.042l-0.009,2.5l-0.012,2.304l-0.021,2.925l-0.017,1.85l-0.029,3.187l-0.016,1.534l-0.046,3.441l-0.017,1.192l-0.058,3.746l-0.013,0.733c-13.146,757.438 -553.712,1387.3 -1269.93,1537.7l-0.021,0.004c-10.2,2.142 -20.442,4.188 -30.721,6.134l-0.012,0.004l-4.025,0.754l-0.584,0.108l-3.762,0.7l-0.829,0.15l-3.65,0.667l-0.904,0.162l-3.642,0.65l-0.838,0.15l-3.745,0.663l-0.521,0.087c-4.254,0.746 -8.567,1.48 -12.888,2.196l-6,0.988l-5.662,0.904c-23.659,3.746 -47.384,6.958 -71.192,9.65l-0.146,0.017l-5.091,0.562l-0.175,0.021l-7.363,0.787l-4.558,0.471c-1.679,0.167 -3.359,0.338 -5.042,0.504l-4.958,0.48l-2.763,0.262l-2.133,0.2l-3.567,0.321l-1.346,0.121l-3.712,0.329l-1.217,0.104l-3.712,0.313l-1.234,0.104l-3.65,0.3l-1.308,0.104l-3.567,0.279l-1.404,0.113l-3.475,0.258l-1.512,0.113l-3.371,0.245l-1.629,0.117l-3.263,0.225l-1.75,0.121l-3.154,0.208l-1.867,0.121l-3.045,0.192l-1.992,0.125l-2.938,0.175l-2.112,0.125l-2.829,0.158l-2.229,0.125l-2.721,0.146l-2.35,0.125l-2.617,0.133l-2.462,0.121l-2.517,0.121l-2.575,0.116l-2.413,0.109l-2.687,0.116l-2.313,0.096l-2.804,0.109l-2.208,0.087l-2.917,0.104l-2.104,0.075l-3.033,0.104l-2,0.063l-3.159,0.096l-1.883,0.054l-3.292,0.087l-1.758,0.05l-3.446,0.08l-1.616,0.037l-3.638,0.075l-1.429,0.029l-3.913,0.071l-1.162,0.017l-4.379,0.062l-0.704,0.013l-5.109,0.054c-1.721,0.017 -3.441,0.029 -5.162,0.042l-5.092,0.025l-1.312,0.004l-3.755,0.004l-1.725,-0.004l-2.195,-0c-249.413,-0.692 -498.642,-59.346 -725.784,-175.863l-3.054,-1.57c-100.379,-51.846 -194.579,-114.005 -281.167,-185.038l-5.125,-4.225l2100.96,-773.075l-87.475,-94.654l-72.713,-78.658l-42.4,-45.863l-62.075,-67.162l-62.779,-67.888l-42.192,-45.654l-77.237,-83.567l-26.233,-28.366l-149.121,-161.309l181.291,-349.696l-261.65,273.821l-319.754,338.013l125.454,93.779l-8.395,6.662l-635.53,447.688l-583.158,616.529c-229.687,-197.396 -402.804,-458.742 -491.604,-756.308c-42.083,-141.417 -64.196,-287.492 -66.346,-433.821l-0.092,-7.542l-0.054,-6.454l-0.021,-4.854l0,-0.071l-0.008,-5.783l0,-0.313l0.025,-7.458l0.025,-3.671l0.008,-1.375l0.034,-3.25l0.004,-0.625c1.721,-157.862 26.687,-315.55 74.883,-467.458c167.892,-527.492 602.034,-936.642 1144.17,-1069.23l1.533,-0.375l1.854,-0.45c97.83,-23.642 197.455,-37.938 297.471,-42.892l1.571,-0.075l2.467,-0.121l3.016,-0.137l1.367,-0.063c23.608,-1.042 47.454,-1.571 71.421,-1.575Z"/></clipPath><g clip-path="url(#_clip1)"><use id="_Image3_" serif:id="_Image3" xlink:href="#_Image2" x="0" y="0" width="768px" height="768px" transform="matrix(4.16667,0,0,4.16667,0,0)"/></g></g></g></g><defs><image id="_Image2" width="768px" height="768px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAMACAYAAACTgQCOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOy9abc1OXKdl71WfdJkS2R3Te2JP9Br2cuyRsuSLcuzLcsyNVEiRXEemxL1pyxroMhmd1dX9fWH99775gBE7AhMAWDvD1X3IIEdyHPyAA/yIPL91kFRFEWF0bd/7gcvH1+9XP73Xnp7fa+uVrzb5vxSHjlLJfa/+c//9LeEKBRFUVRHcUCmKIpqpO/8/A9eMgyfgPVMhVHwD4J/MpQSW/L82K+X49/+F3+GcxRFUVQDcXClKIpy6ju/8MMrwt6pmvCf91DgH4n/b/9LLhAoiqI84uBJURSV0Xd+8YcvCLhey/eA/2QYJ/xfXqKLj2T8t/99+OPf/Vf/Aec4iqKohDg4UhS1vT79xR9dGTRH703hvxH4p6xrwz8QO/c6e9dfiI/Av1T33/15LgwoitpbHAQpitpGn/7Sj9Q7+jXgf7q7/imPkfAv9jP/WoV/xfff/QUuDCiK2kMc7CiKWlKf/tKPnrhXE/5X3fIj1HXv90+U1YT/56ouX1f2fXl06Q/+4n/IeZKiqOXEgY2iqKn16a989cp/OQI/0uUI/Dfd8pNvGG2/fzJUx2Rfybcl/Eu+f/CXuDCgKGpecQCjKGoqffYrX33E/QyIP4RsGynY8pOsvgj8z5Dsi9SVy23wn2r/B3/5z3I+pShqGnHAoigqtD771a8unPcEWwP8l2z5EWJuA/+TJfu2h//8ufzBX+GCgKKouOIARVFUGH32a1fYz0Pk7Q8E/nrA/2Dwv/y5GvynfrUB42c687HIsQCR4D93Tfz7v8pFAUVRMcTBiKKoYfrs1746UdjtYDj4n/yuf8qjFP6B9z4L/4477qP2++fap45hn8OH//37/4YLAoqixoiDD0VR3fTZr//4JUlIHvh33Dn+8NIA/8hdf8hrMPwz2Tcc/Kca/vu/9uc4J1MU1UUcbCiKaqrPfv3Ht7v8tu0b1ybCXX/IK7FwUL0I/7nYkmdT+Ad+ech0JtklpA/Jg6k1rKEfUn0uBiiKaikOMBRFVdXnv/EB+C+QlaMjAbaSd/1TbSAvw11/Ieaq8M8n/WR8APiXqqS9dPhP+f/hf8sFAUVR9cQBhaKoYr1B/3Gk7iI//ni+zB3ywH/qrn+qbU347wX+Keva8D9Tsi/k+7FC7Sf9uOFfqpuqdqv/h3Line truncated

After

Width:  |  Height:  |  Size: 60 KiB

@@ -0,0 +1,21 @@
(function(ot){typeof define=="function"&&define.amd?define(ot):ot()})(function(){"use strict";var ot=document.createElement("style");ot.textContent=`.register-view[data-v-406334d1]{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#00afff,#07c);color:#fff}.container[data-v-406334d1]{width:400px;padding:30px;background:#0000004b;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-radius:20px;box-shadow:0 0 30px #00afff66;border:1px solid rgba(0,175,255,.3);text-align:center}h2[data-v-406334d1]{margin-bottom:20px}.form-group[data-v-406334d1]{margin-bottom:15px}.input-field[data-v-406334d1]{width:100%;padding:20px;margin:10px 0;border:none;border-radius:10px;background:#fff;color:#000;font-size:1rem;outline:none;transition:all .3s ease;box-shadow:0 0 0 1px #00afff4d}.btn-primary[data-v-406334d1]{width:100%;padding:12px;background:linear-gradient(90deg,#4caf50,#45a049);color:#fff;border:none;border-radius:10px;cursor:pointer;margin:10px 0;font-size:1rem;font-weight:600;transition:transform .2s,box-shadow .2s}.btn-primary[data-v-406334d1]:disabled{background:#666;cursor:not-allowed}.success-message[data-v-406334d1]{color:green;margin-top:15px;text-align:center}.error-message[data-v-406334d1]{color:red;margin-top:15px;text-align:center}.password-strength-error[data-v-406334d1]{color:orange;margin-top:15px;text-align:center;font-size:.9em;font-style:italic}.back-to-login[data-v-406334d1]{margin-top:20px}.btn-secondary[data-v-406334d1]{width:100%;padding:10px;background:linear-gradient(90deg,#6bffce,#c74dff);color:#fff;border:none;border-radius:10px;cursor:pointer;margin-top:20px;font-size:1rem;font-weight:600;transition:transform .2s,box-shadow .2s}
/*$vite$:1*/`,document.head.appendChild(ot);/**
* @vue/shared v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/function ls(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const L={},ze=[],ae=()=>{},Xs=()=>!1,Mt=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),cs=e=>e.startsWith("onUpdate:"),z=Object.assign,fs=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},Gr=Object.prototype.hasOwnProperty,j=(e,t)=>Gr.call(e,t),A=Array.isArray,Xe=e=>At(e)==="[object Map]",Zs=e=>At(e)==="[object Set]",M=e=>typeof e=="function",J=e=>typeof e=="string",Ie=e=>typeof e=="symbol",B=e=>e!==null&&typeof e=="object",Qs=e=>(B(e)||M(e))&&M(e.then)&&M(e.catch),en=Object.prototype.toString,At=e=>en.call(e),kr=e=>At(e).slice(8,-1),tn=e=>At(e)==="[object Object]",us=e=>J(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,lt=ls(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),It=e=>{const t=Object.create(null);return s=>t[s]||(t[s]=e(s))},Yr=/-\w/g,Re=It(e=>e.replace(Yr,t=>t.slice(1).toUpperCase())),zr=/\B([A-Z])/g,Ve=It(e=>e.replace(zr,"-$1").toLowerCase()),sn=It(e=>e.charAt(0).toUpperCase()+e.slice(1)),as=It(e=>e?`on${sn(e)}`:""),$e=(e,t)=>!Object.is(e,t),Rt=(e,...t)=>{for(let s=0;s<e.length;s++)e[s](...t)},nn=(e,t,s,n=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},ds=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let rn;const Ft=()=>rn||(rn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function hs(e){if(A(e)){const t={};for(let s=0;s<e.length;s++){const n=e[s],r=J(n)?ei(n):hs(n);if(r)for(const i in r)t[i]=r[i]}return t}else if(J(e)||B(e))return e}const Xr=/;(?![^(]*\))/g,Zr=/:([^]+)/,Qr=/\/\*[^]*?\*\//g;function ei(e){const t={};return e.replace(Qr,"").split(Xr).forEach(s=>{if(s){const n=s.split(Zr);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function Dt(e){let t="";if(J(e))t=e;else if(A(e))for(let s=0;s<e.length;s++){const n=Dt(e[s]);n&&(t+=n+" ")}else if(B(e))for(const s in e)e[s]&&(t+=s+" ");return t.trim()}const ti=ls("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function on(e){return!!e||e===""}const ln=e=>!!(e&&e.__v_isRef===!0),jt=e=>J(e)?e:e==null?"":A(e)||B(e)&&(e.toString===en||!M(e.toString))?ln(e)?jt(e.value):JSON.stringify(e,cn,2):String(e),cn=(e,t)=>ln(t)?cn(e,t.value):Xe(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,r],i)=>(s[ps(n,i)+" =>"]=r,s),{})}:Zs(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>ps(s))}:Ie(t)?ps(t):B(t)&&!A(t)&&!tn(t)?String(t):t,ps=(e,t="")=>{var s;return Ie(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/**
* @vue/reactivity v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/let se;class si{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=se,!t&&se&&(this.index=(se.scopes||(se.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].pause();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].resume();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].resume()}}run(t){if(this._active){const s=se;try{return se=this,t()}finally{se=s}}}on(){++this._on===1&&(this.prevScope=se,se=this)}off(){this._on>0&&--this._on===0&&(se=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s<n;s++)this.effects[s].stop();for(this.effects.length=0,s=0,n=this.cleanups.length;s<n;s++)this.cleanups[s]();if(this.cleanups.length=0,this.scopes){for(s=0,n=this.scopes.length;s<n;s++)this.scopes[s].stop(!0);this.scopes.length=0}if(!this.detached&&this.parent&&!t){const r=this.parent.scopes.pop();r&&r!==this&&(this.parent.scopes[this.index]=r,r.index=this.index)}this.parent=void 0}}}function ni(){return se}let V;const gs=new WeakSet;class fn{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.flags=5,this.next=void 0,this.cleanup=void 0,this.scheduler=void 0,se&&se.active&&se.effects.push(this)}pause(){this.flags|=64}resume(){this.flags&64&&(this.flags&=-65,gs.has(this)&&(gs.delete(this),this.trigger()))}notify(){this.flags&2&&!(this.flags&32)||this.flags&8||an(this)}run(){if(!(this.flags&1))return this.fn();this.flags|=2,mn(this),dn(this);const t=V,s=fe;V=this,fe=!0;try{return this.fn()}finally{hn(this),V=t,fe=s,this.flags&=-3}}stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)xs(t);this.deps=this.depsTail=void 0,mn(this),this.onStop&&this.onStop(),this.flags&=-2}}trigger(){this.flags&64?gs.add(this):this.scheduler?this.scheduler():this.runIfDirty()}runIfDirty(){bs(this)&&this.run()}get dirty(){return bs(this)}}let un=0,ct,ft;function an(e,t=!1){if(e.flags|=8,t){e.next=ft,ft=e;return}e.next=ct,ct=e}function ms(){un++}function _s(){if(--un>0)return;if(ft){let t=ft;for(ft=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;ct;){let t=ct;for(ct=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function dn(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function hn(e){let t,s=e.depsTail,n=s;for(;n;){const r=n.prevDep;n.version===-1?(n===s&&(s=r),xs(n),ri(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=r}e.deps=t,e.depsTail=s}function bs(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(pn(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function pn(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===ut)||(e.globalVersion=ut,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!bs(e))))return;e.flags|=2;const t=e.dep,s=V,n=fe;V=e,fe=!0;try{dn(e);const r=e.fn(e._value);(t.version===0||$e(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{V=s,fe=n,hn(e),e.flags&=-3}}function xs(e,t=!1){const{dep:s,prevSub:n,nextSub:r}=e;if(n&&(n.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let i=s.computed.deps;i;i=i.nextDep)xs(i,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function ri(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let fe=!0;const gn=[];function de(){gn.push(fe),fe=!1}function he(){const e=gn.pop();fe=e===void 0?!0:e}function mn(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=V;V=void 0;try{t()}finally{V=s}}}let ut=0;class ii{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class _n{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!V||!fe||V===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==V)s=this.activeLink=new ii(V,this),V.deps?(s.prevDep=V.depsTail,V.depsTail.nextDep=s,V.depsTail=s):V.deps=V.depsTail=s,bn(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=V.depsTail,s.nextDep=void 0,V.depsTail.nextDep=s,V.depsTail=s,V.deps===s&&(V.deps=n)}return s}trigger(t){this.version++,ut++,this.notify(t)}notify(t){ms();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{_s()}}}function bn(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let nLine truncated
* @vue/runtime-core v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/const pt=[];let Ms=!1;function Vl(e,...t){if(Ms)return;Ms=!0,de();const s=pt.length?pt[pt.length-1].component:null,n=s&&s.appContext.config.warnHandler,r=Ai();if(n)et(n,s,11,[e+t.map(i=>{var o,l;return(l=(o=i.toString)==null?void 0:o.call(i))!=null?l:JSON.stringify(i)}).join(""),s&&s.proxy,r.map(({vnode:i})=>`at <${Sr(s,i.type)}>`).join(`
`),r]);else{const i=[`[Vue warn]: ${e}`,...t];r.length&&i.push(`
`,...Ii(r)),console.warn(...i)}he(),Ms=!1}function Ai(){let e=pt[pt.length-1];if(!e)return[];const t=[];for(;e;){const s=t[0];s&&s.vnode===e?s.recurseCount++:t.push({vnode:e,recurseCount:0});const n=e.component&&e.component.parent;e=n&&n.vnode}return t}function Ii(e){const t=[];return e.forEach((s,n)=>{t.push(...n===0?[]:[`
`],...Ri(s))}),t}function Ri({vnode:e,recurseCount:t}){const s=t>0?`... (${t} recursive calls)`:"",n=e.component?e.component.parent==null:!1,r=` at <${Sr(e.component,e.type,n)}`,i=">"+s;return e.props?[r,...Fi(e.props),i]:[r+i]}function Fi(e){const t=[],s=Object.keys(e);return s.slice(0,3).forEach(n=>{t.push(...Mn(n,e[n]))}),s.length>3&&t.push(" ..."),t}function Mn(e,t,s){return J(t)?(t=JSON.stringify(t),s?t:[`${e}=${t}`]):typeof t=="number"||typeof t=="boolean"||t==null?s?t:[`${e}=${t}`]:Y(t)?(t=Mn(e,H(t.value),!0),s?t:[`${e}=Ref<`,t,">"]):M(t)?[`${e}=fn${t.name?`<${t.name}>`:""}`]:(t=H(t),s?t:[`${e}=`,t])}function et(e,t,s,n){try{return n?e(...n):e()}catch(r){Kt(r,t,s)}}function ge(e,t,s,n){if(M(e)){const r=et(e,t,s,n);return r&&Qs(r)&&r.catch(i=>{Kt(i,t,s)}),r}if(A(e)){const r=[];for(let i=0;i<e.length;i++)r.push(ge(e[i],t,s,n));return r}}function Kt(e,t,s,n=!0){const r=t?t.vnode:null,{errorHandler:i,throwUnhandledErrorInProduction:o}=t&&t.appContext.config||L;if(t){let l=t.parent;const f=t.proxy,h=`https://vuejs.org/error-reference/#runtime-${s}`;for(;l;){const a=l.ec;if(a){for(let p=0;p<a.length;p++)if(a[p](e,f,h)===!1)return}l=l.parent}if(i){de(),et(i,null,10,[e,f,h]),he();return}}Di(e,s,r,n,o)}function Di(e,t,s,n=!0,r=!1){if(r)throw e;console.error(e)}const X=[];let me=-1;const tt=[];let De=null,st=0;const An=Promise.resolve();let Bt=null;function ji(e){const t=Bt||An;return e?t.then(this?e.bind(this):e):t}function Hi(e){let t=me+1,s=X.length;for(;t<s;){const n=t+s>>>1,r=X[n],i=gt(r);i<e||i===e&&r.flags&2?t=n+1:s=n}return t}function As(e){if(!(e.flags&1)){const t=gt(e),s=X[X.length-1];!s||!(e.flags&2)&&t>=gt(s)?X.push(e):X.splice(Hi(t),0,e),e.flags|=1,In()}}function In(){Bt||(Bt=An.then(Dn))}function Ni(e){A(e)?tt.push(...e):De&&e.id===-1?De.splice(st+1,0,e):e.flags&1||(tt.push(e),e.flags|=1),In()}function Rn(e,t,s=me+1){for(;s<X.length;s++){const n=X[s];if(n&&n.flags&2){if(e&&n.id!==e.uid)continue;X.splice(s,1),s--,n.flags&4&&(n.flags&=-2),n(),n.flags&4||(n.flags&=-2)}}}function Fn(e){if(tt.length){const t=[...new Set(tt)].sort((s,n)=>gt(s)-gt(n));if(tt.length=0,De){De.push(...t);return}for(De=t,st=0;st<De.length;st++){const s=De[st];s.flags&4&&(s.flags&=-2),s.flags&8||s(),s.flags&=-2}De=null,st=0}}const gt=e=>e.id==null?e.flags&2?-1:1/0:e.id;function Dn(e){try{for(me=0;me<X.length;me++){const t=X[me];t&&!(t.flags&8)&&(t.flags&4&&(t.flags&=-2),et(t,t.i,t.i?15:14),t.flags&4||(t.flags&=-2))}}finally{for(;me<X.length;me++){const t=X[me];t&&(t.flags&=-2)}me=-1,X.length=0,Fn(),Bt=null,(X.length||tt.length)&&Dn()}}let ce=null,jn=null;function Wt(e){const t=ce;return ce=e,jn=e&&e.type.__scopeId||null,t}function Li(e,t=ce,s){if(!t||e._n)return e;const n=(...r)=>{n._d&&pr(-1);const i=Wt(t);let o;try{o=e(...r)}finally{Wt(i),n._d&&pr(1)}return o};return n._n=!0,n._c=!0,n._d=!0,n}function Hn(e,t){if(ce===null)return e;const s=es(ce),n=e.dirs||(e.dirs=[]);for(let r=0;r<t.length;r++){let[i,o,l,f=L]=t[r];i&&(M(i)&&(i={mounted:i,updated:i}),i.deep&&Oe(o),n.push({dir:i,instance:s,value:o,oldValue:void 0,arg:l,modifiers:f}))}return e}function qe(e,t,s,n){const r=e.dirs,i=t&&t.dirs;for(let o=0;o<r.length;o++){const l=r[o];i&&(l.oldValue=i[o].value);let f=l.dir[n];f&&(de(),ge(f,s,8,[e.el,l,e,t]),he())}}function Vi(e,t){if(Q){let s=Q.provides;const n=Q.parent&&Q.parent.provides;n===s&&(s=Q.provides=Object.create(n)),s[e]=t}}function qt(e,t,s=!1){const n=Uo();if(n||nt){let r=nt?nt._context.provides:n?n.parent==null||n.ce?n.vnode.appContext&&n.vnode.appContext.provides:n.parent.provides:void 0;if(r&&e in r)return r[e];if(arguments.length>1)return s&&M(t)?t.call(n&&n.proxy):t}}const $i=Symbol.for("v-scx"),Ui=()=>qt($i);function Is(e,t,s){return Nn(e,t,s)}function Nn(e,t,s=L){const{immediate:n,deep:r,flush:i,once:o}=s,l=z({},s),f=t&&n||!t&&i!=="post";let h;if(Tt){if(i==="sync"){const T=Ui();h=T.__watcherHandles||(T.__watcherHandles=[])}else if(!f){const T=()=>{};return T.stop=ae,T.resume=ae,T.pause=ae,T}}const a=Q;l.call=(T,I,F)=>ge(T,a,I,F);let p=!1;i==="post"?l.scheduler=T=>{ie(T,a&&a.suspense)}:i!=="sync"&&(p=!0,l.scheduler=(T,I)=>{I?T():As(T)}),l.augmentJob=T=>{t&&(T.flags|=4),p&&(T.flags|=2,a&&(T.id=a.uid,T.i=a))};const S=Mi(e,t,l);return Tt&&(h?h.push(S):f&&S()),S}function Ki(e,t,s){const n=this.proxy,r=J(e)?e.includes(".")?Ln(n,e):()=>n[e]:e.bind(n,n);let i;M(t)?i=t:(i=t.handler,s=t);const o=St(this),l=Nn(r,i.bind(n),s);return o(),l}function Ln(e,t){const s=t.split(".");return()=>{let n=e;for(let r=0;r<s.length&&n;r++)n=n[s[r]];return n}}const Bi=Symbol("_vte"),Wi=e=>e.__isTeleport,qi=Symbol("_leaveCb");function Rs(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Rs(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Vn(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}const Jt=new WeakMap;function mt(e,t,s,n,r=!1){if(A(e)){e.forEach((I,F)=>mt(I,t&&(A(t)?t[F]:t),s,n,r));return}if(_t(n)&&!r){n.shapeFlag&512&&n.type.__asyncReso Line truncated
* @vue/runtime-dom v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/let qs;const Tr=typeof window<"u"&&window.trustedTypes;if(Tr)try{qs=Tr.createPolicy("vue",{createHTML:e=>e})}catch{}const Cr=qs?e=>qs.createHTML(e):e=>e,Zo="http://www.w3.org/2000/svg",Qo="http://www.w3.org/1998/Math/MathML",Ae=typeof document<"u"?document:null,Er=Ae&&Ae.createElement("template"),el={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?Ae.createElementNS(Zo,e):t==="mathml"?Ae.createElementNS(Qo,e):s?Ae.createElement(e,{is:s}):Ae.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>Ae.createTextNode(e),createComment:e=>Ae.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ae.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,i){const o=s?s.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===i||!(r=r.nextSibling)););else{Er.innerHTML=Cr(n==="svg"?`<svg>${e}</svg>`:n==="mathml"?`<math>${e}</math>`:e);const l=Er.content;if(n==="svg"||n==="mathml"){const f=l.firstChild;for(;f.firstChild;)l.appendChild(f.firstChild);l.removeChild(f)}t.insertBefore(l,s)}return[o?o.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},tl=Symbol("_vtc");function sl(e,t,s){const n=e[tl];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const Or=Symbol("_vod"),nl=Symbol("_vsh"),rl=Symbol(""),il=/(?:^|;)\s*display\s*:/;function ol(e,t,s){const n=e.style,r=J(s);let i=!1;if(s&&!r){if(t)if(J(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();s[l]==null&&ts(n,l,"")}else for(const o in t)s[o]==null&&ts(n,o,"");for(const o in s)o==="display"&&(i=!0),ts(n,o,s[o])}else if(r){if(t!==s){const o=n[rl];o&&(s+=";"+o),n.cssText=s,i=il.test(s)}}else t&&e.removeAttribute("style");Or in e&&(e[Or]=i?n.display:"",e[nl]&&(n.display="none"))}const Pr=/\s*!important$/;function ts(e,t,s){if(A(s))s.forEach(n=>ts(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=ll(e,t);Pr.test(s)?e.setProperty(Ve(n),s.replace(Pr,""),"important"):e[n]=s}}const Mr=["Webkit","Moz","ms"],Js={};function ll(e,t){const s=Js[t];if(s)return s;let n=Re(t);if(n!=="filter"&&n in e)return Js[t]=n;n=sn(n);for(let r=0;r<Mr.length;r++){const i=Mr[r]+n;if(i in e)return Js[t]=i}return t}const Ar="http://www.w3.org/1999/xlink";function Ir(e,t,s,n,r,i=ti(t)){n&&t.startsWith("xlink:")?s==null?e.removeAttributeNS(Ar,t.slice(6,t.length)):e.setAttributeNS(Ar,t,s):s==null||i&&!on(s)?e.removeAttribute(t):e.setAttribute(t,i?"":Ie(s)?String(s):s)}function Rr(e,t,s,n,r){if(t==="innerHTML"||t==="textContent"){s!=null&&(e[t]=t==="innerHTML"?Cr(s):s);return}const i=e.tagName;if(t==="value"&&i!=="PROGRESS"&&!i.includes("-")){const l=i==="OPTION"?e.getAttribute("value")||"":e.value,f=s==null?e.type==="checkbox"?"on":"":String(s);(l!==f||!("_value"in e))&&(e.value=f),s==null&&e.removeAttribute(t),e._value=s;return}let o=!1;if(s===""||s==null){const l=typeof e[t];l==="boolean"?s=on(s):s==null&&l==="string"?(s="",o=!0):l==="number"&&(s=0,o=!0)}try{e[t]=s}catch{}o&&e.removeAttribute(r||t)}function it(e,t,s,n){e.addEventListener(t,s,n)}function cl(e,t,s,n){e.removeEventListener(t,s,n)}const Fr=Symbol("_vei");function fl(e,t,s,n,r=null){const i=e[Fr]||(e[Fr]={}),o=i[t];if(n&&o)o.value=n;else{const[l,f]=ul(t);if(n){const h=i[t]=hl(n,r);it(e,l,h,f)}else o&&(cl(e,l,o,f),i[t]=void 0)}}const Dr=/(?:Once|Passive|Capture)$/;function ul(e){let t;if(Dr.test(e)){t={};let n;for(;n=e.match(Dr);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[e[2]===":"?e.slice(3):Ve(e.slice(2)),t]}let Gs=0;const al=Promise.resolve(),dl=()=>Gs||(al.then(()=>Gs=0),Gs=Date.now());function hl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;ge(pl(n,s.value),t,5,[n])};return s.value=e,s.attached=dl(),s}function pl(e,t){if(A(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const jr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gl=(e,t,s,n,r,i)=>{const o=r==="svg";t==="class"?sl(e,n,o):t==="style"?ol(e,s,n):Mt(t)?cs(t)||fl(e,t,s,n,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ml(e,t,n,o))?(Rr(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Ir(e,t,n,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!J(n))?Rr(e,Re(t),n,i,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Ir(e,t,n,o))};function ml(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&jr(t)&&M(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName Line truncated
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3200 3200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><circle cx="1600" cy="1600" r="1599.79" style="fill:#fff;"/><g><g><clipPath id="_clip1"><path d="M1599.85,0l0.821,0l3.037,0.004c214.309,0.546 428.517,43.913 628.742,130.046c140.346,60.525 270.329,140.571 386.483,236.683l-2100.85,773.042l87.496,94.633l72.692,78.654l42.42,45.867l62.075,67.163l62.759,67.908l42.208,45.65l77.242,83.571l26.233,28.346l149.121,161.308l-181.292,349.696l261.65,-273.775l314.029,-332.013l-118.02,-87.262l-4.934,-3.634l-3.608,-2.691l-6.408,-4.721l-14.334,-10.617l695.725,-470.883l558.85,-590.804c332.971,286.137 547.075,706.833 557.613,1177.41l0.012,0.463l0.092,4.433l0.017,1.008l0.07,4.075l0.025,1.392l0.055,3.792l0.02,1.712l0.038,3.538l0.017,2.037l0.025,3.254l0.016,2.534l0.013,2.791l0.008,4.446l0,1.696l-0.008,4.042l-0.009,2.5l-0.012,2.304l-0.021,2.925l-0.017,1.85l-0.029,3.187l-0.016,1.534l-0.046,3.441l-0.017,1.192l-0.058,3.746l-0.013,0.733c-13.146,757.438 -553.712,1387.3 -1269.93,1537.7l-0.021,0.004c-10.2,2.142 -20.442,4.188 -30.721,6.134l-0.012,0.004l-4.025,0.754l-0.584,0.108l-3.762,0.7l-0.829,0.15l-3.65,0.667l-0.904,0.162l-3.642,0.65l-0.838,0.15l-3.745,0.663l-0.521,0.087c-4.254,0.746 -8.567,1.48 -12.888,2.196l-6,0.988l-5.662,0.904c-23.659,3.746 -47.384,6.958 -71.192,9.65l-0.146,0.017l-5.091,0.562l-0.175,0.021l-7.363,0.787l-4.558,0.471c-1.679,0.167 -3.359,0.338 -5.042,0.504l-4.958,0.48l-2.763,0.262l-2.133,0.2l-3.567,0.321l-1.346,0.121l-3.712,0.329l-1.217,0.104l-3.712,0.313l-1.234,0.104l-3.65,0.3l-1.308,0.104l-3.567,0.279l-1.404,0.113l-3.475,0.258l-1.512,0.113l-3.371,0.245l-1.629,0.117l-3.263,0.225l-1.75,0.121l-3.154,0.208l-1.867,0.121l-3.045,0.192l-1.992,0.125l-2.938,0.175l-2.112,0.125l-2.829,0.158l-2.229,0.125l-2.721,0.146l-2.35,0.125l-2.617,0.133l-2.462,0.121l-2.517,0.121l-2.575,0.116l-2.413,0.109l-2.687,0.116l-2.313,0.096l-2.804,0.109l-2.208,0.087l-2.917,0.104l-2.104,0.075l-3.033,0.104l-2,0.063l-3.159,0.096l-1.883,0.054l-3.292,0.087l-1.758,0.05l-3.446,0.08l-1.616,0.037l-3.638,0.075l-1.429,0.029l-3.913,0.071l-1.162,0.017l-4.379,0.062l-0.704,0.013l-5.109,0.054c-1.721,0.017 -3.441,0.029 -5.162,0.042l-5.092,0.025l-1.312,0.004l-3.755,0.004l-1.725,-0.004l-2.195,-0c-249.413,-0.692 -498.642,-59.346 -725.784,-175.863l-3.054,-1.57c-100.379,-51.846 -194.579,-114.005 -281.167,-185.038l-5.125,-4.225l2100.96,-773.075l-87.475,-94.654l-72.713,-78.658l-42.4,-45.863l-62.075,-67.162l-62.779,-67.888l-42.192,-45.654l-77.237,-83.567l-26.233,-28.366l-149.121,-161.309l181.291,-349.696l-261.65,273.821l-319.754,338.013l125.454,93.779l-8.395,6.662l-635.53,447.688l-583.158,616.529c-229.687,-197.396 -402.804,-458.742 -491.604,-756.308c-42.083,-141.417 -64.196,-287.492 -66.346,-433.821l-0.092,-7.542l-0.054,-6.454l-0.021,-4.854l0,-0.071l-0.008,-5.783l0,-0.313l0.025,-7.458l0.025,-3.671l0.008,-1.375l0.034,-3.25l0.004,-0.625c1.721,-157.862 26.687,-315.55 74.883,-467.458c167.892,-527.492 602.034,-936.642 1144.17,-1069.23l1.533,-0.375l1.854,-0.45c97.83,-23.642 197.455,-37.938 297.471,-42.892l1.571,-0.075l2.467,-0.121l3.016,-0.137l1.367,-0.063c23.608,-1.042 47.454,-1.571 71.421,-1.575Z"/></clipPath><g clip-path="url(#_clip1)"><use id="_Image3_" serif:id="_Image3" xlink:href="#_Image2" x="0" y="0" width="768px" height="768px" transform="matrix(4.16667,0,0,4.16667,0,0)"/></g></g></g></g><defs><image id="_Image2" width="768px" height="768px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAMACAYAAACTgQCOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOy9abc1OXKdl71WfdJkS2R3Te2JP9Br2cuyRsuSLcuzLcsyNVEiRXEemxL1pyxroMhmd1dX9fWH99775gBE7AhMAWDvD1X3IIEdyHPyAA/yIPL91kFRFEWF0bd/7gcvH1+9XP73Xnp7fa+uVrzb5vxSHjlLJfa/+c//9LeEKBRFUVRHcUCmKIpqpO/8/A9eMgyfgPVMhVHwD4J/MpQSW/L82K+X49/+F3+GcxRFUVQDcXClKIpy6ju/8MMrwt6pmvCf91DgH4n/b/9LLhAoiqI84uBJURSV0Xd+8YcvCLhey/eA/2QYJ/xfXqKLj2T8t/99+OPf/Vf/Aec4iqKohDg4UhS1vT79xR9dGTRH703hvxH4p6xrwz8QO/c6e9dfiI/Av1T33/15LgwoitpbHAQpitpGn/7Sj9Q7+jXgf7q7/imPkfAv9jP/WoV/xfff/QUuDCiK2kMc7CiKWlKf/tKPnrhXE/5X3fIj1HXv90+U1YT/56ouX1f2fXl06Q/+4n/IeZKiqOXEgY2iqKn16a989cp/OQI/0uUI/Dfd8pNvGG2/fzJUx2Rfybcl/Eu+f/CXuDCgKGpecQCjKGoqffYrX33E/QyIP4RsGynY8pOsvgj8z5Dsi9SVy23wn2r/B3/5z3I+pShqGnHAoigqtD771a8unPcEWwP8l2z5EWJuA/+TJfu2h//8ufzBX+GCgKKouOIARVFUGH32a1fYz0Pk7Q8E/nrA/2Dwv/y5GvynfrUB42c687HIsQCR4D93Tfz7v8pFAUVRMcTBiKKoYfrs1746UdjtYDj4n/yuf8qjFP6B9z4L/4477qP2++fap45hn8OH//37/4YLAoqixoiDD0VR3fTZr//4JUlIHvh33Dn+8NIA/8hdf8hrMPwz2Tcc/Kca/vu/9uc4J1MU1UUcbCiKaqrPfv3Ht7v8tu0b1ybCXX/IK7FwUL0I/7nYkmdT+Ad+ech0JtklpA/Jg6k1rKEfUn0uBiiKaikOMBRFVdXnv/EB+C+QlaMjAbaSd/1TbSAvw11/Ieaq8M8n/WR8APiXqqS9dPhP+f/hf8sFAUVR9cQBhaKoYr1B/3Gk7iI//ni+zB3ywH/qrn+qbU347wX+Keva8D9Tsi/k+7FC7Sf9uOFfqpuqdqv/h3Line truncated

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+11
View File
@@ -0,0 +1,11 @@
{
"index.html": {
"file": "js/index.c1R21Jxr.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"css": [
"css/index.CJO3dQ9s.css"
]
}
}
+1
View File
@@ -0,0 +1 @@
html,body,#app{height:100%;margin:0;padding:0;background:#000;overflow:hidden}.login-container[data-v-6eda41c4]{position:relative;width:100vw;min-height:100vh;display:flex;justify-content:center;align-items:center;overflow:hidden;background:linear-gradient(135deg,#00afff,#07c)}.login-card[data-v-6eda41c4]{background:#0000004b;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-radius:20px;padding:2.5rem;width:360px;box-shadow:0 0 30px #00afff66;border:1px solid rgba(0,175,255,.3);position:relative;z-index:1;text-align:center;display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:300px}.logo[data-v-6eda41c4]{font-family:Arial,sans-serif;font-weight:700;margin-bottom:1.5rem;letter-spacing:1px;color:#fff;text-shadow:0 0 10px rgba(0,175,255,.8),0 0 20px rgba(0,175,255,.6);display:inline-flex;align-items:center;gap:.5rem}.brand[data-v-6eda41c4]{color:#fff;font-size:2.2rem;text-shadow:0 0 10px rgba(0,175,255,.8),0 0 20px rgba(0,175,255,.6)}.icon[data-v-6eda41c4]{color:#0ff;font-size:1.8rem;margin:0 .5rem;text-shadow:0 0 15px rgba(0,255,255,.7)}.input-field[data-v-6eda41c4]{width:80%;padding:.8rem 1rem;margin-bottom:1rem;border:none;border-radius:10px;background:#fff;color:#000;font-size:1rem;outline:none;transition:all .3s ease;box-shadow:0 0 0 1px #00afff4d}.input-field[data-v-6eda41c4]:focus{box-shadow:0 0 0 2px #00afffb3,0 0 10px #00afff66}.error[data-v-6eda41c4]{color:#ff6b6b;font-size:.85rem;margin:-.5rem 0 1rem}.login-btn[data-v-6eda41c4]{width:70%;padding:.8rem;background:linear-gradient(90deg,#00afff,#07c);color:#fff;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;transition:transform .2s,box-shadow .2s;box-shadow:0 0 15px #00afff80}.register-btn[data-v-6eda41c4]{width:70%;padding:.8rem;background:linear-gradient(90deg,#6bffce,#c74dff);color:#fff;border:none;border-radius:10px;font-size:1rem;font-weight:600;cursor:pointer;transition:transform .2s,box-shadow .2s;box-shadow:0 0 15px #ff6b6b80}.login-btn[data-v-6eda41c4]:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 0 20px #00afffb3}.login-btn[data-v-6eda41c4]:disabled{opacity:.7;cursor:not-allowed}.auth-buttons[data-v-6eda41c4]{display:flex;gap:1rem;justify-content:center;margin-top:1.5rem}.footer[data-v-6eda41c4]{margin-top:1.5rem;color:#fff9;font-size:.8rem;text-align:center}.register-btn[data-v-6eda41c4]:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 0 20px #ff6b6bb3}.register-btn[data-v-6eda41c4]:disabled{opacity:.7;cursor:not-allowed}.register-view[data-v-14690773]{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#00afff,#07c);color:#fff}.container[data-v-14690773]{width:400px;padding:30px;background:#0000004b;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-radius:20px;box-shadow:0 0 30px #00afff66;border:1px solid rgba(0,175,255,.3);text-align:center}.input-field[data-v-14690773]{width:90%;padding:20px;margin:10px 0;border:none;border-radius:10px;background:#fff;color:#000;font-size:1rem;outline:none;transition:all .3s ease;box-shadow:0 0 0 1px #00afff4d}.btn-primary[data-v-14690773]{width:100%;padding:12px;background:linear-gradient(90deg,#4caf50,#45a049);color:#fff;border:none;border-radius:10px;cursor:pointer;margin:10px 0;font-size:1rem;font-weight:600;transition:transform .2s,box-shadow .2s}.btn-primary[data-v-14690773]:disabled{background:#666;cursor:not-allowed}.btn-secondary[data-v-14690773]{width:100%;padding:10px;background:linear-gradient(90deg,#6bffce,#c74dff);color:#fff;border:none;border-radius:10px;cursor:pointer;margin-top:20px;font-size:1rem;font-weight:600;transition:transform .2s,box-shadow .2s}.error-msg[data-v-14690773]{color:#ff6b6b;margin:10px 0;font-size:14px}.back-to-login[data-v-14690773]{margin-top:20px}.password-setup-container[data-v-378de9a4]{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#00afff,#07c);color:#fff}.setup-card[data-v-378de9a4]{width:400px;padding:30px;background:#0000004b;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-radius:20px;box-shadow:0 0 30px #00afff66;border:1px solid rgba(0,175,255,.3);text-align:center}.input-group[data-v-378de9a4]{margin-bottom:15px;text-align:left}.input-group label[data-v-378de9a4]{display:block;margin-bottom:5px;font-weight:700}.input-field[data-v-378de9a4]{width:94%;padding:12px;border:none;border-radius:10px;background:#fff;color:#000;font-size:1rem;outline:none;transition:all .3s ease;box-shadow:0 0 0 1px #00afff4d}.btn-primary[data-v-378de9a4]{width:100%;padding:12px;background:linear-gradient(90deg,#4caf50,#45a049);color:#fff;border:none;border-radius:10px;cursor:pointer;margin-top:20px;font-size:1rem;font-weight:600;transition:transform .2s,box-shadow .2s}.error[data-v-378de9a4]{color:#ff6b6b;margin:10px 0;font-size:14px}
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3200 3200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><circle cx="1600" cy="1600" r="1599.79" style="fill:#fff;"/><g><g><clipPath id="_clip1"><path d="M1599.85,0l0.821,0l3.037,0.004c214.309,0.546 428.517,43.913 628.742,130.046c140.346,60.525 270.329,140.571 386.483,236.683l-2100.85,773.042l87.496,94.633l72.692,78.654l42.42,45.867l62.075,67.163l62.759,67.908l42.208,45.65l77.242,83.571l26.233,28.346l149.121,161.308l-181.292,349.696l261.65,-273.775l314.029,-332.013l-118.02,-87.262l-4.934,-3.634l-3.608,-2.691l-6.408,-4.721l-14.334,-10.617l695.725,-470.883l558.85,-590.804c332.971,286.137 547.075,706.833 557.613,1177.41l0.012,0.463l0.092,4.433l0.017,1.008l0.07,4.075l0.025,1.392l0.055,3.792l0.02,1.712l0.038,3.538l0.017,2.037l0.025,3.254l0.016,2.534l0.013,2.791l0.008,4.446l0,1.696l-0.008,4.042l-0.009,2.5l-0.012,2.304l-0.021,2.925l-0.017,1.85l-0.029,3.187l-0.016,1.534l-0.046,3.441l-0.017,1.192l-0.058,3.746l-0.013,0.733c-13.146,757.438 -553.712,1387.3 -1269.93,1537.7l-0.021,0.004c-10.2,2.142 -20.442,4.188 -30.721,6.134l-0.012,0.004l-4.025,0.754l-0.584,0.108l-3.762,0.7l-0.829,0.15l-3.65,0.667l-0.904,0.162l-3.642,0.65l-0.838,0.15l-3.745,0.663l-0.521,0.087c-4.254,0.746 -8.567,1.48 -12.888,2.196l-6,0.988l-5.662,0.904c-23.659,3.746 -47.384,6.958 -71.192,9.65l-0.146,0.017l-5.091,0.562l-0.175,0.021l-7.363,0.787l-4.558,0.471c-1.679,0.167 -3.359,0.338 -5.042,0.504l-4.958,0.48l-2.763,0.262l-2.133,0.2l-3.567,0.321l-1.346,0.121l-3.712,0.329l-1.217,0.104l-3.712,0.313l-1.234,0.104l-3.65,0.3l-1.308,0.104l-3.567,0.279l-1.404,0.113l-3.475,0.258l-1.512,0.113l-3.371,0.245l-1.629,0.117l-3.263,0.225l-1.75,0.121l-3.154,0.208l-1.867,0.121l-3.045,0.192l-1.992,0.125l-2.938,0.175l-2.112,0.125l-2.829,0.158l-2.229,0.125l-2.721,0.146l-2.35,0.125l-2.617,0.133l-2.462,0.121l-2.517,0.121l-2.575,0.116l-2.413,0.109l-2.687,0.116l-2.313,0.096l-2.804,0.109l-2.208,0.087l-2.917,0.104l-2.104,0.075l-3.033,0.104l-2,0.063l-3.159,0.096l-1.883,0.054l-3.292,0.087l-1.758,0.05l-3.446,0.08l-1.616,0.037l-3.638,0.075l-1.429,0.029l-3.913,0.071l-1.162,0.017l-4.379,0.062l-0.704,0.013l-5.109,0.054c-1.721,0.017 -3.441,0.029 -5.162,0.042l-5.092,0.025l-1.312,0.004l-3.755,0.004l-1.725,-0.004l-2.195,-0c-249.413,-0.692 -498.642,-59.346 -725.784,-175.863l-3.054,-1.57c-100.379,-51.846 -194.579,-114.005 -281.167,-185.038l-5.125,-4.225l2100.96,-773.075l-87.475,-94.654l-72.713,-78.658l-42.4,-45.863l-62.075,-67.162l-62.779,-67.888l-42.192,-45.654l-77.237,-83.567l-26.233,-28.366l-149.121,-161.309l181.291,-349.696l-261.65,273.821l-319.754,338.013l125.454,93.779l-8.395,6.662l-635.53,447.688l-583.158,616.529c-229.687,-197.396 -402.804,-458.742 -491.604,-756.308c-42.083,-141.417 -64.196,-287.492 -66.346,-433.821l-0.092,-7.542l-0.054,-6.454l-0.021,-4.854l0,-0.071l-0.008,-5.783l0,-0.313l0.025,-7.458l0.025,-3.671l0.008,-1.375l0.034,-3.25l0.004,-0.625c1.721,-157.862 26.687,-315.55 74.883,-467.458c167.892,-527.492 602.034,-936.642 1144.17,-1069.23l1.533,-0.375l1.854,-0.45c97.83,-23.642 197.455,-37.938 297.471,-42.892l1.571,-0.075l2.467,-0.121l3.016,-0.137l1.367,-0.063c23.608,-1.042 47.454,-1.571 71.421,-1.575Z"/></clipPath><g clip-path="url(#_clip1)"><use id="_Image3_" serif:id="_Image3" xlink:href="#_Image2" x="0" y="0" width="768px" height="768px" transform="matrix(4.16667,0,0,4.16667,0,0)"/></g></g></g></g><defs><image id="_Image2" width="768px" height="768px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAMACAYAAACTgQCOAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOy9abc1OXKdl71WfdJkS2R3Te2JP9Br2cuyRsuSLcuzLcsyNVEiRXEemxL1pyxroMhmd1dX9fWH99775gBE7AhMAWDvD1X3IIEdyHPyAA/yIPL91kFRFEWF0bd/7gcvH1+9XP73Xnp7fa+uVrzb5vxSHjlLJfa/+c//9LeEKBRFUVRHcUCmKIpqpO/8/A9eMgyfgPVMhVHwD4J/MpQSW/L82K+X49/+F3+GcxRFUVQDcXClKIpy6ju/8MMrwt6pmvCf91DgH4n/b/9LLhAoiqI84uBJURSV0Xd+8YcvCLhey/eA/2QYJ/xfXqKLj2T8t/99+OPf/Vf/Aec4iqKohDg4UhS1vT79xR9dGTRH703hvxH4p6xrwz8QO/c6e9dfiI/Av1T33/15LgwoitpbHAQpitpGn/7Sj9Q7+jXgf7q7/imPkfAv9jP/WoV/xfff/QUuDCiK2kMc7CiKWlKf/tKPnrhXE/5X3fIj1HXv90+U1YT/56ouX1f2fXl06Q/+4n/IeZKiqOXEgY2iqKn16a989cp/OQI/0uUI/Dfd8pNvGG2/fzJUx2Rfybcl/Eu+f/CXuDCgKGpecQCjKGoqffYrX33E/QyIP4RsGynY8pOsvgj8z5Dsi9SVy23wn2r/B3/5z3I+pShqGnHAoigqtD771a8unPcEWwP8l2z5EWJuA/+TJfu2h//8ufzBX+GCgKKouOIARVFUGH32a1fYz0Pk7Q8E/nrA/2Dwv/y5GvynfrUB42c687HIsQCR4D93Tfz7v8pFAUVRMcTBiKKoYfrs1746UdjtYDj4n/yuf8qjFP6B9z4L/4477qP2++fap45hn8OH//37/4YLAoqixoiDD0VR3fTZr//4JUlIHvh33Dn+8NIA/8hdf8hrMPwz2Tcc/Kca/vu/9uc4J1MU1UUcbCiKaqrPfv3Ht7v8tu0b1ybCXX/IK7FwUL0I/7nYkmdT+Ad+ech0JtklpA/Jg6k1rKEfUn0uBiiKaikOMBRFVdXnv/EB+C+QlaMjAbaSd/1TbSAvw11/Ieaq8M8n/WR8APiXqqS9dPhP+f/hf8sFAUVR9cQBhaKoYr1B/3Gk7iI//ni+zB3ywH/qrn+qbU347wX+Keva8D9Tsi/k+7FC7Sf9uOFfqpuqdqv/h3Line truncated

After

Width:  |  Height:  |  Size: 60 KiB

+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/assets/sunderer_app/dist/icon3.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>决裂者终端用户中心</title>
<script type="module" crossorigin src="/assets/sunderer_app/dist/js/index.c1R21Jxr.js"></script>
<link rel="stylesheet" crossorigin href="/assets/sunderer_app/dist/css/index.CJO3dQ9s.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))s(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&s(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function s(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();/**
* @vue/shared v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/function _s(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Y={},It=[],Ke=()=>{},Vr=()=>!1,On=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),vs=e=>e.startsWith("onUpdate:"),ue=Object.assign,ys=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},oo=Object.prototype.hasOwnProperty,q=(e,t)=>oo.call(e,t),B=Array.isArray,Nt=e=>Pn(e)==="[object Map]",Br=e=>Pn(e)==="[object Set]",H=e=>typeof e=="function",re=e=>typeof e=="string",pt=e=>typeof e=="symbol",ee=e=>e!==null&&typeof e=="object",Hr=e=>(ee(e)||H(e))&&H(e.then)&&H(e.catch),Ur=Object.prototype.toString,Pn=e=>Ur.call(e),lo=e=>Pn(e).slice(8,-1),jr=e=>Pn(e)==="[object Object]",bs=e=>re(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,qt=_s(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Tn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},co=/-\w/g,at=Tn(e=>e.replace(co,t=>t.slice(1).toUpperCase())),uo=/\B([A-Z])/g,Rt=Tn(e=>e.replace(uo,"-$1").toLowerCase()),Gr=Tn(e=>e.charAt(0).toUpperCase()+e.slice(1)),Un=Tn(e=>e?`on${Gr(e)}`:""),ft=(e,t)=>!Object.is(e,t),gn=(e,...t)=>{for(let n=0;n<e.length;n++)e[n](...t)},Kr=(e,t,n,s=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},Es=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Ws;const In=()=>Ws||(Ws=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function xs(e){if(B(e)){const t={};for(let n=0;n<e.length;n++){const s=e[n],r=re(s)?po(s):xs(s);if(r)for(const i in r)t[i]=r[i]}return t}else if(re(e)||ee(e))return e}const fo=/;(?![^(]*\))/g,ao=/:([^]+)/,ho=/\/\*[^]*?\*\//g;function po(e){const t={};return e.replace(ho,"").split(fo).forEach(n=>{if(n){const s=n.split(ao);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function Rs(e){let t="";if(re(e))t=e;else if(B(e))for(let n=0;n<e.length;n++){const s=Rs(e[n]);s&&(t+=s+" ")}else if(ee(e))for(const n in e)e[n]&&(t+=n+" ");return t.trim()}const go="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",mo=_s(go);function Wr(e){return!!e||e===""}const $r=e=>!!(e&&e.__v_isRef===!0),Ye=e=>re(e)?e:e==null?"":B(e)||ee(e)&&(e.toString===Ur||!H(e.toString))?$r(e)?Ye(e.value):JSON.stringify(e,kr,2):String(e),kr=(e,t)=>$r(t)?kr(e,t.value):Nt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[jn(s,i)+" =>"]=r,n),{})}:Br(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>jn(n))}:pt(t)?jn(t):ee(t)&&!B(t)&&!jr(t)?String(t):t,jn=(e,t="")=>{var n;return pt(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/**
* @vue/reactivity v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/let ve;class _o{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=ve,!t&&ve&&(this.index=(ve.scopes||(ve.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].pause();for(t=0,n=this.effects.length;t<n;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].resume();for(t=0,n=this.effects.length;t<n;t++)this.effects[t].resume()}}run(t){if(this._active){const n=ve;try{return ve=this,t()}finally{ve=n}}}on(){++this._on===1&&(this.prevScope=ve,ve=this)}off(){this._on>0&&--this._on===0&&(ve=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let n,s;for(n=0,s=this.effects.length;n<s;n++)this.effects[n].stop();for(this.effects.length=0,n=0,s=this.cleanups.length;n<s;n++)this.cleanups[n]();if(this.cleanups.length=0,this.scopes){for(n=0,s=this.scopes.length;n<s;n++)this.scopes[n].stop(!0);this.scopes.length=0}if(!this.detached&&this.parent&&!t){const r=this.parent.scopes.pop();r&&r!==this&&(this.parent.scopes[this.index]=r,r.index=this.index)}this.parent=void 0}}}function vo(){return ve}let X;const Gn=new WeakSet;class qr{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.flags=5,this.next=void 0,this.cleanup=void 0,this.scheduler=void 0,ve&&ve.active&&ve.effects.push(this)}pause(){this.flags|=64}resume(){this.flags&64&&(this.flags&=-65,Gn.has(this)&&(Gn.delete(this),this.trigger()))}notify(){this.flags&2&&!(this.flags&32)||this.flags&8||zr(this)}run(){if(!(this.flags&1))return this.fn();this.flags|=2,$s(this),Qr(this);const t=X,n=Ce;X=this,Ce=!0;try{return this.fn()}finally{Yr(this),X=t,Ce=n,this.flags&=-3}}stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)Ss(t);this.deps=this.depsTail=void 0,$s(this),this.onStop&&this.onStop(),this.flags&=-2}}trigger(){this.flags&64?Gn.add(this):this.scheduler?this.scheduler():this.runIfDirty()}runIfDirty(){ns(this)&&this.run()}get dirty(){return ns(this)}}let Jr=0,Jt,zt;function zr(e,t=!1){if(e.flags|=8,t){e.next=zt,zt=e;return}e.next=Jt,Jt=e}function ws(){Jr++}function As(){if(--Jr>0)return;if(zt){let t=zt;for(zt=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Jt;){let t=Jt;for(Jt=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Qr(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Yr(e){let t,n=e.depsTail,s=n;for(;s;){const r=s.prevDep;s.version===-1?(s===n&&(n=r),Ss(s),yo(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=r}e.deps=t,e.depsTail=n}function ns(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Zr(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Zr(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===nn)||(e.globalVersion=nn,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!ns(e))))return;e.flags|=2;const t=e.dep,n=X,s=Ce;X=e,Ce=!0;try{Qr(e);const r=e.fn(e._value);(t.version===0||ft(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{X=n,Ce=s,Yr(e),e.flags&=-3}}function Ss(e,t=!1){const{dep:n,prevSub:s,nextSub:r}=e;if(s&&(s.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let i=n.computed.deps;i;i=i.nextDep)Ss(i,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function yo(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Ce=!0;const Xr=[];function Ze(){Xr.push(Ce),Ce=!1}function Xe(){const e=Xr.pop();Ce=e===void 0?!0:e}function $s(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=X;X=void 0;try{t()}finally{X=n}}}let nn=0;class bo{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Cs{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!X||!Ce||X===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==X)n=this.activeLink=new bo(X,this),X.deps?(n.prevDep=X.depsTail,X.depsTail.nextDep=n,X.depsTail=n):X.deps=X.depsTail=n,ei(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=X.depsTail,n.nextDep=void 0,X.depsTail.nextDep=n,X.depsTail=n,X.deps===n&&(X.deps=s)}return n}trigger(t){this.version++,nn++,this.notify(t)}notify(t){ws();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{As()}}}function ei(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let sLine truncated
* @vue/runtime-core v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/function fn(e,t,n,s){try{return s?e(...s):e()}catch(r){Mn(r,t,n)}}function We(e,t,n,s){if(H(e)){const r=fn(e,t,n,s);return r&&Hr(r)&&r.catch(i=>{Mn(i,t,n)}),r}if(B(e)){const r=[];for(let i=0;i<e.length;i++)r.push(We(e[i],t,n,s));return r}}function Mn(e,t,n,s=!0){const r=t?t.vnode:null,{errorHandler:i,throwUnhandledErrorInProduction:o}=t&&t.appContext.config||Y;if(t){let c=t.parent;const l=t.proxy,d=`https://vuejs.org/error-reference/#runtime-${n}`;for(;c;){const f=c.ec;if(f){for(let h=0;h<f.length;h++)if(f[h](e,l,d)===!1)return}c=c.parent}if(i){Ze(),fn(i,null,10,[e,l,d]),Xe();return}}$o(e,n,r,s,o)}function $o(e,t,n,s=!0,r=!1){if(r)throw e;console.error(e)}const de=[];let He=-1;const Dt=[];let lt=null,Ot=0;const fi=Promise.resolve();let En=null;function ai(e){const t=En||fi;return e?t.then(this?e.bind(this):e):t}function ko(e){let t=He+1,n=de.length;for(;t<n;){const s=t+n>>>1,r=de[s],i=on(r);i<e||i===e&&r.flags&2?t=s+1:n=s}return t}function Ns(e){if(!(e.flags&1)){const t=on(e),n=de[de.length-1];!n||!(e.flags&2)&&t>=on(n)?de.push(e):de.splice(ko(t),0,e),e.flags|=1,di()}}function di(){En||(En=fi.then(pi))}function qo(e){B(e)?Dt.push(...e):lt&&e.id===-1?lt.splice(Ot+1,0,e):e.flags&1||(Dt.push(e),e.flags|=1),di()}function qs(e,t,n=He+1){for(;n<de.length;n++){const s=de[n];if(s&&s.flags&2){if(e&&s.id!==e.uid)continue;de.splice(n,1),n--,s.flags&4&&(s.flags&=-2),s(),s.flags&4||(s.flags&=-2)}}}function hi(e){if(Dt.length){const t=[...new Set(Dt)].sort((n,s)=>on(n)-on(s));if(Dt.length=0,lt){lt.push(...t);return}for(lt=t,Ot=0;Ot<lt.length;Ot++){const n=lt[Ot];n.flags&4&&(n.flags&=-2),n.flags&8||n(),n.flags&=-2}lt=null,Ot=0}}const on=e=>e.id==null?e.flags&2?-1:1/0:e.id;function pi(e){try{for(He=0;He<de.length;He++){const t=de[He];t&&!(t.flags&8)&&(t.flags&4&&(t.flags&=-2),fn(t,t.i,t.i?15:14),t.flags&4||(t.flags&=-2))}}finally{for(;He<de.length;He++){const t=de[He];t&&(t.flags&=-2)}He=-1,de.length=0,hi(),En=null,(de.length||Dt.length)&&pi()}}let we=null,gi=null;function xn(e){const t=we;return we=e,gi=e&&e.type.__scopeId||null,t}function Jo(e,t=we,n){if(!t||e._n)return e;const s=(...r)=>{s._d&&An(-1);const i=xn(t);let o;try{o=e(...r)}finally{xn(i),s._d&&An(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function Ft(e,t){if(we===null)return e;const n=Vn(we),s=e.dirs||(e.dirs=[]);for(let r=0;r<t.length;r++){let[i,o,c,l=Y]=t[r];i&&(H(i)&&(i={mounted:i,updated:i}),i.deep&&Qe(o),s.push({dir:i,instance:n,value:o,oldValue:void 0,arg:c,modifiers:l}))}return e}function _t(e,t,n,s){const r=e.dirs,i=t&&t.dirs;for(let o=0;o<r.length;o++){const c=r[o];i&&(c.oldValue=i[o].value);let l=c.dir[s];l&&(Ze(),We(l,n,8,[e.el,c,e,t]),Xe())}}function mn(e,t){if(pe){let n=pe.provides;const s=pe.parent&&pe.parent.provides;s===n&&(n=pe.provides=Object.create(s)),n[e]=t}}function Pe(e,t,n=!1){const s=zl();if(s||Lt){let r=Lt?Lt._context.provides:s?s.parent==null||s.ce?s.vnode.appContext&&s.vnode.appContext.provides:s.parent.provides:void 0;if(r&&e in r)return r[e];if(arguments.length>1)return n&&H(t)?t.call(s&&s.proxy):t}}const zo=Symbol.for("v-scx"),Qo=()=>Pe(zo);function _n(e,t,n){return mi(e,t,n)}function mi(e,t,n=Y){const{immediate:s,deep:r,flush:i,once:o}=n,c=ue({},n),l=t&&s||!t&&i!=="post";let d;if(cn){if(i==="sync"){const m=Qo();d=m.__watcherHandles||(m.__watcherHandles=[])}else if(!l){const m=()=>{};return m.stop=Ke,m.resume=Ke,m.pause=Ke,m}}const f=pe;c.call=(m,A,P)=>We(m,f,A,P);let h=!1;i==="post"?c.scheduler=m=>{Ee(m,f&&f.suspense)}:i!=="sync"&&(h=!0,c.scheduler=(m,A)=>{A?m():Ns(m)}),c.augmentJob=m=>{t&&(m.flags|=4),h&&(m.flags|=2,f&&(m.id=f.uid,m.i=f))};const g=Wo(e,t,c);return cn&&(d?d.push(g):l&&g()),g}function Yo(e,t,n){const s=this.proxy,r=re(e)?e.includes(".")?_i(s,e):()=>s[e]:e.bind(s,s);let i;H(t)?i=t:(i=t.handler,n=t);const o=an(this),c=mi(r,i.bind(s),n);return o(),c}function _i(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;r<n.length&&s;r++)s=s[n[r]];return s}}const Zo=Symbol("_vte"),Xo=e=>e.__isTeleport,el=Symbol("_leaveCb");function Ms(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Ms(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function vi(e,t){return H(e)?ue({name:e.name},t,{setup:e}):e}function yi(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}const Rn=new WeakMap;function Qt(e,t,n,s,r=!1){if(B(e)){e.forEach((A,P)=>Qt(A,t&&(B(t)?t[P]:t),n,s,r));return}if(Yt(s)&&!r){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Qt(e,t,n,s.component.subTree);return}const i=s.shapeFlag&4?Vn(s.component):s.el,o=r?null:i,{i:c,r:l}=e,d=t&&t.r,f=c.refs===Y?c.refs={}:c.refs,h=c.setupState,g=k(h),m=h===Y?Vr:A=>q(g,A);if(d!=null&&d!==l){if(Js(t),re(d))f[d]=null,m(d)&&(h[d]=null);else if(ce(d)){d.value=null;const A=t;A.k&&(f[A.k]=null)}}if(H(l))fn(l,c,12,[o,f]);else{const A=re(l),P=ce(l);if(A||P){const U=()=>{if(e.f){const N=A?m(l)?h[l]:f[l]:l.value;if(r)B(N)&&ys(N,i);else if(B(N))N.includes(i)||N.push(i);else if(A)f[l Line truncated
* @vue/runtime-dom v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/let ds;const ir=typeof window<"u"&&window.trustedTypes;if(ir)try{ds=ir.createPolicy("vue",{createHTML:e=>e})}catch{}const Wi=ds?e=>ds.createHTML(e):e=>e,nc="http://www.w3.org/2000/svg",sc="http://www.w3.org/1998/Math/MathML",Je=typeof document<"u"?document:null,or=Je&&Je.createElement("template"),rc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?Je.createElementNS(nc,e):t==="mathml"?Je.createElementNS(sc,e):n?Je.createElement(e,{is:n}):Je.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Je.createTextNode(e),createComment:e=>Je.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Je.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{or.innerHTML=Wi(s==="svg"?`<svg>${e}</svg>`:s==="mathml"?`<math>${e}</math>`:e);const c=or.content;if(s==="svg"||s==="mathml"){const l=c.firstChild;for(;l.firstChild;)c.appendChild(l.firstChild);c.removeChild(l)}t.insertBefore(c,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},ic=Symbol("_vtc");function oc(e,t,n){const s=e[ic];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const lr=Symbol("_vod"),lc=Symbol("_vsh"),cc=Symbol(""),uc=/(?:^|;)\s*display\s*:/;function fc(e,t,n){const s=e.style,r=re(n);let i=!1;if(n&&!r){if(t)if(re(t))for(const o of t.split(";")){const c=o.slice(0,o.indexOf(":")).trim();n[c]==null&&yn(s,c,"")}else for(const o in t)n[o]==null&&yn(s,o,"");for(const o in n)o==="display"&&(i=!0),yn(s,o,n[o])}else if(r){if(t!==n){const o=s[cc];o&&(n+=";"+o),s.cssText=n,i=uc.test(n)}}else t&&e.removeAttribute("style");lr in e&&(e[lr]=i?s.display:"",e[lc]&&(s.display="none"))}const cr=/\s*!important$/;function yn(e,t,n){if(B(n))n.forEach(s=>yn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=ac(e,t);cr.test(n)?e.setProperty(Rt(s),n.replace(cr,""),"important"):e[s]=n}}const ur=["Webkit","Moz","ms"],Jn={};function ac(e,t){const n=Jn[t];if(n)return n;let s=at(t);if(s!=="filter"&&s in e)return Jn[t]=s;s=Gr(s);for(let r=0;r<ur.length;r++){const i=ur[r]+s;if(i in e)return Jn[t]=i}return t}const fr="http://www.w3.org/1999/xlink";function ar(e,t,n,s,r,i=mo(t)){s&&t.startsWith("xlink:")?n==null?e.removeAttributeNS(fr,t.slice(6,t.length)):e.setAttributeNS(fr,t,n):n==null||i&&!Wr(n)?e.removeAttribute(t):e.setAttribute(t,i?"":pt(n)?String(n):n)}function dr(e,t,n,s,r){if(t==="innerHTML"||t==="textContent"){n!=null&&(e[t]=t==="innerHTML"?Wi(n):n);return}const i=e.tagName;if(t==="value"&&i!=="PROGRESS"&&!i.includes("-")){const c=i==="OPTION"?e.getAttribute("value")||"":e.value,l=n==null?e.type==="checkbox"?"on":"":String(n);(c!==l||!("_value"in e))&&(e.value=l),n==null&&e.removeAttribute(t),e._value=n;return}let o=!1;if(n===""||n==null){const c=typeof e[t];c==="boolean"?n=Wr(n):n==null&&c==="string"?(n="",o=!0):c==="number"&&(n=0,o=!0)}try{e[t]=n}catch{}o&&e.removeAttribute(r||t)}function Pt(e,t,n,s){e.addEventListener(t,n,s)}function dc(e,t,n,s){e.removeEventListener(t,n,s)}const hr=Symbol("_vei");function hc(e,t,n,s,r=null){const i=e[hr]||(e[hr]={}),o=i[t];if(s&&o)o.value=s;else{const[c,l]=pc(t);if(s){const d=i[t]=_c(s,r);Pt(e,c,d,l)}else o&&(dc(e,c,o,l),i[t]=void 0)}}const pr=/(?:Once|Passive|Capture)$/;function pc(e){let t;if(pr.test(e)){t={};let s;for(;s=e.match(pr);)e=e.slice(0,e.length-s[0].length),t[s[0].toLowerCase()]=!0}return[e[2]===":"?e.slice(3):Rt(e.slice(2)),t]}let zn=0;const gc=Promise.resolve(),mc=()=>zn||(gc.then(()=>zn=0),zn=Date.now());function _c(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;We(vc(s,n.value),t,5,[s])};return n.value=e,n.attached=mc(),n}function vc(e,t){if(B(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const gr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,yc=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?oc(e,s,o):t==="style"?fc(e,n,s):On(t)?vs(t)||hc(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):bc(e,t,s,o))?(dr(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&ar(e,t,s,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!re(s))?dr(e,at(t),s,i,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),ar(e,t,s,o))};function bc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&gr(t)&&H(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagN Line truncated
* vue-router v4.6.4
* (c) 2025 Eduardo San Martin Morote
* @license MIT
*/const Tt=typeof document<"u";function ki(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Pc(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&ki(e.default)}const $=Object.assign;function Yn(e,t){const n={};for(const s in t){const r=t[s];n[s]=Te(r)?r.map(e):e(r)}return n}const tn=()=>{},Te=Array.isArray;function br(e,t){const n={};for(const s in e)n[s]=s in t?t[s]:e[s];return n}const qi=/#/g,Tc=/&/g,Ic=/\//g,Nc=/=/g,Mc=/\?/g,Ji=/\+/g,Dc=/%5B/g,Lc=/%5D/g,zi=/%5E/g,Fc=/%60/g,Qi=/%7B/g,Vc=/%7C/g,Yi=/%7D/g,Bc=/%20/g;function Vs(e){return e==null?"":encodeURI(""+e).replace(Vc,"|").replace(Dc,"[").replace(Lc,"]")}function Hc(e){return Vs(e).replace(Qi,"{").replace(Yi,"}").replace(zi,"^")}function hs(e){return Vs(e).replace(Ji,"%2B").replace(Bc,"+").replace(qi,"%23").replace(Tc,"%26").replace(Fc,"`").replace(Qi,"{").replace(Yi,"}").replace(zi,"^")}function Uc(e){return hs(e).replace(Nc,"%3D")}function jc(e){return Vs(e).replace(qi,"%23").replace(Mc,"%3F")}function Gc(e){return jc(e).replace(Ic,"%2F")}function un(e){if(e==null)return null;try{return decodeURIComponent(""+e)}catch{}return""+e}const Kc=/\/$/,Wc=e=>e.replace(Kc,"");function Zn(e,t,n="/"){let s,r={},i="",o="";const c=t.indexOf("#");let l=t.indexOf("?");return l=c>=0&&l>c?-1:l,l>=0&&(s=t.slice(0,l),i=t.slice(l,c>0?c:t.length),r=e(i.slice(1))),c>=0&&(s=s||t.slice(0,c),o=t.slice(c,t.length)),s=Jc(s??t,n),{fullPath:s+i+o,path:s,query:r,hash:un(o)}}function $c(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function Er(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function kc(e,t,n){const s=t.matched.length-1,r=n.matched.length-1;return s>-1&&s===r&&Ht(t.matched[s],n.matched[r])&&Zi(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function Ht(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Zi(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var n in e)if(!qc(e[n],t[n]))return!1;return!0}function qc(e,t){return Te(e)?xr(e,t):Te(t)?xr(t,e):(e==null?void 0:e.valueOf())===(t==null?void 0:t.valueOf())}function xr(e,t){return Te(t)?e.length===t.length&&e.every((n,s)=>n===t[s]):e.length===1&&e[0]===t}function Jc(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),s=e.split("/"),r=s[s.length-1];(r===".."||r===".")&&s.push("");let i=n.length-1,o,c;for(o=0;o<s.length;o++)if(c=s[o],c!==".")if(c==="..")i>1&&i--;else break;return n.slice(0,i).join("/")+"/"+s.slice(o).join("/")}const it={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0};let ps=function(e){return e.pop="pop",e.push="push",e}({}),Xn=function(e){return e.back="back",e.forward="forward",e.unknown="",e}({});function zc(e){if(!e)if(Tt){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),Wc(e)}const Qc=/^[^#]+#/;function Yc(e,t){return e.replace(Qc,"#")+t}function Zc(e,t){const n=document.documentElement.getBoundingClientRect(),s=e.getBoundingClientRect();return{behavior:t.behavior,left:s.left-n.left-(t.left||0),top:s.top-n.top-(t.top||0)}}const Bn=()=>({left:window.scrollX,top:window.scrollY});function Xc(e){let t;if("el"in e){const n=e.el,s=typeof n=="string"&&n.startsWith("#"),r=typeof n=="string"?s?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!r)return;t=Zc(r,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function Rr(e,t){return(history.state?history.state.position-t:-1)+e}const gs=new Map;function eu(e,t){gs.set(e,t)}function tu(e){const t=gs.get(e);return gs.delete(e),t}function nu(e){return typeof e=="string"||e&&typeof e=="object"}function Xi(e){return typeof e=="string"||typeof e=="symbol"}let ne=function(e){return e[e.MATCHER_NOT_FOUND=1]="MATCHER_NOT_FOUND",e[e.NAVIGATION_GUARD_REDIRECT=2]="NAVIGATION_GUARD_REDIRECT",e[e.NAVIGATION_ABORTED=4]="NAVIGATION_ABORTED",e[e.NAVIGATION_CANCELLED=8]="NAVIGATION_CANCELLED",e[e.NAVIGATION_DUPLICATED=16]="NAVIGATION_DUPLICATED",e}({});const eo=Symbol("");ne.MATCHER_NOT_FOUND+"",ne.NAVIGATION_GUARD_REDIRECT+"",ne.NAVIGATION_ABORTED+"",ne.NAVIGATION_CANCELLED+"",ne.NAVIGATION_DUPLICATED+"";function Ut(e,t){return $(new Error,{type:e,[eo]:!0},t)}function qe(e,t){return e instanceof Error&&eo in e&&(t==null||!!(e.type&t))}const su=["params","query","hash"];function ru(e){if(typeof e=="string")return e;if(e.path!=null)return e.path;const t={};for(const n of su)n in e&&(t[n]=e[n]);return JSON.stringify(t,null,2)}function iu(e){const t={};if(e===""||e==="?")return t;const n=(e[0]==="?"?e.slice(1):e).split("&");for(let s=0;s<n.length;++s){const r=n[s].replace(Ji," "),i=r.indexOf("="),o=un(i<0?r:r.slice(0,i)),c=i<0?null:un(r.slice(i+1));if(o in t){let l=t[o];Te(l)||(l=t[o]=[l]),l.push(c)}else t[o]=c}reLine truncated
* vue-router v4.6.4
* (c) 2025 Eduardo San Martin Morote
* @license MIT
*/let uu=()=>location.protocol+"//"+location.host;function to(e,t){const{pathname:n,search:s,hash:r}=t,i=e.indexOf("#");if(i>-1){let o=r.includes(e.slice(i))?e.slice(i).length:1,c=r.slice(o);return c[0]!=="/"&&(c="/"+c),Er(c,"")}return Er(n,e)+s+r}function fu(e,t,n,s){let r=[],i=[],o=null;const c=({state:g})=>{const m=to(e,location),A=n.value,P=t.value;let U=0;if(g){if(n.value=m,t.value=g,o&&o===A){o=null;return}U=P?g.position-P.position:0}else s(m);r.forEach(N=>{N(n.value,A,{delta:U,type:ps.pop,direction:U?U>0?Xn.forward:Xn.back:Xn.unknown})})};function l(){o=n.value}function d(g){r.push(g);const m=()=>{const A=r.indexOf(g);A>-1&&r.splice(A,1)};return i.push(m),m}function f(){if(document.visibilityState==="hidden"){const{history:g}=window;if(!g.state)return;g.replaceState($({},g.state,{scroll:Bn()}),"")}}function h(){for(const g of i)g();i=[],window.removeEventListener("popstate",c),window.removeEventListener("pagehide",f),document.removeEventListener("visibilitychange",f)}return window.addEventListener("popstate",c),window.addEventListener("pagehide",f),document.addEventListener("visibilitychange",f),{pauseListeners:l,listen:d,destroy:h}}function Sr(e,t,n,s=!1,r=!1){return{back:e,current:t,forward:n,replaced:s,position:window.history.length,scroll:r?Bn():null}}function au(e){const{history:t,location:n}=window,s={value:to(e,n)},r={value:t.state};r.value||i(s.value,{back:null,current:s.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(l,d,f){const h=e.indexOf("#"),g=h>-1?(n.host&&document.querySelector("base")?e:e.slice(h))+l:uu()+e+l;try{t[f?"replaceState":"pushState"](d,"",g),r.value=d}catch(m){console.error(m),n[f?"replace":"assign"](g)}}function o(l,d){i(l,$({},t.state,Sr(r.value.back,l,r.value.forward,!0),d,{position:r.value.position}),!0),s.value=l}function c(l,d){const f=$({},r.value,t.state,{forward:l,scroll:Bn()});i(f.current,f,!0),i(l,$({},Sr(s.value,l,null),{position:f.position+1},d),!1),s.value=l}return{location:s,state:r,push:c,replace:o}}function du(e){e=zc(e);const t=au(e),n=fu(e,t.state,t.location,t.replace);function s(i,o=!0){o||n.pauseListeners(),history.go(i)}const r=$({location:"",base:e,go:s,createHref:Yc.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}let bt=function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.Group=2]="Group",e}({});var se=function(e){return e[e.Static=0]="Static",e[e.Param=1]="Param",e[e.ParamRegExp=2]="ParamRegExp",e[e.ParamRegExpEnd=3]="ParamRegExpEnd",e[e.EscapeNext=4]="EscapeNext",e}(se||{});const hu={type:bt.Static,value:""},pu=/[a-zA-Z0-9_]/;function gu(e){if(!e)return[[]];if(e==="/")return[[hu]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(m){throw new Error(`ERR (${n})/"${d}": ${m}`)}let n=se.Static,s=n;const r=[];let i;function o(){i&&r.push(i),i=[]}let c=0,l,d="",f="";function h(){d&&(n===se.Static?i.push({type:bt.Static,value:d}):n===se.Param||n===se.ParamRegExp||n===se.ParamRegExpEnd?(i.length>1&&(l==="*"||l==="+")&&t(`A repeatable param (${d}) must be alone in its segment. eg: '/:ids+.`),i.push({type:bt.Param,value:d,regexp:f,repeatable:l==="*"||l==="+",optional:l==="*"||l==="?"})):t("Invalid state to consume buffer"),d="")}function g(){d+=l}for(;c<e.length;){if(l=e[c++],l==="\\"&&n!==se.ParamRegExp){s=n,n=se.EscapeNext;continue}switch(n){case se.Static:l==="/"?(d&&h(),o()):l===":"?(h(),n=se.Param):g();break;case se.EscapeNext:g(),n=s;break;case se.Param:l==="("?n=se.ParamRegExp:pu.test(l)?g():(h(),n=se.Static,l!=="*"&&l!=="?"&&l!=="+"&&c--);break;case se.ParamRegExp:l===")"?f[f.length-1]=="\\"?f=f.slice(0,-1)+l:n=se.ParamRegExpEnd:f+=l;break;case se.ParamRegExpEnd:h(),n=se.Static,l!=="*"&&l!=="?"&&l!=="+"&&c--,f="";break;default:t("Unknown state");break}}return n===se.ParamRegExp&&t(`Unfinished custom RegExp for param "${d}"`),h(),o(),r}const Cr="[^/]+?",mu={sensitive:!1,strict:!1,start:!0,end:!0};var ae=function(e){return e[e._multiplier=10]="_multiplier",e[e.Root=90]="Root",e[e.Segment=40]="Segment",e[e.SubSegment=30]="SubSegment",e[e.Static=40]="Static",e[e.Dynamic=20]="Dynamic",e[e.BonusCustomRegExp=10]="BonusCustomRegExp",e[e.BonusWildcard=-50]="BonusWildcard",e[e.BonusRepeatable=-20]="BonusRepeatable",e[e.BonusOptional=-8]="BonusOptional",e[e.BonusStrict=.7000000000000001]="BonusStrict",e[e.BonusCaseSensitive=.25]="BonusCaseSensitive",e}(ae||{});const _u=/[.+*?^${}()[\]/\\]/g;function vu(e,t){const n=$({},mu,t),s=[];let r=n.start?"^":"";const i=[];for(const d of e){const f=d.length?[]:[ae.Root];n.strict&&!d.length&&(r+="/");for(let h=0;h<d.length;h++){const g=d[h];let m=ae.Segment+(n.sensitive?ae.BonusCaseSensitive:0);if(g.type===bt.Static)h||(r+="/"),r+=g.value.replace(_u,"\\$&"),m+=ae.Static;else if(g.type===bt.Param){const{value:A,repeatable:P,optional:U,regexp:N}=g;i.push({name:A,repeatable:P,optional:U});const T=N||Cr;if(T!==Cr){m+=ae.BonusCustomLine truncated
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+90
View File
@@ -0,0 +1,90 @@
# sunderer_app/sunderer_app/setup_doctype.py
import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
def create_signup_key_doctype():
if frappe.db.exists("DocType", "Signup Key"):
print("DocType 'Signup Key' already exists.")
return
print("Creating DocType 'Signup Key'...")
try:
# 创建 DocType 文档对象
signup_key_doctype = frappe.new_doc("DocType")
signup_key_doctype.name = "Signup Key"
signup_key_doctype.module = "Sunderer" # 替换为你应用的模块名
signup_key_doctype.is_submittable = 0
signup_key_doctype.is_tree = 0
signup_key_doctype.istable = 0
# signup_key_doctype.autoname = "field:key" # <--- 移除这一行!
# signup_key_doctype.autoname = "hash" # <--- 或者选择这一行 (如果你想用哈希作为 name)
signup_key_doctype.allow_rename = 0
# 插入 DocType (此时字段还不存在)
signup_key_doctype.insert(ignore_permissions=True)
# 创建字段
# 1. Key 字段
key_field = frappe.new_doc("DocField")
key_field.parent = "Signup Key"
key_field.parenttype = "DocType"
key_field.parentfield = "fields"
key_field.fieldname = "key"
key_field.label = "Key"
key_field.fieldtype = "Data"
key_field.reqd = 1 # 必填
key_field.unique = 1 # 唯一索引,很重要!
key_field.insert(ignore_permissions=True)
# 2. Email 字段
email_field = frappe.new_doc("DocField")
email_field.parent = "Signup Key"
email_field.parenttype = "DocType"
email_field.parentfield = "fields"
email_field.fieldname = "email"
email_field.label = "Email"
email_field.fieldtype = "Data"
email_field.options = "Email"
email_field.reqd = 1 # 必填
email_field.insert(ignore_permissions=True)
# 3. Used 字段
used_field = frappe.new_doc("DocField")
used_field.parent = "Signup Key"
used_field.parenttype = "DocType"
used_field.parentfield = "fields"
used_field.fieldname = "used"
used_field.label = "Used"
used_field.fieldtype = "Check"
used_field.default = "0"
used_field.insert(ignore_permissions=True)
# 4. Expires On 字段 (可选)
expires_on_field = frappe.new_doc("DocField")
expires_on_field.parent = "Signup Key"
expires_on_field.parenttype = "DocType"
expires_on_field.parentfield = "fields"
expires_on_field.fieldname = "expires_on"
expires_on_field.label = "Expires On"
expires_on_field.fieldtype = "Datetime"
expires_on_field.insert(ignore_permissions=True)
# 可选:设置列表视图默认排序
make_property_setter("Signup Key", "list_view_fields", "value", "['key', 'email', 'used', 'expires_on']", "Text", for_doctype=True)
# 保存 DocType 以应用所有更改
signup_key_doctype.save(ignore_permissions=True)
print("Successfully created DocType 'Signup Key' with fields: key, email, used, expires_on.")
except Exception as e:
frappe.log_error(title="Failed to Create Signup Key DocType", message=frappe.get_traceback())
print(f"Error creating DocType 'Signup Key': {str(e)}")
if __name__ == "__main__":
frappe.connect()
create_signup_key_doctype()
frappe.db.commit()
frappe.destroy()
View File
Whitespace-only changes.
Whitespace-only changes.
View File
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,21 @@
<!-- www/sundererapp.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>决裂者终端-用户中心</title>
<!-- 如果你的 Vue 应用构建后生成了独立的 CSS 文件,取消下面这行的注释并调整文件名 -->
<!-- <link rel="stylesheet" href="/sundererapp.[hash].css"> -->
</head>
<body>
<div id="app"></div>
<!-- 引入你的 Vue 应用构建后的 JS 文件 -->
<!-- 路径必须与 vite.config.js 中 outDir 和 rollupOptions.output 的配置结果匹配 -->
<!-- 假设 vite.config.js 中 outDir: '../../public/' 且 input key 为 'sundererapp' -->
<!-- 构建后文件会出现在 sunderer_app/public/ 目录下,例如: sundererapp.a1b2c3d4.js -->
<!-- 所以这里的 src 应该是 /sundererapp.[hash].js -->
<!-- 注意:你需要将 [hash] 替换为实际构建后生成的哈希值 -->
<script src="/assets/sunderer_app/SundererPage/js/sundererapp.BRnFqPg_.js"></script>
</body>
</html>
@@ -0,0 +1,11 @@
# templates/pages/sundererapp/sundererapp.py
import frappe
from frappe import _
def get_context(context):
"""
SPA 路由处理逻辑。
由于 hooks.py 将路由映射到这里,且模板文件名为 sundererapp.html,
Frappe 会自动查找 sundererapp/sundererapp.html 作为模板。
"""
pass # 保持简单,让 Frappe 自动渲染模板
+6
View File
@@ -0,0 +1,6 @@
# sunderer_app/utils.py
import frappe
def update_website_context(context):
context["brand_html"] = "Sunderer"
# 其他上下文覆盖
+20
View File
@@ -0,0 +1,20 @@
<!-- sunderer_app/sunderer_app/www/IncreaseAcc/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>决裂者终端 - 完成账户注册</title>
<!-- 引用构建后生成的 CSS 文件 (如果存在) -->
<!-- <link rel="stylesheet" href="/assets/sunderer_app/css/increaseacc.css"> -->
<!-- 如果没有独立 CSS 文件,则保持注释 -->
</head>
<body>
<div id="app"></div>
<!-- 引用构建后生成的 UMD 格式的 JS 文件 -->
<!-- 路径指向 public/js 目录 -->
<script src="/assets/sunderer_app/IncreaseAcc/increaseacc.js"></script>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/assets/sunderer_app/dist/icon3.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>决裂者终端用户中心</title>
<script type="module" crossorigin src="/assets/sunderer_app/dist/js/index.c1R21Jxr.js"></script>
<link rel="stylesheet" crossorigin href="/assets/sunderer_app/dist/css/index.CJO3dQ9s.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
<!-- www/sundererapp.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>决裂者终端-用户中心</title>
<!-- 如果你的 Vue 应用构建后生成了独立的 CSS 文件,取消下面这行的注释并调整文件名 -->
<!-- <link rel="stylesheet" href="/sundererapp.[hash].css"> -->
</head>
<body>
<div id="app"></div>
<!-- 引入你的 Vue 应用构建后的 JS 文件 -->
<!-- 路径必须与 vite.config.js 中 outDir 和 rollupOptions.output 的配置结果匹配 -->
<!-- 假设 vite.config.js 中 outDir: '../../public/' 且 input key 为 'sundererapp' -->
<!-- 构建后文件会出现在 sunderer_app/public/ 目录下,例如: sundererapp.a1b2c3d4.js -->
<!-- 所以这里的 src 应该是 /sundererapp.[hash].js -->
<!-- 注意:你需要将 [hash] 替换为实际构建后生成的哈希值 -->
<script src="/assets/sunderer_app/SundererPage/js/sundererapp.BRnFqPg_.js"></script>
</body>
</html>