mirror of
https://github.com/Mujinniao/Anonima.git
synced 2025-12-19 00:14:40 +08:00
Delete picx directory
This commit is contained in:
674
picx/LICENSE
674
picx/LICENSE
@@ -1,674 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 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 General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is 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. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
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 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. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
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 Affero 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 special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU 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 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 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 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 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU 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 the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author>
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
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 GPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<a href="https://picx.xpoet.cn" >
|
|
||||||
<img width="100" align="right" alt="PicX" src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/picx-logo.png">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
# PicX 图床
|
|
||||||
|
|
||||||
[](https://github.com/XPoet)
|
|
||||||
[](https://github.com/XPoet/picx/releases)
|
|
||||||
[](https://github.com/XPoet/picx/blob/master/LICENSE)
|
|
||||||
[](https://github.com/XPoet/picx)
|
|
||||||
[](https://github.com/XPoet/picx/issues)
|
|
||||||
[](https://github.com/XPoet/picx/actions/workflows/deploy.yml)
|
|
||||||
[](https://github.com/lin-123/javascript)
|
|
||||||
|
|
||||||
**[PicX](https://picx.xpoet.cn)** 是一款基于 GitHub API 开发的具有 CDN 加速功能的图床管理工具
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
> 在线使用入口 **https://picx.xpoet.cn**
|
|
||||||
|
|
||||||
**灵魂拷问,你为图床问题烦恼过吗?**
|
|
||||||
|
|
||||||
- 用 Hexo、VuePress、Hugo 等静态博客写文章,不知图片保存到哪里去...
|
|
||||||
- 特意花钱租云服务器托管图片,太贵划不来,而且上传配置好繁琐...
|
|
||||||
- 网上复制的心仪图片的链接,用着用着某一天就失效了...
|
|
||||||
- 使用其他的付费图床,速度慢,容量小,还限时、限流量...
|
|
||||||
- 想找一款真正免费、稳定、不限容量、访问速度还很快的图床...
|
|
||||||
- ......
|
|
||||||
|
|
||||||
那么,快来试试 **[PicX 图床](https://picx.xpoet.cn)** 吧,专为技术博主打造,谁用谁知道~
|
|
||||||
|
|
||||||
只需选择一个 GitHub 仓库作为图床,然后在 **[PicX 官网](https://picx.xpoet.cn/)** 完成 Token 绑定和相应配置就能使用了。
|
|
||||||
|
|
||||||
**[PicX 图床](https://picx.xpoet.cn)** 浏览器在线使用,免下载&安装,如此简单。
|
|
||||||
|
|
||||||
🆓 免费 🆓 🏆 稳定 🏆 🚀 极速 🚀 🔒 安全 🔒
|
|
||||||
|
|
||||||
## 功能特性 | Features
|
|
||||||
|
|
||||||
- [x] 支持 **拖拽**、**复制粘贴**、**选择文件** 等方式进行选择图片
|
|
||||||
- [x] 支持图片 **重命名**、**哈希化**(确保图片名唯一)和 **设置命名前缀**
|
|
||||||
- [x] 支持 **批量上传图片**、**批量删除图片** 和 **批量复制图片外链**
|
|
||||||
- [x] 支持 **多级目录** 管理 (创建多级目录 / 查看多级目录图片)
|
|
||||||
- [x] 支持 **一键复制** 图片外链和 **一键转换 Markdown 格式**
|
|
||||||
- [x] 支持 **图床管理**(对仓库图片的 **增删改查**)
|
|
||||||
- [x] 支持 **图片压缩** (内置三款压缩算法,可在上传前自动压缩,效果极佳)
|
|
||||||
- [x] 支持 **暗夜模式** (自由切换 / 自动切换)
|
|
||||||
- [x] 支持 **PWA**
|
|
||||||
- [ ] i18n
|
|
||||||
- [ ] 设置图片水印
|
|
||||||
- [ ] 支持其他 Git 厂商 (例如:Gitee / Coding)
|
|
||||||
|
|
||||||
## 使用教程 | Using the tutorial
|
|
||||||
|
|
||||||
官方文档 >> https://picx-docs.xpoet.cn
|
|
||||||
|
|
||||||
## 快速开始 | Get start
|
|
||||||
|
|
||||||
通过阅读官方文档的快速开始教程,可帮助你迅速上手 PicX 图床
|
|
||||||
|
|
||||||
https://picx-docs.xpoet.cn/tutorial/get-start.html
|
|
||||||
|
|
||||||
## 贡献 | Contribution
|
|
||||||
|
|
||||||
欢迎各种形式的贡献,包括但不限于:美化界面、增加功能、改进代码、 修复 Bug 等
|
|
||||||
|
|
||||||
## 反馈 | Feedback
|
|
||||||
|
|
||||||
在使用过程中,如遇问题,请仔细阅读 **[官方文档](https://picx-docs.xpoet.cn)** ,或给作者提 **[Issue](https://github.com/XPoet/picx/issues)**
|
|
||||||
|
|
||||||
## 许可 | License
|
|
||||||
|
|
||||||
**[GPL-3.0](https://github.com/XPoet/picx/blob/master/LICENSE)**
|
|
||||||
|
|
||||||
Copyright © 2020-Present PicX Dev Team
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = { extends: ['@commitlint/config-conventional'] }
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" href="./logo.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" rel="stylesheet">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js"></script>
|
|
||||||
<title>PicX 图床神器</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "picx",
|
|
||||||
"version": "2.1.0",
|
|
||||||
"private": false,
|
|
||||||
"author": "XPoet",
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/XPoet/picx/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/XPoet/picx#readme",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"serve": "vite preview",
|
|
||||||
"release": "git push --tag && git push -u",
|
|
||||||
"format": "prettier --write",
|
|
||||||
"prepare": "husky install"
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.{vue,js,ts}": "eslint --fix"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@element-plus/icons-vue": "^2.0.4",
|
|
||||||
"@yireen/squoosh-browser": "^1.0.7",
|
|
||||||
"axios": "^0.21.4",
|
|
||||||
"element-plus": "^2.2.5",
|
|
||||||
"vue": "^3.2.18",
|
|
||||||
"vue-router": "^4.0.11",
|
|
||||||
"vuex": "^4.0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@commitlint/cli": "^12.1.1",
|
|
||||||
"@commitlint/config-conventional": "^12.1.1",
|
|
||||||
"@types/node": "^15.0.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
|
||||||
"@typescript-eslint/parser": "^4.22.0",
|
|
||||||
"@vitejs/plugin-vue": "^1.1.5",
|
|
||||||
"@vue/compiler-sfc": "^3.2.18",
|
|
||||||
"commitizen": "^4.2.3",
|
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
|
||||||
"cz-customizable": "^6.3.0",
|
|
||||||
"eslint": "^7.32.0",
|
|
||||||
"eslint-config-airbnb-base": "^14.2.1",
|
|
||||||
"eslint-config-prettier": "^8.3.0",
|
|
||||||
"eslint-plugin-import": "^2.22.1",
|
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
|
||||||
"eslint-plugin-vue": "^9.1.1",
|
|
||||||
"husky": "^6.0.0",
|
|
||||||
"lint-staged": "^10.5.4",
|
|
||||||
"prettier": "^2.2.1",
|
|
||||||
"stylus": "^0.54.8",
|
|
||||||
"typescript": "^4.1.5",
|
|
||||||
"unplugin-auto-import": "^0.8.7",
|
|
||||||
"unplugin-vue-components": "^0.19.6",
|
|
||||||
"vite": "~2.7.13",
|
|
||||||
"vite-plugin-pwa": "^0.11.2"
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"commitizen": {
|
|
||||||
"path": "./node_modules/cz-conventional-changelog"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
picx.xpoet.cn
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-config-provider :size="size" :z-index="zIndex" :locale="locale">
|
|
||||||
<main-container />
|
|
||||||
</el-config-provider>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, reactive, toRefs } from 'vue'
|
|
||||||
import { ElConfigProvider } from 'element-plus'
|
|
||||||
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
|
||||||
import mainContainer from '@/components/main-container/main-container.vue'
|
|
||||||
import setTheme from '@/utils/set-theme-mode'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'App',
|
|
||||||
components: {
|
|
||||||
ElConfigProvider,
|
|
||||||
mainContainer
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
zIndex: 3000,
|
|
||||||
size: 'small', // large | default | small
|
|
||||||
locale: zhCn
|
|
||||||
})
|
|
||||||
|
|
||||||
const elementPlusSizeHandle = (width: number) => {
|
|
||||||
if (width <= 600) {
|
|
||||||
store.dispatch('SET_USER_SETTINGS', {
|
|
||||||
elementPlusSize: 'small'
|
|
||||||
})
|
|
||||||
data.size = 'small'
|
|
||||||
} else if (width <= 800) {
|
|
||||||
store.dispatch('SET_USER_SETTINGS', {
|
|
||||||
elementPlusSize: 'default'
|
|
||||||
})
|
|
||||||
data.size = 'default'
|
|
||||||
} else {
|
|
||||||
store.dispatch('SET_USER_SETTINGS', {
|
|
||||||
elementPlusSize: 'large'
|
|
||||||
})
|
|
||||||
data.size = 'large'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setTheme()
|
|
||||||
elementPlusSizeHandle(window.innerWidth)
|
|
||||||
window.addEventListener('resize', (e: any) => {
|
|
||||||
elementPlusSizeHandle(e.target.innerWidth)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
#app {
|
|
||||||
font-family Avenir, Helvetica, Arial, sans-serif
|
|
||||||
-webkit-font-smoothing antialiased
|
|
||||||
-moz-osx-font-smoothing grayscale
|
|
||||||
box-sizing border-box
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,82 +0,0 @@
|
|||||||
import { computed } from 'vue'
|
|
||||||
import axios from '@/utils/axios'
|
|
||||||
import { store } from '@/store'
|
|
||||||
import { getFileSuffix, isImage } from '@/utils/file-handle-helper'
|
|
||||||
import structureImageObject from '@/utils/image-helper'
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定路径(path)下的目录列表
|
|
||||||
* @param path 路径
|
|
||||||
*/
|
|
||||||
export const getDirListByPath = (path: string = '') => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
axios
|
|
||||||
.get(
|
|
||||||
`/repos/${userConfigInfo.owner}/${userConfigInfo.selectedRepos}/contents/${path}`,
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
ref: userConfigInfo.selectedBranch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((res: any) => {
|
|
||||||
if (res && res.status === 200 && res.data.length > 0) {
|
|
||||||
resolve(
|
|
||||||
res.data
|
|
||||||
.filter((v: any) => v.type === 'dir')
|
|
||||||
.map((x: any) => ({
|
|
||||||
value: x.name,
|
|
||||||
label: x.name
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
resolve(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定路径(path)下的目录和图片
|
|
||||||
* @param path
|
|
||||||
*/
|
|
||||||
export const getContentByReposPath = (path: string = '') => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
axios
|
|
||||||
.get(
|
|
||||||
`/repos/${userConfigInfo.owner}/${userConfigInfo.selectedRepos}/contents/${path}`,
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
ref: userConfigInfo.selectedBranch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((res: any) => {
|
|
||||||
if (res && res.status === 200 && res.data.length > 0) {
|
|
||||||
res.data
|
|
||||||
.filter((v: any) => v.type === 'dir')
|
|
||||||
.forEach((x: any) => store.dispatch('DIR_IMAGE_LIST_ADD_DIR', x.path))
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
res.data
|
|
||||||
.filter((v: any) => v.type === 'file' && isImage(getFileSuffix(v.name)))
|
|
||||||
.forEach((x: any) =>
|
|
||||||
store.dispatch('DIR_IMAGE_LIST_ADD_IMAGE', structureImageObject(x, path))
|
|
||||||
)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
resolve(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
|
||||||
export enum deleteStatusEnum {
|
|
||||||
deleted = 'deleted',
|
|
||||||
allDeleted = 'allDeleted',
|
|
||||||
deleteFail = 'deleteFail'
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export interface DirModel {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DirModeEnum {
|
|
||||||
autoDir = 'autoDir',
|
|
||||||
newDir = 'newDir',
|
|
||||||
rootDir = 'rootDir',
|
|
||||||
reposDir = 'reposDir'
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
enum ExternalLinkType {
|
|
||||||
staticaly = 'staticaly',
|
|
||||||
jsdelivr = 'jsdelivr',
|
|
||||||
github = 'github',
|
|
||||||
cloudflare = 'cloudflare'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExternalLinkType
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const PICX_PREFIX = 'PICX_'
|
|
||||||
|
|
||||||
export const PICX_CONFIG = `${PICX_PREFIX}CONFIG`
|
|
||||||
export const PICX_UPLOADED = `${PICX_PREFIX}UPLOADED`
|
|
||||||
export const PICX_MANAGEMENT = `${PICX_PREFIX}MANAGEMENT_MULTI`
|
|
||||||
export const PICX_SETTINGS = `${PICX_PREFIX}SETTINGS`
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
export enum UploadStatusEnum {
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
uploaded = 'uploaded',
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
allUploaded = 'allUploaded',
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
uploadFail = 'uploadFail'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadedImageModel {
|
|
||||||
type: string
|
|
||||||
uuid: string
|
|
||||||
sha: string
|
|
||||||
dir: string
|
|
||||||
path: string
|
|
||||||
name: string
|
|
||||||
size: any
|
|
||||||
deleting: boolean
|
|
||||||
is_transform_md: boolean
|
|
||||||
checked: boolean
|
|
||||||
github_url: string
|
|
||||||
jsdelivr_cdn_url: string
|
|
||||||
staticaly_cdn_url: string
|
|
||||||
cloudflare_cdn_url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToUploadImageModel {
|
|
||||||
uuid: string
|
|
||||||
|
|
||||||
uploadStatus: {
|
|
||||||
progress: number
|
|
||||||
uploading: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
imgData: {
|
|
||||||
base64Content: string
|
|
||||||
base64Url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInfo: {
|
|
||||||
compressedSize?: number | undefined
|
|
||||||
originSize?: number | undefined
|
|
||||||
size: number | undefined
|
|
||||||
lastModified: number | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
filename: {
|
|
||||||
name: string
|
|
||||||
hash: string
|
|
||||||
suffix: string
|
|
||||||
prefixName: string
|
|
||||||
now: string
|
|
||||||
initName: string
|
|
||||||
newName: string
|
|
||||||
isHashRename: boolean
|
|
||||||
isRename: boolean
|
|
||||||
isPrefix: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
externalLink: {
|
|
||||||
github: string
|
|
||||||
jsdelivr: string
|
|
||||||
staticaly: string
|
|
||||||
cloudflare: string
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadedImg?: UploadedImageModel
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { DirModeEnum, DirModel } from './dir.model'
|
|
||||||
|
|
||||||
export interface ReposModel {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
desc?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BranchModel {
|
|
||||||
value: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BranchModeEnum {
|
|
||||||
newBranch = 'newBranch',
|
|
||||||
reposBranch = 'reposBranch'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserConfigInfoModel {
|
|
||||||
token: string
|
|
||||||
owner: string
|
|
||||||
email: string
|
|
||||||
name: string
|
|
||||||
avatarUrl: string
|
|
||||||
selectedRepos: string
|
|
||||||
reposList: ReposModel[]
|
|
||||||
selectedBranch: string
|
|
||||||
branchMode: BranchModeEnum
|
|
||||||
branchList: BranchModel[]
|
|
||||||
dirMode: DirModeEnum
|
|
||||||
selectedDir: string
|
|
||||||
selectedDirList: string[]
|
|
||||||
dirList: DirModel[]
|
|
||||||
loggingStatus: boolean
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CompressEncoderMap } from '../../utils/compress'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
|
|
||||||
export interface UserSettingsModel {
|
|
||||||
defaultHash: boolean
|
|
||||||
defaultMarkdown: boolean
|
|
||||||
defaultPrefix: boolean
|
|
||||||
prefixName: string
|
|
||||||
themeMode: 'auto' | 'light' | 'dark'
|
|
||||||
autoLightThemeTime: string[]
|
|
||||||
isCompress: boolean
|
|
||||||
compressEncoder: CompressEncoderMap
|
|
||||||
elementPlusSize: 'large' | 'default' | 'small'
|
|
||||||
externalLinkType: ExternalLinkType
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
export declare type Recordable<T = any> = Record<string, T>
|
|
||||||
|
|
||||||
export declare interface ViteEnv {
|
|
||||||
VITE_PORT?: number
|
|
||||||
VITE_USE_PWA?: boolean
|
|
||||||
VITE_PUBLIC_PATH?: string
|
|
||||||
VITE_GLOB_APP_TITLE?: string
|
|
||||||
VITE_GLOB_APP_SHORT_NAME?: string
|
|
||||||
VITE_OPEN_BROWSER?: boolean
|
|
||||||
VITE_CORS?: boolean
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
.copy-external-link-box {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
align-items flex-end
|
|
||||||
padding 2rem
|
|
||||||
|
|
||||||
.markdown-icon-box {
|
|
||||||
width 26rem
|
|
||||||
height 20rem
|
|
||||||
position relative
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.markdown-icon {
|
|
||||||
path {
|
|
||||||
fill var(--markdown-icon-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
path {
|
|
||||||
fill var(--markdown-icon-active-color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.btn-box {
|
|
||||||
position relative
|
|
||||||
box-sizing border-box
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
|
|
||||||
.btn-item {
|
|
||||||
height 20rem
|
|
||||||
box-sizing border-box
|
|
||||||
border-radius 5rem
|
|
||||||
font-size 12rem
|
|
||||||
cursor pointer
|
|
||||||
transition all 0.3s ease
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-url {
|
|
||||||
box-sizing border-box
|
|
||||||
border 1rem solid var(--default-text-color)
|
|
||||||
color var(--default-text-color)
|
|
||||||
padding 1rem 2rem
|
|
||||||
margin-right 4rem
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background var(--default-text-color)
|
|
||||||
color var(--background-color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="copy-external-link-box">
|
|
||||||
<div>
|
|
||||||
<el-tooltip
|
|
||||||
:content="img.is_transform_md ? '点击转换普通外链' : '点击转换 Markdown 格式外链'"
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="markdown-icon-box flex-center"
|
|
||||||
@click="img.is_transform_md = !img.is_transform_md"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
v-if="!img.is_transform_md"
|
|
||||||
t="1631782798077"
|
|
||||||
class="markdown-icon"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="2861"
|
|
||||||
width="26"
|
|
||||||
height="26"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M92 192C42.24 192 0 232.128 0 282.016v459.968C0 791.904 42.24 832 92 832h840C981.76 832 1024 791.872 1024 741.984V282.016C1024 232.16 981.76 192 932 192z m0 64h840c16.512 0 28 12.256 28 26.016v459.968c0 13.76-11.52 26.016-28 26.016H92C75.488 768 64 755.744 64 741.984V282.016c0-13.76 11.52-25.984 28-25.984zM160 352v320h96v-212.992l96 127.008 96-127.04V672h96V352h-96l-96 128-96-128z m544 0v160h-96l144 160 144-160h-96v-160z"
|
|
||||||
p-id="2862"
|
|
||||||
fill="#808080"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
v-if="img.is_transform_md"
|
|
||||||
t="1631784688556"
|
|
||||||
class="markdown-icon active"
|
|
||||||
viewBox="0 0 1280 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="3242"
|
|
||||||
width="26"
|
|
||||||
height="26"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M1187.6 118.2H92.4C41.4 118.2 0 159.6 0 210.4v603c0 51 41.4 92.4 92.4 92.4h1095.4c51 0 92.4-41.4 92.2-92.2V210.4c0-50.8-41.4-92.2-92.4-92.2zM677 721.2H554v-240l-123 153.8-123-153.8v240H184.6V302.8h123l123 153.8 123-153.8h123v418.4z m270.6 6.2L763 512H886V302.8h123V512H1132z"
|
|
||||||
p-id="3243"
|
|
||||||
fill="#3c3c3c"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
<div class="btn-box">
|
|
||||||
<el-tooltip content="点击复制 Staticaly CDN 外链" placement="top">
|
|
||||||
<span
|
|
||||||
class="btn-item copy-url flex-center"
|
|
||||||
@click="copyLink(externalLinkType.staticaly)"
|
|
||||||
>
|
|
||||||
Staticaly
|
|
||||||
</span>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="点击复制 Cloudflare CDN 外链" placement="top">
|
|
||||||
<span
|
|
||||||
class="btn-item copy-url flex-center"
|
|
||||||
@click="copyLink(externalLinkType.cloudflare)"
|
|
||||||
>
|
|
||||||
Cloudflare
|
|
||||||
</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, computed, ref, onUpdated } from 'vue'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
import { store } from '@/store'
|
|
||||||
import { copyExternalLink } from '@/utils/external-link-handler'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
imgObj: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
|
|
||||||
let img = ref(props.imgObj as UploadedImageModel).value
|
|
||||||
const externalLinkType = ExternalLinkType
|
|
||||||
|
|
||||||
const copyLink = (type: ExternalLinkType) => {
|
|
||||||
copyExternalLink(img, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdated(() => {
|
|
||||||
img = props.imgObj
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
img.is_transform_md = userSettings.defaultMarkdown
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "copy-external-link.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
@import '../../style/base.styl'
|
|
||||||
|
|
||||||
.folder-card {
|
|
||||||
position relative
|
|
||||||
width 110rem
|
|
||||||
height 106rem
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
flex-direction column
|
|
||||||
justify-content flex-start
|
|
||||||
cursor pointer
|
|
||||||
box-sizing border-box
|
|
||||||
padding 3rem
|
|
||||||
user-select none
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background var(--second-background-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content center
|
|
||||||
width 50rem
|
|
||||||
height 50rem
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.text {
|
|
||||||
width 90%
|
|
||||||
font-size 14rem
|
|
||||||
margin-top 5rem
|
|
||||||
text-align center
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
display -webkit-box
|
|
||||||
-webkit-box-orient vertical
|
|
||||||
-webkit-line-clamp 2
|
|
||||||
word-wrap break-word
|
|
||||||
word-break break-all
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="folder-card" @dblclick="dblclickFolder">
|
|
||||||
<el-tooltip
|
|
||||||
v-if="mode === 'dir'"
|
|
||||||
effect="dark"
|
|
||||||
content="双击进入下一级目录"
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<div class="icon">
|
|
||||||
<svg
|
|
||||||
t="1639999626518"
|
|
||||||
class="icon"
|
|
||||||
viewBox="0 0 1228 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="3575"
|
|
||||||
width="200"
|
|
||||||
height="200"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M1196.987733 212.5824v540.0576c0 39.594667-34.474667 71.3728-76.765866 71.3728H323.242667c-51.780267 0-88.746667-46.762667-73.250134-92.808533l126.737067-375.808H70.417067C31.675733 355.362133 0 326.4512 0 291.089067V98.372267C0 63.044267 31.675733 34.0992 70.417067 34.0992h378.811733c26.7264 0 51.029333 13.9264 63.010133 35.703467l39.048534 71.406933H1120.256c42.257067 0 76.8 32.119467 76.8 71.3728"
|
|
||||||
fill="#5398DF"
|
|
||||||
p-id="3576"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M1128.721067 997.853867H68.266667a68.266667 68.266667 0 0 1-68.266667-68.266667V280.3712a68.266667 68.266667 0 0 1 68.266667-68.266667h1060.4544a68.266667 68.266667 0 0 1 68.266666 68.266667V929.5872a68.266667 68.266667 0 0 1-68.266666 68.266667"
|
|
||||||
fill="#85BCFF"
|
|
||||||
p-id="3577"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<div class="icon" v-if="mode === 'back'">
|
|
||||||
<svg
|
|
||||||
t="1640264285200"
|
|
||||||
class="icon"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
p-id="29312"
|
|
||||||
width="200"
|
|
||||||
height="200"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M426.666667 384V213.333333l-298.666667 298.666667 298.666667 298.666667v-174.933334c213.333333 0 362.666667 68.266667 469.333333 217.6-42.666667-213.333333-170.666667-426.666667-469.333333-469.333333z"
|
|
||||||
p-id="29313"
|
|
||||||
fill="#85BCFF"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="text" v-if="mode === 'dir'">{{ folderObj.dir }}</div>
|
|
||||||
<div class="text" v-if="mode === 'back'">双击返回</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const props = defineProps({
|
|
||||||
folderObj: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'dir'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const dblclickFolder = () => {
|
|
||||||
const { folderObj, mode } = props
|
|
||||||
|
|
||||||
if (mode === 'back') {
|
|
||||||
const currentDir = userConfigInfo.selectedDir
|
|
||||||
|
|
||||||
if (currentDir === '/') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDirList = currentDir.split('/')
|
|
||||||
|
|
||||||
if (currentDirList.length === 1) {
|
|
||||||
userConfigInfo.selectedDir = '/'
|
|
||||||
} else if (currentDirList.length > 1) {
|
|
||||||
currentDirList.length -= 1
|
|
||||||
userConfigInfo.selectedDir = currentDirList.join('/')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userConfigInfo.selectedDir = folderObj.dirPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selectedDir } = userConfigInfo
|
|
||||||
|
|
||||||
if (selectedDir === '/') {
|
|
||||||
userConfigInfo.selectedDirList = []
|
|
||||||
userConfigInfo.dirMode = 'rootDir'
|
|
||||||
} else {
|
|
||||||
userConfigInfo.selectedDirList = selectedDir.split('/')
|
|
||||||
userConfigInfo.dirMode = 'reposDir'
|
|
||||||
}
|
|
||||||
store.dispatch('USER_CONFIG_INFO_NOT_PERSIST')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import 'folder-card.styl';
|
|
||||||
</style>
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
.header {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
background var(--background-color)
|
|
||||||
padding 0 20rem
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
align-items center
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
justify-content flex-start
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
justify-content flex-start
|
|
||||||
align-items center
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width 46rem
|
|
||||||
height 46rem
|
|
||||||
margin-right 10rem
|
|
||||||
|
|
||||||
img {
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size 36rem
|
|
||||||
font-weight bold
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.website-count {
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
align-items flex-end
|
|
||||||
font-size 14rem
|
|
||||||
margin-left 10rem
|
|
||||||
padding-bottom 12rem
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
+picx-mobile() {
|
|
||||||
display none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.header-right {
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.username {
|
|
||||||
font-size: 16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 38rem;
|
|
||||||
height: 38rem;
|
|
||||||
color: var(--default-text-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1rem solid var(--default-text-color);
|
|
||||||
margin-left: 10rem;
|
|
||||||
padding: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<header class="header">
|
|
||||||
<div class="header-left">
|
|
||||||
<div class="brand" @click="router.push('/')">
|
|
||||||
<div class="logo">
|
|
||||||
<img src="../../assets/logo.png" alt="PicX" />
|
|
||||||
</div>
|
|
||||||
<div class="title">PicX</div>
|
|
||||||
</div>
|
|
||||||
<div class="website-count" @click="goGitHubRepo">
|
|
||||||
<el-tooltip content="感觉好用,点 Star 支持作者(* ̄︶ ̄)" placement="bottom">
|
|
||||||
<site-count :isuv="false" />
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header-right">
|
|
||||||
<div class="user-info" @click="onUserInfoClick">
|
|
||||||
<div class="username">
|
|
||||||
{{ userConfigInfo.owner ? userConfigInfo.owner : defaultUsername }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="avatar" v-if="!userConfigInfo?.avatarUrl">
|
|
||||||
<el-icon :size="22"><UserFilled /></el-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-dropdown
|
|
||||||
trigger="click"
|
|
||||||
@command="handleCommand"
|
|
||||||
v-if="userConfigInfo?.avatarUrl"
|
|
||||||
>
|
|
||||||
<span class="el-dropdown-link">
|
|
||||||
<span class="avatar">
|
|
||||||
<img :src="userConfigInfo?.avatarUrl" :alt="userConfigInfo?.owner" />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item command="logout"> 退出登录 </el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive, computed, toRefs } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import siteCount from '@/components/site-count/site-count.vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'header-content',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
siteCount
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const router = useRouter()
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const reactiveData = reactive({
|
|
||||||
defaultUsername: '未登录',
|
|
||||||
userConfigInfo: computed(() => store.state.userConfigInfoModule.userConfigInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
const onUserInfoClick = () => {
|
|
||||||
if (
|
|
||||||
!reactiveData.userConfigInfo.loggingStatus &&
|
|
||||||
router.currentRoute.value.path !== '/config'
|
|
||||||
) {
|
|
||||||
router.push('/config')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logout = () => {
|
|
||||||
store.dispatch('LOGOUT')
|
|
||||||
router.push('/config')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCommand = (command: string) => {
|
|
||||||
// eslint-disable-next-line default-case
|
|
||||||
switch (command) {
|
|
||||||
case 'upload':
|
|
||||||
router.push('/')
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'config':
|
|
||||||
router.push('/config')
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'management':
|
|
||||||
router.push('/management')
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'logout':
|
|
||||||
logout()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const goGitHubRepo = () => {
|
|
||||||
window.open('https://github.com/XPoet/picx')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(reactiveData),
|
|
||||||
router,
|
|
||||||
onUserInfoClick,
|
|
||||||
handleCommand,
|
|
||||||
goGitHubRepo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "header-content.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
$infoBoxHeight = 56rem
|
|
||||||
|
|
||||||
.image-card {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-shadow 1rem 2rem 3rem var(--shadow-color)
|
|
||||||
box-sizing border-box
|
|
||||||
padding-bottom $infoBoxHeight
|
|
||||||
user-select none
|
|
||||||
|
|
||||||
&.checked, &:hover {
|
|
||||||
box-shadow 0 0 10rem #666
|
|
||||||
}
|
|
||||||
|
|
||||||
&.listing {
|
|
||||||
display flex
|
|
||||||
justify-content flex-start
|
|
||||||
align-items center
|
|
||||||
padding 5rem
|
|
||||||
border-radius $box-border-radius
|
|
||||||
|
|
||||||
.image-box {
|
|
||||||
height 45rem
|
|
||||||
width 45rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
position relative
|
|
||||||
width 80%
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-loading-mask) {
|
|
||||||
.el-loading-spinner {
|
|
||||||
margin-top -25rem
|
|
||||||
|
|
||||||
.circular {
|
|
||||||
height 24rem
|
|
||||||
width 24rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-loading-text {
|
|
||||||
margin 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.image-box {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
|
|
||||||
img {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
object-fit cover
|
|
||||||
cursor pointer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
width 100%
|
|
||||||
height $infoBoxHeight
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
left 0
|
|
||||||
|
|
||||||
.image-info {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding 5rem
|
|
||||||
color var(--default-text-color)
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
flex-direction column
|
|
||||||
justify-content space-between
|
|
||||||
|
|
||||||
.rename-input {
|
|
||||||
height 20rem
|
|
||||||
display flex
|
|
||||||
margin-bottom 4rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.filename {
|
|
||||||
height 16rem
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
white-space nowrap
|
|
||||||
font-size 14rem
|
|
||||||
margin-bottom 6rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.operation-box {
|
|
||||||
position absolute
|
|
||||||
top 10rem
|
|
||||||
right 8rem
|
|
||||||
width calc(100% - 16rem)
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
|
|
||||||
.operation-left {
|
|
||||||
.picked-btn {
|
|
||||||
i {
|
|
||||||
font-weight bold
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-right {
|
|
||||||
display flex
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-btn {
|
|
||||||
width 32rem
|
|
||||||
height 32rem
|
|
||||||
border-radius 50%
|
|
||||||
box-shadow 0 0 6rem #555
|
|
||||||
cursor pointer
|
|
||||||
background var(--background-color)
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
margin-right 8rem
|
|
||||||
font-size 18rem
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="image-card"
|
|
||||||
:class="{ listing: listing, checked: imageObj.checked }"
|
|
||||||
v-loading="imageObj.deleting"
|
|
||||||
element-loading-text="删除中..."
|
|
||||||
@mouseenter="isShowDelBtn = true"
|
|
||||||
@mouseleave="isShowDelBtn = false"
|
|
||||||
>
|
|
||||||
<div class="image-box">
|
|
||||||
<img data-fancybox="gallery" :src="imgUrl" />
|
|
||||||
</div>
|
|
||||||
<div class="info-box">
|
|
||||||
<div class="image-info">
|
|
||||||
<el-input
|
|
||||||
size="small"
|
|
||||||
v-if="renameValue && props.modelValue === props.index"
|
|
||||||
class="rename-input"
|
|
||||||
v-model="renameValue"
|
|
||||||
@blur="renameInputBlur"
|
|
||||||
@keydown.enter.prevent="updateRename"
|
|
||||||
ref="renameInputRef"
|
|
||||||
></el-input>
|
|
||||||
<div class="filename" v-else>{{ imageObj.name }}</div>
|
|
||||||
<div class="image-operation">
|
|
||||||
<copy-external-link :img-obj="imageObj" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="operation-box"
|
|
||||||
v-show="isShowDelBtn || dropdownVisible || imageObj.checked"
|
|
||||||
>
|
|
||||||
<div class="operation-left">
|
|
||||||
<div
|
|
||||||
v-if="isManagementPage"
|
|
||||||
:class="[imageObj.checked ? 'picked-btn' : 'pick-btn', 'operation-btn']"
|
|
||||||
@click="trigglePick(imageObj)"
|
|
||||||
>
|
|
||||||
<el-icon v-if="imageObj.checked"><Check /></el-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="operation-right">
|
|
||||||
<el-dropdown size="default" trigger="click" @visible-change="visibleChange">
|
|
||||||
<div class="operation-btn">
|
|
||||||
<el-icon><MoreFilled /></el-icon>
|
|
||||||
</div>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item @click="deleteImageTips(imageObj)">
|
|
||||||
删除
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item @click.self="renameImage(imageObj)">
|
|
||||||
重命名
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item @click="viewImageProperties(imageObj)">
|
|
||||||
属性
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, nextTick, ref } from 'vue'
|
|
||||||
import type { ElInput } from 'element-plus'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import axios from '@/utils/axios'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
import { getBase64ByImageUrl, getImage } from '@/utils/rename-image'
|
|
||||||
import { uploadImage_single } from '@/utils/upload-helper'
|
|
||||||
import { getFilename, getFileSize, getFileSuffix } from '@/utils/file-handle-helper'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
listing: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
imageObj: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
isUploaded: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Number
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const router = useRoute()
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
const isManagementPage = computed(() => {
|
|
||||||
return router.path === '/management'
|
|
||||||
})
|
|
||||||
|
|
||||||
const imgUrl = computed(() => {
|
|
||||||
switch (userSettings.externalLinkType) {
|
|
||||||
case ExternalLinkType.jsdelivr:
|
|
||||||
return props.imageObj.jsdelivr_cdn_url
|
|
||||||
case ExternalLinkType.staticaly:
|
|
||||||
return props.imageObj.staticaly_cdn_url
|
|
||||||
case ExternalLinkType.cloudflare:
|
|
||||||
return props.imageObj.cloudflare_cdn_url
|
|
||||||
default:
|
|
||||||
return props.imageObj.github_url
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const renameInputRef = ref<InstanceType<typeof ElInput>>()
|
|
||||||
|
|
||||||
const isShowDelBtn = ref(false)
|
|
||||||
|
|
||||||
const renameValue = ref('')
|
|
||||||
|
|
||||||
const dropdownVisible = ref<Boolean>(false)
|
|
||||||
|
|
||||||
const doDeleteImage = (
|
|
||||||
imageObj: UploadedImageModel,
|
|
||||||
isRename: boolean = false
|
|
||||||
): Promise<Boolean> => {
|
|
||||||
if (!isRename) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = true
|
|
||||||
}
|
|
||||||
const { owner, selectedRepos } = userConfigInfo
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
axios
|
|
||||||
.delete(`/repos/${owner}/${selectedRepos}/contents/${imageObj.path}`, {
|
|
||||||
data: {
|
|
||||||
owner,
|
|
||||||
repo: selectedRepos,
|
|
||||||
path: imageObj.path,
|
|
||||||
message: 'Delete picture via PicX(https://github.com/XPoet/picx)',
|
|
||||||
sha: imageObj.sha
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
console.log('[deleteImage] ', res)
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = false
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
ElMessage.success(`${isRename ? '更新' : '删除'}成功!`)
|
|
||||||
store.dispatch('UPLOADED_LIST_REMOVE', imageObj.uuid)
|
|
||||||
store.dispatch('DIR_IMAGE_LIST_REMOVE', imageObj)
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteImageTips = (imageObj: UploadedImageModel) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`
|
|
||||||
<div>此操作将会永久删除图片:</div>
|
|
||||||
<strong>${imageObj.name}</strong>
|
|
||||||
`,
|
|
||||||
`删除提示`,
|
|
||||||
{
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
doDeleteImage(imageObj)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('取消删除')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const trigglePick = (imageObj: UploadedImageModel) => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.checked = !imageObj.checked
|
|
||||||
store.commit('IMAGE_CARD', { imageObj })
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameImage = async (imgObj: UploadedImageModel) => {
|
|
||||||
emits('update:modelValue', props.index)
|
|
||||||
renameValue.value = getFilename(imgObj.name)
|
|
||||||
await nextTick(() => {
|
|
||||||
const temp = setTimeout(() => {
|
|
||||||
renameInputRef.value?.focus()
|
|
||||||
clearTimeout(temp)
|
|
||||||
}, 150)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameInputBlur = () => {
|
|
||||||
emits('update:modelValue', undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateRename = async () => {
|
|
||||||
renameInputBlur()
|
|
||||||
|
|
||||||
const { imageObj }: any = props
|
|
||||||
|
|
||||||
if (renameValue.value === getFilename(imageObj.name) || !renameValue.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameFn = async () => {
|
|
||||||
const loading = ElLoading.service({
|
|
||||||
lock: true,
|
|
||||||
text: '正在重命名...'
|
|
||||||
})
|
|
||||||
|
|
||||||
const suffix = getFileSuffix(imageObj.name)
|
|
||||||
|
|
||||||
const imgInfo = {
|
|
||||||
name: renameValue.value + imageObj.name.substring(imageObj.name.indexOf('.')),
|
|
||||||
size: imageObj.size,
|
|
||||||
lastModified: Date.now(),
|
|
||||||
type: `image/${suffix}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const base64 = await getBase64ByImageUrl(imgUrl.value, suffix)
|
|
||||||
|
|
||||||
if (base64) {
|
|
||||||
const newImgObj = getImage(base64, imgInfo)
|
|
||||||
if (newImgObj) {
|
|
||||||
const isUploadSuccess = await uploadImage_single(userConfigInfo, newImgObj)
|
|
||||||
|
|
||||||
if (isUploadSuccess) {
|
|
||||||
renameValue.value = ''
|
|
||||||
await doDeleteImage(imageObj, true)
|
|
||||||
await store.dispatch('UPLOADED_LIST_REMOVE', newImgObj.uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loading.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
ElMessageBox.confirm(`该图片重命名为 ${renameValue.value} ?`, `提示`, {
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
await renameFn()
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('取消图片重命名')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const visibleChange = (e: boolean) => {
|
|
||||||
dropdownVisible.value = e
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewImageProperties = (imgObj: UploadedImageModel) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`
|
|
||||||
<div>图片名称:<strong>${imgObj.name}</strong></div>
|
|
||||||
<div>图片大小:<strong>${getFileSize(imgObj.size)} KB</strong></div>
|
|
||||||
`,
|
|
||||||
`属性`,
|
|
||||||
{
|
|
||||||
showCancelButton: false,
|
|
||||||
showConfirmButton: false,
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
type: 'info'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import 'image-card.styl';
|
|
||||||
</style>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
.selector-wrapper {
|
|
||||||
padding 4rem 12rem
|
|
||||||
width 100%
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
align-items center
|
|
||||||
border-bottom 1rem solid var(--third-background-color)
|
|
||||||
|
|
||||||
|
|
||||||
.selector-left-box {
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
|
|
||||||
:deep(.el-checkbox) {
|
|
||||||
font-weight unset
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-checkbox__label ) {
|
|
||||||
line-height unset
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-select-btn {
|
|
||||||
color #576b95
|
|
||||||
cursor pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
div.item {
|
|
||||||
margin-left 8rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-right-box {
|
|
||||||
.btn-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 22rem;
|
|
||||||
margin-left: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.temp-batch-externalink {
|
|
||||||
opacity 0
|
|
||||||
position absolute
|
|
||||||
left -9999rem
|
|
||||||
top -9999rem
|
|
||||||
width 0
|
|
||||||
height 0
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="selector-wrapper" v-if="getImageCardCheckedNum">
|
|
||||||
<div class="selector-left-box">
|
|
||||||
<el-checkbox
|
|
||||||
:label="checked ? '取消全选' : '全选'"
|
|
||||||
v-model="checked"
|
|
||||||
@change="triggleFullCheck"
|
|
||||||
></el-checkbox>
|
|
||||||
<div class="item">已选择 {{ getImageCardCheckedNum }} 张图片</div>
|
|
||||||
<div class="item cancel-select-btn" @click="cancelPick">取消选择</div>
|
|
||||||
</div>
|
|
||||||
<div class="selector-right-box">
|
|
||||||
<el-tooltip placement="top" content="批量复制外链">
|
|
||||||
<el-icon class="btn-icon" @click="copyLink"><CopyDocument /></el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip placement="top" content="批量删除图片">
|
|
||||||
<el-icon class="btn-icon" @click="batchDeleteImage"><Delete /></el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, onMounted, watch, ref } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
import { batchCopyExternalLink } from '@/utils/external-link-handler'
|
|
||||||
import { delelteBatchImage } from '@/utils/delete-image-card'
|
|
||||||
import { deleteStatusEnum } from '@/common/model/delete.model'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
currentDirImageList: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:initImageList'])
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const checked = ref(false)
|
|
||||||
|
|
||||||
const getImageCardCheckedArr = computed(() => store.getters.getImageCardCheckedArr)
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
const getImageCardCheckedNum = computed(() => getImageCardCheckedArr.value.length || 0)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => getImageCardCheckedNum.value,
|
|
||||||
(newVal) => {
|
|
||||||
const newValCheckedNum = props.currentDirImageList.length
|
|
||||||
checked.value = newVal === newValCheckedNum
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function copyLink() {
|
|
||||||
batchCopyExternalLink(getImageCardCheckedArr.value, userSettings.externalLinkType)
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelPick() {
|
|
||||||
props.currentDirImageList.forEach((item: UploadedImageModel) => {
|
|
||||||
if (item.checked) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
item.checked = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function batchDeleteImage() {
|
|
||||||
if (getImageCardCheckedArr.value?.length > 0) {
|
|
||||||
ElMessageBox.confirm('是否批量删除已选中的图片?', '删除提示', {
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
const res = await delelteBatchImage(getImageCardCheckedArr.value, userConfigInfo)
|
|
||||||
if (res === deleteStatusEnum.allDeleted) {
|
|
||||||
ElMessage.success('批量删除成功!')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('取消批量删除')
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('请先选择图片')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggleFullCheck() {
|
|
||||||
let checkedImgArr: Array<UploadedImageModel> = []
|
|
||||||
props.currentDirImageList.forEach((item: UploadedImageModel) => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
item.checked = checked.value
|
|
||||||
})
|
|
||||||
checkedImgArr = props.currentDirImageList as any
|
|
||||||
store.commit('REPLACE_IMAGE_CARD', { checkedImgArr })
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
triggleFullCheck()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import 'image-selector.styl';
|
|
||||||
</style>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
$transition-duration = 0.3s
|
|
||||||
$transition-delay = 0s
|
|
||||||
|
|
||||||
.image-viewer {
|
|
||||||
position fixed
|
|
||||||
left 0
|
|
||||||
top 0
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content center
|
|
||||||
background rgba(0, 0, 0, 0)
|
|
||||||
visibility hidden
|
|
||||||
z-index 1000
|
|
||||||
padding 6%
|
|
||||||
box-sizing border-box
|
|
||||||
transition-property visibility, background
|
|
||||||
transition-delay $transition-delay, $transition-delay
|
|
||||||
transition-duration $transition-duration, $transition-duration
|
|
||||||
transition-timing-function ease, ease
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background rgba(0, 0, 0, 0.6)
|
|
||||||
visibility visible
|
|
||||||
|
|
||||||
.image-box {
|
|
||||||
transform scale(1)
|
|
||||||
padding 2rem
|
|
||||||
|
|
||||||
.image-info {
|
|
||||||
display block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.image-box {
|
|
||||||
position relative;
|
|
||||||
width 60%
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
flex-direction column
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
transform scale(0)
|
|
||||||
transition-property transform
|
|
||||||
transition-delay $transition-delay
|
|
||||||
transition-duration $transition-duration
|
|
||||||
transition-timing-function ease
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
width 80%
|
|
||||||
}
|
|
||||||
|
|
||||||
.img {
|
|
||||||
cursor zoom-out
|
|
||||||
max-width 100%
|
|
||||||
max-height 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-info {
|
|
||||||
display none
|
|
||||||
padding 10rem
|
|
||||||
|
|
||||||
.item {
|
|
||||||
margin 0 6rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="image-viewer"
|
|
||||||
:class="{ active: imageViewer.isShow }"
|
|
||||||
@click="imageViewer.isShow = false"
|
|
||||||
>
|
|
||||||
<div class="image-box" v-if="imageViewer?.imgInfo?.url">
|
|
||||||
<img class="img" :src="imageViewer?.imgInfo?.url" />
|
|
||||||
<div class="image-info" v-if="imageViewer.imgInfo">
|
|
||||||
<el-tag class="item" size="small" v-if="imageViewer.imgInfo.name">
|
|
||||||
图片名:{{ imageViewer.imgInfo.name }}
|
|
||||||
</el-tag>
|
|
||||||
<el-tag class="item" size="small" v-if="imageViewer.imgInfo.size">
|
|
||||||
图片大小:{{ parseFileSize(imageViewer.imgInfo.size) }}
|
|
||||||
</el-tag>
|
|
||||||
<el-tag class="item" size="small" v-if="imageViewer.imgInfo.lastModified">
|
|
||||||
最后修改时间:{{ formatLastModified(imageViewer.imgInfo.lastModified) }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { getFileSize } from '@/utils/file-handle-helper'
|
|
||||||
import TimeHelper from '@/utils/time-helper'
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const imageViewer = computed(() => store.getters.getImageViewer)
|
|
||||||
|
|
||||||
function parseFileSize(size: number) {
|
|
||||||
return `${getFileSize(size)} KB`
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLastModified(t: number) {
|
|
||||||
return TimeHelper.formatTimestamp(t)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "image-viewer.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
$top-height = 60rem
|
|
||||||
$left-side-width = 80rem
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
position absolute
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
background var(--second-background-color)
|
|
||||||
padding-top $top-height
|
|
||||||
font-size 15rem
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.top {
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height $top-height
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
position relative
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding-top $component-interval
|
|
||||||
|
|
||||||
|
|
||||||
.container {
|
|
||||||
position relative
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding-left $left-side-width
|
|
||||||
|
|
||||||
|
|
||||||
.left {
|
|
||||||
position absolute
|
|
||||||
box-sizing border-box
|
|
||||||
width $left-side-width
|
|
||||||
height 100%
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.right {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
padding 0 $component-interval 0 $component-interval
|
|
||||||
|
|
||||||
|
|
||||||
.content {
|
|
||||||
position relative
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<template>
|
|
||||||
<main class="main-container" @click="changeUploadAreaActive">
|
|
||||||
<div class="top">
|
|
||||||
<header-content />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bottom">
|
|
||||||
<div class="container">
|
|
||||||
<div class="left">
|
|
||||||
<nav-content />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right">
|
|
||||||
<div class="content">
|
|
||||||
<router-view />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<image-viewer></image-viewer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted } from 'vue'
|
|
||||||
import headerContent from '@/components/header-content/header-content.vue'
|
|
||||||
import navContent from '@/components/nav-content/nav-content.vue'
|
|
||||||
import imageViewer from '@/components/image-viewer/image-viewer.vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import userConfigInfoModel from '@/utils/set-theme-mode'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'main-container',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
headerContent,
|
|
||||||
navContent,
|
|
||||||
imageViewer
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const changeUploadAreaActive = (e: any) => {
|
|
||||||
store.commit(
|
|
||||||
'CHANGE_UPLOAD_AREA_ACTIVE',
|
|
||||||
e.target.classList.contains('active-upload')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
userConfigInfoModel()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
changeUploadAreaActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "main-container.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
background var(--background-color)
|
|
||||||
|
|
||||||
ul.nav-list {
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
li.nav-item {
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height 76rem
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
font-weight bold
|
|
||||||
background var(--second-background-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-content {
|
|
||||||
display flex
|
|
||||||
flex-direction column
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
|
|
||||||
.nav-name {
|
|
||||||
margin-top 5rem
|
|
||||||
font-size 12rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
<template>
|
|
||||||
<aside class="nav">
|
|
||||||
<ul class="nav-list">
|
|
||||||
<li
|
|
||||||
class="nav-item flex-center"
|
|
||||||
v-for="(navItem, index) in navList"
|
|
||||||
:key="index"
|
|
||||||
:class="{ active: navItem.isActive }"
|
|
||||||
@click="navClick(navItem)"
|
|
||||||
v-show="navItem.isShow"
|
|
||||||
>
|
|
||||||
<div class="nav-content">
|
|
||||||
<el-icon :size="navIconSize">
|
|
||||||
<component :is="navItem.icon"></component>
|
|
||||||
</el-icon>
|
|
||||||
<span class="nav-name">{{ navItem.name }}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, watch, computed, ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
|
|
||||||
const navIconSize = computed(() => {
|
|
||||||
switch (userSettings.elementPlusSize) {
|
|
||||||
case 'small':
|
|
||||||
return 22
|
|
||||||
case 'large':
|
|
||||||
return 30
|
|
||||||
default:
|
|
||||||
return 26
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const navList = ref([
|
|
||||||
{
|
|
||||||
name: '图床配置',
|
|
||||||
icon: 'edit',
|
|
||||||
isActive: false,
|
|
||||||
path: '/config',
|
|
||||||
isShow: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '上传图片',
|
|
||||||
icon: 'upload',
|
|
||||||
isActive: false,
|
|
||||||
path: '/upload',
|
|
||||||
isShow: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '图床管理',
|
|
||||||
icon: 'box',
|
|
||||||
isActive: false,
|
|
||||||
path: '/management',
|
|
||||||
isShow: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '我的设置',
|
|
||||||
icon: 'setting',
|
|
||||||
isActive: false,
|
|
||||||
path: '/settings',
|
|
||||||
isShow: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '使用教程',
|
|
||||||
icon: 'magic-stick',
|
|
||||||
isActive: false,
|
|
||||||
path: '/tutorials',
|
|
||||||
isShow: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '帮助反馈',
|
|
||||||
icon: 'chat-dot-round',
|
|
||||||
isActive: false,
|
|
||||||
path: '/about',
|
|
||||||
isShow: true
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const navClick = (e: any) => {
|
|
||||||
const { path } = e
|
|
||||||
|
|
||||||
if (path === '/management') {
|
|
||||||
if (userConfigInfo.selectedRepos === '') {
|
|
||||||
ElMessage.warning('请选择一个仓库!')
|
|
||||||
router.push('/config')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userConfigInfo.selectedDir === '') {
|
|
||||||
ElMessage.warning('目录不能为空!')
|
|
||||||
router.push('/config')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
router.push(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeNavActive = (currentPath: string) => {
|
|
||||||
navList.value.forEach((v) => {
|
|
||||||
const temp = v
|
|
||||||
temp.isActive = v.path === currentPath
|
|
||||||
return temp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => router.currentRoute.value,
|
|
||||||
(_n) => {
|
|
||||||
changeNavActive(_n.path)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => userConfigInfo.loggingStatus,
|
|
||||||
(_n) => {
|
|
||||||
navList.value.forEach((v: any) => {
|
|
||||||
// eslint-disable-next-line default-case
|
|
||||||
switch (v.path) {
|
|
||||||
case '/management':
|
|
||||||
case '/settings':
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
v.isShow = _n
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
router.isReady().then(() => {
|
|
||||||
changeNavActive(router.currentRoute.value.path)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "nav-content.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
.selected-info-bar-box {
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content flex-start
|
|
||||||
font-size 12rem
|
|
||||||
box-sizing border-box
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
margin-right 8rem
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="selected-info-bar-box" v-if="userConfigInfo.selectedRepos">
|
|
||||||
<span class="info-item">
|
|
||||||
仓库:
|
|
||||||
<el-tag>
|
|
||||||
{{ userConfigInfo.selectedRepos }}
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
|
||||||
<span class="info-item" v-if="userConfigInfo.selectedBranch">
|
|
||||||
分支:
|
|
||||||
<el-tag>
|
|
||||||
{{ userConfigInfo.selectedBranch }}
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
|
||||||
<span class="info-item" v-if="userConfigInfo.selectedDir">
|
|
||||||
目录:
|
|
||||||
<el-tag>
|
|
||||||
{{ userConfigInfo.selectedDir }}
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo)
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "selected-info-bar.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<template>
|
|
||||||
<span class="site-count" ref="siteCountDom" v-show="isShow">
|
|
||||||
超过
|
|
||||||
<span id="busuanzi_value_site_uv" class="uv" v-show="isuv"></span>
|
|
||||||
<span id="busuanzi_value_site_pv" class="pv" v-show="!isuv"></span>
|
|
||||||
次被使用
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, onMounted, ref, Ref } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'site-count',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
isuv: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, ctx) {
|
|
||||||
const siteCountDom: Ref = ref<null | HTMLElement>(null)
|
|
||||||
const isShow: Ref<boolean> = ref(false)
|
|
||||||
|
|
||||||
const getInnerText = (dom, isuv) => {
|
|
||||||
return dom.querySelector(`.${isuv ? 'u' : 'p'}v`).innerText
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const script: any = document.createElement('script')
|
|
||||||
script.async = true
|
|
||||||
script.src = '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js'
|
|
||||||
siteCountDom.value.appendChild(script)
|
|
||||||
|
|
||||||
script.onload = () => {
|
|
||||||
const tempT = setTimeout(() => {
|
|
||||||
if (getInnerText(siteCountDom.value, props.isuv)) {
|
|
||||||
isShow.value = true
|
|
||||||
}
|
|
||||||
clearTimeout(tempT)
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
siteCountDom,
|
|
||||||
isShow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<style lang="stylus">
|
|
||||||
.site-count {
|
|
||||||
transition all 0.2s ease-in
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
$info-item-height = 68rem
|
|
||||||
$info-item-border = 1rem
|
|
||||||
$info-item-padding = 5rem
|
|
||||||
$compressed-file-background-color = #228eff
|
|
||||||
$image-width = $info-item-height - ($info-item-border * 2)
|
|
||||||
|
|
||||||
.to-upload-image-list-card {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
box-sizing border-box
|
|
||||||
margin-top 6rem
|
|
||||||
|
|
||||||
.header {
|
|
||||||
width 100%
|
|
||||||
height 30rem
|
|
||||||
box-sizing border-box
|
|
||||||
font-size 12rem
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content space-between
|
|
||||||
padding-bottom 6rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
max-height 170rem
|
|
||||||
overflow-y auto
|
|
||||||
box-sizing border-box
|
|
||||||
padding 10rem
|
|
||||||
border 1rem solid var(--border-color)
|
|
||||||
margin-top 10rem
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width 5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
border-radius 2rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-uploading-info-box {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
box-sizing border-box
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
.image-uploading-info-item {
|
|
||||||
position relative
|
|
||||||
box-sizing border-box
|
|
||||||
width 100%
|
|
||||||
height $info-item-height
|
|
||||||
border $info-item-border solid var(--border-color)
|
|
||||||
border-radius 5rem
|
|
||||||
margin-bottom 10rem
|
|
||||||
overflow hidden
|
|
||||||
font-size 15rem
|
|
||||||
padding-left $image-width
|
|
||||||
transition all 0.3s ease
|
|
||||||
|
|
||||||
&.disable {
|
|
||||||
pointer-events none
|
|
||||||
cursor not-allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom 0
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow 0 0 5rem var(--shadow-hover-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-image-box {
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width $image-width
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
margin-right 5rem
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit cover
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
overflow hidden
|
|
||||||
cursor pointer
|
|
||||||
border-top-left-radius 5rem
|
|
||||||
border-bottom-left-radius 5rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.right-operation-box {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
padding $info-item-padding 20rem $info-item-padding $info-item-padding
|
|
||||||
|
|
||||||
.top, .bottom {
|
|
||||||
width 100%
|
|
||||||
height 50%
|
|
||||||
box-sizing border-box
|
|
||||||
padding 0 5rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
|
||||||
display flex
|
|
||||||
justify-content space-between
|
|
||||||
|
|
||||||
.image-name,
|
|
||||||
.image-info {
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
box-sizing border-box
|
|
||||||
height 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-name {
|
|
||||||
font-size 13rem
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
white-space nowrap
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-info {
|
|
||||||
font-size 12rem
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding 1rem 4rem
|
|
||||||
background var(--third-background-color)
|
|
||||||
border-radius 2rem
|
|
||||||
margin-left 10rem
|
|
||||||
|
|
||||||
&.compressed {
|
|
||||||
color $compressed-file-background-color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
|
|
||||||
&.rename-operation {
|
|
||||||
|
|
||||||
.el-checkbox {
|
|
||||||
margin-right 20rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.rename-input {
|
|
||||||
input {
|
|
||||||
height 23rem
|
|
||||||
line-height 23rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.upload-status-box {
|
|
||||||
box-sizing border-box
|
|
||||||
color #fff
|
|
||||||
position absolute
|
|
||||||
right -17rem
|
|
||||||
top -7rem
|
|
||||||
width 46rem
|
|
||||||
height 26rem
|
|
||||||
text-align center
|
|
||||||
transform rotate(45deg)
|
|
||||||
box-shadow 0 1rem 1rem var(--border-color)
|
|
||||||
|
|
||||||
&.wait-upload {
|
|
||||||
background var(--await-upload-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
&.uploading {
|
|
||||||
background var(--uploading-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
&.uploaded {
|
|
||||||
background var(--uploaded-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size 12rem
|
|
||||||
margin-top 12rem
|
|
||||||
transform rotate(-45deg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-to-upload-image {
|
|
||||||
position absolute
|
|
||||||
bottom 5rem
|
|
||||||
right 5rem
|
|
||||||
cursor pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="to-upload-image-list-card"
|
|
||||||
v-if="toUploadImage.list.length || userConfigInfo.selectedRepos"
|
|
||||||
>
|
|
||||||
<div class="header">
|
|
||||||
<div>
|
|
||||||
<selected-info-bar />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span v-if="toUploadImage.list.length">
|
|
||||||
已上传:{{ toUploadImage.uploadedNumber }} / {{ toUploadImage.list.length }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body" v-if="toUploadImage.list.length">
|
|
||||||
<ul class="image-uploading-info-box">
|
|
||||||
<li
|
|
||||||
class="image-uploading-info-item"
|
|
||||||
:class="{ disable: loadingAllImage }"
|
|
||||||
v-for="(imgItem, index) in toUploadImage.list"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div class="left-image-box">
|
|
||||||
<img data-fancybox="gallery" :src="imgItem.imgData.base64Url" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-operation-box">
|
|
||||||
<div class="top">
|
|
||||||
<div class="image-name">
|
|
||||||
{{ imgItem.filename.now }}
|
|
||||||
</div>
|
|
||||||
<div class="image-info">
|
|
||||||
<span class="file-size item" v-if="userSettings.isCompress">
|
|
||||||
<del>
|
|
||||||
{{ getFileSize(imgItem.fileInfo.originSize) }}
|
|
||||||
</del>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="file-size item"
|
|
||||||
:class="{ compressed: userSettings.isCompress }"
|
|
||||||
>
|
|
||||||
{{ getFileSize(imgItem.fileInfo.size) }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="last-modified item">
|
|
||||||
{{ formatLastModified(imgItem.fileInfo.lastModified) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="bottom rename-operation"
|
|
||||||
v-if="
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress !== 100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!-- 哈希化 -->
|
|
||||||
<el-checkbox
|
|
||||||
label="哈希化"
|
|
||||||
v-model="imgItem.filename.isHashRename"
|
|
||||||
@change="hashRename($event, imgItem)"
|
|
||||||
></el-checkbox>
|
|
||||||
|
|
||||||
<!-- 重命名 -->
|
|
||||||
<el-checkbox
|
|
||||||
label="重命名"
|
|
||||||
v-model="imgItem.filename.isRename"
|
|
||||||
@change="rename($event, imgItem)"
|
|
||||||
></el-checkbox>
|
|
||||||
<el-input
|
|
||||||
class="rename-input"
|
|
||||||
size="small"
|
|
||||||
v-if="imgItem.filename.isRename"
|
|
||||||
v-model="imgItem.filename.newName"
|
|
||||||
@input="rename($event, imgItem)"
|
|
||||||
clearable
|
|
||||||
></el-input>
|
|
||||||
|
|
||||||
<!-- 命名前缀 -->
|
|
||||||
<el-checkbox
|
|
||||||
label="命名前缀"
|
|
||||||
v-if="
|
|
||||||
!imgItem.filename.isRename &&
|
|
||||||
userConfigInfo.defaultPrefix &&
|
|
||||||
userConfigInfo.prefixName
|
|
||||||
"
|
|
||||||
v-model="imgItem.filename.isPrefix"
|
|
||||||
@change="prefixName($event, imgItem)"
|
|
||||||
></el-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="bottom rename-operation"
|
|
||||||
v-if="
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress === 100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<copy-externalLink :img-obj="imgItem.uploadedImg" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="upload-status-box"
|
|
||||||
:class="{
|
|
||||||
'wait-upload':
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress !== 100,
|
|
||||||
uploading:
|
|
||||||
imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress !== 100,
|
|
||||||
uploaded:
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress === 100
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress !== 100
|
|
||||||
"
|
|
||||||
><Upload
|
|
||||||
/></el-icon>
|
|
||||||
|
|
||||||
<el-icon
|
|
||||||
class="is-loading"
|
|
||||||
v-if="
|
|
||||||
imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress !== 100
|
|
||||||
"
|
|
||||||
><Loading
|
|
||||||
/></el-icon>
|
|
||||||
|
|
||||||
<el-icon
|
|
||||||
v-if="
|
|
||||||
!imgItem.uploadStatus.uploading && imgItem.uploadStatus.progress === 100
|
|
||||||
"
|
|
||||||
><Check
|
|
||||||
/></el-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="remove-to-upload-image"
|
|
||||||
v-if="
|
|
||||||
imgItem.uploadStatus.progress !== 100 && !imgItem.uploadStatus.uploading
|
|
||||||
"
|
|
||||||
@click="removeToUploadImage(imgItem)"
|
|
||||||
>
|
|
||||||
<el-tooltip effect="dark" content="移除" placement="top">
|
|
||||||
<el-icon><Delete /></el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, reactive, toRefs, onMounted } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { getFileSize } from '@/utils/file-handle-helper'
|
|
||||||
import { UserConfigInfoModel } from '@/common/model/user-config-info.model'
|
|
||||||
import { ToUploadImageModel, UploadStatusEnum } from '@/common/model/upload.model'
|
|
||||||
import TimeHelper from '@/utils/time-helper'
|
|
||||||
import copyExternalLink from '@/components/copy-external-link/copy-external-link.vue'
|
|
||||||
import selectedInfoBar from '@/components/selected-info-bar/selected-info-bar.vue'
|
|
||||||
import { uploadImage_single } from '@/utils/upload-helper'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'to-upload-image-card',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
copyExternalLink,
|
|
||||||
selectedInfoBar
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
loadingAllImage: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const reactiveData = reactive({
|
|
||||||
isShowDialog: false,
|
|
||||||
curImgInfo: {
|
|
||||||
size: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
userConfigInfo: computed(() => store.getters.getUserConfigInfo).value,
|
|
||||||
userSettings: computed(() => store.getters.getUserSettings).value,
|
|
||||||
toUploadImage: computed(() => store.getters.getToUploadImage).value,
|
|
||||||
|
|
||||||
hashRename(e: boolean, img: any) {
|
|
||||||
if (e) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.hash}.${img.filename.suffix}`
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.suffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
prefixName(e: boolean, img: any) {
|
|
||||||
if (e) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.name = `${img.filename.prefixName}${img.filename.initName}`
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.name = `${img.filename.initName}`
|
|
||||||
}
|
|
||||||
if (img.filename.isHashRename) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.hash}.${img.filename.suffix}`
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.suffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
rename(e: boolean, img: any) {
|
|
||||||
if (e) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.name = img.filename.newName.trim().replace(/\s+/g, '-')
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
reactiveData.prefixName(img.filename.isPrefix, img) // 恢复列表prefix选项
|
|
||||||
}
|
|
||||||
|
|
||||||
if (img.filename.isHashRename) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.hash}.${img.filename.suffix}`
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.filename.now = `${img.filename.name}.${img.filename.suffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getFileSize(size: number) {
|
|
||||||
return `${getFileSize(size)} KB`
|
|
||||||
},
|
|
||||||
|
|
||||||
formatLastModified(t: number) {
|
|
||||||
return TimeHelper.formatTimestamp(t)
|
|
||||||
},
|
|
||||||
|
|
||||||
async uploadImage_all(userConfigInfo: UserConfigInfoModel) {
|
|
||||||
const uploadIndex = this.toUploadImage.uploadedNumber
|
|
||||||
|
|
||||||
if (uploadIndex >= this.toUploadImage.list.length) {
|
|
||||||
return UploadStatusEnum.uploaded
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
await uploadImage_single(userConfigInfo, this.toUploadImage.list[uploadIndex])
|
|
||||||
) {
|
|
||||||
if (uploadIndex < this.toUploadImage.list.length) {
|
|
||||||
await this.uploadImage_all(userConfigInfo)
|
|
||||||
return UploadStatusEnum.allUploaded
|
|
||||||
}
|
|
||||||
return UploadStatusEnum.uploaded
|
|
||||||
}
|
|
||||||
return UploadStatusEnum.uploadFail
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const removeToUploadImage = (imgItem: ToUploadImageModel) => {
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_LIST_REMOVE', imgItem.uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const {
|
|
||||||
defaultHash: isHash,
|
|
||||||
defaultPrefix: isPrefix,
|
|
||||||
prefixName
|
|
||||||
} = reactiveData.userSettings
|
|
||||||
reactiveData.toUploadImage.list.forEach((v: ToUploadImageModel) => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
v.filename.isPrefix = isPrefix
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
v.filename.prefixName = prefixName
|
|
||||||
reactiveData.prefixName(isPrefix, v)
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
v.filename.isHashRename = isHash
|
|
||||||
reactiveData.hashRename(isHash, v)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(reactiveData),
|
|
||||||
removeToUploadImage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
@import "to-upload-image-card.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="tutorials-step-1">
|
|
||||||
<h3>
|
|
||||||
创建一个用来存储图片的
|
|
||||||
<span class="go-create-repo" @click="goCreateRepo"> GitHub 仓库</span>
|
|
||||||
</h3>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.j1486dtk68n.png"
|
|
||||||
alt="Create GitHub Repository"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
function goCreateRepo() {
|
|
||||||
window.open('https://github.com/new')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
|
|
||||||
.tutorials-step-1 {
|
|
||||||
|
|
||||||
width 800rem
|
|
||||||
|
|
||||||
.go-create-repo {
|
|
||||||
cursor pointer
|
|
||||||
color #1c81e9
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color #085fb8
|
|
||||||
border-bottom 1rem solid #085fb8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="tutorials-step-2">
|
|
||||||
<h3>
|
|
||||||
创建一个有 repo 权限的
|
|
||||||
<span class="go-create-token" @click="goCreateToken"> GitHub Token</span>
|
|
||||||
</h3>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.lpt1xl9fu.png"
|
|
||||||
alt="Create GitHub Token"
|
|
||||||
/>
|
|
||||||
<p>然后点击 Generate token 按钮,即可生成一个token,如下图:</p>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.pzmcp6b80fk.png"
|
|
||||||
alt="token-demo"
|
|
||||||
/>
|
|
||||||
<p style="color: red">
|
|
||||||
<em>新生成的 Token 只会显示一次,请妥善保存!如有遗失,重新生成即可。</em>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
function goCreateToken() {
|
|
||||||
window.open('https://github.com/settings/tokens/new')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
|
|
||||||
.tutorials-step-2 {
|
|
||||||
|
|
||||||
width 800rem
|
|
||||||
|
|
||||||
.go-create-token {
|
|
||||||
cursor pointer
|
|
||||||
color #1c81e9
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color #085fb8
|
|
||||||
border-bottom 1rem solid #085fb8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
img {
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-weight bold
|
|
||||||
padding-top 20rem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="tutorials-step-3">
|
|
||||||
<h3>进行图床配置(绑定 GitHub Token、存储图片的仓库和目录)</h3>
|
|
||||||
<p>1、填写 Token,自动获取该用户下的仓库</p>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.4g8q5m7c8sq0.png"
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<p>2、在仓库的下拉列表中,选择一个作为图床的仓库</p>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.746g75olruk0.png"
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<p>3、选择一种目录方式(目录即仓库里存放图片的文件夹)</p>
|
|
||||||
<img
|
|
||||||
src="https://cdn.staticaly.com/gh/XPoet/image-hosting@master/PicX/image.5ydmhgxjhgo0.png"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="dir-desc-box">
|
|
||||||
<ul>
|
|
||||||
<li>新建目录:需手动输入一个新目录。</li>
|
|
||||||
<li>根目录:图片将直接存储在仓库根目录下。</li>
|
|
||||||
<li>自动目录:自动生成日期格式 YYYYMMDD 的目录。例如:20200909</li>
|
|
||||||
<li>选择仓库目录:自动获取仓库下所有目录,选择一个即可。</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
|
|
||||||
.tutorials-step-3 {
|
|
||||||
|
|
||||||
width 800rem
|
|
||||||
|
|
||||||
img {
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-weight bold
|
|
||||||
padding-top 20rem
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.dir-desc-box {
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
|
|
||||||
ul {
|
|
||||||
width 100%
|
|
||||||
padding 0
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding-top 10rem
|
|
||||||
text-align left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
.upload-area {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 300rem;
|
|
||||||
border: 4rem dashed var(--third-text-color)
|
|
||||||
box-sizing border-box
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
|
||||||
|
|
||||||
&.focus {
|
|
||||||
border-color: var(--upload-area-focus-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--upload-area-focus-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1000;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
position: absolute;
|
|
||||||
left: -9999rem;
|
|
||||||
top: -9999rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips {
|
|
||||||
text-align: center;
|
|
||||||
color: #aaa;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 100rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
cursor: default;
|
|
||||||
font-size: 20rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: cover;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="upload-area active-upload"
|
|
||||||
:class="{ focus: uploadAreaActive }"
|
|
||||||
@dragover.prevent
|
|
||||||
@drop.stop.prevent="onDrop"
|
|
||||||
@paste="onPaste"
|
|
||||||
v-loading="imageLoading"
|
|
||||||
element-loading-text="图片上传中..."
|
|
||||||
element-loading-background="rgba(0, 0, 0, 0.5)"
|
|
||||||
>
|
|
||||||
<label for="uploader" class="active-upload" v-if="uploadAreaActive"></label>
|
|
||||||
<input id="uploader" type="file" @change="onSelect" multiple="multiple" />
|
|
||||||
<div class="tips active-upload" v-if="!toUploadImage.curImgBase64Url">
|
|
||||||
<el-icon class="icon active-upload"><UploadFilled /></el-icon>
|
|
||||||
<div class="text active-upload">拖拽、粘贴、或点击此处上传</div>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
class="active-upload"
|
|
||||||
v-if="toUploadImage.curImgBase64Url"
|
|
||||||
:src="toUploadImage.curImgBase64Url"
|
|
||||||
alt="Pictures to be uploaded"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, reactive, toRefs } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { filenameHandle } from '@/utils/file-handle-helper'
|
|
||||||
import selectedFileHandle, { handleResult } from '@/utils/selected-file-handle'
|
|
||||||
import createToUploadImageObject from '@/utils/create-to-upload-image'
|
|
||||||
import paste from '@/utils/paste'
|
|
||||||
import Upload from '@/views/upload/upload.vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'upload-area',
|
|
||||||
props: {
|
|
||||||
imageLoading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const reactiveData = reactive({
|
|
||||||
userConfigInfo: computed(() => store.getters.getUserConfigInfo).value,
|
|
||||||
userSettings: computed(() => store.getters.getUserSettings).value,
|
|
||||||
uploadAreaActive: computed((): boolean => store.getters.getUploadAreaActive),
|
|
||||||
uploadSettings: computed(() => store.getters.getUploadSettings).value,
|
|
||||||
toUploadImage: computed(() => store.getters.getToUploadImage).value,
|
|
||||||
|
|
||||||
// 选择图片
|
|
||||||
onSelect(e: any) {
|
|
||||||
store.commit('CHANGE_UPLOAD_AREA_ACTIVE', true)
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const file of e.target.files) {
|
|
||||||
selectedFileHandle(file, this.uploadSettings.imageMaxSize)?.then((result) => {
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { base64, originalFile, compressFile } = result
|
|
||||||
this.getImage(base64, originalFile, compressFile)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 拖拽图片
|
|
||||||
onDrop(e: any) {
|
|
||||||
store.commit('CHANGE_UPLOAD_AREA_ACTIVE', true)
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const file of e.dataTransfer.files) {
|
|
||||||
selectedFileHandle(file, this.uploadSettings.imageMaxSize)?.then((result) => {
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { base64, originalFile, compressFile } = result
|
|
||||||
this.getImage(base64, originalFile, compressFile)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 复制图片
|
|
||||||
async onPaste(e: any) {
|
|
||||||
const { base64, originalFile, compressFile }: handleResult = await paste(
|
|
||||||
e,
|
|
||||||
this.uploadSettings.imageMaxSize
|
|
||||||
)
|
|
||||||
this.getImage(base64, originalFile, compressFile)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取图片对象
|
|
||||||
getImage(base64Data: string, originFile: File, compressFile?: File) {
|
|
||||||
if (
|
|
||||||
this.toUploadImage.list.length === this.toUploadImage.uploadedNumber &&
|
|
||||||
this.toUploadImage.list.length > 0 &&
|
|
||||||
this.toUploadImage.uploadedNumber > 0
|
|
||||||
) {
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_CLEAN_LIST')
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_CLEAN_UPLOADED_NUMBER')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { defaultHash, isCompress, defaultPrefix, prefixName } = this.userSettings
|
|
||||||
const file = isCompress ? compressFile : originFile
|
|
||||||
const curImg = createToUploadImageObject()
|
|
||||||
|
|
||||||
curImg.imgData.base64Url = base64Data
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
curImg.imgData.base64Content = base64Data.split(',')[1]
|
|
||||||
|
|
||||||
const { name, hash, suffix } = filenameHandle(file?.name)
|
|
||||||
curImg.uuid = hash
|
|
||||||
curImg.fileInfo.compressedSize = compressFile?.size
|
|
||||||
curImg.fileInfo.originSize = originFile.size
|
|
||||||
curImg.fileInfo.size = file?.size
|
|
||||||
curImg.fileInfo.lastModified = file?.lastModified
|
|
||||||
|
|
||||||
curImg.filename.initName = name
|
|
||||||
curImg.filename.name = defaultPrefix ? `${prefixName}${name}` : name
|
|
||||||
curImg.filename.prefixName = prefixName
|
|
||||||
curImg.filename.hash = hash
|
|
||||||
curImg.filename.suffix = suffix
|
|
||||||
curImg.filename.now = defaultHash
|
|
||||||
? `${curImg.filename.name}.${hash}.${suffix}`
|
|
||||||
: `${curImg.filename.name}.${suffix}`
|
|
||||||
curImg.filename.isHashRename = defaultHash
|
|
||||||
curImg.filename.isPrefix = defaultPrefix
|
|
||||||
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_LIST_ADD', JSON.parse(JSON.stringify(curImg)))
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_SET_CURRENT', {
|
|
||||||
uuid: hash,
|
|
||||||
base64Url: base64Data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(reactiveData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "upload-area.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { createApp } from 'vue'
|
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
||||||
import router from '@/router/index'
|
|
||||||
import { key, store } from '@/store'
|
|
||||||
import App from './App.vue'
|
|
||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
||||||
|
|
||||||
if (import.meta.env.MODE === 'production') {
|
|
||||||
// @ts-ignore
|
|
||||||
import('@/utils/register-sw.ts')
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
// import element-plus icons
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
||||||
app.component(key, component)
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
app.use(router).use(store, key).mount('#app')
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import type { Plugin } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
import { ViteEnv } from '@/common/model/vite-config.model'
|
|
||||||
|
|
||||||
import configPWAPlugin from './pwa'
|
|
||||||
|
|
||||||
export default function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
|
||||||
const vitePlugins: (Plugin | Plugin[])[] = [vue()]
|
|
||||||
|
|
||||||
// On-demand import style for Element Plus
|
|
||||||
vitePlugins.push(
|
|
||||||
AutoImport({
|
|
||||||
resolvers: [ElementPlusResolver()]
|
|
||||||
}),
|
|
||||||
Components({
|
|
||||||
resolvers: [ElementPlusResolver()]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// production env
|
|
||||||
if (isBuild) {
|
|
||||||
// add plugin vite-plugin-pwa
|
|
||||||
if (viteEnv.VITE_USE_PWA) {
|
|
||||||
vitePlugins.push(configPWAPlugin(viteEnv))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vitePlugins
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Zero config PWA for Vite
|
|
||||||
* Plugin: vite-plugin-pwa
|
|
||||||
* https://github.com/antfu/vite-plugin-pwa
|
|
||||||
*/
|
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
|
||||||
import { ViteEnv } from '@/common/model/vite-config.model'
|
|
||||||
|
|
||||||
export default function configPWAPlugin(env: ViteEnv) {
|
|
||||||
return VitePWA({
|
|
||||||
registerType: 'autoUpdate',
|
|
||||||
manifest: {
|
|
||||||
name: env.VITE_GLOB_APP_TITLE,
|
|
||||||
short_name: env.VITE_GLOB_APP_SHORT_NAME,
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: './logo@192x192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
|
||||||
import config from '@/views/config/config.vue'
|
|
||||||
import upload from '@/views/upload/upload.vue'
|
|
||||||
import management from '@/views/management/management.vue'
|
|
||||||
import tutorials from '@/views/tutorials/tutorials.vue'
|
|
||||||
import settings from '@/views/settings/settings.vue'
|
|
||||||
import { store } from '@/store'
|
|
||||||
|
|
||||||
const titleSuffix = ` | PicX 图床神器`
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'index',
|
|
||||||
redirect: {
|
|
||||||
name: 'upload'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/config',
|
|
||||||
name: 'config',
|
|
||||||
component: config,
|
|
||||||
meta: {
|
|
||||||
title: `图床配置${titleSuffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upload',
|
|
||||||
name: 'upload',
|
|
||||||
component: upload,
|
|
||||||
meta: {
|
|
||||||
title: `图片上传${titleSuffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management',
|
|
||||||
name: 'Management',
|
|
||||||
component: management,
|
|
||||||
meta: {
|
|
||||||
title: `图床管理${titleSuffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/tutorials',
|
|
||||||
name: 'tutorials',
|
|
||||||
component: tutorials,
|
|
||||||
meta: {
|
|
||||||
title: `使用教程${titleSuffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
component: () => import('@/views/about/about.vue'),
|
|
||||||
meta: {
|
|
||||||
title: `帮助反馈${titleSuffix}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/settings',
|
|
||||||
name: 'settings',
|
|
||||||
component: settings,
|
|
||||||
meta: {
|
|
||||||
title: `我的设置${titleSuffix}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHashHistory(),
|
|
||||||
routes
|
|
||||||
})
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (to.meta.title) (<any>window).document.title = to.meta.title
|
|
||||||
if (from.path === '/management') {
|
|
||||||
store.dispatch('USER_CONFIG_INFO_RESET')
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
6
picx/src/shims-vue.d.ts
vendored
6
picx/src/shims-vue.d.ts
vendored
@@ -1,6 +0,0 @@
|
|||||||
declare module '*.vue' {
|
|
||||||
import { DefineComponent } from 'vue'
|
|
||||||
|
|
||||||
const component: DefineComponent<{}, {}, any>
|
|
||||||
export default component
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { InjectionKey } from 'vue'
|
|
||||||
import { createStore, Store, useStore as baseUseStore } from 'vuex'
|
|
||||||
import RootStateTypes, { AllStateTypes } from './types'
|
|
||||||
import dirImageListModule from './modules/dir-image-list'
|
|
||||||
import toUploadImageModule from './modules/to-upload-image'
|
|
||||||
import uploadedImageListModule from './modules/uploaded-image-list'
|
|
||||||
import userConfigInfoModule from './modules/user-config-info'
|
|
||||||
import imageViewerModule from './modules/image-viewer'
|
|
||||||
import imageCardModule from './modules/image-card'
|
|
||||||
import uploadAreaActiveModule from './modules/upload-area-active'
|
|
||||||
import uploadSettingsModule from './modules/upload-settings'
|
|
||||||
import userSettingsModule from './modules/user-settings'
|
|
||||||
|
|
||||||
// Create a new store instance.
|
|
||||||
export const store = createStore<RootStateTypes>({
|
|
||||||
modules: {
|
|
||||||
dirImageListModule,
|
|
||||||
toUploadImageModule,
|
|
||||||
uploadedImageListModule,
|
|
||||||
userConfigInfoModule,
|
|
||||||
imageViewerModule,
|
|
||||||
imageCardModule,
|
|
||||||
uploadAreaActiveModule,
|
|
||||||
uploadSettingsModule,
|
|
||||||
userSettingsModule
|
|
||||||
},
|
|
||||||
|
|
||||||
state: {
|
|
||||||
rootName: 'root'
|
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 退出登录(删除 localStorage 和 sessionStorage 数据,清空 state 的值)
|
|
||||||
LOGOUT({ dispatch, commit }) {
|
|
||||||
dispatch('DIR_IMAGE_LOGOUT')
|
|
||||||
dispatch('TO_UPLOAD_IMAGE_LOGOUT')
|
|
||||||
dispatch('UPLOADED_LIST_LOGOUT')
|
|
||||||
dispatch('USER_CONFIG_INFO_LOGOUT')
|
|
||||||
commit('IMAGE_VIEWER_LOGOUT')
|
|
||||||
commit('UPLOAD_AREA_ACTIVE_LOGOUT')
|
|
||||||
commit('UPLOAD_SETTINGS_LOGOUT')
|
|
||||||
dispatch('USER_SETTINGS_LOGOUT')
|
|
||||||
localStorage.clear()
|
|
||||||
sessionStorage.clear()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const key: InjectionKey<Store<RootStateTypes>> = Symbol('vuex-store')
|
|
||||||
|
|
||||||
export function useStore<T = AllStateTypes>() {
|
|
||||||
return baseUseStore<T>(key)
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import { PICX_MANAGEMENT } from '@/common/model/storage.model'
|
|
||||||
import DirImageListStateTypes, { DirObject } from './types'
|
|
||||||
import RootStateTypes from '../../types'
|
|
||||||
import {
|
|
||||||
createDirObject,
|
|
||||||
getUpLevelDirList,
|
|
||||||
getUpOneLevelDir
|
|
||||||
} from '@/store/modules/dir-image-list/utils'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
import { getDirContent } from '@/views/management/management.util'
|
|
||||||
|
|
||||||
const initDirObject = () => {
|
|
||||||
const dirObj = localStorage.getItem(PICX_MANAGEMENT)
|
|
||||||
return dirObj ? JSON.parse(dirObj) : createDirObject('/', '/')
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirImageListModule: Module<DirImageListStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
name: 'dirImageListModule',
|
|
||||||
dirObject: initDirObject()
|
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 图床管理 - 增加目录
|
|
||||||
DIR_IMAGE_LIST_ADD_DIR({ state, dispatch }, dirPath: string) {
|
|
||||||
if (dirPath === '/') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const findAssign = (dirObj: DirObject, dir: string, dirPath: string) => {
|
|
||||||
if (dirObj) {
|
|
||||||
if (!dirObj.childrenDirs.some((v: DirObject) => v.dir === dir)) {
|
|
||||||
dirObj.childrenDirs.push(createDirObject(dir, dirPath))
|
|
||||||
}
|
|
||||||
const temp = dirObj.childrenDirs.find((x: DirObject) => x.dir === dir)
|
|
||||||
return temp || createDirObject(dir, dirPath)
|
|
||||||
}
|
|
||||||
return createDirObject(dir, dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList: string[] = dirPath.split('/')
|
|
||||||
let dirPathC = ''
|
|
||||||
let tempDirObj: DirObject = state.dirObject
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0, len = dirList.length; i < len; i++) {
|
|
||||||
const dirName = dirList[i]
|
|
||||||
dirPathC += `${i > 0 ? '/' : ''}${dirName}`
|
|
||||||
tempDirObj = findAssign(tempDirObj, dirName, dirPathC)
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
dispatch('USER_CONFIG_INFO_ADD_DIR', dirName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 删除目录
|
|
||||||
DIR_IMAGE_LIST_REMOVE_DIR({ state, dispatch }, dirPath: string) {
|
|
||||||
if (dirPath === '/') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const rmDir = (dirObj: DirObject, dir: string, isRm: boolean) => {
|
|
||||||
if (dir === '/') {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = dirObj.childrenDirs.find((v) => v.dir === dir)
|
|
||||||
if (!temp) {
|
|
||||||
return dirObj
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRm) {
|
|
||||||
const rmIndex = dirObj.childrenDirs.findIndex((v: any) => v.dir === dir)
|
|
||||||
if (rmIndex !== -1) {
|
|
||||||
dirObj.childrenDirs.splice(rmIndex, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList = dirPath.split('/')
|
|
||||||
|
|
||||||
let tempDirObj = state.dirObject
|
|
||||||
dirList.forEach((d, i) => {
|
|
||||||
tempDirObj = rmDir(tempDirObj, d, i === dirList.length - 1)
|
|
||||||
|
|
||||||
// 删除在用户配置信息模块里的目录项
|
|
||||||
if (i === 0) {
|
|
||||||
dispatch('USER_CONFIG_INFO_REMOVE_DIR', d)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 增加图片
|
|
||||||
DIR_IMAGE_LIST_ADD_IMAGE({ state, dispatch }, item: UploadedImageModel) {
|
|
||||||
const addImg = (
|
|
||||||
dirObj: DirObject,
|
|
||||||
dir: string,
|
|
||||||
Img: UploadedImageModel,
|
|
||||||
isAdd: boolean = false
|
|
||||||
) => {
|
|
||||||
if (!dirObj) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = dirObj.childrenDirs?.find((x: DirObject) => x.dir === dir)
|
|
||||||
if (!temp) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAdd && !temp.imageList.some((v) => v.name === Img.name)) {
|
|
||||||
temp.imageList.push(Img)
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
|
|
||||||
let tempDirObj: DirObject = state.dirObject
|
|
||||||
|
|
||||||
if (item.dir === '/') {
|
|
||||||
if (!tempDirObj.imageList.some((v) => v.name === item.name)) {
|
|
||||||
tempDirObj.imageList.push(item)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const dirList: string[] = item.dir.split('/')
|
|
||||||
dirList.forEach((dir, i) => {
|
|
||||||
tempDirObj = addImg(tempDirObj, dir, item, i === dirList.length - 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 删除图片(即删除指定目录里的指定图片)
|
|
||||||
DIR_IMAGE_LIST_REMOVE({ state, dispatch }, item: any) {
|
|
||||||
// 删除
|
|
||||||
const rm = (list: UploadedImageModel[], uuid: string) => {
|
|
||||||
if (list.length) {
|
|
||||||
const rmIndex = list.findIndex((v: any) => v.uuid === uuid)
|
|
||||||
if (rmIndex !== -1) {
|
|
||||||
list.splice(rmIndex, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除图片
|
|
||||||
const rmImg = (
|
|
||||||
dirObj: DirObject,
|
|
||||||
dir: string,
|
|
||||||
img: UploadedImageModel,
|
|
||||||
isRm: boolean
|
|
||||||
) => {
|
|
||||||
if (!dirObj) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = dirObj.childrenDirs.find((x: DirObject) => x.dir === dir)
|
|
||||||
if (!temp) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temp.dir === dir && isRm) {
|
|
||||||
rm(temp.imageList, img.uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
|
|
||||||
const { dir, uuid } = item
|
|
||||||
|
|
||||||
if (dir === '/') {
|
|
||||||
rm(state.dirObject.imageList, uuid)
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList: string[] = dir.split('/')
|
|
||||||
let tempDirObj: DirObject = state.dirObject
|
|
||||||
|
|
||||||
dirList.forEach((d, i) => {
|
|
||||||
tempDirObj = rmImg(tempDirObj, d, item, i === dirList.length - 1)
|
|
||||||
if (!tempDirObj.imageList.length && !tempDirObj.childrenDirs.length) {
|
|
||||||
const dirPathList = getUpLevelDirList(tempDirObj.dirPath)
|
|
||||||
|
|
||||||
// 循环遍历判断上一级目录的内容是否为空,为空则删除,依次往上查找,直到根目录
|
|
||||||
dirPathList.forEach((dp) => {
|
|
||||||
const dpc = getDirContent(dp, state.dirObject)
|
|
||||||
if (dpc && !dpc.imageList.length && !dpc.childrenDirs.length) {
|
|
||||||
const { dirPath } = getUpOneLevelDir(dp)
|
|
||||||
dispatch('SET_USER_CONFIG_INFO', { selectedDir: dirPath })
|
|
||||||
dispatch('DIR_IMAGE_LIST_REMOVE_DIR', dp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 初始化指定目录(即删除指定目录的子目录列表和图片列表) -- OK
|
|
||||||
DIR_IMAGE_LIST_INIT_DIR({ state, dispatch }, dirPath: string) {
|
|
||||||
let tempDirObj = state.dirObject
|
|
||||||
|
|
||||||
if (dirPath === '/') {
|
|
||||||
tempDirObj.imageList = []
|
|
||||||
tempDirObj.childrenDirs = []
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const initDirObject = (dirObj: DirObject, dir: string, isInit: boolean) => {
|
|
||||||
if (!dirObj) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = dirObj.childrenDirs?.find((x: DirObject) => x.dir === dir)
|
|
||||||
if (!temp) {
|
|
||||||
return state.dirObject
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInit) {
|
|
||||||
temp.imageList = []
|
|
||||||
temp.childrenDirs = []
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList = dirPath.split('/')
|
|
||||||
|
|
||||||
dirList.forEach((d, i) => {
|
|
||||||
tempDirObj = initDirObject(tempDirObj, d, i === dirList.length - 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch('DIR_IMAGE_LIST_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 持久化存储 -- OK
|
|
||||||
DIR_IMAGE_LIST_PERSIST({ state }) {
|
|
||||||
localStorage.setItem(PICX_MANAGEMENT, JSON.stringify(state.dirObject))
|
|
||||||
},
|
|
||||||
|
|
||||||
// 图床管理 - 退出登录
|
|
||||||
DIR_IMAGE_LOGOUT({ state }) {
|
|
||||||
state.dirObject = createDirObject('/', '/')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
getDirObject: (state: any) => state.dirObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default dirImageListModule
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
export interface DirObject {
|
|
||||||
type: 'dir'
|
|
||||||
dir: string
|
|
||||||
dirPath: string
|
|
||||||
childrenDirs: DirObject[]
|
|
||||||
imageList: UploadedImageModel[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default interface DirImageListStateTypes {
|
|
||||||
name: string
|
|
||||||
dirObject: DirObject
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { DirObject } from '@/store/modules/dir-image-list/types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造一个新的目录对象
|
|
||||||
* @param dir
|
|
||||||
* @param dirPath
|
|
||||||
*/
|
|
||||||
export const createDirObject = (dir: string, dirPath: string): DirObject => {
|
|
||||||
return {
|
|
||||||
type: 'dir',
|
|
||||||
dir,
|
|
||||||
dirPath,
|
|
||||||
childrenDirs: [],
|
|
||||||
imageList: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取上一级目录
|
|
||||||
* @param dirPath
|
|
||||||
*/
|
|
||||||
export const getUpOneLevelDir = (dirPath: string) => {
|
|
||||||
if (dirPath === '/') {
|
|
||||||
return {
|
|
||||||
currentDir: '/',
|
|
||||||
dirPath: '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList = dirPath.split('/')
|
|
||||||
|
|
||||||
if (dirList.length === 1) {
|
|
||||||
return {
|
|
||||||
currentDir: '/',
|
|
||||||
dirPath: '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dirList.length > 1) {
|
|
||||||
dirList.length -= 1
|
|
||||||
return {
|
|
||||||
currentDir: dirList[dirList.length - 1],
|
|
||||||
dirPath: dirList.join('/')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentDir: '/',
|
|
||||||
dirPath: '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取上级目录列表
|
|
||||||
* @param dirPath
|
|
||||||
*/
|
|
||||||
export const getUpLevelDirList = (dirPath: string) => {
|
|
||||||
if (dirPath === '/') {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirList = dirPath.split('/')
|
|
||||||
|
|
||||||
const tempL: string[] = []
|
|
||||||
let tempP = ''
|
|
||||||
|
|
||||||
dirList.forEach((d, i) => {
|
|
||||||
tempP += `${i > 0 ? '/' : ''}${d}`
|
|
||||||
tempL.unshift(tempP)
|
|
||||||
})
|
|
||||||
|
|
||||||
return tempL
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import { ImageCardStateTypes } from './types'
|
|
||||||
import RootStateTypes from '../../types'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
const imageCardModule: Module<ImageCardStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
imgCardArr: []
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
IMAGE_CARD(state: ImageCardStateTypes, { imageObj }) {
|
|
||||||
const { uuid, checked } = imageObj
|
|
||||||
if (checked) {
|
|
||||||
state.imgCardArr.forEach((item) => {
|
|
||||||
if (item.uuid === uuid) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
item.checked = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
REPLACE_IMAGE_CARD(state: ImageCardStateTypes, { checkedImgArr }) {
|
|
||||||
if (checkedImgArr.length > 0) {
|
|
||||||
state.imgCardArr = checkedImgArr
|
|
||||||
} else {
|
|
||||||
state.imgCardArr = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {},
|
|
||||||
getters: {
|
|
||||||
getImageCardArr: (state: ImageCardStateTypes) => state.imgCardArr,
|
|
||||||
getImageCardCheckedArr: (state: ImageCardStateTypes) => {
|
|
||||||
return state.imgCardArr.filter((item: UploadedImageModel) => {
|
|
||||||
return item.checked
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default imageCardModule
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
export interface ImageCardStateTypes {
|
|
||||||
imgCardArr: UploadedImageModel[]
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import ImageViewerStateTypes from './types'
|
|
||||||
import RootStateTypes from '../../types'
|
|
||||||
|
|
||||||
const imageViewerModule: Module<ImageViewerStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
imageViewer: {
|
|
||||||
imgInfo: null,
|
|
||||||
isShow: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
IMAGE_VIEWER(state: ImageViewerStateTypes, { imgInfo, isShow }) {
|
|
||||||
state.imageViewer.imgInfo = imgInfo
|
|
||||||
state.imageViewer.isShow = isShow
|
|
||||||
},
|
|
||||||
|
|
||||||
IMAGE_VIEWER_LOGOUT(state: ImageViewerStateTypes) {
|
|
||||||
state.imageViewer.isShow = false
|
|
||||||
state.imageViewer.imgInfo = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {},
|
|
||||||
getters: {
|
|
||||||
getImageViewer: (state: ImageViewerStateTypes) => state.imageViewer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default imageViewerModule
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
export interface ImgInfo {
|
|
||||||
name: string
|
|
||||||
size: number
|
|
||||||
lastModified: number
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
export default interface ImageViewerStateTypes {
|
|
||||||
imageViewer: {
|
|
||||||
imgInfo: ImgInfo | null
|
|
||||||
isShow: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { ToUploadImageModel } from '@/common/model/upload.model'
|
|
||||||
import { Module } from 'vuex'
|
|
||||||
import ToUploadImageStateTypes from '@/store/modules/to-upload-image/types'
|
|
||||||
import RootStateTypes from '@/store/types'
|
|
||||||
|
|
||||||
const toUploadImageModule: Module<ToUploadImageStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
curImgBase64Url: '',
|
|
||||||
curImgUuid: '',
|
|
||||||
list: [],
|
|
||||||
uploadedNumber: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 要上传的图片列表 - 增加图片项
|
|
||||||
TO_UPLOAD_IMAGE_LIST_ADD({ state }, item: ToUploadImageModel) {
|
|
||||||
state.list.unshift(item)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 设置当前图片的 Base64Url
|
|
||||||
TO_UPLOAD_IMAGE_SET_CURRENT({ state }, { uuid, base64Url }) {
|
|
||||||
state.curImgUuid = uuid
|
|
||||||
state.curImgBase64Url = base64Url
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 上传完成的图片数量 +1
|
|
||||||
TO_UPLOAD_IMAGE_UPLOADED({ state }) {
|
|
||||||
state.uploadedNumber += 1
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 删除图片项
|
|
||||||
TO_UPLOAD_IMAGE_LIST_REMOVE({ state }, uuid: string) {
|
|
||||||
if (state.list.length > 0) {
|
|
||||||
const rmIndex = state.list.findIndex((v: ToUploadImageModel) => v.uuid === uuid)
|
|
||||||
if (rmIndex !== -1) {
|
|
||||||
state.list.splice(rmIndex, 1)
|
|
||||||
}
|
|
||||||
if (state.list.length === 0) {
|
|
||||||
state.curImgBase64Url = ''
|
|
||||||
state.uploadedNumber = 0
|
|
||||||
} else if (state.curImgUuid === uuid) {
|
|
||||||
const cur = state.list[0]
|
|
||||||
state.curImgBase64Url = cur.imgData.base64Url
|
|
||||||
state.curImgUuid = cur.uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 上传失败时,在列表中移除已上传的图片
|
|
||||||
TO_UPLOAD_IMAGE_LIST_FAIL({ state }) {
|
|
||||||
if (state.list.length > 0) {
|
|
||||||
const temp: ToUploadImageModel[] = state.list.filter(
|
|
||||||
(v: ToUploadImageModel) => v.uploadStatus.progress !== 100
|
|
||||||
)
|
|
||||||
if (temp.length > 0) {
|
|
||||||
state.list = temp
|
|
||||||
state.uploadedNumber = 0
|
|
||||||
state.curImgBase64Url = temp[0].imgData.base64Url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 清空 Url
|
|
||||||
TO_UPLOAD_IMAGE_CLEAN_URL({ state }) {
|
|
||||||
state.curImgBase64Url = ''
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 清空 List
|
|
||||||
TO_UPLOAD_IMAGE_CLEAN_LIST({ state }) {
|
|
||||||
state.list = []
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 清空上传完成数量
|
|
||||||
TO_UPLOAD_IMAGE_CLEAN_UPLOADED_NUMBER({ state }) {
|
|
||||||
state.uploadedNumber = 0
|
|
||||||
},
|
|
||||||
|
|
||||||
// 要上传的图片列表 - 退出登录
|
|
||||||
TO_UPLOAD_IMAGE_LOGOUT({ state }) {
|
|
||||||
state.curImgBase64Url = ''
|
|
||||||
state.list = []
|
|
||||||
state.uploadedNumber = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
getToUploadImageList: (state: ToUploadImageStateTypes) => state.list,
|
|
||||||
getToUploadImage: (state: ToUploadImageStateTypes) => state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default toUploadImageModule
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default interface ToUploadImageStateTypes {
|
|
||||||
curImgBase64Url: string
|
|
||||||
curImgUuid: string
|
|
||||||
list: any[]
|
|
||||||
uploadedNumber: number
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import UploadAreaActiveStateTypes from './types'
|
|
||||||
import RootStateTypes from '../../types'
|
|
||||||
|
|
||||||
const uploadAreaActiveModule: Module<UploadAreaActiveStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
uploadAreaActive: false
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
// 修改上传区域激活状态
|
|
||||||
CHANGE_UPLOAD_AREA_ACTIVE(state: UploadAreaActiveStateTypes, isActive: boolean) {
|
|
||||||
state.uploadAreaActive = isActive
|
|
||||||
},
|
|
||||||
|
|
||||||
UPLOAD_AREA_ACTIVE_LOGOUT(state: UploadAreaActiveStateTypes) {
|
|
||||||
state.uploadAreaActive = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {},
|
|
||||||
getters: {
|
|
||||||
getUploadAreaActive: (state: UploadAreaActiveStateTypes) => state.uploadAreaActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default uploadAreaActiveModule
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default interface UploadAreaActiveStateTypes {
|
|
||||||
uploadAreaActive: boolean
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import UploadAreaActiveStateTypes from './types'
|
|
||||||
import RootStateTypes from '../../types'
|
|
||||||
|
|
||||||
const uploadSettingsModule: Module<UploadAreaActiveStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
uploadSettings: {
|
|
||||||
isSetMaxSize: true,
|
|
||||||
imageMaxSize: 30 * 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
UPLOAD_SETTINGS_LOGOUT(state: UploadAreaActiveStateTypes) {
|
|
||||||
state.uploadSettings.isSetMaxSize = true
|
|
||||||
state.uploadSettings.imageMaxSize = 50 * 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {},
|
|
||||||
getters: {
|
|
||||||
getUploadSettings: (state) => state.uploadSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default uploadSettingsModule
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default interface UploadSettingsStateTypes {
|
|
||||||
uploadSettings: {
|
|
||||||
isSetMaxSize: boolean
|
|
||||||
imageMaxSize: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import UploadedImageListStateTypes from '@/store/modules/uploaded-image-list/types'
|
|
||||||
import RootStateTypes from '@/store/types'
|
|
||||||
import { PICX_UPLOADED } from '@/common/model/storage.model'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
const initUploadedImageList = (): UploadedImageModel[] => {
|
|
||||||
const imageList: string | null = sessionStorage.getItem(PICX_UPLOADED)
|
|
||||||
return imageList ? JSON.parse(imageList) : []
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadedImageListModule: Module<UploadedImageListStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
uploadedImageList: initUploadedImageList()
|
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 上传完成的图片列表 - 增加
|
|
||||||
UPLOADED_LIST_ADD({ state, dispatch }, item: UploadedImageModel) {
|
|
||||||
state.uploadedImageList.unshift(item)
|
|
||||||
dispatch('UPLOADED_LIST_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传完成的图片列表 - 删除
|
|
||||||
UPLOADED_LIST_REMOVE({ state, dispatch }, uuid: string) {
|
|
||||||
if (state.uploadedImageList.length > 0) {
|
|
||||||
const rmIndex = state.uploadedImageList.findIndex((v) => v.uuid === uuid)
|
|
||||||
if (rmIndex !== -1) {
|
|
||||||
state.uploadedImageList.splice(rmIndex, 1)
|
|
||||||
dispatch('UPLOADED_LIST_PERSIST')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传完成的图片列表 - 持久化
|
|
||||||
UPLOADED_LIST_PERSIST({ state }) {
|
|
||||||
sessionStorage.setItem(PICX_UPLOADED, JSON.stringify(state.uploadedImageList))
|
|
||||||
},
|
|
||||||
|
|
||||||
// 上传完成的图片列表 - 退出登录
|
|
||||||
UPLOADED_LIST_LOGOUT({ state }) {
|
|
||||||
state.uploadedImageList = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
getUploadedImageList: (state: any) => state.uploadedImageList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default uploadedImageListModule
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
export default interface UploadedImageListStateTypes {
|
|
||||||
uploadedImageList: UploadedImageModel[]
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import {
|
|
||||||
BranchModeEnum,
|
|
||||||
UserConfigInfoModel
|
|
||||||
} from '@/common/model/user-config-info.model'
|
|
||||||
import { PICX_CONFIG } from '@/common/model/storage.model'
|
|
||||||
import { deepAssignObject, cleanObject } from '@/utils/object-helper'
|
|
||||||
import UserConfigInfoStateTypes from '@/store/modules/user-config-info/types'
|
|
||||||
import RootStateTypes from '@/store/types'
|
|
||||||
import { DirModeEnum } from '@/common/model/dir.model'
|
|
||||||
import TimeHelper from '@/utils/time-helper'
|
|
||||||
|
|
||||||
const initUserConfigInfo = (): UserConfigInfoModel => {
|
|
||||||
const initConfig: UserConfigInfoModel = {
|
|
||||||
token: '',
|
|
||||||
owner: '',
|
|
||||||
email: '',
|
|
||||||
name: '',
|
|
||||||
avatarUrl: '',
|
|
||||||
selectedRepos: '',
|
|
||||||
reposList: [],
|
|
||||||
branchMode: BranchModeEnum.reposBranch,
|
|
||||||
branchList: [],
|
|
||||||
selectedBranch: '',
|
|
||||||
selectedDir: '',
|
|
||||||
dirMode: DirModeEnum.reposDir,
|
|
||||||
dirList: [],
|
|
||||||
loggingStatus: false,
|
|
||||||
selectedDirList: []
|
|
||||||
}
|
|
||||||
|
|
||||||
const LSConfig: string | null = localStorage.getItem(PICX_CONFIG)
|
|
||||||
|
|
||||||
if (LSConfig) {
|
|
||||||
// Assign: oldConfig -> initConfig
|
|
||||||
deepAssignObject(initConfig, JSON.parse(LSConfig))
|
|
||||||
|
|
||||||
if (initConfig.selectedBranch && !initConfig.branchList.length) {
|
|
||||||
initConfig.branchList = [
|
|
||||||
{
|
|
||||||
value: initConfig.selectedBranch,
|
|
||||||
label: initConfig.selectedBranch
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initConfig.dirMode === DirModeEnum.autoDir) {
|
|
||||||
initConfig.selectedDir = TimeHelper.getYyyyMmDd()
|
|
||||||
}
|
|
||||||
|
|
||||||
return initConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
return initConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
const userConfigInfoUpdate = (state: UserConfigInfoStateTypes): void => {
|
|
||||||
const { selectedDir, selectedBranch, dirMode } = state.userConfigInfo
|
|
||||||
if (dirMode === 'newDir') {
|
|
||||||
const strList = selectedDir.split('')
|
|
||||||
let count = 0
|
|
||||||
let newStr = ''
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0; i < strList.length; i++) {
|
|
||||||
if (strList[i] === ' ' || strList[i] === '.' || strList[i] === '、') {
|
|
||||||
strList[i] = '-'
|
|
||||||
}
|
|
||||||
if (strList[i] === '/') {
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
if (count >= 3) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
newStr += strList[i]
|
|
||||||
}
|
|
||||||
state.userConfigInfo.selectedDir = newStr
|
|
||||||
}
|
|
||||||
state.userConfigInfo.selectedBranch = selectedBranch.replace(/\s+/g, '-')
|
|
||||||
}
|
|
||||||
|
|
||||||
const userConfigInfoModule: Module<UserConfigInfoStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
userConfigInfo: initUserConfigInfo()
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 持久化状态获取
|
|
||||||
USER_CONFIG_INFO_RESET({ state }) {
|
|
||||||
state.userConfigInfo = initUserConfigInfo()
|
|
||||||
},
|
|
||||||
// 设置用户配置信息
|
|
||||||
SET_USER_CONFIG_INFO(
|
|
||||||
{ state, dispatch },
|
|
||||||
configInfo: UserConfigInfoStateTypes,
|
|
||||||
needPersist: boolean = true
|
|
||||||
) {
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const key in configInfo) {
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (state.userConfigInfo.hasOwnProperty(key)) {
|
|
||||||
// @ts-ignore
|
|
||||||
state.userConfigInfo[key] = configInfo[key]
|
|
||||||
} else if (key === 'needPersist') {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
needPersist = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!needPersist) return
|
|
||||||
dispatch('USER_CONFIG_INFO_PERSIST')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 用户配置信息 - 增加目录
|
|
||||||
USER_CONFIG_INFO_ADD_DIR({ state, dispatch }, dir: string) {
|
|
||||||
if (!state.userConfigInfo.dirList.some((v: any) => v.value === dir)) {
|
|
||||||
state.userConfigInfo.dirList.push({ label: dir, value: dir })
|
|
||||||
dispatch('USER_CONFIG_INFO_PERSIST')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 用户配置信息 - 删除目录列表的某个目录
|
|
||||||
USER_CONFIG_INFO_REMOVE_DIR({ state, dispatch }, dir: string) {
|
|
||||||
const { dirList } = state.userConfigInfo
|
|
||||||
if (dirList.some((v: any) => v.value === dir)) {
|
|
||||||
const rmIndex = dirList.findIndex((v: any) => v.value === dir)
|
|
||||||
dirList.splice(rmIndex, 1)
|
|
||||||
dispatch('USER_CONFIG_INFO_PERSIST')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 持久化用户配置信息
|
|
||||||
USER_CONFIG_INFO_PERSIST({ state }) {
|
|
||||||
userConfigInfoUpdate(state)
|
|
||||||
localStorage.setItem(PICX_CONFIG, JSON.stringify(state.userConfigInfo))
|
|
||||||
},
|
|
||||||
|
|
||||||
// 修改 userConfigInfo 但无需持久化 (目前提供图床管理页面使用)
|
|
||||||
USER_CONFIG_INFO_NOT_PERSIST({ state }) {
|
|
||||||
userConfigInfoUpdate(state)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
USER_CONFIG_INFO_LOGOUT({ state }) {
|
|
||||||
cleanObject(state.userConfigInfo)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
getUserLoggingStatus: (state: UserConfigInfoStateTypes): boolean =>
|
|
||||||
state.userConfigInfo.loggingStatus,
|
|
||||||
getUserConfigInfo: (state: UserConfigInfoStateTypes): UserConfigInfoModel =>
|
|
||||||
state.userConfigInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default userConfigInfoModule
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { UserConfigInfoModel } from '@/common/model/user-config-info.model'
|
|
||||||
|
|
||||||
export default interface UserConfigInfoStateTypes {
|
|
||||||
userConfigInfo: UserConfigInfoModel
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { Module } from 'vuex'
|
|
||||||
import { PICX_SETTINGS } from '@/common/model/storage.model'
|
|
||||||
import { deepAssignObject } from '@/utils/object-helper'
|
|
||||||
import UserConfigInfoStateTypes from '@/store/modules/user-config-info/types'
|
|
||||||
import RootStateTypes from '@/store/types'
|
|
||||||
import { CompressEncoderMap } from '@/utils/compress'
|
|
||||||
import { UserSettingsModel } from '@/common/model/user-settings.model'
|
|
||||||
import UserSettingsStateTypes from '@/store/modules/user-settings/types'
|
|
||||||
import { getLocalItem } from '@/utils/common-utils'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
|
|
||||||
const initSettings: UserSettingsModel = {
|
|
||||||
defaultHash: true,
|
|
||||||
defaultMarkdown: false,
|
|
||||||
defaultPrefix: false,
|
|
||||||
prefixName: '',
|
|
||||||
isCompress: true,
|
|
||||||
compressEncoder: CompressEncoderMap.webP,
|
|
||||||
themeMode: 'light',
|
|
||||||
autoLightThemeTime: ['08:00', '19:00'],
|
|
||||||
elementPlusSize: 'default',
|
|
||||||
externalLinkType: ExternalLinkType.staticaly
|
|
||||||
}
|
|
||||||
|
|
||||||
const initUserSettings = (): UserSettingsModel => {
|
|
||||||
const LSSettings = getLocalItem(PICX_SETTINGS)
|
|
||||||
if (LSSettings) {
|
|
||||||
deepAssignObject(initSettings, LSSettings)
|
|
||||||
}
|
|
||||||
return initSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSettingsModule: Module<UserSettingsStateTypes, RootStateTypes> = {
|
|
||||||
state: {
|
|
||||||
userSettings: initUserSettings()
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 设置
|
|
||||||
SET_USER_SETTINGS({ state }, configInfo: UserConfigInfoStateTypes) {
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const key in configInfo) {
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (state.userSettings.hasOwnProperty(key)) {
|
|
||||||
// @ts-ignore
|
|
||||||
state.userSettings[key] = configInfo[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 持久化
|
|
||||||
USER_SETTINGS_PERSIST({ state }) {
|
|
||||||
localStorage.setItem(PICX_SETTINGS, JSON.stringify(state.userSettings))
|
|
||||||
},
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
USER_SETTINGS_LOGOUT({ state }) {
|
|
||||||
state.userSettings = initSettings
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
getUserSettings: (state): UserSettingsModel => state.userSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default userSettingsModule
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { UserSettingsModel } from '@/common/model/user-settings.model'
|
|
||||||
|
|
||||||
export default interface UserSettingsStateTypes {
|
|
||||||
userSettings: UserSettingsModel
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import DirImageListStateTypes from './modules/dir-image-list/types'
|
|
||||||
import ToUploadImageStateTypes from './modules/to-upload-image/types'
|
|
||||||
import UploadedImageListStateTypes from './modules/uploaded-image-list/types'
|
|
||||||
import UserConfigInfoStateTypes from './modules/user-config-info/types'
|
|
||||||
import ImageViewerStateTypes from './modules/image-viewer/types'
|
|
||||||
import UploadAreaActiveStateTypes from './modules/upload-area-active/types'
|
|
||||||
import UploadSettingsStateTypes from './modules/upload-settings/types'
|
|
||||||
|
|
||||||
export default interface RootStateTypes {
|
|
||||||
rootName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AllStateTypes extends RootStateTypes {
|
|
||||||
dirImageListModule: DirImageListStateTypes
|
|
||||||
toUploadImageModule: ToUploadImageStateTypes
|
|
||||||
uploadedImageListModule: UploadedImageListStateTypes
|
|
||||||
userConfigInfoModule: UserConfigInfoStateTypes
|
|
||||||
imageViewerModule: ImageViewerStateTypes
|
|
||||||
uploadAreaActiveModule: UploadAreaActiveStateTypes
|
|
||||||
uploadSettingsModule: UploadSettingsStateTypes
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
@import './theme.styl'
|
|
||||||
@import './variables.styl'
|
|
||||||
|
|
||||||
$component-interval = 16rem
|
|
||||||
$box-border-radius = 6rem
|
|
||||||
$content-max-width = 888rem
|
|
||||||
$scrollbar-size = 8rem
|
|
||||||
|
|
||||||
:root {
|
|
||||||
font-size 1px
|
|
||||||
|
|
||||||
+picx-tablet() {
|
|
||||||
font-size 0.9px
|
|
||||||
}
|
|
||||||
|
|
||||||
+picx-mobile() {
|
|
||||||
font-size 0.8px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
position relative
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
color var(--default-text-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration none
|
|
||||||
font-size 1.5rem
|
|
||||||
color var(--default-text-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
a:link {
|
|
||||||
color var(--default-text-color)
|
|
||||||
text-decoration none
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, ol, li {
|
|
||||||
list-style none
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height $scrollbar-size
|
|
||||||
width $scrollbar-size
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background var(--scrollbar-color)
|
|
||||||
border-radius $box-border-radius
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background transparent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.flex-center {
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.flex-start {
|
|
||||||
display flex
|
|
||||||
justify-content flex-start
|
|
||||||
align-items center
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.page-container {
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
padding 30rem
|
|
||||||
background var(--background-color)
|
|
||||||
border-top-left-radius $box-border-radius
|
|
||||||
border-top-right-radius $box-border-radius
|
|
||||||
overflow-y auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
&::after {
|
|
||||||
content ''
|
|
||||||
display block
|
|
||||||
clear both
|
|
||||||
visibility hidden
|
|
||||||
overflow hidden
|
|
||||||
height 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
@import './variables.styl'
|
|
||||||
|
|
||||||
:root {
|
|
||||||
root-color('light')
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme light) {
|
|
||||||
:root {
|
|
||||||
root-color('light')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme dark) {
|
|
||||||
:root {
|
|
||||||
root-color('dark')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.light {
|
|
||||||
root-color('light')
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
root-color('dark')
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
// ========================================================================================
|
|
||||||
// media query
|
|
||||||
// ========================================================================================
|
|
||||||
$media-max-width-tablet = 800px; // media query max width (tablet)
|
|
||||||
$media-max-width-mobile = 500px; // media query max width (mobile)
|
|
||||||
|
|
||||||
picx-tablet()
|
|
||||||
@media (max-width: $media-max-width-tablet)
|
|
||||||
{ block }
|
|
||||||
|
|
||||||
picx-mobile()
|
|
||||||
@media (max-width: $media-max-width-mobile)
|
|
||||||
{ block }
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================================
|
|
||||||
// z-index
|
|
||||||
// ========================================================================================
|
|
||||||
$z-index-1 = 1001;
|
|
||||||
$z-index-2 = 1002;
|
|
||||||
$z-index-3 = 1003;
|
|
||||||
$z-index-4 = 1004;
|
|
||||||
$z-index-5 = 1005;
|
|
||||||
$z-index-6 = 1006;
|
|
||||||
$z-index-7 = 1007;
|
|
||||||
$z-index-8 = 1008;
|
|
||||||
$z-index-9 = 1009;
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================================
|
|
||||||
// light mode color
|
|
||||||
// ========================================================================================
|
|
||||||
$primary-color = #0066CC;
|
|
||||||
$background-color = #fff;
|
|
||||||
$second-background-color = darken($background-color, 5%);
|
|
||||||
$third-background-color = darken($background-color, 10%);
|
|
||||||
$default-text-color = #50505c;
|
|
||||||
$first-text-color = darken($default-text-color, 10%);
|
|
||||||
$second-text-color = darken($default-text-color, 5%);
|
|
||||||
$third-text-color = lighten($default-text-color, 30%);
|
|
||||||
$fourth-text-color = lighten($default-text-color, 90%);
|
|
||||||
$border-color = darken($background-color, 30%);
|
|
||||||
$selection-color = lighten($primary-color, 10%);
|
|
||||||
$shadow-color = rgba(0, 0, 0, 0.2);
|
|
||||||
$shadow-hover-color = rgba(0, 0, 0, 0.28);
|
|
||||||
$scrollbar-color = darken($background-color, 20%);
|
|
||||||
$scroll-bar-bg-color = darken($background-color, 30%);
|
|
||||||
$upload-area-focus-color = #0066CC;
|
|
||||||
$await-upload-color = #E6A23C;
|
|
||||||
$uploading-color = #409EFF;
|
|
||||||
$uploaded-color = #67C23A;
|
|
||||||
$markdown-icon-color = #808080;
|
|
||||||
$markdown-icon-active-color = darken($markdown-icon-color, 30%);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================================
|
|
||||||
// dark mode color
|
|
||||||
// ========================================================================================
|
|
||||||
$dark-primary-color = #0066CC;
|
|
||||||
$dark-background-color = #2a2a2f;
|
|
||||||
$dark-second-background-color = darken($dark-background-color, 10%);
|
|
||||||
$dark-third-background-color = darken($dark-background-color, 15%);
|
|
||||||
$dark-default-text-color = #bebec6;
|
|
||||||
$dark-first-text-color = lighten($dark-default-text-color, 30%);
|
|
||||||
$dark-second-text-color = lighten($dark-default-text-color, 20%);
|
|
||||||
$dark-third-text-color = darken($dark-default-text-color, 20%);
|
|
||||||
$dark-fourth-text-color = darken($dark-default-text-color, 80%);
|
|
||||||
$dark-border-color = lighten($dark-background-color, 20%);
|
|
||||||
$dark-selection-color = $selection-color;
|
|
||||||
$dark-shadow-color = rgba(128, 128, 128, 0.2);
|
|
||||||
$dark-shadow-hover-color = rgba(128, 128, 128, 0.28);
|
|
||||||
$dark-scrollbar-color = darken($dark-background-color, 20%);
|
|
||||||
$dark-scroll-bar-bg-color = lighten($dark-background-color, 30%);
|
|
||||||
$dark-upload-area-focus-color = #1070d0;
|
|
||||||
$dark-await-upload-color = #c08327;
|
|
||||||
$dark-uploading-color = #287dd5;
|
|
||||||
$dark-uploaded-color = #55b626;
|
|
||||||
$dark-markdown-icon-color = #aaa;
|
|
||||||
$dark-markdown-icon-active-color = lighten($dark-markdown-icon-color, 30%);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// light/dark mode color
|
|
||||||
// ========================================================================
|
|
||||||
root-color(mode) {
|
|
||||||
--background-color: mode == 'light' ? $background-color : $dark-background-color;
|
|
||||||
--second-background-color: mode == 'light' ? $second-background-color : $dark-second-background-color;
|
|
||||||
--third-background-color: mode == 'light' ? $third-background-color : $dark-third-background-color;
|
|
||||||
--primary-color: mode == 'light' ? $primary-color : $dark-primary-color;
|
|
||||||
--first-text-color: mode == 'light' ? $first-text-color : $dark-first-text-color;
|
|
||||||
--second-text-color: mode == 'light' ? $second-text-color : $dark-second-text-color;
|
|
||||||
--third-text-color: mode == 'light' ? $third-text-color : $dark-third-text-color;
|
|
||||||
--fourth-text-color: mode == 'light' ? $fourth-text-color : $dark-fourth-text-color;
|
|
||||||
--default-text-color: mode == 'light' ? $default-text-color : $dark-default-text-color;
|
|
||||||
--border-color: mode == 'light' ? $border-color : $dark-border-color;
|
|
||||||
--selection-color: mode == 'light' ? $selection-color : $dark-selection-color;
|
|
||||||
--shadow-color: mode == 'light' ? $shadow-color : $dark-shadow-color;
|
|
||||||
--shadow-hover-color: mode == 'light' ? $shadow-hover-color : $dark-shadow-hover-color;
|
|
||||||
--scrollbar-color: mode == 'light' ? $scrollbar-color : $dark-scrollbar-color;
|
|
||||||
--scroll-bar-bg-color: mode == 'light' ? $scroll-bar-bg-color : $dark-scroll-bar-bg-color;
|
|
||||||
--upload-area-focus-color : mode == 'light' ? $upload-area-focus-color : $dark-upload-area-focus-color;
|
|
||||||
--await-upload-color : mode == 'light' ? $await-upload-color : $dark-await-upload-color;
|
|
||||||
--uploading-color : mode == 'light' ? $uploading-color : $dark-uploading-color;
|
|
||||||
--uploaded-color : mode == 'light' ? $uploaded-color : $dark-uploaded-color;
|
|
||||||
--markdown-icon-color : mode == 'light' ? $markdown-icon-color : $dark-markdown-icon-color;
|
|
||||||
--markdown-icon-active-color : mode == 'light' ? $markdown-icon-active-color : $dark-markdown-icon-active-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import Axios from 'axios'
|
|
||||||
import { PICX_CONFIG } from '@/common/model/storage.model'
|
|
||||||
|
|
||||||
const baseURL = 'https://api.github.com'
|
|
||||||
|
|
||||||
const axios = Axios.create({
|
|
||||||
baseURL,
|
|
||||||
timeout: 300000 // request timeout 请求超时 5m
|
|
||||||
})
|
|
||||||
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json'
|
|
||||||
|
|
||||||
// 发起请求之前的拦截器(前置拦截)
|
|
||||||
axios.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
const userConfig = localStorage.getItem(PICX_CONFIG)
|
|
||||||
|
|
||||||
if (userConfig) {
|
|
||||||
const { token } = JSON.parse(userConfig)
|
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = `token ${token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 响应拦截器
|
|
||||||
axios.interceptors.response.use(
|
|
||||||
(response) => {
|
|
||||||
return response
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
if (error.response && error.response.data) {
|
|
||||||
const code = error.response.status
|
|
||||||
const msg = error.response.data.message
|
|
||||||
ElMessage.error(`Code: ${code}, Message: ${msg}`)
|
|
||||||
console.error(`[PicX Error]`, error.response)
|
|
||||||
} else {
|
|
||||||
ElMessage.error(`${error}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.response
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default axios
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Get JavaScript basic data types
|
|
||||||
* @param data
|
|
||||||
* @returns {string} array | string | number ...
|
|
||||||
*/
|
|
||||||
export const getType = (data: string) => {
|
|
||||||
const type = Object.prototype.toString.call(data).split(' ')[1]
|
|
||||||
return type.substring(0, type.length - 1).toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a string(uuid) that is not repeated
|
|
||||||
* @returns uuid {string}
|
|
||||||
*/
|
|
||||||
export const getUuid = () => {
|
|
||||||
return Number(Math.random().toString().substr(2, 5) + Date.now()).toString(36)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get localStorage value
|
|
||||||
* @param key
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
export const getLocalItem = (key: string) => {
|
|
||||||
const temp = window.localStorage.getItem(key)
|
|
||||||
return temp ? JSON.parse(temp) : null
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import Compress from '@yireen/squoosh-browser'
|
|
||||||
import {
|
|
||||||
defaultPreprocessorState,
|
|
||||||
defaultProcessorState,
|
|
||||||
encoderMap,
|
|
||||||
EncoderState
|
|
||||||
} from '@yireen/squoosh-browser/dist/client/lazy-app/feature-meta'
|
|
||||||
|
|
||||||
export enum CompressEncoderMap {
|
|
||||||
mozJPEG = 'mozJPEG',
|
|
||||||
avif = 'avif',
|
|
||||||
webP = 'webP'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const compress = async (file: File, encoder: CompressEncoderMap) => {
|
|
||||||
const compress = new Compress(file, {
|
|
||||||
encoderState: {
|
|
||||||
type: encoder,
|
|
||||||
options: encoderMap[encoder].meta.defaultOptions
|
|
||||||
} as EncoderState,
|
|
||||||
processorState: defaultProcessorState,
|
|
||||||
preprocessorState: defaultPreprocessorState
|
|
||||||
})
|
|
||||||
|
|
||||||
return compress.process()
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { ToUploadImageModel } from '../common/model/upload.model'
|
|
||||||
|
|
||||||
export default (): ToUploadImageModel => {
|
|
||||||
return {
|
|
||||||
uuid: '',
|
|
||||||
|
|
||||||
uploadStatus: {
|
|
||||||
progress: 0,
|
|
||||||
uploading: false
|
|
||||||
},
|
|
||||||
|
|
||||||
imgData: {
|
|
||||||
base64Content: '',
|
|
||||||
base64Url: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
fileInfo: {
|
|
||||||
size: 0,
|
|
||||||
lastModified: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
filename: {
|
|
||||||
name: '',
|
|
||||||
hash: '',
|
|
||||||
suffix: '',
|
|
||||||
prefixName: '',
|
|
||||||
now: '',
|
|
||||||
initName: '',
|
|
||||||
newName: 'xxx',
|
|
||||||
isHashRename: true,
|
|
||||||
isRename: false,
|
|
||||||
isPrefix: false
|
|
||||||
},
|
|
||||||
|
|
||||||
externalLink: {
|
|
||||||
github: '',
|
|
||||||
jsdelivr: '',
|
|
||||||
staticaly: '',
|
|
||||||
cloudflare: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { UploadedImageModel } from '../common/model/upload.model'
|
|
||||||
import { UserConfigInfoModel } from '../common/model/user-config-info.model'
|
|
||||||
import axios from '@/utils/axios'
|
|
||||||
import { deleteStatusEnum } from '../common/model/delete.model'
|
|
||||||
import { store } from '@/store'
|
|
||||||
|
|
||||||
let deleteIndex = 0
|
|
||||||
|
|
||||||
export async function deleteSingleImage(
|
|
||||||
imageObj: UploadedImageModel,
|
|
||||||
userConfigInfo: UserConfigInfoModel
|
|
||||||
): Promise<boolean> {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = true
|
|
||||||
const { owner, selectedRepos } = userConfigInfo
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.delete(`/repos/${owner}/${selectedRepos}/contents/${imageObj.path}`, {
|
|
||||||
data: {
|
|
||||||
owner,
|
|
||||||
repo: selectedRepos,
|
|
||||||
path: imageObj.path,
|
|
||||||
message: 'delete picture via PicX(https://github.com/XPoet/picx)',
|
|
||||||
sha: imageObj.sha
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = false
|
|
||||||
store.dispatch('UPLOADED_LIST_REMOVE', imageObj.uuid)
|
|
||||||
store.dispatch('DIR_IMAGE_LIST_REMOVE', imageObj)
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageObj.deleting = false
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function delelteBatchImage(
|
|
||||||
imgCardArr: Array<UploadedImageModel>,
|
|
||||||
userConfigInfo: UserConfigInfoModel
|
|
||||||
) {
|
|
||||||
if (deleteIndex >= imgCardArr.length) {
|
|
||||||
return deleteStatusEnum.deleted
|
|
||||||
}
|
|
||||||
if (await deleteSingleImage(imgCardArr[deleteIndex], userConfigInfo)) {
|
|
||||||
if (deleteIndex < imgCardArr.length) {
|
|
||||||
deleteIndex += 1
|
|
||||||
if (await delelteBatchImage(imgCardArr, userConfigInfo)) {
|
|
||||||
deleteIndex = 0
|
|
||||||
return deleteStatusEnum.allDeleted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deleteStatusEnum.deleted
|
|
||||||
}
|
|
||||||
return deleteStatusEnum.deleteFail
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* eslint-disable no-restricted-syntax */
|
|
||||||
import { Recordable, ViteEnv } from '@/common/model/vite-config.model'
|
|
||||||
|
|
||||||
// Read all environment variable configuration files to process.env
|
|
||||||
export default function wrapperEnv(envConf: Recordable): ViteEnv {
|
|
||||||
const ret: any = {}
|
|
||||||
|
|
||||||
for (const envName of Object.keys(envConf)) {
|
|
||||||
let realName = envConf[envName].replace(/\\n/g, '\n')
|
|
||||||
if (realName === 'true') {
|
|
||||||
realName = true
|
|
||||||
} else if (realName === 'false') {
|
|
||||||
realName = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envName === 'VITE_PORT') {
|
|
||||||
realName = Number(realName)
|
|
||||||
}
|
|
||||||
ret[envName] = realName
|
|
||||||
process.env[envName] = realName
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
import { UserConfigInfoModel } from '@/common/model/user-config-info.model'
|
|
||||||
import { getFilename } from '@/utils/file-handle-helper'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建承载图片外链文本的 DOM 元素
|
|
||||||
*/
|
|
||||||
export const createExternalLinkDom = () => {
|
|
||||||
let externalLinkDom: any = document.querySelector('.temp-external-link-txt')
|
|
||||||
if (!externalLinkDom) {
|
|
||||||
externalLinkDom = document.createElement('textarea')
|
|
||||||
externalLinkDom.setAttribute('class', 'temp-external-link-txt')
|
|
||||||
externalLinkDom.style.position = 'absolute'
|
|
||||||
externalLinkDom.style.top = '-99999rem'
|
|
||||||
externalLinkDom.style.left = '-99999rem'
|
|
||||||
document.body.appendChild(externalLinkDom)
|
|
||||||
}
|
|
||||||
return externalLinkDom
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成图片外链
|
|
||||||
* @param type
|
|
||||||
* @param content
|
|
||||||
* @param config
|
|
||||||
*/
|
|
||||||
export const generateExternalLink = (
|
|
||||||
type: ExternalLinkType,
|
|
||||||
content: any,
|
|
||||||
config: UserConfigInfoModel
|
|
||||||
): string => {
|
|
||||||
const staticalyLink: string = `https://cdn.staticaly.com/gh/${config.owner}/${config.selectedRepos}@${config.selectedBranch}/${content.path}`
|
|
||||||
const cloudflareLink: string = `https://git.poker/${config.owner}/${config.selectedRepos}/blob/${config.selectedBranch}/${content.path}?raw=true`
|
|
||||||
const jsdelivrLink: string = `https://cdn.jsdelivr.net/gh/${config.owner}/${config.selectedRepos}@${config.selectedBranch}/${content.path}`
|
|
||||||
const githubLink: string = decodeURI(content.download_url)
|
|
||||||
|
|
||||||
// eslint-disable-next-line default-case
|
|
||||||
switch (type) {
|
|
||||||
case ExternalLinkType.staticaly:
|
|
||||||
return staticalyLink
|
|
||||||
|
|
||||||
case ExternalLinkType.cloudflare:
|
|
||||||
return cloudflareLink
|
|
||||||
|
|
||||||
case ExternalLinkType.jsdelivr:
|
|
||||||
return jsdelivrLink
|
|
||||||
|
|
||||||
case ExternalLinkType.github:
|
|
||||||
return githubLink
|
|
||||||
|
|
||||||
default:
|
|
||||||
return githubLink
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片外链转换为 Markdown 格式
|
|
||||||
* @param name 图片名
|
|
||||||
* @param url 图片外链
|
|
||||||
*/
|
|
||||||
export const formatMarkdown = (name: string, url: string): string => {
|
|
||||||
return ``
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复制图片外链
|
|
||||||
* @param img 图片对象
|
|
||||||
* @param type CDN 类型
|
|
||||||
*/
|
|
||||||
export const copyExternalLink = (img: UploadedImageModel, type: ExternalLinkType) => {
|
|
||||||
let externalLink = ''
|
|
||||||
let successInfo = ''
|
|
||||||
const { name, is_transform_md: isMD } = img
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ExternalLinkType.jsdelivr:
|
|
||||||
if (isMD) {
|
|
||||||
externalLink = formatMarkdown(name, img.jsdelivr_cdn_url)
|
|
||||||
successInfo = 'Markdown 格式的 jsDelivr CDN'
|
|
||||||
} else {
|
|
||||||
externalLink = img.jsdelivr_cdn_url
|
|
||||||
successInfo = 'jsDelivr CDN'
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case ExternalLinkType.staticaly:
|
|
||||||
if (isMD) {
|
|
||||||
externalLink = formatMarkdown(name, img.staticaly_cdn_url)
|
|
||||||
successInfo = 'Markdown 格式的 Staticaly CDN'
|
|
||||||
} else {
|
|
||||||
externalLink = img.staticaly_cdn_url
|
|
||||||
successInfo = 'Staticaly CDN'
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case ExternalLinkType.cloudflare:
|
|
||||||
if (isMD) {
|
|
||||||
externalLink = formatMarkdown(name, img.cloudflare_cdn_url)
|
|
||||||
successInfo = 'Markdown 格式的 Cloudflare CDN'
|
|
||||||
} else {
|
|
||||||
externalLink = img.cloudflare_cdn_url
|
|
||||||
successInfo = 'Cloudflare CDN'
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (isMD) {
|
|
||||||
externalLink = formatMarkdown(name, img.github_url)
|
|
||||||
successInfo = 'Markdown 格式的 GitHub'
|
|
||||||
} else {
|
|
||||||
externalLink = img.github_url
|
|
||||||
successInfo = 'GitHub'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const externalLinkDom: any = createExternalLinkDom()
|
|
||||||
|
|
||||||
externalLinkDom.value = externalLink
|
|
||||||
externalLinkDom.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
ElMessage.success(`${successInfo} 外链复制成功!`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量复制图片外链
|
|
||||||
* @param imgCardList 图片列表
|
|
||||||
* @param type 当前选择的外链类型
|
|
||||||
*/
|
|
||||||
export const batchCopyExternalLink = (
|
|
||||||
imgCardList: Array<UploadedImageModel>,
|
|
||||||
type: ExternalLinkType
|
|
||||||
) => {
|
|
||||||
let externalLink = ''
|
|
||||||
const externalLinkDom: any = createExternalLinkDom()
|
|
||||||
externalLinkDom.value = ''
|
|
||||||
if (imgCardList?.length > 0) {
|
|
||||||
imgCardList.forEach((item: UploadedImageModel, index) => {
|
|
||||||
const isMD = item.is_transform_md
|
|
||||||
switch (type) {
|
|
||||||
case ExternalLinkType.jsdelivr:
|
|
||||||
externalLink = isMD
|
|
||||||
? formatMarkdown(item.name, item.jsdelivr_cdn_url)
|
|
||||||
: item.jsdelivr_cdn_url
|
|
||||||
break
|
|
||||||
|
|
||||||
case ExternalLinkType.staticaly:
|
|
||||||
externalLink = isMD
|
|
||||||
? formatMarkdown(item.name, item.staticaly_cdn_url)
|
|
||||||
: item.staticaly_cdn_url
|
|
||||||
break
|
|
||||||
|
|
||||||
case ExternalLinkType.cloudflare:
|
|
||||||
externalLink = isMD
|
|
||||||
? formatMarkdown(item.name, item.cloudflare_cdn_url)
|
|
||||||
: item.cloudflare_cdn_url
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
externalLink = isMD
|
|
||||||
? formatMarkdown(item.name, item.github_url)
|
|
||||||
: item.github_url
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < imgCardList.length - 1) {
|
|
||||||
// eslint-disable-next-line prefer-template
|
|
||||||
externalLinkDom.value += externalLink + '\n'
|
|
||||||
} else {
|
|
||||||
externalLinkDom.value += externalLink
|
|
||||||
}
|
|
||||||
})
|
|
||||||
externalLinkDom.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
ElMessage.success(`批量复制图片链接成功`)
|
|
||||||
} else {
|
|
||||||
console.warn('请先选择图片')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { getUuid } from './common-utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get filename
|
|
||||||
* @param filename
|
|
||||||
*/
|
|
||||||
export const getFilename = (filename: string) => {
|
|
||||||
const splitIndex = filename.indexOf('.')
|
|
||||||
return filename.substr(0, splitIndex).trim().replace(/\s+/g, '-')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get filename suffix
|
|
||||||
* @param filename
|
|
||||||
*/
|
|
||||||
export const getFileSuffix = (filename: string) => {
|
|
||||||
const splitIndex = filename.lastIndexOf('.')
|
|
||||||
return filename.substr(splitIndex + 1, filename.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isImage = (suffix: string): boolean => {
|
|
||||||
return /(png|jpg|gif|jpeg|webp|avif|svg\+xml|image\/x-icon)$/.test(suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get file size (KB)
|
|
||||||
* @param size
|
|
||||||
*/
|
|
||||||
export const getFileSize = (size: number) => {
|
|
||||||
return Number((size / 1024).toFixed(2))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* filename handle
|
|
||||||
* @param filename
|
|
||||||
*/
|
|
||||||
export const filenameHandle = (filename: string | undefined) => {
|
|
||||||
if (filename) {
|
|
||||||
return {
|
|
||||||
name: getFilename(filename),
|
|
||||||
hash: getUuid(),
|
|
||||||
suffix: getFileSuffix(filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: '',
|
|
||||||
hash: '',
|
|
||||||
suffix: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { computed } from 'vue'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
import { getUuid } from '@/utils/common-utils'
|
|
||||||
import { generateExternalLink } from '@/utils/external-link-handler'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
import { store } from '@/store'
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
|
|
||||||
export default function structureImageObject(
|
|
||||||
item: any,
|
|
||||||
selectedDir: string
|
|
||||||
): UploadedImageModel {
|
|
||||||
return {
|
|
||||||
type: 'image',
|
|
||||||
uuid: getUuid(),
|
|
||||||
dir: selectedDir,
|
|
||||||
name: item.name,
|
|
||||||
path: item.path,
|
|
||||||
sha: item.sha,
|
|
||||||
deleting: false,
|
|
||||||
is_transform_md: false,
|
|
||||||
size: item.size,
|
|
||||||
checked: false,
|
|
||||||
github_url: generateExternalLink(ExternalLinkType.github, item, userConfigInfo),
|
|
||||||
jsdelivr_cdn_url: generateExternalLink(
|
|
||||||
ExternalLinkType.jsdelivr,
|
|
||||||
item,
|
|
||||||
userConfigInfo
|
|
||||||
),
|
|
||||||
staticaly_cdn_url: generateExternalLink(
|
|
||||||
ExternalLinkType.staticaly,
|
|
||||||
item,
|
|
||||||
userConfigInfo
|
|
||||||
),
|
|
||||||
cloudflare_cdn_url: generateExternalLink(
|
|
||||||
ExternalLinkType.cloudflare,
|
|
||||||
item,
|
|
||||||
userConfigInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { getType } from './common-utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据 object 每个 key 上值的数据类型,赋对应的初始值
|
|
||||||
* @param object
|
|
||||||
*/
|
|
||||||
export const cleanObject = (object: any) => {
|
|
||||||
// eslint-disable-next-line guard-for-in,no-restricted-syntax
|
|
||||||
for (const key in object) {
|
|
||||||
// eslint-disable-next-line default-case
|
|
||||||
switch (getType(object[key])) {
|
|
||||||
case 'object':
|
|
||||||
cleanObject(object[key])
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'string':
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
object[key] = ''
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'array':
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
object[key] = []
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'number':
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
object[key] = 0
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'boolean':
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
object[key] = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 obj2 对象的值深度赋值给 obj1 对象
|
|
||||||
* @param obj1{Object}
|
|
||||||
* @param obj2{Object}
|
|
||||||
*/
|
|
||||||
export const deepAssignObject = (obj1: object, obj2: object): any => {
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const key in obj2) {
|
|
||||||
// @ts-ignore
|
|
||||||
if (getType(obj2[key]) !== 'object') {
|
|
||||||
if (obj1) {
|
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
obj1[key] = obj2[key]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
deepAssignObject(obj1[key], obj2[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import selectedFileHandle from './selected-file-handle'
|
|
||||||
|
|
||||||
const onPaste = (e: any, maxsize: number): Promise<any> | null => {
|
|
||||||
if (!(e.clipboardData && e.clipboardData.items)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0, len = e.clipboardData.items.length; i < len; i++) {
|
|
||||||
const item = e.clipboardData.items[i]
|
|
||||||
if (item.kind === 'file') {
|
|
||||||
const pasteFile = item.getAsFile()
|
|
||||||
|
|
||||||
selectedFileHandle(pasteFile, maxsize)?.then((result) => {
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { base64, originalFile, compressFile } = result
|
|
||||||
resolve({ base64, originalFile, compressFile })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default onPaste
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { registerSW } from 'virtual:pwa-register'
|
|
||||||
|
|
||||||
registerSW()
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { computed } from 'vue'
|
|
||||||
import { store } from '@/store'
|
|
||||||
import createToUploadImageObject from '@/utils/create-to-upload-image'
|
|
||||||
import { filenameHandle } from './file-handle-helper'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据图片链接获取图片 base64 编码
|
|
||||||
* @param url 图片路径
|
|
||||||
* @param ext 图片格式
|
|
||||||
*/
|
|
||||||
export function getBase64ByImageUrl(url: string, ext: string): Promise<string | null> {
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
const img = new Image()
|
|
||||||
img.crossOrigin = 'Anonymous'
|
|
||||||
img.src = url
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
img.onload = () => {
|
|
||||||
const { width } = img
|
|
||||||
const { height } = img
|
|
||||||
canvas.width = width // 指定画板的高度,自定义
|
|
||||||
canvas.height = height // 指定画板的宽度,自定义
|
|
||||||
ctx?.drawImage(img, 0, 0, width, height) // 参数可自定义
|
|
||||||
const dataURL: string = canvas.toDataURL(`image/${ext}`)
|
|
||||||
resolve(dataURL)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取图片对象
|
|
||||||
export function getImage(base64Data: string, file: any) {
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
const curImg = createToUploadImageObject()
|
|
||||||
|
|
||||||
curImg.imgData.base64Url = base64Data
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
curImg.imgData.base64Content = base64Data.split(',')[1]
|
|
||||||
|
|
||||||
const { name, hash, suffix } = filenameHandle(file.name)
|
|
||||||
curImg.uuid = hash
|
|
||||||
|
|
||||||
curImg.fileInfo.size = file.size
|
|
||||||
curImg.fileInfo.originSize = file.size
|
|
||||||
curImg.fileInfo.lastModified = file.lastModified
|
|
||||||
|
|
||||||
curImg.filename.name = name
|
|
||||||
curImg.filename.hash = hash
|
|
||||||
curImg.filename.suffix = suffix
|
|
||||||
curImg.filename.now = userSettings.defaultHash
|
|
||||||
? `${name}.${hash}.${suffix}`
|
|
||||||
: `${name}.${suffix}`
|
|
||||||
curImg.filename.initName = name
|
|
||||||
curImg.filename.isHashRename = userSettings.defaultHash
|
|
||||||
|
|
||||||
return curImg
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { store } from '@/store'
|
|
||||||
import { compress } from './compress'
|
|
||||||
import { getFileSize, isImage } from './file-handle-helper'
|
|
||||||
|
|
||||||
export type handleResult = { base64: string; originalFile: File; compressFile?: File }
|
|
||||||
|
|
||||||
const selectedFileHandle = async (
|
|
||||||
file: File,
|
|
||||||
maxsize: number
|
|
||||||
): Promise<handleResult | null> => {
|
|
||||||
if (!file) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isImage(file.type)) {
|
|
||||||
ElMessage.error('该文件格式不支持!')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let compressFile: NonNullable<File>
|
|
||||||
const { isCompress, compressEncoder } = store.getters.getUserSettings
|
|
||||||
const isGif = file.type === 'image/gif'
|
|
||||||
if (!isGif && isCompress) {
|
|
||||||
const loadingInstance = ElLoading.service({
|
|
||||||
target: '.upload-area',
|
|
||||||
text: '正在压缩图片'
|
|
||||||
})
|
|
||||||
compressFile = await compress(file, compressEncoder)
|
|
||||||
loadingInstance.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.readAsDataURL(!isGif && isCompress ? compressFile : file)
|
|
||||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
|
||||||
const base64: any = e.target?.result
|
|
||||||
const curImgSize = getFileSize(base64.length)
|
|
||||||
|
|
||||||
if (curImgSize >= maxsize) {
|
|
||||||
// 给出提示,引导用户自行去压缩图片
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`当前图片 ${(curImgSize / 1024).toFixed(
|
|
||||||
2
|
|
||||||
)} M,CDN 只能加速小于 50 MB 的图片,建议使用第三方工具 TinyPNG 压缩`,
|
|
||||||
'图片过大,禁止上传',
|
|
||||||
{
|
|
||||||
confirmButtonText: '前往 TinyPNG',
|
|
||||||
cancelButtonText: '放弃上传'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
window.open('https://tinypng.com/')
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('放弃上传')
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
resolve({
|
|
||||||
base64,
|
|
||||||
originalFile: file,
|
|
||||||
compressFile: !isGif && isCompress ? compressFile : file
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default selectedFileHandle
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { watch, nextTick } from 'vue'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { UserSettingsModel } from '@/common/model/user-settings.model'
|
|
||||||
|
|
||||||
const setThemeMode = () => {
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const setBodyClassName = async (theme: 'dark' | 'light') => {
|
|
||||||
await nextTick(() => {
|
|
||||||
const body = document.getElementsByTagName('html')[0]
|
|
||||||
if (theme === 'dark') {
|
|
||||||
body.classList.remove('light')
|
|
||||||
body.classList.add('dark')
|
|
||||||
} else {
|
|
||||||
body.classList.remove('dark')
|
|
||||||
body.classList.add('light')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoThemeModeTimeHandle = (autoLightThemeTime: string[]) => {
|
|
||||||
const getTimestamp = (i: number) => {
|
|
||||||
const D = new Date()
|
|
||||||
const yyyy = D.getFullYear()
|
|
||||||
const mm = D.getMonth() + 1
|
|
||||||
const dd = D.getDate()
|
|
||||||
return new Date(`${yyyy}/${mm}/${dd} ${autoLightThemeTime[i]}:00`).getTime()
|
|
||||||
}
|
|
||||||
const now = Date.now()
|
|
||||||
return getTimestamp(0) <= now && now <= getTimestamp(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setThemeByConfigFn = (settings: UserSettingsModel) => {
|
|
||||||
const { themeMode, autoLightThemeTime } = settings
|
|
||||||
if (themeMode === 'auto') {
|
|
||||||
setBodyClassName(autoThemeModeTimeHandle(autoLightThemeTime) ? 'light' : 'dark')
|
|
||||||
} else {
|
|
||||||
setBodyClassName(themeMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
(): UserSettingsModel => store.getters.getUserSettings,
|
|
||||||
(newValue) => {
|
|
||||||
setThemeByConfigFn(newValue)
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default setThemeMode
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
export default class TimeHelper {
|
|
||||||
private static zerofill(n: number) {
|
|
||||||
return n < 10 ? `0${n}` : n
|
|
||||||
}
|
|
||||||
|
|
||||||
static getYyyyMmDd(now: number = Date.now()) {
|
|
||||||
const date: Date = new Date(now)
|
|
||||||
const yyyy = date.getFullYear()
|
|
||||||
const MM = date.getMonth() + 1
|
|
||||||
const DD = date.getDate()
|
|
||||||
return `${yyyy}${this.zerofill(MM)}${this.zerofill(DD)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
static formatTimestamp(now: number = Date.now()) {
|
|
||||||
const date: Date = new Date(now)
|
|
||||||
const YYYY = date.getFullYear()
|
|
||||||
const MM = date.getMonth() + 1
|
|
||||||
const DD = date.getDate()
|
|
||||||
const hh = date.getHours()
|
|
||||||
const mm = date.getMinutes()
|
|
||||||
const ss = date.getSeconds()
|
|
||||||
return `${YYYY}-${this.zerofill(MM)}-${this.zerofill(DD)} ${this.zerofill(
|
|
||||||
hh
|
|
||||||
)}:${this.zerofill(mm)}:${this.zerofill(ss)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { UserConfigInfoModel } from '@/common/model/user-config-info.model'
|
|
||||||
import { ToUploadImageModel, UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
import axios from '@/utils/axios'
|
|
||||||
import { store } from '@/store'
|
|
||||||
import { generateExternalLink } from '@/utils/external-link-handler'
|
|
||||||
import ExternalLinkType from '@/common/model/external-link.model'
|
|
||||||
|
|
||||||
export const uploadUrlHandle = (
|
|
||||||
config: UserConfigInfoModel,
|
|
||||||
filename: string
|
|
||||||
): string => {
|
|
||||||
let path = ''
|
|
||||||
if (config.selectedDir !== '/') {
|
|
||||||
path = `${config.selectedDir}/`
|
|
||||||
}
|
|
||||||
return `/repos/${config.owner}/${config.selectedRepos}/contents/${path}${filename}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uploadImage_single(
|
|
||||||
userConfigInfo: UserConfigInfoModel,
|
|
||||||
img: ToUploadImageModel
|
|
||||||
): Promise<Boolean> {
|
|
||||||
const { selectedBranch, email, owner } = userConfigInfo
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.uploadStatus.uploading = true
|
|
||||||
|
|
||||||
const data: any = {
|
|
||||||
message: 'Upload picture via PicX(https://github.com/XPoet/picx)',
|
|
||||||
branch: selectedBranch,
|
|
||||||
content: img.imgData.base64Content
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
data.committer = {
|
|
||||||
name: owner,
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.put(uploadUrlHandle(userConfigInfo, img.filename.now), data)
|
|
||||||
.then((res) => {
|
|
||||||
if (res && res.status === 201) {
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
uploadedHandle(res, img, userConfigInfo)
|
|
||||||
store.dispatch('TO_UPLOAD_IMAGE_UPLOADED', img.uuid)
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.uploadStatus.uploading = false
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadedHandle(
|
|
||||||
res: any,
|
|
||||||
img: ToUploadImageModel,
|
|
||||||
userConfigInfo: UserConfigInfoModel
|
|
||||||
) {
|
|
||||||
const userSettings = store.getters.getUserSettings
|
|
||||||
|
|
||||||
// 上传状态处理
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.uploadStatus.progress = 100
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.uploadStatus.uploading = false
|
|
||||||
|
|
||||||
// 生成 GitHub 外链
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.externalLink.github = generateExternalLink(
|
|
||||||
ExternalLinkType.github,
|
|
||||||
res.data.content,
|
|
||||||
userConfigInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
// 生成 jsDelivr CDN 外链
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.externalLink.jsdelivr = generateExternalLink(
|
|
||||||
ExternalLinkType.jsdelivr,
|
|
||||||
res.data.content,
|
|
||||||
userConfigInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
// 生成 Staticaly CDN 外链
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.externalLink.staticaly = generateExternalLink(
|
|
||||||
ExternalLinkType.staticaly,
|
|
||||||
res.data.content,
|
|
||||||
userConfigInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
// 生成 Cloudflare CDN 外链
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.externalLink.cloudflare = generateExternalLink(
|
|
||||||
ExternalLinkType.cloudflare,
|
|
||||||
res.data.content,
|
|
||||||
userConfigInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
const item: UploadedImageModel = {
|
|
||||||
checked: false,
|
|
||||||
type: 'image',
|
|
||||||
uuid: img.uuid,
|
|
||||||
dir: userConfigInfo.selectedDir,
|
|
||||||
name: res.data.content.name,
|
|
||||||
path: res.data.content.path,
|
|
||||||
sha: res.data.content.sha,
|
|
||||||
github_url: img.externalLink.github,
|
|
||||||
jsdelivr_cdn_url: img.externalLink.jsdelivr,
|
|
||||||
staticaly_cdn_url: img.externalLink.staticaly,
|
|
||||||
cloudflare_cdn_url: img.externalLink.cloudflare,
|
|
||||||
is_transform_md: userSettings.defaultMarkdown,
|
|
||||||
deleting: false,
|
|
||||||
size: img.fileInfo.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
img.uploadedImg = item
|
|
||||||
|
|
||||||
// uploadedList 增加图片
|
|
||||||
store.dispatch('UPLOADED_LIST_ADD', item)
|
|
||||||
|
|
||||||
// dirImageList 增加目录
|
|
||||||
store.dispatch('DIR_IMAGE_LIST_ADD_DIR', userConfigInfo.selectedDir)
|
|
||||||
|
|
||||||
// dirImageList 增加图片
|
|
||||||
store.dispatch('DIR_IMAGE_LIST_ADD_IMAGE', item)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
.feedback-page-container {
|
|
||||||
|
|
||||||
.help-info-item {
|
|
||||||
font-size: 16rem;
|
|
||||||
padding: 6rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 28rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-text {
|
|
||||||
color: #de1a1a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-container feedback-page-container">
|
|
||||||
<div class="help-info-item description">
|
|
||||||
PicX 是一款基于 GitHub API & jsDelivr 开发的具有 CDN 加速功能的图床工具。
|
|
||||||
<br />
|
|
||||||
无需下载!无需安装!打开网站即用!免费!极速!稳定!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">建议将本站添加至浏览器收藏夹,方便下次使用 😊</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">
|
|
||||||
作者:
|
|
||||||
<el-link type="primary" href="https://xpoet.cn/" target="_blank">@XPoet</el-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">
|
|
||||||
仓库:
|
|
||||||
<el-link type="primary" href="https://github.com/XPoet/picx" target="_blank">
|
|
||||||
https://github.com/XPoet/picx
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">
|
|
||||||
教程:
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
href="https://github.com/XPoet/picx/blob/master/README.md"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
https://github.com/XPoet/picx/blob/master/README.md
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">
|
|
||||||
在使用过程中遇到问题,请仔细阅读文档,或者给作者提
|
|
||||||
<el-link
|
|
||||||
type="primary"
|
|
||||||
style="font-size: 16rem"
|
|
||||||
href="https://github.com/XPoet/picx/issues"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Issue
|
|
||||||
</el-link>
|
|
||||||
。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item red-text">
|
|
||||||
图片上传缓慢或加载不出来等情况,可借助
|
|
||||||
<el-link
|
|
||||||
style="font-size: 16rem"
|
|
||||||
type="primary"
|
|
||||||
href="https://github.com/Alvin9999/new-pac/wiki"
|
|
||||||
target="_blank"
|
|
||||||
>VPN 工具
|
|
||||||
</el-link>
|
|
||||||
。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-info-item">
|
|
||||||
<strong>
|
|
||||||
郑重声明:请勿通过本站上传违反你当地法律的图片,所造成的一切后果与本站无关。
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'about'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "about.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
.config-page-container {
|
|
||||||
.operation {
|
|
||||||
text-align right
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
margin-left 20rem
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,498 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-container config-page-container">
|
|
||||||
<!-- Token -->
|
|
||||||
<el-form label-width="70rem" :label-position="labelPosition" class="config-form">
|
|
||||||
<el-form-item label="Token">
|
|
||||||
<el-input
|
|
||||||
v-model="userConfigInfo.token"
|
|
||||||
clearable
|
|
||||||
:autofocus="!userConfigInfo.token"
|
|
||||||
type="password"
|
|
||||||
show-password
|
|
||||||
placeholder="请输入 GitHub Token"
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item class="operation">
|
|
||||||
<el-button
|
|
||||||
plain
|
|
||||||
type="primary"
|
|
||||||
native-type="submit"
|
|
||||||
@click.prevent="getUserInfo()"
|
|
||||||
>
|
|
||||||
确认 Token
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<el-form
|
|
||||||
label-width="70rem"
|
|
||||||
:label-position="labelPosition"
|
|
||||||
v-if="userConfigInfo.token"
|
|
||||||
v-loading="loading"
|
|
||||||
element-loading-text="加载中..."
|
|
||||||
>
|
|
||||||
<el-form-item v-if="userConfigInfo.owner" label="用户名">
|
|
||||||
<el-input v-model="userConfigInfo.owner" readonly></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.email" label="邮箱">
|
|
||||||
<el-input v-model="userConfigInfo.email" readonly></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.reposList.length" label="选择仓库">
|
|
||||||
<el-select
|
|
||||||
v-model="userConfigInfo.selectedRepos"
|
|
||||||
filterable
|
|
||||||
style="width: 100%"
|
|
||||||
placeholder="请选择图床仓库..."
|
|
||||||
@change="selectRepos"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(repos, index) in userConfigInfo.reposList"
|
|
||||||
:key="index"
|
|
||||||
:label="repos.label"
|
|
||||||
:value="repos.value"
|
|
||||||
>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 分支 -->
|
|
||||||
<el-form
|
|
||||||
label-width="70rem"
|
|
||||||
:label-position="labelPosition"
|
|
||||||
v-if="userConfigInfo.selectedRepos && userConfigInfo.branchList.length"
|
|
||||||
v-loading="branchLoading"
|
|
||||||
element-loading-text="加载中..."
|
|
||||||
>
|
|
||||||
<!-- 因未验证 API 是否能创建空分支,暂时不开启分支选择方式 && 0 -->
|
|
||||||
<el-form-item v-if="userConfigInfo.selectedRepos && 0" label="分支方式">
|
|
||||||
<el-radio-group v-model="userConfigInfo.branchMode" @change="branchModeChange">
|
|
||||||
<el-tooltip
|
|
||||||
v-if="userConfigInfo.branchList.length"
|
|
||||||
:content="'选择 ' + userConfigInfo.selectedRepos + ' 仓库下的一个分支'"
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<el-radio label="reposBranch">
|
|
||||||
选择 {{ userConfigInfo.selectedRepos }} 仓库下的分支
|
|
||||||
</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="手动创建一个新分支" placement="top">
|
|
||||||
<el-radio label="newBranch">新建分支</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item
|
|
||||||
v-if="
|
|
||||||
userConfigInfo.branchList.length > 1 &&
|
|
||||||
userConfigInfo.branchMode === 'reposBranch'
|
|
||||||
"
|
|
||||||
label="选择分支"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
v-model="userConfigInfo.selectedBranch"
|
|
||||||
filterable
|
|
||||||
style="width: 100%"
|
|
||||||
placeholder="请选择分支..."
|
|
||||||
@change="selectBranch"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="(repos, reposIndex) in userConfigInfo.branchList"
|
|
||||||
:key="reposIndex"
|
|
||||||
:label="repos.label"
|
|
||||||
:value="repos.value"
|
|
||||||
>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.branchMode === 'newBranch'" label="新建分支">
|
|
||||||
<el-input
|
|
||||||
v-model="userConfigInfo.selectedBranch"
|
|
||||||
@input="persistUserConfigInfo()"
|
|
||||||
clearable
|
|
||||||
placeholder="请输入新建的分支..."
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 目录 -->
|
|
||||||
<el-form
|
|
||||||
label-width="70rem"
|
|
||||||
:label-position="labelPosition"
|
|
||||||
v-if="userConfigInfo.selectedBranch"
|
|
||||||
v-loading="dirLoading"
|
|
||||||
element-loading-text="加载中..."
|
|
||||||
>
|
|
||||||
<el-form-item v-if="userConfigInfo.selectedBranch" label="目录方式">
|
|
||||||
<el-radio-group v-model="userConfigInfo.dirMode" @change="dirModeChange">
|
|
||||||
<el-tooltip content="手动输入一个新目录" placement="top" :offset="-1">
|
|
||||||
<el-radio label="newDir">新建目录</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip
|
|
||||||
:content="'图片存储在 ' + userConfigInfo.selectedBranch + ' 分支的根目录下'"
|
|
||||||
placement="top"
|
|
||||||
:offset="-1"
|
|
||||||
>
|
|
||||||
<el-radio label="rootDir">根目录</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip
|
|
||||||
:content="'根据日期自动创建格式 YYYYMMDD 的目录'"
|
|
||||||
placement="top"
|
|
||||||
:offset="-1"
|
|
||||||
>
|
|
||||||
<el-radio label="autoDir">自动目录</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip
|
|
||||||
v-if="
|
|
||||||
userConfigInfo.dirList.length && userConfigInfo.branchMode !== 'newBranch'
|
|
||||||
"
|
|
||||||
:content="'选择 ' + userConfigInfo.selectedBranch + ' 分支下的一个目录'"
|
|
||||||
placement="top"
|
|
||||||
:offset="-1"
|
|
||||||
>
|
|
||||||
<el-radio label="reposDir">
|
|
||||||
选择 {{ userConfigInfo.selectedRepos }} 仓库目录
|
|
||||||
</el-radio>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.dirMode === 'autoDir'" label="自动目录">
|
|
||||||
<el-input v-model="userConfigInfo.selectedDir" readonly></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.dirMode === 'rootDir'" label="根目录">
|
|
||||||
<el-input v-model="userConfigInfo.selectedDir" readonly></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item v-if="userConfigInfo.dirMode === 'newDir'" label="新建目录">
|
|
||||||
<el-input
|
|
||||||
v-model="userConfigInfo.selectedDir"
|
|
||||||
@input="persistUserConfigInfo()"
|
|
||||||
clearable
|
|
||||||
placeholder="请输入新建的目录..."
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item
|
|
||||||
v-if="
|
|
||||||
userConfigInfo.dirList.length &&
|
|
||||||
userConfigInfo.dirMode === 'reposDir' &&
|
|
||||||
userConfigInfo.branchMode !== 'newBranch'
|
|
||||||
"
|
|
||||||
label="选择目录"
|
|
||||||
>
|
|
||||||
<el-cascader
|
|
||||||
style="width: 100%"
|
|
||||||
:props="cascaderProps"
|
|
||||||
:key="elCascaderKey"
|
|
||||||
v-model="userConfigInfo.selectedDirList"
|
|
||||||
filterable
|
|
||||||
placeholder="请选择一个目录..."
|
|
||||||
clearable
|
|
||||||
@change="cascaderChange"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 操作(重置、完成配置) -->
|
|
||||||
<el-form label-width="70rem" :label-position="labelPosition">
|
|
||||||
<el-form-item class="operation">
|
|
||||||
<el-button plain type="warning" @click="reset()" v-if="userConfigInfo.owner">
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
plain
|
|
||||||
type="success"
|
|
||||||
@click="goUpload"
|
|
||||||
v-if="userConfigInfo.selectedRepos"
|
|
||||||
>
|
|
||||||
完成配置
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { DirModeEnum } from '@/common/model/dir.model'
|
|
||||||
import { BranchModeEnum } from '@/common/model/user-config-info.model'
|
|
||||||
import axios from '@/utils/axios'
|
|
||||||
import TimeHelper from '@/utils/time-helper'
|
|
||||||
import { getDirListByPath } from '@/common/api'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const loggingStatus = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const dirLoading = ref(false)
|
|
||||||
const branchLoading = ref(false)
|
|
||||||
|
|
||||||
const labelPosition = computed(() => {
|
|
||||||
return userSettings.elementPlusSize === 'default' ? 'top' : 'right'
|
|
||||||
})
|
|
||||||
|
|
||||||
const elCascaderKey = ref<string>('elCascaderKey')
|
|
||||||
|
|
||||||
function persistUserConfigInfo() {
|
|
||||||
store.dispatch('USER_CONFIG_INFO_PERSIST')
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUserInfo(res: any) {
|
|
||||||
userConfigInfo.loggingStatus = true
|
|
||||||
userConfigInfo.owner = res.data.login
|
|
||||||
userConfigInfo.name = res.data.name
|
|
||||||
userConfigInfo.email = res.data.email
|
|
||||||
userConfigInfo.avatarUrl = res.data.avatar_url
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReposList(reposUrl: string) {
|
|
||||||
axios
|
|
||||||
.get(reposUrl, {
|
|
||||||
params: {
|
|
||||||
type: 'public',
|
|
||||||
sort: 'created',
|
|
||||||
per_page: 100
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
console.log('[getReposList] ', res)
|
|
||||||
if (res.status === 200 && res.data.length > 0) {
|
|
||||||
userConfigInfo.reposList = []
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const repos of res.data) {
|
|
||||||
if (!repos.fork && !repos.private) {
|
|
||||||
userConfigInfo.reposList.push({
|
|
||||||
value: repos.name,
|
|
||||||
label: repos.name,
|
|
||||||
desc: repos.description
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loading.value = false
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDirList() {
|
|
||||||
dirLoading.value = true
|
|
||||||
userConfigInfo.dirList = await getDirListByPath()
|
|
||||||
persistUserConfigInfo()
|
|
||||||
dirLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function dirModeChange(dirMode: DirModeEnum) {
|
|
||||||
switch (dirMode) {
|
|
||||||
case DirModeEnum.rootDir:
|
|
||||||
// 根目录
|
|
||||||
userConfigInfo.selectedDir = '/'
|
|
||||||
break
|
|
||||||
|
|
||||||
case DirModeEnum.autoDir:
|
|
||||||
// 自动目录,根据当天日期自动生成
|
|
||||||
userConfigInfo.selectedDir = TimeHelper.getYyyyMmDd()
|
|
||||||
break
|
|
||||||
|
|
||||||
case DirModeEnum.newDir:
|
|
||||||
// 手动输入的新建目录
|
|
||||||
userConfigInfo.selectedDir = 'xxx'
|
|
||||||
break
|
|
||||||
|
|
||||||
case DirModeEnum.reposDir:
|
|
||||||
// 仓库目录
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const { dirList } = userConfigInfo
|
|
||||||
if (dirList.length) {
|
|
||||||
userConfigInfo.selectedDir = dirList[0].value
|
|
||||||
} else {
|
|
||||||
userConfigInfo.selectedDir = ''
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
userConfigInfo.selectedDir = '/'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBranchList(repos: string) {
|
|
||||||
branchLoading.value = true
|
|
||||||
axios.get(`/repos/${userConfigInfo.owner}/${repos}/branches`).then((res: any) => {
|
|
||||||
console.log('[getBranchList] ', res)
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
branchLoading.value = false
|
|
||||||
if (res.data.length > 0) {
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (const item of res.data) {
|
|
||||||
userConfigInfo.branchList.push({
|
|
||||||
value: item.name,
|
|
||||||
label: item.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
userConfigInfo.branchList.reverse()
|
|
||||||
userConfigInfo.selectedBranch = userConfigInfo.branchList[0].value
|
|
||||||
userConfigInfo.branchMode = BranchModeEnum.reposBranch
|
|
||||||
getDirList()
|
|
||||||
} else {
|
|
||||||
userConfigInfo.selectedBranch = 'master'
|
|
||||||
userConfigInfo.branchMode = BranchModeEnum.newBranch
|
|
||||||
}
|
|
||||||
dirModeChange(userConfigInfo.dirMode)
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserInfo() {
|
|
||||||
if (userConfigInfo.token) {
|
|
||||||
loading.value = true
|
|
||||||
axios
|
|
||||||
.get('/user', {
|
|
||||||
headers: { Authorization: `token ${userConfigInfo.token}` }
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
console.log('[getUserInfo] ', res)
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
saveUserInfo(res)
|
|
||||||
getReposList(res.data.repos_url)
|
|
||||||
} else {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('Token 不能为空!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectRepos(repos: string) {
|
|
||||||
userConfigInfo.branchList = []
|
|
||||||
userConfigInfo.dirList = []
|
|
||||||
getBranchList(repos)
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectBranch(branch: string) {
|
|
||||||
userConfigInfo.selectedBranch = branch
|
|
||||||
await getDirList()
|
|
||||||
elCascaderKey.value = userConfigInfo.selectedBranch
|
|
||||||
userConfigInfo.selectedDir = userConfigInfo.dirList[0].value
|
|
||||||
userConfigInfo.selectedDirList = [userConfigInfo.selectedDir]
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
function branchModeChange(mode: BranchModeEnum) {
|
|
||||||
const selBranch = userConfigInfo.selectedBranch
|
|
||||||
const bv = userConfigInfo.branchList[0].value
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case BranchModeEnum.newBranch:
|
|
||||||
userConfigInfo.selectedBranch = 'xxx'
|
|
||||||
userConfigInfo.dirMode = DirModeEnum.newDir
|
|
||||||
userConfigInfo.selectedDir = 'xxx'
|
|
||||||
break
|
|
||||||
|
|
||||||
case BranchModeEnum.reposBranch:
|
|
||||||
if (selBranch !== bv) {
|
|
||||||
userConfigInfo.selectedBranch = bv
|
|
||||||
getDirList()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
userConfigInfo.selectedBranch = ''
|
|
||||||
break
|
|
||||||
}
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
loading.value = false
|
|
||||||
dirLoading.value = false
|
|
||||||
store.dispatch('LOGOUT')
|
|
||||||
}
|
|
||||||
|
|
||||||
function goUpload() {
|
|
||||||
const { selectedDir, dirMode } = userConfigInfo
|
|
||||||
let warningMessage: string = '目录不能为空!'
|
|
||||||
|
|
||||||
if (selectedDir === '') {
|
|
||||||
switch (dirMode) {
|
|
||||||
case DirModeEnum.newDir:
|
|
||||||
warningMessage = '请在输入框输入一个新目录!'
|
|
||||||
break
|
|
||||||
case DirModeEnum.reposDir:
|
|
||||||
warningMessage = `请选择 ${userConfigInfo.selectedRepos} 仓库下的一个目录!`
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
warningMessage = '请在输入框输入一个新目录!'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ElMessage.warning(warningMessage)
|
|
||||||
} else {
|
|
||||||
router.push('/upload')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cascaderProps = {
|
|
||||||
lazy: true,
|
|
||||||
checkStrictly: true,
|
|
||||||
async lazyLoad(node: any, resolve: any) {
|
|
||||||
const { level, pathLabels } = node
|
|
||||||
let dirs: any
|
|
||||||
if (level === 0) {
|
|
||||||
dirs = userConfigInfo.dirList
|
|
||||||
} else {
|
|
||||||
dirs = await getDirListByPath(pathLabels.join('/'))
|
|
||||||
}
|
|
||||||
if (dirs) {
|
|
||||||
resolve(
|
|
||||||
dirs.map((x: any) => ({
|
|
||||||
value: x.value,
|
|
||||||
label: x.label,
|
|
||||||
leaf: level >= 2
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
resolve([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cascaderChange(e: string[]) {
|
|
||||||
userConfigInfo.selectedDirList = e
|
|
||||||
userConfigInfo.selectedDir = e.join('/')
|
|
||||||
persistUserConfigInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => loggingStatus,
|
|
||||||
(_n) => {
|
|
||||||
if (!_n) {
|
|
||||||
loading.value = false
|
|
||||||
dirLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "config.styl"
|
|
||||||
</style>
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
@import "../../style/base.styl"
|
|
||||||
|
|
||||||
$infoBarHeight = 50rem
|
|
||||||
|
|
||||||
.management-page-container {
|
|
||||||
padding-bottom 0 !important
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding-top $infoBarHeight
|
|
||||||
box-sizing border-box
|
|
||||||
|
|
||||||
.top {
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
height $infoBarHeight
|
|
||||||
box-sizing border-box
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content space-between
|
|
||||||
font-size 14rem
|
|
||||||
padding-bottom 20rem
|
|
||||||
|
|
||||||
.right {
|
|
||||||
|
|
||||||
.btn-icon {
|
|
||||||
cursor pointer
|
|
||||||
font-size 22rem
|
|
||||||
margin-left 10rem
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
position relative
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
box-sizing border-box
|
|
||||||
border 1rem solid var(--border-color)
|
|
||||||
|
|
||||||
.image-list {
|
|
||||||
width 100%
|
|
||||||
//height 100%
|
|
||||||
//max-height calc(100% - 60rem)
|
|
||||||
margin 0
|
|
||||||
padding 2rem
|
|
||||||
list-style none
|
|
||||||
overflow-y auto
|
|
||||||
box-sizing border-box
|
|
||||||
|
|
||||||
li.image-item {
|
|
||||||
float left
|
|
||||||
box-sizing border-box
|
|
||||||
padding 10rem
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { Store } from 'vuex'
|
|
||||||
import { DirObject } from '@/store/modules/dir-image-list/types'
|
|
||||||
|
|
||||||
function getContent(targetContent: any, dirList: string[], n: number): any {
|
|
||||||
if (targetContent) {
|
|
||||||
if (dirList.length === n) {
|
|
||||||
return targetContent
|
|
||||||
}
|
|
||||||
return getContent(
|
|
||||||
targetContent.childrenDirs?.find((v: any) => v.dir === dirList[n]),
|
|
||||||
dirList,
|
|
||||||
// eslint-disable-next-line no-param-reassign,no-plusplus
|
|
||||||
++n
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前目录下所有内容(子目录和图片)
|
|
||||||
* @param dirPath
|
|
||||||
* @param dirObj
|
|
||||||
*/
|
|
||||||
export const getDirContent = (dirPath: string, dirObj: DirObject) => {
|
|
||||||
if (dirPath === '/') {
|
|
||||||
return dirObj
|
|
||||||
}
|
|
||||||
const dirList: string[] = dirPath.split('/')
|
|
||||||
return getContent(dirObj, dirList, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过滤当前目录的内容(子目录或图片)
|
|
||||||
* @param dirPath
|
|
||||||
* @param content
|
|
||||||
* @param type
|
|
||||||
*/
|
|
||||||
export const filterDirContent = (dirPath: string, content: any, type: string): any => {
|
|
||||||
if (type === 'dir') {
|
|
||||||
return content.childrenDirs?.filter((x: any) => x.type === 'dir')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'image') {
|
|
||||||
return content.imageList.filter((x: any) => x.type === 'image')
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dirModeHandle = (dir: string, store: Store<any>) => {
|
|
||||||
if (dir === '/') {
|
|
||||||
store.dispatch('SET_USER_CONFIG_INFO', {
|
|
||||||
dirMode: 'rootDir',
|
|
||||||
needPersist: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-container management-page-container">
|
|
||||||
<div class="content-container">
|
|
||||||
<div class="top">
|
|
||||||
<div class="left">
|
|
||||||
<selected-info-bar />
|
|
||||||
</div>
|
|
||||||
<div class="right flex-start">
|
|
||||||
<el-tooltip
|
|
||||||
placement="top"
|
|
||||||
:content="listing ? '切换方块展示' : '切换列表展示'"
|
|
||||||
>
|
|
||||||
<el-icon class="btn-icon" @click.stop="toggleListing">
|
|
||||||
<Tickets v-if="listing" />
|
|
||||||
<Menu v-if="!listing" />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip placement="top" content="重新加载图片">
|
|
||||||
<el-icon class="btn-icon" @click.stop="reloadCurrentDirContent">
|
|
||||||
<Refresh />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bottom" v-loading="loadingImageList" element-loading-text="加载中...">
|
|
||||||
<image-selector
|
|
||||||
v-if="currentPathImageList.length"
|
|
||||||
:currentDirImageList="currentPathImageList"
|
|
||||||
@update:initImageList="currentPathImageList"
|
|
||||||
:key="renderKey"
|
|
||||||
></image-selector>
|
|
||||||
<ul
|
|
||||||
class="image-list"
|
|
||||||
:style="{
|
|
||||||
height: isShowBatchTools ? 'calc(100% - 50rem)' : '100%'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<li class="image-item" v-if="userConfigInfo.selectedDir !== '/'">
|
|
||||||
<folder-card mode="back" />
|
|
||||||
</li>
|
|
||||||
<li class="image-item" v-for="(dir, index) in currentPathDirList" :key="index">
|
|
||||||
<folder-card :folder-obj="dir" />
|
|
||||||
</li>
|
|
||||||
<div class="clear"></div>
|
|
||||||
<li
|
|
||||||
class="image-item"
|
|
||||||
v-for="(image, index) in currentPathImageList"
|
|
||||||
:key="index"
|
|
||||||
:style="{
|
|
||||||
width: listing ? '50%' : '230rem',
|
|
||||||
height: listing ? '80rem' : '240rem'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<image-card
|
|
||||||
:image-obj="image"
|
|
||||||
:listing="listing"
|
|
||||||
v-model="activeIndex"
|
|
||||||
:index="index"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, onMounted, watch, ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
|
||||||
import { getContentByReposPath } from '@/common/api'
|
|
||||||
import {
|
|
||||||
dirModeHandle,
|
|
||||||
filterDirContent,
|
|
||||||
getDirContent
|
|
||||||
} from '@/views/management/management.util'
|
|
||||||
|
|
||||||
import imageCard from '@/components/image-card/image-card.vue'
|
|
||||||
import selectedInfoBar from '@/components/selected-info-bar/selected-info-bar.vue'
|
|
||||||
import folderCard from '@/components/folder-card/folder-card.vue'
|
|
||||||
import imageSelector from '@/components/image-selector/image-selector.vue'
|
|
||||||
import { UploadedImageModel } from '@/common/model/upload.model'
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const userConfigInfo = computed(() => store.getters.getUserConfigInfo).value
|
|
||||||
const loggingStatus = computed(() => store.getters.getUserLoggingStatus).value
|
|
||||||
const dirObject = computed(() => store.getters.getDirObject).value
|
|
||||||
|
|
||||||
const renderKey = ref(new Date().getTime()) // key for update image-selector component
|
|
||||||
const loadingImageList = ref(false)
|
|
||||||
const listing = ref(false)
|
|
||||||
const activeIndex = ref<number>()
|
|
||||||
|
|
||||||
const currentPathDirList = ref([])
|
|
||||||
const currentPathImageList = ref([])
|
|
||||||
|
|
||||||
async function dirContentHandle(dir: string) {
|
|
||||||
loadingImageList.value = true
|
|
||||||
|
|
||||||
const dirContent = getDirContent(dir, dirObject)
|
|
||||||
if (dirContent) {
|
|
||||||
const dirs = filterDirContent(dir, dirContent, 'dir')
|
|
||||||
const images = filterDirContent(dir, dirContent, 'image')
|
|
||||||
if (!dirs.length && !images.length) {
|
|
||||||
await getContentByReposPath(dir)
|
|
||||||
} else {
|
|
||||||
currentPathDirList.value = dirs
|
|
||||||
currentPathImageList.value = images
|
|
||||||
store.commit('REPLACE_IMAGE_CARD', { checkedImgArr: currentPathImageList.value })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await getContentByReposPath(dir)
|
|
||||||
}
|
|
||||||
loadingImageList.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initDirImageList() {
|
|
||||||
const { selectedDir, dirMode } = userConfigInfo
|
|
||||||
|
|
||||||
if (
|
|
||||||
(dirMode === 'newDir' || dirMode === 'autoDir') &&
|
|
||||||
!getDirContent(selectedDir, dirObject)
|
|
||||||
) {
|
|
||||||
userConfigInfo.selectedDir = '/'
|
|
||||||
userConfigInfo.dirMode = 'rootDir'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dirObject.imageList.length && !dirObject.childrenDirs.length) {
|
|
||||||
await getContentByReposPath(userConfigInfo.selectedDir)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await dirContentHandle(userConfigInfo.selectedDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleListing() {
|
|
||||||
listing.value = !listing.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新加载当前目录内容(网络请求)
|
|
||||||
async function reloadCurrentDirContent() {
|
|
||||||
const { selectedDir } = userConfigInfo
|
|
||||||
await store.dispatch('DIR_IMAGE_LIST_INIT_DIR', selectedDir)
|
|
||||||
loadingImageList.value = true
|
|
||||||
await getContentByReposPath(selectedDir)
|
|
||||||
loadingImageList.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initDirImageList()
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => loggingStatus,
|
|
||||||
(nv) => {
|
|
||||||
if (nv === false) {
|
|
||||||
router.push('/config')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => userConfigInfo.selectedDir,
|
|
||||||
async (nDir) => {
|
|
||||||
dirModeHandle(nDir, store)
|
|
||||||
await dirContentHandle(nDir)
|
|
||||||
renderKey.value += 1
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => dirObject,
|
|
||||||
(nv: any) => {
|
|
||||||
const { selectedDir } = userConfigInfo
|
|
||||||
const dirContent = getDirContent(selectedDir, nv)
|
|
||||||
if (dirContent) {
|
|
||||||
currentPathDirList.value = filterDirContent(selectedDir, dirContent, 'dir')
|
|
||||||
currentPathImageList.value = filterDirContent(selectedDir, dirContent, 'image')
|
|
||||||
store.commit('REPLACE_IMAGE_CARD', { checkedImgArr: currentPathImageList.value })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const isShowBatchTools = ref(false)
|
|
||||||
watch(
|
|
||||||
() => currentPathImageList.value,
|
|
||||||
(nv: UploadedImageModel[]) => {
|
|
||||||
isShowBatchTools.value = nv.filter((x) => x.checked).length > 0
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import 'management.styl';
|
|
||||||
</style>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
.setting-title {
|
|
||||||
font-size 16rem
|
|
||||||
font-weight bold
|
|
||||||
margin 40rem 0 20rem 0
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-list {
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
margin-bottom 10rem
|
|
||||||
|
|
||||||
&.last-child {
|
|
||||||
margin-bottom 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.prefix-input {
|
|
||||||
width calc(100% - 50rem)
|
|
||||||
margin-left 50rem
|
|
||||||
margin-top 15rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-encoder-title {
|
|
||||||
margin-bottom 12rem
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep() .el-radio-group {
|
|
||||||
|
|
||||||
display inline-block
|
|
||||||
|
|
||||||
.el-radio {
|
|
||||||
display block
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-container settings-page-container">
|
|
||||||
<div class="setting-title">个性设置:</div>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-switch
|
|
||||||
v-model="userSettings.defaultHash"
|
|
||||||
@change="persistUserSettings"
|
|
||||||
active-text="上传时给图片名加上哈希码(确保图片名唯一,强烈建议开启)"
|
|
||||||
></el-switch>
|
|
||||||
</li>
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-switch
|
|
||||||
v-model="userSettings.defaultPrefix"
|
|
||||||
@change="persistUserSettings"
|
|
||||||
active-text="上传时给图片名加上配置的前缀(示例:abc-image.jpg,abc- 为前缀)"
|
|
||||||
></el-switch>
|
|
||||||
<el-input
|
|
||||||
class="prefix-input"
|
|
||||||
v-if="userSettings.defaultPrefix"
|
|
||||||
v-model="userSettings.prefixName"
|
|
||||||
placeholder="请输入命名前缀"
|
|
||||||
@input="persistUserSettings"
|
|
||||||
clearable
|
|
||||||
autofocus
|
|
||||||
></el-input>
|
|
||||||
</li>
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-switch
|
|
||||||
v-model="userSettings.defaultMarkdown"
|
|
||||||
@change="persistUserSettings"
|
|
||||||
active-text="上传成功后复制的图片外链启用 Markdown 格式()"
|
|
||||||
></el-switch>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="setting-title">CDN 提供商:</div>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-select
|
|
||||||
v-model="userSettings.externalLinkType"
|
|
||||||
placeholder="选择 CDN 提供商"
|
|
||||||
@change="saveUserSettings"
|
|
||||||
>
|
|
||||||
<el-option label="Staticaly" value="staticaly"></el-option>
|
|
||||||
<el-option label="Cloudflare" value="cloudflare"></el-option>
|
|
||||||
<el-option label="jsDelivr" value="jsdelivr"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="setting-title">压缩设置:</div>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-switch
|
|
||||||
v-model="userSettings.isCompress"
|
|
||||||
@change="persistUserSettings"
|
|
||||||
active-text="是否压缩图片"
|
|
||||||
></el-switch>
|
|
||||||
</li>
|
|
||||||
<li class="setting-item">
|
|
||||||
<div class="img-encoder-title">选择图像编码器(压缩算法):</div>
|
|
||||||
<el-radio-group
|
|
||||||
:disabled="!userSettings.isCompress"
|
|
||||||
v-model="userSettings.compressEncoder"
|
|
||||||
@change="persistUserSettings"
|
|
||||||
>
|
|
||||||
<el-radio :label="compressEncoder.webP">
|
|
||||||
{{ compressEncoder.webP }} (压缩后图片格式为 webp,大多数现代浏览器支持)
|
|
||||||
</el-radio>
|
|
||||||
<el-radio :label="compressEncoder.mozJPEG">
|
|
||||||
{{ compressEncoder.mozJPEG }} (压缩后图片格式为 jpg,兼容性最好)
|
|
||||||
</el-radio>
|
|
||||||
<el-radio :label="compressEncoder.avif">
|
|
||||||
{{ compressEncoder.avif }}
|
|
||||||
(压缩后图片格式为 avif,压缩比最高,目前仅谷歌浏览器支持)
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="setting-title">主题设置:</div>
|
|
||||||
<ul class="setting-list">
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-select
|
|
||||||
v-model="userSettings.themeMode"
|
|
||||||
placeholder="主题模式"
|
|
||||||
@change="saveUserSettings"
|
|
||||||
>
|
|
||||||
<el-option label="自动设置" value="auto"></el-option>
|
|
||||||
<el-option label="暗夜主题" value="dark"></el-option>
|
|
||||||
<el-option label="白昼主题" value="light"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="setting-title" v-if="userSettings.themeMode === 'auto'">
|
|
||||||
设置白昼模式时间区间:
|
|
||||||
</div>
|
|
||||||
<ul class="setting-list" v-if="userSettings.themeMode === 'auto'">
|
|
||||||
<li class="setting-item">
|
|
||||||
<el-form ref="form">
|
|
||||||
<el-form-item>
|
|
||||||
<el-time-select
|
|
||||||
v-model="userSettings.autoLightThemeTime[0]"
|
|
||||||
start="00:00"
|
|
||||||
step="00:30"
|
|
||||||
end="23:59"
|
|
||||||
@change="saveUserSettings"
|
|
||||||
></el-time-select>
|
|
||||||
<span class="time-middle-space"> ~ </span>
|
|
||||||
<el-time-select
|
|
||||||
v-model="userSettings.autoLightThemeTime[1]"
|
|
||||||
:start="userSettings.autoLightThemeTime[0]"
|
|
||||||
step="00:30"
|
|
||||||
end="23:59"
|
|
||||||
@change="saveUserSettings"
|
|
||||||
></el-time-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { store } from '@/store'
|
|
||||||
import { CompressEncoderMap } from '@/utils/compress'
|
|
||||||
|
|
||||||
const userSettings = computed(() => store.getters.getUserSettings).value
|
|
||||||
|
|
||||||
const persistUserSettings = () => {
|
|
||||||
store.dispatch('USER_SETTINGS_PERSIST')
|
|
||||||
}
|
|
||||||
|
|
||||||
const compressEncoder = CompressEncoderMap
|
|
||||||
|
|
||||||
const saveUserSettings = () => {
|
|
||||||
store.dispatch('SET_USER_SETTINGS', {
|
|
||||||
...userSettings
|
|
||||||
})
|
|
||||||
persistUserSettings()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
|
||||||
@import "settings.styl"
|
|
||||||
</style>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user