Import local app source
No files matched your search
@@ -0,0 +1,21 @@
|
||||
# Root editor config file
|
||||
root = true
|
||||
|
||||
# Common settings
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
# python, js indentation settings
|
||||
[{*.py,*.js,*.vue,*.css,*.scss,*.html}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
max_line_length = 99
|
||||
|
||||
# JSON files - mostly doctype schema files
|
||||
[{*.json}]
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es2022": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"brace-style": "off",
|
||||
"no-mixed-spaces-and-tabs": "off",
|
||||
"no-useless-escape": "off",
|
||||
"space-unary-ops": ["error", { "words": true }],
|
||||
"linebreak-style": "off",
|
||||
"quotes": ["off"],
|
||||
"semi": "off",
|
||||
"camelcase": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-console": ["warn"],
|
||||
"no-extra-boolean-cast": ["off"],
|
||||
"no-control-regex": ["off"],
|
||||
},
|
||||
"root": true,
|
||||
"globals": {
|
||||
"frappe": true,
|
||||
"Vue": true,
|
||||
"SetVueGlobals": true,
|
||||
"__": true,
|
||||
"repl": true,
|
||||
"Class": true,
|
||||
"locals": true,
|
||||
"cint": true,
|
||||
"cstr": true,
|
||||
"cur_frm": true,
|
||||
"cur_dialog": true,
|
||||
"cur_page": true,
|
||||
"cur_list": true,
|
||||
"cur_tree": true,
|
||||
"msg_dialog": true,
|
||||
"is_null": true,
|
||||
"in_list": true,
|
||||
"has_common": true,
|
||||
"posthog": true,
|
||||
"has_words": true,
|
||||
"validate_email": true,
|
||||
"open_web_template_values_editor": true,
|
||||
"validate_name": true,
|
||||
"validate_phone": true,
|
||||
"validate_url": true,
|
||||
"get_number_format": true,
|
||||
"format_number": true,
|
||||
"format_currency": true,
|
||||
"comment_when": true,
|
||||
"open_url_post": true,
|
||||
"toTitle": true,
|
||||
"lstrip": true,
|
||||
"rstrip": true,
|
||||
"strip": true,
|
||||
"strip_html": true,
|
||||
"replace_all": true,
|
||||
"flt": true,
|
||||
"precision": true,
|
||||
"CREATE": true,
|
||||
"AMEND": true,
|
||||
"CANCEL": true,
|
||||
"copy_dict": true,
|
||||
"get_number_format_info": true,
|
||||
"strip_number_groups": true,
|
||||
"print_table": true,
|
||||
"Layout": true,
|
||||
"web_form_settings": true,
|
||||
"$c": true,
|
||||
"$a": true,
|
||||
"$i": true,
|
||||
"$bg": true,
|
||||
"$y": true,
|
||||
"$c_obj": true,
|
||||
"refresh_many": true,
|
||||
"refresh_field": true,
|
||||
"toggle_field": true,
|
||||
"get_field_obj": true,
|
||||
"get_query_params": true,
|
||||
"unhide_field": true,
|
||||
"hide_field": true,
|
||||
"set_field_options": true,
|
||||
"getCookie": true,
|
||||
"getCookies": true,
|
||||
"get_url_arg": true,
|
||||
"md5": true,
|
||||
"$": true,
|
||||
"jQuery": true,
|
||||
"moment": true,
|
||||
"hljs": true,
|
||||
"Awesomplete": true,
|
||||
"Sortable": true,
|
||||
"Showdown": true,
|
||||
"Taggle": true,
|
||||
"Gantt": true,
|
||||
"Slick": true,
|
||||
"Webcam": true,
|
||||
"PhotoSwipe": true,
|
||||
"PhotoSwipeUI_Default": true,
|
||||
"io": true,
|
||||
"JsBarcode": true,
|
||||
"L": true,
|
||||
"Chart": true,
|
||||
"DataTable": true,
|
||||
"Cypress": true,
|
||||
"cy": true,
|
||||
"it": true,
|
||||
"describe": true,
|
||||
"expect": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true,
|
||||
"qz": true,
|
||||
"localforage": true,
|
||||
"extend_cscript": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.egg-info
|
||||
*.swp
|
||||
tags
|
||||
node_modules
|
||||
__pycache__
|
||||
@@ -0,0 +1,69 @@
|
||||
exclude: 'node_modules|.git'
|
||||
default_stages: [pre-commit]
|
||||
fail_fast: false
|
||||
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
files: "sunderer_app.*"
|
||||
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
|
||||
- id: check-merge-conflict
|
||||
- id: check-ast
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: "Run ruff import sorter"
|
||||
args: ["--select=I", "--fix"]
|
||||
|
||||
- id: ruff
|
||||
name: "Run ruff linter"
|
||||
|
||||
- id: ruff-format
|
||||
name: "Run ruff formatter"
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.7.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, vue, scss]
|
||||
# Ignore any files that might contain jinja / bundles
|
||||
exclude: |
|
||||
(?x)^(
|
||||
sunderer_app/public/dist/.*|
|
||||
.*node_modules.*|
|
||||
.*boilerplate.*|
|
||||
sunderer_app/templates/includes/.*|
|
||||
sunderer_app/public/js/lib/.*
|
||||
)$
|
||||
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.44.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
types_or: [javascript]
|
||||
args: ['--quiet']
|
||||
# Ignore any files that might contain jinja / bundles
|
||||
exclude: |
|
||||
(?x)^(
|
||||
sunderer_app/public/dist/.*|
|
||||
cypress/.*|
|
||||
.*node_modules.*|
|
||||
.*boilerplate.*|
|
||||
sunderer_app/templates/includes/.*|
|
||||
sunderer_app/public/js/lib/.*
|
||||
)$
|
||||
|
||||
ci:
|
||||
autoupdate_schedule: weekly
|
||||
skip: []
|
||||
submodules: false
|
||||
@@ -0,0 +1,33 @@
|
||||
### Sunderer
|
||||
|
||||
test
|
||||
|
||||
### Installation
|
||||
|
||||
You can install this app using the [bench](https://github.com/frappe/bench) CLI:
|
||||
|
||||
```bash
|
||||
cd $PATH_TO_YOUR_BENCH
|
||||
bench get-app $URL_OF_THIS_REPO --branch develop
|
||||
bench install-app sunderer_app
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
|
||||
|
||||
```bash
|
||||
cd apps/sunderer_app
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
Pre-commit is configured to use the following tools for checking and formatting your code:
|
||||
|
||||
- ruff
|
||||
- eslint
|
||||
- prettier
|
||||
- pyupgrade
|
||||
|
||||
### License
|
||||
|
||||
agpl-3.0
|
||||
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,56 @@
|
||||
[project]
|
||||
name = "sunderer_app"
|
||||
authors = [
|
||||
{ name = "test", email = "898879719@qq.com"}
|
||||
]
|
||||
description = "test"
|
||||
requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
dynamic = ["version"]
|
||||
dependencies = [
|
||||
# "frappe~=15.0.0" # Installed and managed by bench.
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core >=3.4,<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
# These dependencies are only installed when developer mode is enabled
|
||||
[tool.bench.dev-dependencies]
|
||||
# package_name = "~=1.1.0"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 110
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"F",
|
||||
"E",
|
||||
"W",
|
||||
"I",
|
||||
"UP",
|
||||
"B",
|
||||
"RUF",
|
||||
]
|
||||
ignore = [
|
||||
"B017", # assertRaises(Exception) - should be more specific
|
||||
"B018", # useless expression, not assigned to anything
|
||||
"B023", # function doesn't bind loop variable - will have last iteration's value
|
||||
"B904", # raise inside except without from
|
||||
"E101", # indentation contains mixed spaces and tabs
|
||||
"E402", # module level import not at top of file
|
||||
"E501", # line too long
|
||||
"E741", # ambiguous variable name
|
||||
"F401", # "unused" imports
|
||||
"F403", # can't detect undefined names from * import
|
||||
"F405", # can't detect undefined names from * import
|
||||
"F722", # syntax error in forward type annotation
|
||||
"W191", # indentation contains tabs
|
||||
]
|
||||
typing-modules = ["frappe.types.DF"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "tab"
|
||||
docstring-code-format = true
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "0.0.1"
|
||||
@@ -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("激活您的账户时发生错误。请稍后重试或联系技术支持。")
|
||||
@@ -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)}"}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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?
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
@@ -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).
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 @@
|
||||
<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,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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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]';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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 标签全局引入
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Sunderer
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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,11 @@
|
||||
{
|
||||
"index.html": {
|
||||
"file": "js/index.c1R21Jxr.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
"css": [
|
||||
"css/index.CJO3dQ9s.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -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,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>
|
||||
@@ -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
|
||||
@@ -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,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()
|
||||
@@ -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 自动渲染模板
|
||||
@@ -0,0 +1,6 @@
|
||||
# sunderer_app/utils.py
|
||||
import frappe
|
||||
|
||||
def update_website_context(context):
|
||||
context["brand_html"] = "Sunderer"
|
||||
# 其他上下文覆盖
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||