init
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
target
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
/.idea/
|
||||
*.iml
|
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. [http://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} {fullname}
|
||||
|
||||
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 [http://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:
|
||||
|
||||
OneBlog(DBlog) Copyright (C) 2018 yadong.zhang
|
||||
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
|
||||
[http://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
|
||||
[http://www.gnu.org/philosophy/why-not-lgpl.html].
|
104
README.md
Normal file
@ -0,0 +1,104 @@
|
||||
一个简洁美观、功能强大并且自适应的Java博客。使用springboot开发,前端使用Bootstrap。支持移动端自适应,配有完备的前台和后台管理功能。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://gitee.com/yadong.zhang/DBlog/blob/master/LICENSE)
|
||||
|
||||
|
||||
# 功能简介
|
||||
|
||||
- **多种编辑器**:支持wangEditor和Markdown两种富文本编辑器,可以自行选择
|
||||
- **自动申请友情链接**:在线申请友情链接,无需站长手动配置,只需申请方添加完站长的连接后自行申请即可
|
||||
- **百度推送**:支持百度推送功能,加速百度搜索引擎收录博文
|
||||
- **评论系统**:自研的评论系统,支持显示用户地址、浏览器和os信息,后台可审核评论、开启匿名评论、回复和邮件通知评论
|
||||
- **权限管理**:后台配备完善的权限管理
|
||||
- **SEO**:自带robots、sitemap等seo模板,实现自动生成robots和sitemap
|
||||
- **实时通讯**:管理员可向在线的用户发送实时消息(需用户授权 - 基于websocket实现,具体参考[DBlog建站之Websocket的使用](https://www.zhyd.me/article/111))
|
||||
- **系统配置支持快速配置**:可通过后台手动修改诸如域名信息、SEO优化、赞赏码、七牛云以及更新维护通知等
|
||||
- **多种文件存储**:集成七牛云、阿里云OSS,实现文件云存储,同时支持本地文件存储
|
||||
- **文件搬运工**:集成[blog-hunter](https://gitee.com/yadong.zhang/blog-hunter)实现“文章搬运工”功能,支持一键同步imooc、csdn、iteye或者cnblogs上的文章,可抓取列表和单个文章
|
||||
- **第三方授权登录**:集成[JustAuth](https://gitee.com/yadong.zhang/JustAuth)实现第三方授权登录
|
||||
# Demo 演示
|
||||
|
||||
[前台demo](https://one.luckday.cn/)
|
||||
|
||||
[后台demo(root,123456)](https://one.luckday.cn/)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
----
|
||||
|
||||
# 模块划分
|
||||
|
||||
| 模块 | 释义 | 备注 |
|
||||
| :------------: | :------------: | :------------: |
|
||||
| blog-core | 核心业务类模块,提供基本的数据操作、工具处理等 | 该模块只是作为核心依赖包存在 |
|
||||
| blog-admin | 后台管理模块 | 该模块作为单独项目打包部署 |
|
||||
| blog-web | 前台模块 | 该模块作为单独项目打包部署 |
|
||||
| blog-file | 文件存储功能模块 | 支持local、七牛云和阿里云OSS |
|
||||
| ~~blog-spider~~ | 爬虫相关代码模块 | 已使用[blog-hunter](https://gitee.com/yadong.zhang/blog-hunter)插件替代 |
|
||||
|
||||
|
||||
# 技术栈
|
||||
|
||||
- Springboot 2.0.8
|
||||
- Apache Shiro 1.2.2
|
||||
- Logback
|
||||
- Redis
|
||||
- Lombok
|
||||
- Websocket
|
||||
- MySQL、Mybatis、Mapper、Pagehelper
|
||||
- Freemarker
|
||||
- Bootstrap 3.3.0
|
||||
- wangEditor
|
||||
- jQuery 1.11.1、jQuery Lazyload 1.9.7、fancybox、iCheck
|
||||
- 阿里云OSS
|
||||
- kaptcha
|
||||
- Qiniu
|
||||
- webMagic
|
||||
- ...
|
||||
|
||||
|
||||
# 使用方法(以blog-web项目为例)
|
||||
|
||||
1. 使用IDE导入本项目
|
||||
2. 新建数据库`CREATE DATABASE dblog;`
|
||||
3. 导入数据库`docs/db/dblog.sql`
|
||||
4. 初始化数据库`docs/db/init_data.sql`
|
||||
5. 修改配置文件
|
||||
1. 数据库链接属性(在`[blog-core]/resources/config/application-center-{env}.yml`配置文件中搜索`datasource`或定位到L.5)
|
||||
2. redis配置(在`[blog-core]/resources/config/application-center-{env}.yml`配置文件中搜索`redis`或定位到L.14)
|
||||
3. 以上两个必备的配置项修改完成后就能启动项目了。关于其他配置项,请参考后台“系统配置”页面
|
||||
6. 运行项目(三种方式,任选其一)
|
||||
1. 项目根目录下执行`mvn -X clean package -Dmaven.test.skip=true -Ptest`编译打包(注:-Ptest中的test为环境标识),然后cd到blog-web目录下执行`java -jar target/blog-web.jar`
|
||||
2. 在`blog-web`项目根目录下执行`mvn spring-boot:run`(注,如果报依赖错误,可在相关的依赖模块先执行install操作)
|
||||
3. 直接运行`BlogWebApplication.java`
|
||||
7. 浏览器访问`http://127.0.0.1:8443`
|
||||
8. `blog-admin`项目的启动方式与`blog-web`类似,请参考上面的使用说明
|
||||
|
||||
----
|
||||
|
||||
# 特别感谢
|
||||
|
||||
- 广大的开源爱好者
|
||||
- 无私的网友
|
||||
- [gentelella](https://github.com/puikinsh/gentelella): 一款开源的Bootstrap3后台管理模板
|
||||
- [七牛云](https://portal.qiniu.com/signup?code=3l8yx2v0f21ci): 强大的对象存储、CDN等服务提供商
|
||||
- [emoji表情列表](https://github.com/caiyongji/emoji-list#nature): emoji表情列表
|
||||
- [blog-hunter](https://github.com/zhangyd-c/blog-hunter): 博客猎手,基于webMagic的博客爬取工具,支持慕课、csdn、iteye、cnblogs、掘金和V2EX等各大主流博客平台。
|
||||
- [JustAuth](https://gitee.com/yadong.zhang/JustAuth): 史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。 Login, so easy!
|
||||
- 待续...
|
||||
|
||||
# 开源协议
|
||||
|
||||
[](https://gitee.com/yadong.zhang/DBlog/blob/master/LICENSE)
|
26
blog-admin/.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/src/main/resources/application-prod.yml
|
58
blog-admin/pom.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>blog-admin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>blog-admin</name>
|
||||
<description>OneBlog 后台程序</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.zyd</groupId>
|
||||
<artifactId>blog</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.zyd</groupId>
|
||||
<artifactId>blog-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 解决@xx@无法解析的问题 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<configuration>
|
||||
<delimiters>
|
||||
<delimiter>@</delimiter>
|
||||
</delimiters>
|
||||
<useDefaultDelimiters>false</useDefaultDelimiters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.zyd.blog.BlogAdminApplication</mainClass>
|
||||
<layout>JAR</layout>
|
||||
<!--构建完整可执行程序,可以直接运行-->
|
||||
<executable>true</executable>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,21 @@
|
||||
package com.zyd.blog;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
|
||||
/**
|
||||
* 程序启动类
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ServletComponentScan
|
||||
public class BlogAdminApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BlogAdminApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.zyd.blog.plugin.kaptcha.Captcha;
|
||||
import com.zyd.blog.plugin.kaptcha.GifCaptcha;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class KaptchaController {
|
||||
|
||||
@GetMapping("/getKaptcha")
|
||||
@ResponseBody
|
||||
public void getKaptcha(HttpServletResponse response) {
|
||||
try {
|
||||
response.setHeader("Pragma", "No-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setDateHeader("Expires", 0);
|
||||
response.setContentType("image/gif");
|
||||
/**
|
||||
* gif格式动画验证码
|
||||
* 宽,高,位数。
|
||||
*/
|
||||
Captcha captcha = new GifCaptcha(146,33,4);
|
||||
//输出
|
||||
captcha.out(response.getOutputStream());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("获取验证码异常:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.UserPwd;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import com.zyd.blog.framework.holder.RequestHolder;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.framework.property.AppProperties;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import com.zyd.blog.util.SessionUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.util.SavedRequest;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
/**
|
||||
* 登录相关
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping(value = "/passport")
|
||||
public class PassportController {
|
||||
|
||||
@Autowired
|
||||
private AppProperties config;
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
|
||||
@BussinessLog("进入登录页面")
|
||||
@GetMapping("/login")
|
||||
public ModelAndView login(Model model) {
|
||||
model.addAttribute("enableKaptcha", config.isEnableKaptcha());
|
||||
return ResultUtil.view("/login");
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
@BussinessLog("[{1}]登录系统")
|
||||
@PostMapping("/signin")
|
||||
@ResponseBody
|
||||
public ResponseVO submitLogin(String username, String password, boolean rememberMe, String kaptcha) {
|
||||
if (config.isEnableKaptcha()) {
|
||||
if (StringUtils.isEmpty(kaptcha) || !kaptcha.equals(SessionUtil.getKaptcha())) {
|
||||
return ResultUtil.error("验证码错误!");
|
||||
}
|
||||
SessionUtil.removeKaptcha();
|
||||
}
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
|
||||
//获取当前的Subject
|
||||
Subject currentUser = SecurityUtils.getSubject();
|
||||
try {
|
||||
// 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
|
||||
// 每个Realm都能在必要时对提交的AuthenticationTokens作出反应
|
||||
// 所以这一步在调用login(token)方法时,它会走到xxRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
|
||||
currentUser.login(token);
|
||||
SavedRequest savedRequest = WebUtils.getSavedRequest(RequestHolder.getRequest());
|
||||
String historyUrl = null;
|
||||
if(null != savedRequest) {
|
||||
if(!savedRequest.getMethod().equals("POST")) {
|
||||
historyUrl = savedRequest.getRequestUrl();
|
||||
}
|
||||
}
|
||||
return ResultUtil.success(null, historyUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("登录失败,用户名[{}]:{}", username, e.getMessage());
|
||||
token.clear();
|
||||
return ResultUtil.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@BussinessLog("修改密码")
|
||||
@PostMapping("/updatePwd")
|
||||
@ResponseBody
|
||||
public ResponseVO updatePwd(@Validated UserPwd userPwd, BindingResult bindingResult) throws Exception {
|
||||
if (bindingResult.hasErrors()) {
|
||||
return ResultUtil.error(bindingResult.getFieldError().getDefaultMessage());
|
||||
}
|
||||
boolean result = userService.updatePwd(userPwd);
|
||||
SessionUtil.removeAllSession();
|
||||
return ResultUtil.success(result ? "密码已修改成功,请重新登录" : "密码修改失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用权限管理工具进行用户的退出,跳出登录,给出提示信息
|
||||
*
|
||||
* @param redirectAttributes
|
||||
* @return
|
||||
*/
|
||||
@BussinessLog("退出系统")
|
||||
@GetMapping("/logout")
|
||||
public ModelAndView logout(RedirectAttributes redirectAttributes) {
|
||||
// http://www.oschina.net/question/99751_91561
|
||||
// 此处有坑: 退出登录,其实不用实现任何东西,只需要保留这个接口即可,也不可能通过下方的代码进行退出
|
||||
// SecurityUtils.getSubject().logout();
|
||||
// 因为退出操作是由Shiro控制的
|
||||
redirectAttributes.addFlashAttribute("message", "您已安全退出");
|
||||
return ResultUtil.redirect("index");
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
/**
|
||||
* 页面渲染相关 -- 页面跳转
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Article;
|
||||
import com.zyd.blog.business.service.BizArticleService;
|
||||
import com.zyd.blog.business.service.SysConfigService;
|
||||
import com.zyd.blog.core.websocket.server.ZydWebsocketServer;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import me.zhyd.hunter.config.HunterConfigTemplate;
|
||||
import me.zhyd.hunter.config.platform.Platform;
|
||||
import me.zhyd.hunter.enums.ExitWayEnum;
|
||||
import org.apache.shiro.authz.annotation.RequiresAuthentication;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.apache.shiro.authz.annotation.RequiresUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* 页面跳转类
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Controller
|
||||
public class RenderController {
|
||||
|
||||
@Autowired
|
||||
private BizArticleService articleService;
|
||||
@Autowired
|
||||
private SysConfigService configService;
|
||||
@Autowired
|
||||
private ZydWebsocketServer websocketServer;
|
||||
|
||||
@RequiresAuthentication
|
||||
@BussinessLog("进入首页")
|
||||
@GetMapping(value = {""})
|
||||
public ModelAndView home() {
|
||||
return ResultUtil.view("index");
|
||||
}
|
||||
|
||||
@RequiresPermissions("users")
|
||||
@BussinessLog("进入用户列表页")
|
||||
@GetMapping("/users")
|
||||
public ModelAndView user() {
|
||||
return ResultUtil.view("user/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("resources")
|
||||
@BussinessLog("进入资源列表页")
|
||||
@GetMapping("/resources")
|
||||
public ModelAndView resources() {
|
||||
return ResultUtil.view("resources/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("roles")
|
||||
@BussinessLog("进入角色列表页")
|
||||
@GetMapping("/roles")
|
||||
public ModelAndView roles() {
|
||||
return ResultUtil.view("role/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("articles")
|
||||
@BussinessLog("进入文章列表页")
|
||||
@GetMapping("/articles")
|
||||
public ModelAndView articles() {
|
||||
return ResultUtil.view("article/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("article:publish")
|
||||
@BussinessLog(value = "进入发表文章页[html]")
|
||||
@GetMapping("/article/publish")
|
||||
public ModelAndView publish() {
|
||||
return ResultUtil.view("article/publish");
|
||||
}
|
||||
|
||||
@RequiresPermissions("article:publish")
|
||||
@BussinessLog(value = "进入发表文章页[markdown]")
|
||||
@GetMapping("/article/publishMd")
|
||||
public ModelAndView publishMd() {
|
||||
return ResultUtil.view("article/publish-md");
|
||||
}
|
||||
|
||||
@RequiresPermissions("article:publish")
|
||||
@BussinessLog(value = "进入修改文章页[id={1}]")
|
||||
@GetMapping("/article/update/{id}")
|
||||
public ModelAndView edit(@PathVariable("id") Long id, Model model) {
|
||||
model.addAttribute("id", id);
|
||||
Article article = articleService.getByPrimaryKey(id);
|
||||
if(article.getIsMarkdown()){
|
||||
return ResultUtil.view("article/publish-md");
|
||||
}
|
||||
return ResultUtil.view("article/publish");
|
||||
}
|
||||
|
||||
@RequiresPermissions("types")
|
||||
@BussinessLog("进入分类列表页")
|
||||
@GetMapping("/article/types")
|
||||
public ModelAndView types() {
|
||||
return ResultUtil.view("article/types");
|
||||
}
|
||||
|
||||
@RequiresPermissions("tags")
|
||||
@BussinessLog("进入标签列表页")
|
||||
@GetMapping("/article/tags")
|
||||
public ModelAndView tags() {
|
||||
return ResultUtil.view("article/tags");
|
||||
}
|
||||
|
||||
@RequiresPermissions("links")
|
||||
@BussinessLog("进入链接页")
|
||||
@GetMapping("/links")
|
||||
public ModelAndView links() {
|
||||
return ResultUtil.view("link/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("comments")
|
||||
@BussinessLog("进入评论页")
|
||||
@GetMapping("/comments")
|
||||
public ModelAndView comments() {
|
||||
return ResultUtil.view("comment/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("notices")
|
||||
@BussinessLog("进入系统通知页")
|
||||
@GetMapping("/notices")
|
||||
public ModelAndView notices() {
|
||||
return ResultUtil.view("notice/list");
|
||||
}
|
||||
|
||||
@RequiresRoles("role:root")
|
||||
@BussinessLog("进入系统配置页")
|
||||
@GetMapping("/config")
|
||||
public ModelAndView config() {
|
||||
return ResultUtil.view("config");
|
||||
}
|
||||
|
||||
@RequiresPermissions("templates")
|
||||
@BussinessLog("进入模板管理页")
|
||||
@GetMapping("/templates")
|
||||
public ModelAndView templates() {
|
||||
return ResultUtil.view("template/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("updateLogs")
|
||||
@BussinessLog("进入更新记录管理页")
|
||||
@GetMapping("/updates")
|
||||
public ModelAndView updates() {
|
||||
return ResultUtil.view("update/list");
|
||||
}
|
||||
|
||||
@RequiresPermissions("icons")
|
||||
@BussinessLog(value = "进入icons页")
|
||||
@GetMapping("/icons")
|
||||
public ModelAndView icons(Model model) {
|
||||
return ResultUtil.view("other/icons");
|
||||
}
|
||||
|
||||
@RequiresPermissions("shiro")
|
||||
@BussinessLog(value = "进入shiro示例页")
|
||||
@GetMapping("/shiro")
|
||||
public ModelAndView shiro(Model model) {
|
||||
return ResultUtil.view("other/shiro");
|
||||
}
|
||||
|
||||
@RequiresUser
|
||||
@BussinessLog("进入编辑器测试用例页面")
|
||||
@GetMapping("/editor")
|
||||
public ModelAndView editor(Model model) {
|
||||
return ResultUtil.view("other/editor");
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice")
|
||||
@BussinessLog("进入通知管理页")
|
||||
@GetMapping("/notice")
|
||||
public ModelAndView notice(Model model) {
|
||||
model.addAttribute("online", websocketServer.getOnlineUserCount());
|
||||
return ResultUtil.view("laboratory/notification");
|
||||
}
|
||||
|
||||
@RequiresUser
|
||||
@BussinessLog("进入搬运工页面")
|
||||
@GetMapping("/remover")
|
||||
public ModelAndView remover(Model model) {
|
||||
model.addAttribute("exitWayList", ExitWayEnum.values());
|
||||
model.addAttribute("spiderConfig", HunterConfigTemplate.configTemplate.toJSONString());
|
||||
model.addAttribute("platforms", Platform.values());
|
||||
return ResultUtil.view("laboratory/remover");
|
||||
}
|
||||
|
||||
@RequiresPermissions("files")
|
||||
@BussinessLog("进入文件管理页面")
|
||||
@GetMapping("/files")
|
||||
public ModelAndView files(Model model) {
|
||||
return ResultUtil.view("file/list");
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.enums.FileUploadType;
|
||||
import com.zyd.blog.core.websocket.server.ZydWebsocketServer;
|
||||
import com.zyd.blog.core.websocket.util.WebSocketUtil;
|
||||
import com.zyd.blog.file.FileUploader;
|
||||
import com.zyd.blog.file.entity.VirtualFile;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.plugin.file.GlobalFileUploader;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 其他api性质的接口
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class RestApiController {
|
||||
|
||||
@Autowired
|
||||
private ZydWebsocketServer websocketServer;
|
||||
|
||||
@BussinessLog("wangEditor编辑器中上传文件")
|
||||
@RequiresPermissions("article:publish")
|
||||
@PostMapping("/uploadFile")
|
||||
public ResponseVO uploadFile(@RequestParam("file") MultipartFile file) {
|
||||
FileUploader uploader = new GlobalFileUploader();
|
||||
VirtualFile virtualFile = uploader.upload(file, FileUploadType.SIMPLE.getPath(), true);
|
||||
return ResultUtil.success("图片上传成功", virtualFile.getFullFilePath());
|
||||
}
|
||||
|
||||
@BussinessLog("simpleMD编辑器中上传文件")
|
||||
@RequiresPermissions("article:publish")
|
||||
@PostMapping("/uploadFileForMd")
|
||||
public Object uploadFileForMd(@RequestParam("file") MultipartFile file) {
|
||||
FileUploader uploader = new GlobalFileUploader();
|
||||
VirtualFile virtualFile = uploader.upload(file, FileUploadType.SIMPLE.getPath(), true);
|
||||
Map<String, Object> resultMap = new HashMap<>(3);
|
||||
resultMap.put("success", 1);
|
||||
resultMap.put("message", "上传成功");
|
||||
resultMap.put("filename", virtualFile.getFullFilePath());
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息通知
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequiresPermissions("notice")
|
||||
@PostMapping("/notice")
|
||||
@BussinessLog("通过websocket向前台发送通知")
|
||||
public ResponseVO notice(String msg) throws UnsupportedEncodingException {
|
||||
WebSocketUtil.sendNotificationMsg(msg, websocketServer.getOnlineUsers());
|
||||
return ResultUtil.success("消息发送成功");
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.consts.CommonConst;
|
||||
import com.zyd.blog.business.entity.Article;
|
||||
import com.zyd.blog.business.enums.BaiduPushTypeEnum;
|
||||
import com.zyd.blog.business.enums.ConfigKeyEnum;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.BizArticleService;
|
||||
import com.zyd.blog.business.service.RedisService;
|
||||
import com.zyd.blog.business.service.SysConfigService;
|
||||
import com.zyd.blog.business.util.BaiduPushUtil;
|
||||
import com.zyd.blog.business.vo.ArticleConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import com.zyd.blog.util.UrlBuildUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文章管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/article")
|
||||
public class RestArticleController {
|
||||
@Autowired
|
||||
private BizArticleService articleService;
|
||||
@Autowired
|
||||
private SysConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@RequiresPermissions("articles")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(ArticleConditionVO vo) {
|
||||
PageInfo<Article> pageInfo = articleService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"article:batchDelete", "article:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除文章[{1}]")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
articleService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 篇文章");
|
||||
}
|
||||
|
||||
@RequiresPermissions("article:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取文章[{1}]详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.articleService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"article:edit", "article:publish"}, logical = Logical.OR)
|
||||
@PostMapping("/save")
|
||||
@BussinessLog("发布文章")
|
||||
public ResponseVO edit(Article article, Long[] tags, MultipartFile file) {
|
||||
articleService.publish(article, tags, file);
|
||||
redisService.delBatch(CommonConst.WEB_CACHE_SUFFIX);
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"article:top", "article:recommend"}, logical = Logical.OR)
|
||||
@PostMapping("/update/{type}")
|
||||
@BussinessLog("修改文章[{2}]的状态[{1}]")
|
||||
public ResponseVO update(@PathVariable("type") String type, Long id) {
|
||||
articleService.updateTopOrRecommendedById(type, id);
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"article:batchPush", "article:push"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/pushToBaidu/{type}")
|
||||
@BussinessLog("推送文章[{2}]到百度站长平台")
|
||||
public ResponseVO pushToBaidu(@PathVariable("type") BaiduPushTypeEnum type, Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
Map config = configService.getConfigs();
|
||||
String siteUrl = (String) config.get(ConfigKeyEnum.SITE_URL.getKey());
|
||||
StringBuilder params = new StringBuilder();
|
||||
for (Long id : ids) {
|
||||
params.append(siteUrl).append("/article/").append(id).append("\n");
|
||||
}
|
||||
// urls: 推送, update: 更新, del: 删除
|
||||
String url = UrlBuildUtil.getBaiduPushUrl(type.toString(), (String) config.get(ConfigKeyEnum.SITE_URL.getKey()), (String) config.get(ConfigKeyEnum.BAIDU_PUSH_TOKEN.getKey()));
|
||||
String result = BaiduPushUtil.doPush(url, params.toString(), (String) config.get(ConfigKeyEnum.BAIDU_PUSH_COOKIE.getKey()));
|
||||
log.info(result);
|
||||
JSONObject resultJson = JSONObject.parseObject(result);
|
||||
|
||||
if (resultJson.containsKey("error")) {
|
||||
return ResultUtil.error(resultJson.getString("message"));
|
||||
}
|
||||
return ResultUtil.success(null, result);
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"article:publish"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/batchPublish")
|
||||
@BussinessLog("批量发布文章[{1}]")
|
||||
public ResponseVO batchPublish(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
articleService.batchUpdateStatus(ids, true);
|
||||
return ResultUtil.success("批量发布完成");
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Comment;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.enums.TemplateKeyEnum;
|
||||
import com.zyd.blog.business.service.BizCommentService;
|
||||
import com.zyd.blog.business.service.MailService;
|
||||
import com.zyd.blog.business.vo.CommentConditionVO;
|
||||
import com.zyd.blog.framework.exception.ZhydCommentException;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.apache.shiro.authz.annotation.RequiresUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 评论管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/comment")
|
||||
public class RestCommentController {
|
||||
@Autowired
|
||||
private BizCommentService commentService;
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
@RequiresPermissions("comments")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(CommentConditionVO vo) {
|
||||
PageInfo<Comment> pageInfo = commentService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("comment:reply")
|
||||
@PostMapping(value = "/reply")
|
||||
@BussinessLog("回复评论")
|
||||
public ResponseVO reply(Comment comment) {
|
||||
try {
|
||||
commentService.commentForAdmin(comment);
|
||||
} catch (ZhydCommentException e){
|
||||
return ResultUtil.error(e.getMessage());
|
||||
}
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"comment:batchDelete", "comment:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除评论[{1}]")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
commentService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 条评论");
|
||||
}
|
||||
|
||||
@RequiresPermissions("comments")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取评论[{1}]详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.commentService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("comments")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑评论")
|
||||
public ResponseVO edit(Comment comment) {
|
||||
try {
|
||||
commentService.updateSelective(comment);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("评论修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@RequiresPermissions("comment:audit")
|
||||
@PostMapping("/audit")
|
||||
@BussinessLog("审核评论")
|
||||
public ResponseVO audit(Comment comment, String contentText, Boolean sendEmail) {
|
||||
try {
|
||||
commentService.updateSelective(comment);
|
||||
if(!StringUtils.isEmpty(contentText)){
|
||||
comment.setContent(contentText);
|
||||
commentService.commentForAdmin(comment);
|
||||
}
|
||||
if(null != sendEmail && sendEmail){
|
||||
Comment commentDB = commentService.getByPrimaryKey(comment.getId());
|
||||
mailService.send(commentDB, TemplateKeyEnum.TM_COMMENT_AUDIT, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("评论审核失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正在审核的
|
||||
*
|
||||
* @param comment
|
||||
* @return
|
||||
*/
|
||||
@RequiresUser
|
||||
@PostMapping("/listVerifying")
|
||||
public ResponseVO listVerifying(Comment comment) {
|
||||
return ResultUtil.success(null, commentService.listVerifying(10));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.service.SysConfigService;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/config")
|
||||
public class RestConfigController {
|
||||
@Autowired
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
@RequiresRoles("role:root")
|
||||
@PostMapping("/get")
|
||||
public ResponseVO get() {
|
||||
return ResultUtil.success(null, sysConfigService.getConfigs());
|
||||
}
|
||||
|
||||
@RequiresRoles("role:root")
|
||||
@PostMapping("/save")
|
||||
@BussinessLog("修改系统配置")
|
||||
public ResponseVO save(@RequestParam Map<String, String> configs,
|
||||
@RequestParam(required = false) MultipartFile wxPraiseCode,
|
||||
@RequestParam(required = false) MultipartFile zfbPraiseCode) {
|
||||
try {
|
||||
sysConfigService.saveConfig(configs);
|
||||
sysConfigService.saveFile("wxPraiseCode", wxPraiseCode);
|
||||
sysConfigService.saveFile("zfbPraiseCode", zfbPraiseCode);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("系统配置修改失败");
|
||||
}
|
||||
return ResultUtil.success("系统配置修改成功");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.service.BizFileService;
|
||||
import com.zyd.blog.business.vo.FileConditionVO;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 文件管理
|
||||
*
|
||||
|
||||
* @date 2019/2/14 11:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class RestFileController {
|
||||
@Autowired
|
||||
private BizFileService fileService;
|
||||
|
||||
@RequiresPermissions("files")
|
||||
@PostMapping("/list")
|
||||
public PageInfo list(FileConditionVO vo) {
|
||||
vo.setPageSize(20);
|
||||
return fileService.findPageBreakByCondition(vo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("files")
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除文件,ids:{1}")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
fileService.remove(ids);
|
||||
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 张图片");
|
||||
}
|
||||
|
||||
@RequiresPermissions("files")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加文件")
|
||||
public ResponseVO add(MultipartFile[] file) {
|
||||
if (null == file || file.length == 0) {
|
||||
return ResultUtil.error("请至少选择一张图片!");
|
||||
}
|
||||
int res = fileService.upload(file);
|
||||
return ResultUtil.success("成功上传" + res + "张图片");
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Link;
|
||||
import com.zyd.blog.business.enums.LinkSourceEnum;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.enums.TemplateKeyEnum;
|
||||
import com.zyd.blog.business.service.MailService;
|
||||
import com.zyd.blog.business.service.SysLinkService;
|
||||
import com.zyd.blog.business.vo.LinkConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 友情链接
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/link")
|
||||
public class RestLinkController {
|
||||
@Autowired
|
||||
private SysLinkService linkService;
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
@RequiresPermissions("links")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(LinkConditionVO vo) {
|
||||
PageInfo<Link> pageInfo = linkService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("link:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加友情链接")
|
||||
public ResponseVO add(Link link) {
|
||||
link.setSource(LinkSourceEnum.ADMIN);
|
||||
linkService.insert(link);
|
||||
mailService.send(link, TemplateKeyEnum.TM_LINKS);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"link:batchDelete", "link:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除友情链接")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
linkService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个友情链接");
|
||||
}
|
||||
|
||||
@RequiresPermissions("link:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取友情链接详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.linkService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("link:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑友情链接")
|
||||
public ResponseVO edit(Link link) {
|
||||
try {
|
||||
linkService.updateSelective(link);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("友情链接修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Notice;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.enums.NoticeStatusEnum;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysNoticeService;
|
||||
import com.zyd.blog.business.vo.NoticeConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import com.zyd.blog.util.SessionUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 系统通知-- 首页菜单下方滚动显示
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/notice")
|
||||
public class RestNoticeController {
|
||||
@Autowired
|
||||
private SysNoticeService noticeService;
|
||||
|
||||
@RequiresPermissions("notices")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(NoticeConditionVO vo) {
|
||||
PageInfo<Notice> pageInfo = noticeService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加公告通知")
|
||||
public ResponseVO add(Notice notice) {
|
||||
User user = SessionUtil.getUser();
|
||||
if (null != user) {
|
||||
notice.setUserId(user.getId());
|
||||
}
|
||||
noticeService.insert(notice);
|
||||
return ResultUtil.success("系统通知添加成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"notice:batchDelete", "notice:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除公告通知")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
noticeService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个系统通知");
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取公告通知详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.noticeService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑公告通知")
|
||||
public ResponseVO edit(Notice notice) {
|
||||
try {
|
||||
noticeService.updateSelective(notice);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("系统通知修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice:release")
|
||||
@PostMapping("/release/{id}")
|
||||
@BussinessLog("发布公告通知")
|
||||
public ResponseVO release(@PathVariable Long id) {
|
||||
try {
|
||||
Notice notice = new Notice();
|
||||
notice.setId(id);
|
||||
notice.setStatus(NoticeStatusEnum.RELEASE.toString());
|
||||
noticeService.updateSelective(notice);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("通知发布失败,状态不变!");
|
||||
}
|
||||
return ResultUtil.success("该通知已发布,可去前台页面查看效果!");
|
||||
}
|
||||
|
||||
@RequiresPermissions("notice:withdraw")
|
||||
@PostMapping("/withdraw/{id}")
|
||||
@BussinessLog("撤回公告通知")
|
||||
public ResponseVO withdraw(@PathVariable Long id) {
|
||||
try {
|
||||
Notice notice = new Notice();
|
||||
notice.setId(id);
|
||||
notice.setStatus(NoticeStatusEnum.NOT_RELEASE.toString());
|
||||
noticeService.updateSelective(notice);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("通知撤回失败,状态不变!");
|
||||
}
|
||||
return ResultUtil.success("该通知已撤回,可修改后重新发布!");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.consts.DateConst;
|
||||
import com.zyd.blog.business.service.RemoverService;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import me.zhyd.hunter.config.HunterConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Remover:搬运工(英语渣渣,实在想不出好玩的名字了)
|
||||
*
|
||||
|
||||
* @date 2018/8/14 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/remover")
|
||||
public class RestRemoverController {
|
||||
|
||||
@Autowired
|
||||
private RemoverService removerService;
|
||||
|
||||
@PostMapping("/run")
|
||||
@ResponseBody
|
||||
@BussinessLog("运行文章搬运工")
|
||||
public void run(Long typeId, HunterConfig config, HttpServletResponse response) throws IOException, InterruptedException {
|
||||
removerService.run(typeId, config, response.getWriter());
|
||||
}
|
||||
|
||||
@PostMapping("/stop")
|
||||
@ResponseBody
|
||||
@BussinessLog("停止文章搬运工")
|
||||
public ResponseVO stop() {
|
||||
try {
|
||||
removerService.stop();
|
||||
} catch (Exception e) {
|
||||
return ResultUtil.error(e.getMessage());
|
||||
}
|
||||
return ResultUtil.success("程序已停止运行,当前时间 " + DateUtil.format(new Date(), DateConst.YYYY_MM_DD_HH_MM_SS_EN));
|
||||
}
|
||||
|
||||
@PostMapping("/single")
|
||||
@ResponseBody
|
||||
@BussinessLog("抓取单个文章")
|
||||
public void single(Long typeId, String[] url, boolean convertImg, HttpServletResponse response) throws IOException, InterruptedException {
|
||||
removerService.crawlSingle(typeId, url, convertImg, response.getWriter());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Resources;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysResourcesService;
|
||||
import com.zyd.blog.business.vo.ResourceConditionVO;
|
||||
import com.zyd.blog.core.shiro.ShiroService;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统资源管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/resources")
|
||||
public class RestResourcesController {
|
||||
|
||||
@Autowired
|
||||
private SysResourcesService resourcesService;
|
||||
@Autowired
|
||||
private ShiroService shiroService;
|
||||
|
||||
@RequiresPermissions("resources")
|
||||
@PostMapping("/list")
|
||||
public PageResult getAll(ResourceConditionVO vo) {
|
||||
vo.setPageSize(Integer.MAX_VALUE);
|
||||
PageInfo<Resources> pageInfo = resourcesService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("role:allotResource")
|
||||
@PostMapping("/resourcesWithSelected")
|
||||
public ResponseVO<List<Resources>> resourcesWithSelected(Long rid) {
|
||||
return ResultUtil.success(null, resourcesService.queryResourcesListWithSelected(rid));
|
||||
}
|
||||
|
||||
@RequiresPermissions("resource:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加资源")
|
||||
public ResponseVO add(Resources resources) {
|
||||
resourcesService.insert(resources);
|
||||
//更新权限
|
||||
shiroService.updatePermission();
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"resource:batchDelete", "resource:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除资源")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
resourcesService.removeByPrimaryKey(id);
|
||||
}
|
||||
|
||||
//更新权限
|
||||
shiroService.updatePermission();
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个资源");
|
||||
}
|
||||
|
||||
@RequiresPermissions("resource:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取资源详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.resourcesService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("resource:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑资源")
|
||||
public ResponseVO edit(Resources resources) {
|
||||
try {
|
||||
resourcesService.updateSelective(resources);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("资源修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Role;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysRoleResourcesService;
|
||||
import com.zyd.blog.business.service.SysRoleService;
|
||||
import com.zyd.blog.business.vo.RoleConditionVO;
|
||||
import com.zyd.blog.core.shiro.ShiroService;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统角色管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/roles")
|
||||
public class RestRoleController {
|
||||
@Autowired
|
||||
private SysRoleService roleService;
|
||||
@Autowired
|
||||
private SysRoleResourcesService roleResourcesService;
|
||||
@Autowired
|
||||
private ShiroService shiroService;
|
||||
|
||||
@RequiresPermissions("roles")
|
||||
@PostMapping("/list")
|
||||
public PageResult getAll(RoleConditionVO vo) {
|
||||
PageInfo<Role> pageInfo = roleService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("user:allotRole")
|
||||
@PostMapping("/rolesWithSelected")
|
||||
public ResponseVO<List<Role>> rolesWithSelected(Integer uid) {
|
||||
return ResultUtil.success(null, roleService.queryRoleListWithSelected(uid));
|
||||
}
|
||||
|
||||
@RequiresPermissions("role:allotResource")
|
||||
@PostMapping("/saveRoleResources")
|
||||
@BussinessLog("分配角色拥有的资源")
|
||||
public ResponseVO saveRoleResources(Long roleId, String resourcesId) {
|
||||
if (StringUtils.isEmpty(roleId)) {
|
||||
return ResultUtil.error("error");
|
||||
}
|
||||
roleResourcesService.addRoleResources(roleId, resourcesId);
|
||||
// 重新加载所有拥有roleId的用户的权限信息
|
||||
shiroService.reloadAuthorizingByRoleId(roleId);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions("role:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加角色")
|
||||
public ResponseVO add(Role role) {
|
||||
roleService.insert(role);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"role:batchDelete", "role:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除角色")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
roleService.removeByPrimaryKey(id);
|
||||
roleResourcesService.removeByRoleId(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个角色");
|
||||
}
|
||||
|
||||
@RequiresPermissions("role:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取角色详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.roleService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("role:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑角色")
|
||||
public ResponseVO edit(Role role) {
|
||||
try {
|
||||
roleService.updateSelective(role);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("角色修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.zyd.blog.business.service.BizStatisticsService;
|
||||
import com.zyd.blog.business.service.SysConfigService;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018/5/22 16:47
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/statistics")
|
||||
public class RestStatisticsController {
|
||||
|
||||
@Autowired
|
||||
private SysConfigService configService;
|
||||
@Autowired
|
||||
private BizStatisticsService statisticsService;
|
||||
|
||||
@RequestMapping("/siteInfo")
|
||||
public ResponseVO getSiteInfo(){
|
||||
return ResultUtil.success("", configService.getSiteInfo());
|
||||
}
|
||||
|
||||
@RequestMapping("/listSpider")
|
||||
public ResponseVO listSpider(){
|
||||
return ResultUtil.success("", statisticsService.listSpider(10));
|
||||
}
|
||||
|
||||
@RequestMapping("/listType")
|
||||
public ResponseVO listType(){
|
||||
return ResultUtil.success("", statisticsService.listType(10));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Tags;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.BizTagsService;
|
||||
import com.zyd.blog.business.vo.TagsConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 文章标签管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/tag")
|
||||
public class RestTagController {
|
||||
@Autowired
|
||||
private BizTagsService tagsService;
|
||||
|
||||
@RequiresPermissions("tags")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(TagsConditionVO vo) {
|
||||
PageInfo<Tags> pageInfo = tagsService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("tag:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加标签")
|
||||
public ResponseVO add(Tags tags) {
|
||||
tags = tagsService.insert(tags);
|
||||
return ResultUtil.success("标签添加成功!新标签 - " + tags.getName(), tags);
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"tag:batchDelete", "tag:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除标签")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
tagsService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个标签");
|
||||
}
|
||||
|
||||
@RequiresPermissions("tag:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取标签详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.tagsService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("tag:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑标签")
|
||||
public ResponseVO edit(Tags tags) {
|
||||
try {
|
||||
tagsService.updateSelective(tags);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("标签修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@PostMapping("/listAll")
|
||||
public ResponseVO list() {
|
||||
return ResultUtil.success(null, tagsService.listAll());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Template;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysTemplateService;
|
||||
import com.zyd.blog.business.vo.TemplateConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 模板管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/template")
|
||||
public class RestTemplateController {
|
||||
@Autowired
|
||||
private SysTemplateService templateService;
|
||||
|
||||
@RequiresPermissions("templates")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(TemplateConditionVO vo) {
|
||||
PageInfo<Template> pageInfo = templateService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("template:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加模板")
|
||||
public ResponseVO add(Template template) {
|
||||
templateService.insert(template);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"template:batchDelete", "template:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除模板")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
templateService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个模板");
|
||||
}
|
||||
|
||||
@RequiresPermissions("template:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取模板详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.templateService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("template:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑模板")
|
||||
public ResponseVO edit(Template template) {
|
||||
try {
|
||||
templateService.updateSelective(template);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("模板修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.Type;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.BizTypeService;
|
||||
import com.zyd.blog.business.vo.TypeConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 文章类型管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/type")
|
||||
public class RestTypeController {
|
||||
@Autowired
|
||||
private BizTypeService typeService;
|
||||
|
||||
@RequiresPermissions("types")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(TypeConditionVO vo) {
|
||||
PageInfo<Type> pageInfo = typeService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("type:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加分类")
|
||||
public ResponseVO add(Type type) {
|
||||
typeService.insert(type);
|
||||
return ResultUtil.success("文章类型添加成功!新类型 - " + type.getName());
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"type:batchDelete", "type:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除分类")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
typeService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个文章类型");
|
||||
}
|
||||
|
||||
@RequiresPermissions("type:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取分类详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.typeService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("type:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑分类")
|
||||
public ResponseVO edit(Type type) {
|
||||
try {
|
||||
typeService.updateSelective(type);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("文章类型修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
@PostMapping("/listAll")
|
||||
public ResponseVO listType() {
|
||||
return ResultUtil.success(null, typeService.listTypeForMenu());
|
||||
}
|
||||
|
||||
@PostMapping("/listParent")
|
||||
public ResponseVO listParent() {
|
||||
return ResultUtil.success(null, typeService.listParent());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.UpdateRecorde;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysUpdateRecordeService;
|
||||
import com.zyd.blog.business.vo.UpdateRecordeConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 系统更新日志
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/update")
|
||||
public class RestUpdateController {
|
||||
@Autowired
|
||||
private SysUpdateRecordeService updateRecordeService;
|
||||
|
||||
@RequiresPermissions("updateLogs")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(UpdateRecordeConditionVO vo) {
|
||||
PageInfo<UpdateRecorde> pageInfo = updateRecordeService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
@RequiresPermissions("updateLog:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加更新日志")
|
||||
public ResponseVO add(UpdateRecorde updateRecorde) {
|
||||
updateRecordeService.insert(updateRecorde);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"updateLog:batchDelete", "updateLog:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除更新日志")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
updateRecordeService.removeByPrimaryKey(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个更新记录");
|
||||
}
|
||||
|
||||
@RequiresPermissions("updateLog:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取更新日志详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.updateRecordeService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("updateLog:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑更新日志")
|
||||
public ResponseVO edit(UpdateRecorde updateRecorde) {
|
||||
try {
|
||||
updateRecordeService.updateSelective(updateRecorde);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("更新记录修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package com.zyd.blog.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.zyd.blog.business.annotation.BussinessLog;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.enums.ResponseStatus;
|
||||
import com.zyd.blog.business.service.SysUserRoleService;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import com.zyd.blog.business.vo.UserConditionVO;
|
||||
import com.zyd.blog.framework.object.PageResult;
|
||||
import com.zyd.blog.framework.object.ResponseVO;
|
||||
import com.zyd.blog.util.PasswordUtil;
|
||||
import com.zyd.blog.util.ResultUtil;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class RestUserController {
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
@Autowired
|
||||
private SysUserRoleService userRoleService;
|
||||
|
||||
@RequiresPermissions("users")
|
||||
@PostMapping("/list")
|
||||
public PageResult list(UserConditionVO vo) {
|
||||
PageInfo<User> pageInfo = userService.findPageBreakByCondition(vo);
|
||||
return ResultUtil.tablePage(pageInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户角色
|
||||
*
|
||||
* @param userId
|
||||
* @param roleIds
|
||||
* 用户角色
|
||||
* 此处获取的参数的角色id是以 “,” 分隔的字符串
|
||||
* @return
|
||||
*/
|
||||
@RequiresPermissions("user:allotRole")
|
||||
@PostMapping("/saveUserRoles")
|
||||
@BussinessLog("分配用户角色")
|
||||
public ResponseVO saveUserRoles(Long userId, String roleIds) {
|
||||
if (StringUtils.isEmpty(userId)) {
|
||||
return ResultUtil.error("error");
|
||||
}
|
||||
userRoleService.addUserRole(userId, roleIds);
|
||||
return ResultUtil.success("成功");
|
||||
}
|
||||
|
||||
@RequiresPermissions("user:add")
|
||||
@PostMapping(value = "/add")
|
||||
@BussinessLog("添加用户")
|
||||
public ResponseVO add(User user) {
|
||||
User u = userService.getByUserName(user.getUsername());
|
||||
if (u != null) {
|
||||
return ResultUtil.error("该用户名["+user.getUsername()+"]已存在!请更改用户名");
|
||||
}
|
||||
try {
|
||||
user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));
|
||||
userService.insert(user);
|
||||
return ResultUtil.success("成功");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("error");
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermissions(value = {"user:batchDelete", "user:delete"}, logical = Logical.OR)
|
||||
@PostMapping(value = "/remove")
|
||||
@BussinessLog("删除用户")
|
||||
public ResponseVO remove(Long[] ids) {
|
||||
if (null == ids) {
|
||||
return ResultUtil.error(500, "请至少选择一条记录");
|
||||
}
|
||||
for (Long id : ids) {
|
||||
userService.removeByPrimaryKey(id);
|
||||
userRoleService.removeByUserId(id);
|
||||
}
|
||||
return ResultUtil.success("成功删除 [" + ids.length + "] 个用户");
|
||||
}
|
||||
|
||||
@RequiresPermissions("user:get")
|
||||
@PostMapping("/get/{id}")
|
||||
@BussinessLog("获取用户详情")
|
||||
public ResponseVO get(@PathVariable Long id) {
|
||||
return ResultUtil.success(null, this.userService.getByPrimaryKey(id));
|
||||
}
|
||||
|
||||
@RequiresPermissions("user:edit")
|
||||
@PostMapping("/edit")
|
||||
@BussinessLog("编辑用户")
|
||||
public ResponseVO edit(User user) {
|
||||
try {
|
||||
userService.updateSelective(user);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return ResultUtil.error("用户修改失败!");
|
||||
}
|
||||
return ResultUtil.success(ResponseStatus.SUCCESS);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
package com.zyd.blog.core.config;
|
||||
|
||||
import com.zyd.blog.core.shiro.ShiroService;
|
||||
import com.zyd.blog.core.shiro.credentials.RetryLimitCredentialsMatcher;
|
||||
import com.zyd.blog.core.shiro.realm.ShiroRealm;
|
||||
import com.zyd.blog.framework.property.RedisProperties;
|
||||
import com.zyd.blog.framework.property.ShiroProperties;
|
||||
import com.zyd.blog.framework.redis.CustomRedisManager;
|
||||
import org.apache.shiro.codec.Base64;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.CookieRememberMeManager;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.shiro.web.servlet.SimpleCookie;
|
||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.crazycake.shiro.RedisCacheManager;
|
||||
import org.crazycake.shiro.RedisManager;
|
||||
import org.crazycake.shiro.RedisSessionDAO;
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shiro配置类
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@Order(1)
|
||||
public class ShiroConfig {
|
||||
|
||||
@Autowired
|
||||
private ShiroService shiroService;
|
||||
@Autowired
|
||||
private RedisProperties redisProperties;
|
||||
@Autowired
|
||||
private ShiroProperties shiroProperties;
|
||||
|
||||
@Bean(name = "lifecycleBeanPostProcessor")
|
||||
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
|
||||
return new LifecycleBeanPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复UnavailableSecurityManagerException(详见issues#IK7C3)
|
||||
*
|
||||
* @param securityManager
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {
|
||||
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
|
||||
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
|
||||
bean.setArguments(securityManager);
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ShiroFilterFactoryBean 处理拦截资源文件问题。
|
||||
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
|
||||
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
|
||||
* Filter Chain定义说明
|
||||
* 1、一个URL可以配置多个Filter,使用逗号分隔
|
||||
* 2、当设置多个过滤器时,全部验证通过,才视为通过
|
||||
* 3、部分过滤器可指定参数,如perms,roles
|
||||
*/
|
||||
@Bean(name = "shiroFilter")
|
||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
|
||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||
// 必须设置 SecurityManager
|
||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
|
||||
shiroFilterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());
|
||||
// 登录成功后要跳转的链接
|
||||
shiroFilterFactoryBean.setSuccessUrl(shiroProperties.getSuccessUrl());
|
||||
// 未授权界面;
|
||||
shiroFilterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());
|
||||
// 配置数据库中的resource
|
||||
Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn("lifecycleBeanPostProcessor")
|
||||
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
|
||||
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
|
||||
creator.setProxyTargetClass(true);
|
||||
return creator;
|
||||
}
|
||||
|
||||
@Bean(name = "securityManager")
|
||||
public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
// 设置realm.
|
||||
securityManager.setRealm(authRealm);
|
||||
securityManager.setCacheManager(redisCacheManager());
|
||||
// 自定义session管理 使用redis
|
||||
securityManager.setSessionManager(sessionManager());
|
||||
// 注入记住我管理器
|
||||
securityManager.setRememberMeManager(rememberMeManager());
|
||||
return securityManager;
|
||||
}
|
||||
|
||||
@Bean(name = "shiroRealm")
|
||||
public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) {
|
||||
ShiroRealm shiroRealm = new ShiroRealm();
|
||||
shiroRealm.setCredentialsMatcher(credentialsMatcher());
|
||||
return shiroRealm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 凭证匹配器
|
||||
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
|
||||
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
|
||||
* )
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "credentialsMatcher")
|
||||
public RetryLimitCredentialsMatcher credentialsMatcher() {
|
||||
return new RetryLimitCredentialsMatcher();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开启shiro aop注解支持.
|
||||
* 使用代理方式;所以需要开启代码支持;
|
||||
*
|
||||
* @param securityManager
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
|
||||
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
|
||||
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
|
||||
return authorizationAttributeSourceAdvisor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置shiro redisManager
|
||||
* 使用的是shiro-redis开源插件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RedisManager redisManager() {
|
||||
CustomRedisManager redisManager = new CustomRedisManager();
|
||||
redisManager.setHost(redisProperties.getHost());
|
||||
redisManager.setPort(redisProperties.getPort());
|
||||
redisManager.setDatabase(redisProperties.getDatabase());
|
||||
redisManager.setExpire(redisProperties.getExpire());
|
||||
redisManager.setTimeout(redisProperties.getTimeout().getNano() * 1000);
|
||||
redisManager.setPassword(redisProperties.getPassword());
|
||||
return redisManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* cacheManager 缓存 redis实现
|
||||
* 使用的是shiro-redis开源插件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisCacheManager redisCacheManager() {
|
||||
RedisCacheManager redisCacheManager = new RedisCacheManager();
|
||||
redisCacheManager.setRedisManager(redisManager());
|
||||
return redisCacheManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RedisSessionDAO shiro sessionDao层的实现 通过redis
|
||||
* 使用的是shiro-redis开源插件
|
||||
*/
|
||||
// @Bean
|
||||
public RedisSessionDAO redisSessionDAO() {
|
||||
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
|
||||
redisSessionDAO.setRedisManager(redisManager());
|
||||
return redisSessionDAO;
|
||||
}
|
||||
|
||||
/**
|
||||
* shiro session的管理
|
||||
*/
|
||||
@Bean
|
||||
public DefaultWebSessionManager sessionManager() {
|
||||
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
|
||||
sessionManager.setGlobalSessionTimeout(redisProperties.getExpire() * 1000L);
|
||||
sessionManager.setSessionDAO(redisSessionDAO());
|
||||
return sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* cookie对象;
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SimpleCookie rememberMeCookie() {
|
||||
// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
|
||||
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
|
||||
// 记住我cookie生效时间30天 ,单位秒。 注释掉,默认永久不过期 2018-07-15
|
||||
simpleCookie.setMaxAge(redisProperties.getExpire());
|
||||
return simpleCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* cookie管理对象;记住我功能
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CookieRememberMeManager rememberMeManager() {
|
||||
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
|
||||
cookieRememberMeManager.setCookie(rememberMeCookie());
|
||||
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
|
||||
cookieRememberMeManager.setCipherKey(Base64.decode("1QWLxg+NYmxraMoxAXu/Iw=="));
|
||||
return cookieRememberMeManager;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.zyd.blog.core.config;
|
||||
|
||||
import com.zyd.blog.core.interceptor.RememberAuthenticationInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
|
||||
* @date 2018/7/15 15:03
|
||||
* @since 1.0
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private RememberAuthenticationInterceptor rememberAuthenticationInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(rememberAuthenticationInterceptor)
|
||||
.excludePathPatterns("/passport/**", "/error/**", "/assets/**", "/getKaptcha/**", "/websocket", "favicon.ico")
|
||||
.addPathPatterns("/**");
|
||||
}
|
||||
}
|
60
blog-admin/src/main/java/com/zyd/blog/core/interceptor/RememberAuthenticationInterceptor.java
Normal file
@ -0,0 +1,60 @@
|
||||
package com.zyd.blog.core.interceptor;
|
||||
|
||||
import com.zyd.blog.business.consts.SessionConst;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import com.zyd.blog.util.PasswordUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.session.Session;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
|
||||
* @date 2018/7/15 15:24
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RememberAuthenticationInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (subject.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
Session session = subject.getSession(true);
|
||||
if (session.getAttribute(SessionConst.USER_SESSION_KEY) != null) {
|
||||
return true;
|
||||
}
|
||||
if(!subject.isRemembered()) {
|
||||
log.warn("未设置“记住我”,跳转到登录页...");
|
||||
response.sendRedirect(request.getContextPath() + "/passport/login");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Long userId = Long.parseLong(subject.getPrincipal().toString());
|
||||
User user = userService.getByPrimaryKey(userId);
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), PasswordUtil.decrypt(user.getPassword(), user.getUsername()), true);
|
||||
subject.login(token);
|
||||
session.setAttribute(SessionConst.USER_SESSION_KEY, user);
|
||||
log.info("[{}] - 已自动登录", user.getUsername());
|
||||
} catch (Exception e) {
|
||||
log.error("自动登录失败", e);
|
||||
response.sendRedirect(request.getContextPath() + "/passport/login");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.zyd.blog.core.shiro;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
||||
* @date 2019/2/11 10:07
|
||||
* @since 1.8
|
||||
*/
|
||||
public interface ShiroService {
|
||||
|
||||
Map<String, String> loadFilterChainDefinitions();
|
||||
|
||||
void updatePermission();
|
||||
|
||||
void reloadAuthorizingByRoleId(Long roleId);
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package com.zyd.blog.core.shiro;
|
||||
|
||||
import com.zyd.blog.business.entity.Resources;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.service.SysResourcesService;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import com.zyd.blog.core.shiro.realm.ShiroRealm;
|
||||
import com.zyd.blog.framework.exception.ZhydException;
|
||||
import com.zyd.blog.framework.holder.SpringContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.RealmSecurityManager;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
|
||||
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
|
||||
import org.apache.shiro.web.servlet.AbstractShiroFilter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shiro-权限相关的业务处理
|
||||
*
|
||||
|
||||
* @date 2018/4/25 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ShiroServiceImpl implements ShiroService {
|
||||
|
||||
@Autowired
|
||||
private SysResourcesService resourcesService;
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
|
||||
/**
|
||||
* 初始化权限
|
||||
*/
|
||||
public Map<String, String> loadFilterChainDefinitions() {
|
||||
/*
|
||||
配置访问权限
|
||||
- anon:所有url都都可以匿名访问
|
||||
- authc: 需要认证才能进行访问(此处指所有非匿名的路径都需要登录才能访问)
|
||||
- user:配置记住我或认证通过可以访问
|
||||
*/
|
||||
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
||||
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
|
||||
filterChainDefinitionMap.put("/passport/logout", "logout");
|
||||
filterChainDefinitionMap.put("/passport/login", "anon");
|
||||
filterChainDefinitionMap.put("/passport/signin", "anon");
|
||||
filterChainDefinitionMap.put("/websocket", "anon");
|
||||
filterChainDefinitionMap.put("/favicon.ico", "anon");
|
||||
filterChainDefinitionMap.put("/error", "anon");
|
||||
filterChainDefinitionMap.put("/assets/**", "anon");
|
||||
filterChainDefinitionMap.put("/plugin/**", "anon");
|
||||
filterChainDefinitionMap.put("/vendors/**", "anon");
|
||||
filterChainDefinitionMap.put("/getKaptcha", "anon");
|
||||
// 加载数据库中配置的资源权限列表
|
||||
List<Resources> resourcesList = resourcesService.listUrlAndPermission();
|
||||
if (CollectionUtils.isEmpty(resourcesList)) {
|
||||
throw new ZhydException("未加载到resources内容,请确认是否执行了init_data.sql");
|
||||
}
|
||||
for (Resources resources : resourcesList) {
|
||||
if (!StringUtils.isEmpty(resources.getUrl()) && !StringUtils.isEmpty(resources.getPermission())) {
|
||||
String permission = "perms[" + resources.getPermission() + "]";
|
||||
filterChainDefinitionMap.put(resources.getUrl(), permission);
|
||||
}
|
||||
}
|
||||
// 本博客中并不存在什么特别关键的操作,所以直接使用user认证。如果有朋友是参考本博客的shiro开发其他安全功能(比如支付等)时,建议针对这类操作使用authc权限 by yadong.zhang
|
||||
filterChainDefinitionMap.put("/**", "user");
|
||||
return filterChainDefinitionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载权限
|
||||
*/
|
||||
public void updatePermission() {
|
||||
ShiroFilterFactoryBean shirFilter = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);
|
||||
synchronized (shirFilter) {
|
||||
AbstractShiroFilter shiroFilter = null;
|
||||
try {
|
||||
shiroFilter = (AbstractShiroFilter) shirFilter.getObject();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
|
||||
}
|
||||
|
||||
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
|
||||
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
|
||||
|
||||
// 清空老的权限控制
|
||||
manager.getFilterChains().clear();
|
||||
|
||||
shirFilter.getFilterChainDefinitionMap().clear();
|
||||
shirFilter.setFilterChainDefinitionMap(loadFilterChainDefinitions());
|
||||
// 重新构建生成
|
||||
Map<String, String> chains = shirFilter.getFilterChainDefinitionMap();
|
||||
for (Map.Entry<String, String> entry : chains.entrySet()) {
|
||||
String url = entry.getKey();
|
||||
String chainDefinition = entry.getValue().trim().replace(" ", "");
|
||||
manager.createChain(url, chainDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载用户权限
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
private void reloadAuthorizingByUserId(User user) {
|
||||
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
|
||||
ShiroRealm shiroRealm = (ShiroRealm) rsm.getRealms().iterator().next();
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
String realmName = subject.getPrincipals().getRealmNames().iterator().next();
|
||||
SimplePrincipalCollection principals = new SimplePrincipalCollection(user.getId(), realmName);
|
||||
subject.runAs(principals);
|
||||
shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
|
||||
subject.releaseRunAs();
|
||||
|
||||
log.info("用户[{}]的权限更新成功!!", user.getUsername());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载所有拥有roleId角色的用户的权限
|
||||
*
|
||||
* @param roleId
|
||||
*/
|
||||
public void reloadAuthorizingByRoleId(Long roleId) {
|
||||
List<User> userList = userService.listByRoleId(roleId);
|
||||
if (CollectionUtils.isEmpty(userList)) {
|
||||
return;
|
||||
}
|
||||
for (User user : userList) {
|
||||
reloadAuthorizingByUserId(user);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.zyd.blog.core.shiro.credentials;
|
||||
|
||||
import com.zyd.blog.util.PasswordUtil;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
|
||||
|
||||
/**
|
||||
* Shiro-密码凭证匹配器(验证密码有效性)
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
public class CredentialsMatcher extends SimpleCredentialsMatcher {
|
||||
|
||||
@Override
|
||||
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
|
||||
UsernamePasswordToken utoken = (UsernamePasswordToken) token;
|
||||
//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
|
||||
String inPassword = new String(utoken.getPassword());
|
||||
//获得数据库中的密码
|
||||
String dbPassword = (String) info.getCredentials();
|
||||
try {
|
||||
dbPassword = PasswordUtil.decrypt(dbPassword, utoken.getUsername());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
//进行密码的比对
|
||||
return this.equals(inPassword, dbPassword);
|
||||
}
|
||||
}
|
114
blog-admin/src/main/java/com/zyd/blog/core/shiro/credentials/RetryLimitCredentialsMatcher.java
Normal file
@ -0,0 +1,114 @@
|
||||
package com.zyd.blog.core.shiro.credentials;
|
||||
|
||||
import com.zyd.blog.business.consts.SessionConst;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.service.SysConfigService;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.AccountException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.ExcessiveAttemptsException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Shiro-密码输入错误的状态下重试次数的匹配管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RetryLimitCredentialsMatcher extends CredentialsMatcher {
|
||||
|
||||
/**
|
||||
* 用户登录次数计数 redisKey 前缀
|
||||
*/
|
||||
private static final String SHIRO_LOGIN_COUNT = "shiro_login_count_";
|
||||
/**
|
||||
* 用户登录是否被锁定 一小时 redisKey 前缀
|
||||
*/
|
||||
private static final String SHIRO_IS_LOCK = "shiro_is_lock_";
|
||||
/**
|
||||
* 登录失败时重试的次数,默认5次
|
||||
*/
|
||||
private static final int DEFAULT_RETRY_NUM = 5;
|
||||
/**
|
||||
* session有效期,默认1小时
|
||||
*/
|
||||
private static final int DEFAULT_SESSIONTIME_OUT = 1;
|
||||
/**
|
||||
* session有效期的时间单位,默认小时
|
||||
*/
|
||||
private static final TimeUnit DEFAULT_SESSIONTIME_OUT_UNIT = TimeUnit.HOURS;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
@Autowired
|
||||
private SysUserService userService;
|
||||
@Autowired
|
||||
private SysConfigService configService;
|
||||
|
||||
@Override
|
||||
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
|
||||
Long userId = (Long) info.getPrincipals().getPrimaryPrincipal();
|
||||
User user = userService.getByPrimaryKey(userId);
|
||||
String username = user.getUsername();
|
||||
// 访问一次,计数一次
|
||||
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
|
||||
String loginCountKey = SHIRO_LOGIN_COUNT + username;
|
||||
String isLockKey = SHIRO_IS_LOCK + username;
|
||||
opsForValue.increment(loginCountKey, 1);
|
||||
|
||||
if (redisTemplate.hasKey(isLockKey)) {
|
||||
String unit = "分钟";
|
||||
long time = TimeUnit.SECONDS.toMinutes(redisTemplate.getExpire(isLockKey));
|
||||
if (time <= 0) {
|
||||
unit = "秒";
|
||||
time = TimeUnit.SECONDS.toSeconds(redisTemplate.getExpire(isLockKey));
|
||||
} else if (time > 60) {
|
||||
unit = "小时";
|
||||
time = TimeUnit.SECONDS.toHours(redisTemplate.getExpire(isLockKey));
|
||||
}
|
||||
throw new ExcessiveAttemptsException("帐号[" + username + "]已被禁止登录!剩余" + time + unit);
|
||||
}
|
||||
|
||||
Map<String, Object> configs = configService.getConfigs();
|
||||
Object loginRetryNumObj = configs.get("loginRetryNum");
|
||||
Object sessionTimeOutObj = configs.get("sessionTimeOut");
|
||||
Object sessionTimeOutUnitObj = configs.get("sessionTimeOutUnit");
|
||||
int loginRetryNum = StringUtils.isEmpty(loginRetryNumObj) ? DEFAULT_RETRY_NUM : Integer.parseInt(String.valueOf(loginRetryNumObj));
|
||||
int sessionTimeOut = StringUtils.isEmpty(sessionTimeOutObj) ? DEFAULT_SESSIONTIME_OUT : Integer.parseInt(String.valueOf(sessionTimeOutObj));
|
||||
TimeUnit sessionTimeOutUnit = StringUtils.isEmpty(sessionTimeOutUnitObj) ? DEFAULT_SESSIONTIME_OUT_UNIT : TimeUnit.valueOf(String.valueOf(sessionTimeOutUnitObj));
|
||||
|
||||
String loginCount = String.valueOf(opsForValue.get(loginCountKey));
|
||||
int retryCount = ((loginRetryNum + 1) - Integer.parseInt(loginCount));
|
||||
if (retryCount <= 0) {
|
||||
opsForValue.set(isLockKey, "LOCK");
|
||||
redisTemplate.expire(isLockKey, sessionTimeOut, sessionTimeOutUnit);
|
||||
redisTemplate.expire(loginCountKey, sessionTimeOut, sessionTimeOutUnit);
|
||||
throw new ExcessiveAttemptsException("由于密码输入错误次数过多,帐号[" + username + "]已被禁止登录!");
|
||||
}
|
||||
|
||||
boolean matches = super.doCredentialsMatch(token, info);
|
||||
if (!matches) {
|
||||
throw new AccountException("帐号或密码不正确!您还剩" + retryCount + "次重试的机会");
|
||||
}
|
||||
|
||||
//清空登录计数
|
||||
redisTemplate.delete(loginCountKey);
|
||||
try {
|
||||
userService.updateUserLastLoginInfo(user);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 当验证都通过后,把用户信息放在session里
|
||||
// 注:User必须实现序列化
|
||||
SecurityUtils.getSubject().getSession().setAttribute(SessionConst.USER_SESSION_KEY, user);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package com.zyd.blog.core.shiro.realm;
|
||||
|
||||
import com.zyd.blog.business.entity.Resources;
|
||||
import com.zyd.blog.business.entity.Role;
|
||||
import com.zyd.blog.business.entity.User;
|
||||
import com.zyd.blog.business.enums.UserStatusEnum;
|
||||
import com.zyd.blog.business.enums.UserTypeEnum;
|
||||
import com.zyd.blog.business.service.SysResourcesService;
|
||||
import com.zyd.blog.business.service.SysRoleService;
|
||||
import com.zyd.blog.business.service.SysUserService;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.*;
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.util.ByteSource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Shiro-密码输入错误的状态下重试次数的匹配管理
|
||||
*
|
||||
|
||||
* @date 2018/4/24 14:37
|
||||
* @since 1.0
|
||||
*/
|
||||
public class ShiroRealm extends AuthorizingRealm {
|
||||
|
||||
@Resource
|
||||
private SysUserService userService;
|
||||
@Resource
|
||||
private SysResourcesService resourcesService;
|
||||
@Resource
|
||||
private SysRoleService roleService;
|
||||
|
||||
/**
|
||||
* 提供账户信息返回认证信息(用户的角色信息集合)
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||
//获取用户的输入的账号.
|
||||
String username = (String) token.getPrincipal();
|
||||
User user = userService.getByUserName(username);
|
||||
if (user == null) {
|
||||
throw new UnknownAccountException("账号不存在!");
|
||||
}
|
||||
if (user.getStatus() != null && UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {
|
||||
throw new LockedAccountException("帐号已被锁定,禁止登录!");
|
||||
}
|
||||
|
||||
// principal参数使用用户Id,方便动态刷新用户权限
|
||||
return new SimpleAuthenticationInfo(
|
||||
user.getId(),
|
||||
user.getPassword(),
|
||||
ByteSource.Util.bytes(username),
|
||||
getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限认证,为当前登录的Subject授予角色和权限(角色的权限信息集合)
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
|
||||
// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
|
||||
Long userId = (Long) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
// 赋予角色
|
||||
List<Role> roleList = roleService.listRolesByUserId(userId);
|
||||
for (Role role : roleList) {
|
||||
info.addRole(role.getName());
|
||||
}
|
||||
|
||||
// 赋予权限
|
||||
List<Resources> resourcesList = null;
|
||||
User user = userService.getByPrimaryKey(userId);
|
||||
if (null == user) {
|
||||
return info;
|
||||
}
|
||||
// ROOT用户默认拥有所有权限
|
||||
if (UserTypeEnum.ROOT.toString().equalsIgnoreCase(user.getUserType())) {
|
||||
resourcesList = resourcesService.listAll();
|
||||
} else {
|
||||
resourcesList = resourcesService.listByUserId(userId);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(resourcesList)) {
|
||||
Set<String> permissionSet = new HashSet<>();
|
||||
for (Resources resources : resourcesList) {
|
||||
String permission = null;
|
||||
if (!StringUtils.isEmpty(permission = resources.getPermission())) {
|
||||
permissionSet.addAll(Arrays.asList(permission.trim().split(",")));
|
||||
}
|
||||
}
|
||||
info.setStringPermissions(permissionSet);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.zyd.blog.core.websocket.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
/**
|
||||
* websocket配置类
|
||||
*
|
||||
|
||||
* @date 2018/4/18 11:48
|
||||
* @since 1.0
|
||||
*/
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
/**
|
||||
* ServerEndpointExporter会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.zyd.blog.core.websocket.server;
|
||||
|
||||
import com.zyd.blog.core.websocket.util.WebSocketUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
|
||||
* @date 2018/4/18 11:48
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@ServerEndpoint(value = "/websocket")
|
||||
@Component
|
||||
public class ZydWebsocketServer {
|
||||
|
||||
/**
|
||||
* 线程安全的socket集合
|
||||
*/
|
||||
private static CopyOnWriteArraySet<Session> webSocketSet = new CopyOnWriteArraySet<>();
|
||||
/**
|
||||
* 初始在线人数
|
||||
*/
|
||||
private static AtomicInteger onlineCount = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* 连接建立成功调用的方法
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session) {
|
||||
webSocketSet.add(session);
|
||||
int count = onlineCount.incrementAndGet();
|
||||
log.info("[Socket] 有链接加入,当前在线人数为: {}", count);
|
||||
|
||||
WebSocketUtil.sendOnlineMsg(Integer.toString(count), webSocketSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭调用的方法
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
int count = onlineCount.decrementAndGet();
|
||||
log.info("[Socket] 有链接关闭,当前在线人数为: {}", count);
|
||||
WebSocketUtil.sendOnlineMsg(Integer.toString(count), webSocketSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到客户端消息后调用的方法
|
||||
*
|
||||
* @param message
|
||||
* 客户端发送过来的消息
|
||||
*/
|
||||
@OnMessage
|
||||
public void onMessage(String message, Session session) {
|
||||
log.info("[Socket] {}来自客户端的消息:{}", session.getId(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户数量
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getOnlineUserCount() {
|
||||
return onlineCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线用户的会话信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CopyOnWriteArraySet<Session> getOnlineUsers() {
|
||||
return webSocketSet;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.zyd.blog.core.websocket.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.websocket.Session;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* websocket工具类,支持单条发送和批量发送
|
||||
*
|
||||
|
||||
* @date 2018/4/18 11:48
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class WebSocketUtil {
|
||||
|
||||
private static final String ONLINE_MSG_KEY = "online";
|
||||
private static final String NOTIFICATION_MSG_KEY = "notification";
|
||||
|
||||
private WebSocketUtil() {
|
||||
// 私有化构造方法,禁止new
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据消息类型,生成发送到客户端的最终消息内容
|
||||
*
|
||||
* @param type
|
||||
* 消息类型
|
||||
* @param content
|
||||
* 消息正文
|
||||
* @return
|
||||
*/
|
||||
private static String generateMsg(String type, String content) {
|
||||
return String.format("{\"fun\": \"%s\", \"msg\":\"%s\"}", type, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送在线用户的消息
|
||||
*
|
||||
* @param msg
|
||||
* @param sessionSet
|
||||
*/
|
||||
public static void sendOnlineMsg(String msg, Set<Session> sessionSet) {
|
||||
broadcast(generateMsg(ONLINE_MSG_KEY, msg), sessionSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知的消息
|
||||
*
|
||||
* @param msg
|
||||
* @param sessionSet
|
||||
*/
|
||||
public static void sendNotificationMsg(String msg, Set<Session> sessionSet) throws UnsupportedEncodingException {
|
||||
// 为了防止消息中存在特殊字符(比如换行符)等造成前台解析错误,此处编码一次。前台对应的需要解码
|
||||
broadcast(generateMsg(NOTIFICATION_MSG_KEY, URLEncoder.encode(msg, Charsets.UTF_8.displayName())), sessionSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向客户端发送消息
|
||||
*
|
||||
* @param message
|
||||
* 消息内容
|
||||
* @param session
|
||||
* 客户端session
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void sendMessage(String message, Session session) {
|
||||
try {
|
||||
session.getAsyncRemote().sendText(message);
|
||||
} catch (Exception e) {
|
||||
log.error("[Socket] websocket-->向客户端发送数据发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 群发
|
||||
*
|
||||
* @param message
|
||||
* 消息内容
|
||||
* @param sessionSet
|
||||
* 客户端session列表
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void broadcast(String message, Set<Session> sessionSet) {
|
||||
if (CollectionUtils.isEmpty(sessionSet)) {
|
||||
return;
|
||||
}
|
||||
// 多线程群发
|
||||
for (Session entry : sessionSet) {
|
||||
if (null != entry && entry.isOpen()) {
|
||||
sendMessage(message, entry);
|
||||
} else {
|
||||
sessionSet.remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
blog-admin/src/main/resources/HunterConfig.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"imooc": {
|
||||
"domain": "www.imooc.com",
|
||||
"titleRegex": "//span[@class=js-title]/html()",
|
||||
"authorRegex": "//div[@class=name_con]/p[@class=name]/a[@class=nick]/html()",
|
||||
"releaseDateRegex": "//div[@class='dc-profile']/div[@class='l']/span[@class='spacer']/text()",
|
||||
"contentRegex": "//div[@class=detail-content]/html()",
|
||||
"tagRegex": "//div[@class=cat-box]/div[@class=cat-wrap]/a[@class=cat]/html()",
|
||||
"descriptionRegex": "//meta[@name=Description]/@content",
|
||||
"targetLinksRegex": "/article/[0-9]{1,10}",
|
||||
"header": [
|
||||
"Host=www.imooc.com",
|
||||
"Referer=https://www.imooc.com"
|
||||
],
|
||||
"entryUrls": [
|
||||
"https://www.imooc.com/u/{uid}/articles?page=1"
|
||||
]
|
||||
},
|
||||
"csdn": {
|
||||
"domain": "blog.csdn.net",
|
||||
"titleRegex": "//h1[@class=title-article]/html()",
|
||||
"authorRegex": "//a[@class=follow-nickName]/html()",
|
||||
"releaseDateRegex": "//div[@class=article-bar-top]/span[@class=time]/html()",
|
||||
"contentRegex": "//div[@id=content_views]/html()",
|
||||
"tagRegex": "//span[@class=artic-tag-box]/a[@class=tag-link]/html()",
|
||||
"targetLinksRegex": ".*blog\\.csdn\\.net/{uid}/article/details/[0-9a-zA-Z]{1,15}",
|
||||
"header": [
|
||||
"Host=blog.csdn.net",
|
||||
"Referer=https://blog.csdn.net/{uid}/article/list/1"
|
||||
],
|
||||
"cookie": "uuid_tt_dd=10_10331769530-1547536548454-504065; __yadk_uid=eg5NQPFTcIj2VFX6xv3ZJR5C8Q6PVnhm; smidV2=201901161027267de8378708fa178ab707894a70a126f100f32016b8489dd20; UN=u011197448; _ga=GA1.2.1772643969.1548209590; UM_distinctid=16882db136258b-0ce8092de75b71-6655742e-13c680-16882db1363437; gr_user_id=adf433f5-b683-45d5-80dd-caf4b7110360; _dg_id.40e39cb6d36d5282.c482=d846039aa9f3ff23%7C%7C%7C1548746364%7C%7C%7C1%7C%7C%7C1548746364%7C%7C%7C1548746364%7C%7C%7C%7C%7C%7Cf00a97bae3087c52%7C%7C%7Chttps%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DvtlNP9hvgRV6hAa9E1qaewZSIyCGR03ISdsDpTn-zP6muC9Cyop5IucCWeBKKtany7DrcUXgLmy83PTI98aNbSymNKzXgMUYl_c8xbxdt_W%26wd%3D%26eqid%3D85e43a6f000de5ab000000025c4ffe71%7C%7C%7Chttps%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DvtlNP9hvgRV6hAa9E1qaewZSIyCGR03ISdsDpTn-zP6muC9Cyop5IucCWeBKKtany7DrcUXgLmy83PTI98aNbSymNKzXgMUYl_c8xbxdt_W%26wd%3D%26eqid%3D85e43a6f000de5ab000000025c4ffe71%7C%7C%7C1%7C%7C%7Cundefined; pt_7cd998c4=uid=XsOJJs2ynt2SEUray9/meA&nid=1&vid=skmHxYQg4a0C8dk8c5hQuA&vn=1&pvn=1&sact=1548746403744&to_flag=0&pl=lW7Wzh7yRjgqeNTTgGYbFw*pt*1548746363475; ADHOC_MEMBERSHIP_CLIENT_ID1.0=c85c851e-bf2b-2e69-9332-5ef1b69d869d; BT=1551156352906; Hm_ct_6bcd52f51e9b3dce32bec4a3997715ac=1788*1*PC_VC!5744*1*u011197448!6525*1*10_10331769530-1547536548454-504065; dc_session_id=10_1553784106452.549598; bdshare_firstime=1555313012400; acw_tc=2760823b15578883310602927e32b811a6a04228c70de517201efc5c6a91ba; SESSION=a5fb6772-59f5-441a-9728-9d485c859155; dc_tos=prksrn; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1557888343,1557888344,1557910597,1557974435; Hm_lpvt_6bcd52f51e9b3dce32bec4a3997715ac=1557974435; c-login-auto=5; acw_sc__v3=5cdcd164b6b2dd555c665f17642d63ea924e82cf; acw_sc__v2=5cdcd15e0d1889a5308a8f22e3c72110f788dbce",
|
||||
"entryUrls": [
|
||||
"https://blog.csdn.net/{uid}/article/list/1"
|
||||
]
|
||||
},
|
||||
"iteye": {
|
||||
"domain": "{uid}.iteye.com",
|
||||
"titleRegex": "//div[@class=blog_title]/h3/a/html()",
|
||||
"authorRegex": "//div[@id=blog_owner_name]/html()",
|
||||
"releaseDateRegex": "//div[@class=blog_bottom]/ul/li[1]/html()",
|
||||
"contentRegex": "//div[@class=iteye-blog-content-contain]/html()",
|
||||
"tagRegex": "//div[@class=news_tag]/a/html()",
|
||||
"targetLinksRegex": ".*{uid}\\.iteye\\.com/blog/[0-9]+",
|
||||
"header": [
|
||||
"Host={uid}.iteye.com",
|
||||
"Referer=http://{uid}.iteye.com/"
|
||||
],
|
||||
"entryUrls": [
|
||||
"http://{uid}.iteye.com/?page=1"
|
||||
]
|
||||
},
|
||||
"cnblogs": {
|
||||
"domain": "www.cnblogs.com",
|
||||
"titleRegex": "//a[@id=cb_post_title_url]/html()",
|
||||
"authorRegex": "//div[@class=postDesc]/a[1]/html()",
|
||||
"releaseDateRegex": "//span[@id=post-date]/html()",
|
||||
"contentRegex": "//div[@id=cnblogs_post_body]/html()",
|
||||
"tagRegex": "//div[@id=EntryTag]/a/html()",
|
||||
"descriptionRegex": "//meta[@property=\"og:description\"]/@content",
|
||||
"targetLinksRegex": ".*www\\.cnblogs\\.com/{uid}/p/[\\w\\d]+\\.html",
|
||||
"header": [
|
||||
"Host=www.cnblogs.com",
|
||||
"Referer=https://www.cnblogs.com/"
|
||||
],
|
||||
"entryUrls": [
|
||||
"https://www.cnblogs.com/{uid}/default.html?page=1"
|
||||
]
|
||||
},
|
||||
"juejin": {
|
||||
"domain": "juejin.im",
|
||||
"titleRegex": "//h1[@class=article-title]/html()",
|
||||
"authorRegex": "//div[@itemprop=author]/meta[@itemprop=\"name\"]/@content",
|
||||
"releaseDateRegex": "//meta[@itemprop=\"datePublished\"]/@content",
|
||||
"contentRegex": "//div[@class=article-content]/html()",
|
||||
"tagRegex": "//div[@class=tag-title]/html()",
|
||||
"targetLinksRegex": ".*juejin\\.im/post/[\\w\\d]+",
|
||||
"header": [
|
||||
"Host=juejin.im",
|
||||
"Referer=https://juejin.im"
|
||||
],
|
||||
"entryUrls": [
|
||||
"https://juejin.im/user/{uid}/posts"
|
||||
]
|
||||
},
|
||||
"v2ex": {
|
||||
"domain": "v2ex.com",
|
||||
"titleRegex": "//*[@id=Main]/div[@class=box]/div[@class=header]/h1/html()",
|
||||
"authorRegex": "//*[@id=Main]/div[@class=box]/div[@class=header]/small/a/html()",
|
||||
"releaseDateRegex": "//meta[@property=\"article:published_time\"]/@content",
|
||||
"contentRegex": "//div[@class=markdown_body]/html()",
|
||||
"tagRegex": "//*[@id=\"Main\"]/div[6]/div/a/html()",
|
||||
"descriptionRegex": "//meta[@property=\"og:description\"]/@content",
|
||||
"targetLinksRegex": ".*www\\.v2ex\\.com/t/[\\w\\d]+",
|
||||
"header": [
|
||||
"Host=www.v2ex.com",
|
||||
"Referer=https://www.v2ex.com"
|
||||
],
|
||||
"entryUrls": [
|
||||
"https://www.v2ex.com/member/{uid}"
|
||||
]
|
||||
}
|
||||
}
|
63
blog-admin/src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,63 @@
|
||||
# Server settings
|
||||
server:
|
||||
tomcat:
|
||||
basedir: /var/tmp/website-blog-admin
|
||||
# SPRING PROFILES
|
||||
spring:
|
||||
profiles:
|
||||
include: [center-dev]
|
||||
# 指定默认MimeMessage的编码,默认为: UTF-8
|
||||
mail:
|
||||
default-encoding: UTF-8
|
||||
# 指定SMTP server使用的协议,默认为: smtp
|
||||
protocol: smtp
|
||||
# 指定SMTP server host.
|
||||
host: xxx
|
||||
port: 465
|
||||
# 指定SMTP server的用户名.
|
||||
username: xxx
|
||||
# 指定SMTP server登录密码:
|
||||
password: xxx
|
||||
# 指定是否在启动时测试邮件服务器连接,默认为false
|
||||
test-connection: false
|
||||
properties:
|
||||
mail.smtp.auth: true
|
||||
# 腾讯企业邮箱 下两个配置必须!!!
|
||||
mail.smtp.ssl.enable: true
|
||||
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
|
||||
mail.smtp.socketFactory.port: 465
|
||||
mail.smtp.starttls.enable: true
|
||||
mail.smtp.starttls.required: true
|
||||
mail.smtp.connectiontimeout: 50000
|
||||
mail.smtp.timeout: 30000
|
||||
mail.smtp.writetimeout: 50000
|
||||
# Redis数据库索引(默认为0)
|
||||
redis:
|
||||
jedis:
|
||||
pool:
|
||||
# 连接池最大连接数(使用负值表示没有限制)
|
||||
max-active: 8
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 5000ms
|
||||
# 默认的数据过期时间,主要用于shiro权限管理
|
||||
expire: 2592000
|
||||
|
||||
# logging settings
|
||||
logging:
|
||||
path: /var/tmp/website-blog-admin
|
||||
####################################自定义配置##########################################
|
||||
app:
|
||||
# 是否启用kaptcha验证码
|
||||
enableKaptcha: false
|
||||
# shiro配置项
|
||||
shiro:
|
||||
loginUrl: "/passport/login/"
|
||||
successUrl: "/"
|
||||
unauthorizedUrl: "/error/403"
|
||||
####################################自定义配置##########################################
|
62
blog-admin/src/main/resources/application-test.yml
Normal file
@ -0,0 +1,62 @@
|
||||
# Server settings
|
||||
server:
|
||||
tomcat:
|
||||
basedir: /var/tmp/website-blog-admin
|
||||
# SPRING PROFILES
|
||||
spring:
|
||||
profiles:
|
||||
include: [center-test]
|
||||
# 指定默认MimeMessage的编码,默认为: UTF-8
|
||||
mail:
|
||||
default-encoding: UTF-8
|
||||
# 指定SMTP server使用的协议,默认为: smtp
|
||||
protocol: smtp
|
||||
# 指定SMTP server host.
|
||||
host: xxx
|
||||
port: 465
|
||||
# 指定SMTP server的用户名.
|
||||
username: xxx
|
||||
# 指定SMTP server登录密码:
|
||||
password: xxx
|
||||
# 指定是否在启动时测试邮件服务器连接,默认为false
|
||||
test-connection: false
|
||||
properties:
|
||||
mail.smtp.auth: true
|
||||
# 腾讯企业邮箱 下两个配置必须!!!
|
||||
mail.smtp.ssl.enable: true
|
||||
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
|
||||
mail.smtp.socketFactory.port: 465
|
||||
mail.smtp.starttls.enable: true
|
||||
mail.smtp.starttls.required: true
|
||||
mail.smtp.connectiontimeout: 50000
|
||||
mail.smtp.timeout: 30000
|
||||
mail.smtp.writetimeout: 50000
|
||||
# Redis数据库索引(默认为0)
|
||||
redis:
|
||||
jedis:
|
||||
pool:
|
||||
# 连接池最大连接数(使用负值表示没有限制)
|
||||
max-active: 8
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 5000ms
|
||||
# 默认的数据过期时间,主要用于shiro权限管理
|
||||
expire: 2592000
|
||||
# logging settings
|
||||
logging:
|
||||
path: /var/tmp/website-blog-admin
|
||||
####################################自定义配置##########################################
|
||||
app:
|
||||
# 是否启用kaptcha验证码
|
||||
enableKaptcha: false
|
||||
# shiro配置项
|
||||
shiro:
|
||||
loginUrl: "/passport/login/"
|
||||
successUrl: "/"
|
||||
unauthorizedUrl: "/error/403"
|
||||
####################################自定义配置##########################################
|
78
blog-admin/src/main/resources/application.yml
Normal file
@ -0,0 +1,78 @@
|
||||
# Server settings
|
||||
server:
|
||||
port: 8085
|
||||
# HTTP请求和响应头的最大量,以字节为单位,默认值为4096字节,超过此长度的部分不予处理,一般8K。解决java.io.EOFException: null问题
|
||||
max-http-header-size: 8192
|
||||
use-forward-headers: true
|
||||
compression:
|
||||
enabled: true
|
||||
min-response-size: 1024
|
||||
mime-types: text/plain,text/css,text/xml,text/javascript,application/json,application/javascript,application/xml,application/xml+rss,application/x-javascript,application/x-httpd-php,image/jpeg,image/gif,image/png
|
||||
tomcat:
|
||||
remote-ip-header: X-Forwarded-for
|
||||
protocol-header: X-Forwarded-Proto
|
||||
port-header: X-Forwarded-Port
|
||||
uri-encoding: UTF-8
|
||||
# SPRING PROFILES
|
||||
spring:
|
||||
profiles:
|
||||
active: '@profileActive@'
|
||||
application:
|
||||
name: blog-admin
|
||||
freemarker:
|
||||
allow-request-override: false
|
||||
allow-session-override: false
|
||||
cache: false
|
||||
charset: UTF-8
|
||||
check-template-location: true
|
||||
content-type: text/html
|
||||
enabled: true
|
||||
expose-request-attributes: false
|
||||
expose-session-attributes: false
|
||||
expose-spring-macro-helpers: true
|
||||
prefer-file-system-access: true
|
||||
suffix: .ftl
|
||||
template-loader-path: classpath:/templates/
|
||||
settings:
|
||||
template_update_delay: 0
|
||||
default_encoding: UTF-8
|
||||
classic_compatible: true
|
||||
# HTTP ENCODING
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB
|
||||
max-request-size: 50MB
|
||||
http:
|
||||
encoding:
|
||||
enabled: true
|
||||
charset: UTF-8
|
||||
force: true
|
||||
messages:
|
||||
encoding: UTF-8
|
||||
jmx:
|
||||
enabled: true
|
||||
default-domain: agentservice
|
||||
resources:
|
||||
chain:
|
||||
strategy:
|
||||
content:
|
||||
enabled: true
|
||||
paths: /**
|
||||
banner:
|
||||
charset: UTF-8
|
||||
# MyBatis
|
||||
mybatis:
|
||||
type-aliases-package: com.zyd.blog.persistence.beans
|
||||
mapper-locations: classpath:/mybatis/*.xml
|
||||
# mapper
|
||||
mapper:
|
||||
mappers:
|
||||
- com.zyd.blog.plugin.BaseMapper
|
||||
not-empty: false
|
||||
identity: MYSQL
|
||||
# pagehelper
|
||||
pagehelper:
|
||||
helper-dialect: mysql
|
||||
reasonable: true
|
||||
support-methods-arguments: true
|
||||
params: count=countSql
|
17
blog-admin/src/main/resources/banner.txt
Normal file
@ -0,0 +1,17 @@
|
||||
${AnsiColor.BLUE}
|
||||
====================================================================================================================================
|
||||
|
||||
欢迎使用博客系统 - Powered By http://www.skying.cn
|
||||
|
||||
|
||||
______ _
|
||||
| ___ \ | |
|
||||
| |_/ / | | ___ __ _
|
||||
| ___ \ | | / _ \ / _` |
|
||||
| |_/ / | | | (_) | | (_| |
|
||||
\____/ |_| \___/ \__, |
|
||||
__/ |
|
||||
|___/
|
||||
|
||||
当前SpringBoot版本 :: ${spring-boot.version}
|
||||
====================================================================================================================================
|
56
blog-admin/src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!--定义日志文件的存储路径-->
|
||||
<!--<property name="LOG_HOME" value="/var/tmp/website-blog-admin"/>-->
|
||||
<springProperty scope="context" name="logdir" source="logging.path"/>
|
||||
|
||||
<!-- 控制台 appender -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] %-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--按天生成日志-->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<Prudent>true</Prudent>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>
|
||||
${logdir}/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
|
||||
</FileNamePattern>
|
||||
</rollingPolicy>
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>
|
||||
%d{yyyy-MM-dd HH:mm:ss} -%msg%n
|
||||
</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.core.env" level="ERROR"/>
|
||||
<logger name="us.codecraft.webmagic.downloader" level="WARN"/>
|
||||
<logger name="com.zyd.blog.framework.runner" level="INFO"/>
|
||||
|
||||
<!-- 测试环境+开发环境,日志级别为INFO且不写日志文件 -->
|
||||
<springProfile name="test,dev">
|
||||
<logger name="com.zyd.blog" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 生产环境. 日志级别为WARN且写日志文件-->
|
||||
<springProfile name="prod">
|
||||
<logger name="com.zyd.blog" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE" />
|
||||
</logger>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
</configuration>
|
17
blog-admin/src/main/resources/static/assets/css/bootstrap-treetable.css
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018/9/3 20:28
|
||||
*/
|
||||
.bootstrap-tree-table .treetable-indent {width:16px; height: 16px; display: inline-block; position: relative;}
|
||||
.bootstrap-tree-table .treetable-expander {width:16px; height: 16px; display: inline-block; position: relative; cursor: pointer;}
|
||||
.bootstrap-tree-table .treetable-selected{background: #f5f5f5 !important;}
|
||||
.bootstrap-tree-table .treetable-table{margin-bottom:0}
|
||||
.bootstrap-tree-table .treetable-table tbody {display:block;height:auto;overflow-y:auto;}
|
||||
.bootstrap-tree-table .treetable-table thead, .treetable-table tbody tr {display:table;width:100%;table-layout:fixed;}
|
||||
.bootstrap-tree-table .treetable-thead th{border: 0 !important;background:#f3f3f4 !important;border-radius: 4px;border-left:1px solid #e7eaec !important;border-bottom:2px solid #e7eaec !important;text-align: center;}
|
||||
.bootstrap-tree-table .treetable-thead tr :first-child{border-left:0 !important}
|
||||
.bootstrap-tree-table .treetable-tbody td{border: 0 !important;border-left:1px solid #e7eaec !important;border-bottom:1px solid #e7eaec !important;overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
|
||||
.bootstrap-tree-table .treetable-tbody tr :first-child{border-left:0 !important}
|
||||
.treetable-bars{padding:10px 0;}
|
9
blog-admin/src/main/resources/static/assets/css/jquery-confirm.min.css
vendored
Normal file
5168
blog-admin/src/main/resources/static/assets/css/zhyd.core.css
Normal file
BIN
blog-admin/src/main/resources/static/assets/images/favicon.ico
Normal file
After Width: | Height: | Size: 3.4 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1550121992911" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1116" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M620.757 170.667c-155.434 0-285.76 107.2-321.194 251.669a182.336 182.336 0 0 0-9.558-0.256c-113.024 0-204.672 91.627-204.672 204.672a203.797 203.797 0 0 0 59.947 144.64 203.733 203.733 0 0 0 144.725 59.861 204.437 204.437 0 0 0 144.79-60.202c32.106-32.107 50.133-75.094 55.317-122.838L416 642.39c-16.79-2.09-22.187-14.805-11.968-28.373L529.6 458.666c10.155-13.546 23.957-11.882 30.613 3.755l71.19 173.974c6.656 15.637-1.75 26.688-18.56 24.618l-55.126-5.077c-6.25 57.643-32.192 108.523-68.458 148.864A329.963 329.963 0 0 0 620.757 832c182.614 0 330.667-148.053 330.667-330.667 0-182.613-148.053-330.666-330.667-330.666z" fill="#1afa29" p-id="1117"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1550123290127" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9629" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M512 0L68.48 256v512L512 1024l443.52-256V256L512 0z m256 707.84c0 30.08-27.562667 55.04-65.237333 55.04-26.922667 0-57.642667-10.88-76.842667-34.56l-256-304.682667v284.16c0 30.762667-24.32 55.04-54.357333 55.04H312.32c-30.762667 0-55.04-25.6-55.04-55.04V316.16c0-30.08 26.88-55.04 64-55.04 27.562667 0 58.88 10.88 78.08 34.56l254.72 304.682667V316.16c0-30.762667 25.6-55.04 55.04-55.04h3.2c30.72 0 55.04 25.6 55.04 55.04v391.68H768z" fill="" p-id="9630"></path></svg>
|
After Width: | Height: | Size: 851 B |
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1550122232738" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6335" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M893.456828 709.055005" p-id="6336" fill="#1296db"></path><path d="M491.889987 337.939709" p-id="6337" fill="#1296db"></path><path d="M568.154951 338.993714" p-id="6338" fill="#1296db"></path><path d="M957.329555 316.566936c-23.74889-31.844266-72.608691-63.954591-133.394103-71.808466 5.588275-17.800411 9.174963-37.445844 10.657734-58.690704 2.850931-40.815591-2.993171-74.390267-3.242857-75.795267-2.979868-16.790408-16.110933-29.937846-32.893154-32.939203-2.39556-0.429789-4.796236-0.64059-7.172353-0.64059-14.251585 0-27.692712 7.505951-35.07996 20.09978-19.028379 32.253588-51.264571 67.782779-72.496128 75.150584-41.60456-26.2652-93.867878-40.437991-152.375409-41.195238-0.908696-0.062422-1.824555-0.092098-2.749624-0.092098-0.357134 0-0.712221 0.005117-1.065262 0.01228-0.355087-0.007163-0.708128-0.01228-1.065262-0.01228-0.927115 0-1.845021 0.029676-2.755764 0.092098-58.500369 0.760317-110.76164 14.930037-152.367223 41.195238-21.236674-7.369851-53.482075-42.913369-72.505337-75.172073-7.401574-12.55392-20.840654-20.045545-35.065634-20.047592-2.39249 0-4.805446 0.213871-7.218402 0.647753-16.753569 3.02387-29.870308 16.132422-32.846082 32.894178-1.76418 9.92914-11.889795 73.821309 7.175423 134.506437-60.670801 7.902994-109.437481 39.950897-133.155672 71.751161-16.750499 22.458501-22.535249 46.384423-16.291035 67.37141 12.311397 41.376363 61.907978 67.080791 129.432885 67.080791 26.872021 0 53.380768-4.174066 77.605495-11.95017l34.764781 258.95582c-1.578961 4.487198-3.849678 9.904581-6.208399 15.530718-8.450463 20.169365-18.97005 46.167482-18.97005 75.740008 0 72.982198 56.741305 124.853589 137.984654 124.853589l90.420359 0 2.130524 0 90.420359 0c81.243349 0 137.984654-51.871391 137.984654-124.853589 0-29.572526-10.519588-55.120388-18.97005-75.289754-2.358721-5.626137-4.629437-11.269671-6.208399-15.755846l34.764781-259.069407c24.226774 7.777127 50.734498 11.895935 77.607542 11.895935 67.528999 0 117.125581-25.736151 129.432885-67.115584C979.858664 362.936009 974.074937 339.02032 957.329555 316.566936zM934.624437 372.290051c-7.144724 24.01802-45.799092 38.002522-90.440825 38.002522-37.678134 0-79.624479-9.967002-110.462834-32.270984l-43.3626 322.997392c5.27412 23.605627 25.952068 54.469566 25.952068 88.106664 0 53.754275-43.561122 84.047208-97.303116 84.047208l-90.420359 0-2.130524 0-90.420359 0c-53.741995 0-97.303116-30.292933-97.303116-84.047208 0-33.637098 20.677948-64.113203 25.952068-87.718831L321.320193 378.213971c-30.843472 22.307052-72.780607 32.175817-110.461811 32.175817-44.63457 0-83.296101-14.035668-90.441848-38.051641-8.448416-28.390607 53.212946-88.713484 138.91177-88.708368 14.59951 0.002047 29.91124 1.74269 45.678342 5.700839-62.258972-53.657061-41.257659-171.950234-41.257659-171.950234s56.562227 96.627734 114.841561 96.627734c1.130753 0 2.891863-0.039909 4.02364-0.11461 37.637202-27.804253 84.059488-42.132586 145.081283-42.49279 61.021795 0.360204 107.272165 14.685467 144.905274 42.491767 1.13587 0.074701 2.490727 0.11154 3.625574 0.11154 58.277288 0 115.068735-96.630804 115.068735-96.630804s20.996197 118.284987-41.262776 171.942047c15.773242-3.960195 31.071669-5.711072 45.678342-5.711072C881.401258 283.607266 943.070806 343.901491 934.624437 372.290051z" p-id="6339" fill="#1296db"></path><path d="M669.376307 413.20695c0-14.808264-11.988032-26.818809-26.820855-26.818809-14.843056 0-26.825972 12.022825-26.825972 26.818809 0 14.830777 11.982916 26.829042 26.825972 26.829042C657.388275 440.035992 669.376307 428.025447 669.376307 413.20695z" p-id="6340" fill="#1296db"></path><path d="M404.962172 386.388141c-14.843056 0-26.825972 12.022825-26.825972 26.818809 0 14.830777 11.982916 26.829042 26.825972 26.829042 14.833847 0 26.820855-12.010545 26.820855-26.829042C431.78405 398.398686 419.796018 386.388141 404.962172 386.388141z" p-id="6341" fill="#1296db"></path><path d="M400.901693 751.926418c-14.842033 0-26.823925 12.022825-26.823925 26.816762 0 14.833847 11.981892 26.831089 26.823925 26.831089 14.836917 0 26.823925-12.010545 26.823925-26.831089C427.725618 763.936963 415.738609 751.926418 400.901693 751.926418z" p-id="6342" fill="#1296db"></path><path d="M653.713582 751.926418c-14.842033 0-26.823925 12.022825-26.823925 26.816762 0 14.833847 11.981892 26.831089 26.823925 26.831089 14.835893 0 26.823925-12.010545 26.823925-26.831089C680.537508 763.936963 668.549475 751.926418 653.713582 751.926418z" p-id="6343" fill="#1296db"></path></svg>
|
After Width: | Height: | Size: 4.7 KiB |
BIN
blog-admin/src/main/resources/static/assets/images/loading.gif
Normal file
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 336 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 78 KiB |
BIN
blog-admin/src/main/resources/static/assets/images/user.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
208
blog-admin/src/main/resources/static/assets/js/ajaxfileupload.js
Normal file
@ -0,0 +1,208 @@
|
||||
jQuery.extend({
|
||||
|
||||
|
||||
createUploadIframe: function (id, uri) {
|
||||
//create frame
|
||||
var frameId = 'jUploadFrame' + id;
|
||||
|
||||
if (window.ActiveXObject) {
|
||||
var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
|
||||
if (typeof uri == 'boolean') {
|
||||
io.src = 'javascript:false';
|
||||
}
|
||||
else if (typeof uri == 'string') {
|
||||
io.src = uri;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var io = document.createElement('iframe');
|
||||
io.id = frameId;
|
||||
io.name = frameId;
|
||||
}
|
||||
io.style.position = 'absolute';
|
||||
io.style.top = '-1000px';
|
||||
io.style.left = '-1000px';
|
||||
|
||||
document.body.appendChild(io);
|
||||
|
||||
return io
|
||||
},
|
||||
createUploadForm: function (id, fileElementId) {
|
||||
//create form
|
||||
var formId = 'jUploadForm' + id;
|
||||
var fileId = 'jUploadFile' + id;
|
||||
var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>');
|
||||
var oldElement = $('#' + fileElementId);
|
||||
var newElement = $(oldElement).clone();
|
||||
$(oldElement).attr('id', fileId);
|
||||
$(oldElement).before(newElement);
|
||||
$(oldElement).appendTo(form);
|
||||
//set attributes
|
||||
$(form).css('position', 'absolute');
|
||||
$(form).css('top', '-1200px');
|
||||
$(form).css('left', '-1200px');
|
||||
$(form).appendTo('body');
|
||||
return form;
|
||||
},
|
||||
addOtherRequestsToForm: function (form, data) {
|
||||
// add extra parameter
|
||||
var originalElement = $('<input type="hidden" name="" value="">');
|
||||
for (var key in data) {
|
||||
name = key;
|
||||
value = data[key];
|
||||
var cloneElement = originalElement.clone();
|
||||
cloneElement.attr({'name': name, 'value': value});
|
||||
$(cloneElement).appendTo(form);
|
||||
}
|
||||
return form;
|
||||
},
|
||||
|
||||
ajaxFileUpload: function (s) {
|
||||
// TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
|
||||
s = jQuery.extend({}, jQuery.ajaxSettings, s);
|
||||
var id = new Date().getTime()
|
||||
var form = jQuery.createUploadForm(id, s.fileElementId);
|
||||
if (s.data) form = jQuery.addOtherRequestsToForm(form, s.data);
|
||||
var io = jQuery.createUploadIframe(id, s.secureuri);
|
||||
var frameId = 'jUploadFrame' + id;
|
||||
var formId = 'jUploadForm' + id;
|
||||
// Watch for a new set of requests
|
||||
if (s.global && !jQuery.active++) {
|
||||
jQuery.event.trigger("ajaxStart");
|
||||
}
|
||||
var requestDone = false;
|
||||
// Create the request object
|
||||
var xml = {}
|
||||
if (s.global)
|
||||
jQuery.event.trigger("ajaxSend", [xml, s]);
|
||||
// Wait for a response to come back
|
||||
var uploadCallback = function (isTimeout) {
|
||||
var io = document.getElementById(frameId);
|
||||
try {
|
||||
if (io.contentWindow) {
|
||||
xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null;
|
||||
xml.responseXML = io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument : io.contentWindow.document;
|
||||
|
||||
} else if (io.contentDocument) {
|
||||
xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null;
|
||||
xml.responseXML = io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument : io.contentDocument.document;
|
||||
}
|
||||
} catch (e) {
|
||||
jQuery.handleError(s, xml, null, e);
|
||||
}
|
||||
if (xml || isTimeout == "timeout") {
|
||||
requestDone = true;
|
||||
var status;
|
||||
try {
|
||||
status = isTimeout != "timeout" ? "success" : "error";
|
||||
// Make sure that the request was successful or notmodified
|
||||
if (status != "error") {
|
||||
// process the data (runs the xml through httpData regardless of callback)
|
||||
var data = jQuery.uploadHttpData(xml, s.dataType);
|
||||
// If a local callback was specified, fire it and pass it the data
|
||||
if (s.success)
|
||||
s.success(data, status);
|
||||
|
||||
// Fire the global callback
|
||||
if (s.global)
|
||||
jQuery.event.trigger("ajaxSuccess", [xml, s]);
|
||||
} else
|
||||
jQuery.handleError(s, xml, status);
|
||||
} catch (e) {
|
||||
status = "error";
|
||||
console.log(xml);
|
||||
jQuery.handleError(s, xml, status, e);
|
||||
}
|
||||
|
||||
// The request was completed
|
||||
if (s.global)
|
||||
jQuery.event.trigger("ajaxComplete", [xml, s]);
|
||||
|
||||
// Handle the global AJAX counter
|
||||
if (s.global && !--jQuery.active)
|
||||
jQuery.event.trigger("ajaxStop");
|
||||
|
||||
// Process result
|
||||
if (s.complete)
|
||||
s.complete(xml, status);
|
||||
|
||||
jQuery(io).unbind()
|
||||
|
||||
setTimeout(function () {
|
||||
try {
|
||||
$(io).remove();
|
||||
$(form).remove();
|
||||
|
||||
} catch (e) {
|
||||
jQuery.handleError(s, xml, null, e);
|
||||
}
|
||||
|
||||
}, 100)
|
||||
|
||||
xml = null
|
||||
|
||||
}
|
||||
}
|
||||
// Timeout checker
|
||||
if (s.timeout > 0) {
|
||||
setTimeout(function () {
|
||||
// Check to see if the request is still happening
|
||||
if (!requestDone) uploadCallback("timeout");
|
||||
}, s.timeout);
|
||||
}
|
||||
try {
|
||||
// var io = $('#' + frameId);
|
||||
var form = $('#' + formId);
|
||||
$(form).attr('action', s.url);
|
||||
$(form).attr('method', 'POST');
|
||||
$(form).attr('target', frameId);
|
||||
if (form.encoding) {
|
||||
form.encoding = 'multipart/form-data';
|
||||
}
|
||||
else {
|
||||
form.enctype = 'multipart/form-data';
|
||||
}
|
||||
$(form).submit();
|
||||
|
||||
} catch (e) {
|
||||
jQuery.handleError(s, xml, null, e);
|
||||
}
|
||||
if (window.attachEvent) {
|
||||
document.getElementById(frameId).attachEvent('onload', uploadCallback);
|
||||
}
|
||||
else {
|
||||
document.getElementById(frameId).addEventListener('load', uploadCallback, false);
|
||||
}
|
||||
return {
|
||||
abort: function () {
|
||||
}
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
uploadHttpData: function (r, type) {
|
||||
var data = !type;
|
||||
data = type == "xml" || data ? r.responseXML : r.responseText;
|
||||
// If the type is "script", eval it in global context
|
||||
if (type == "script")
|
||||
jQuery.globalEval(data);
|
||||
// Get the JavaScript object, if JSON is used.
|
||||
if (type == "json") {
|
||||
// If you add mimetype in your response,
|
||||
// you have to delete the '<pre></pre>' tag.
|
||||
// The pre tag in Chrome has attribute, so have to use regex to remove
|
||||
var data = r.responseText;
|
||||
var rx = new RegExp("<pre.*?>(.*?)</pre>", "i");
|
||||
var am = rx.exec(data);
|
||||
//this is the desired data extracted
|
||||
var data = (am) ? am[1] : ""; //the only submatch or empty
|
||||
eval("data = " + data);
|
||||
}
|
||||
// evaluate scripts within html
|
||||
if (type == "html")
|
||||
jQuery("<div>").html(data).evalScripts();
|
||||
//alert($('param', data).each(function(){alert($(this).attr('value'));}));
|
||||
return data;
|
||||
}
|
||||
})
|
||||
|
535
blog-admin/src/main/resources/static/assets/js/bootstrap-treetable.js
vendored
Normal file
@ -0,0 +1,535 @@
|
||||
/**
|
||||
* bootstrapTreeTable
|
||||
* v0.0.1
|
||||
* @author swifly
|
||||
* @modify yadong.zhang
|
||||
* 修改data格式
|
||||
* 增加简单分页
|
||||
* 增加全选操作
|
||||
*/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$.fn.bootstrapTreeTable = function (options, param) {
|
||||
var target = $(this).data('bootstrap.tree.table');
|
||||
target = target ? target : $(this);
|
||||
// 如果是调用方法
|
||||
if (typeof options == 'string') {
|
||||
return $.fn.bootstrapTreeTable.methods[options](target, param);
|
||||
}
|
||||
// 如果是初始化组件
|
||||
options = $.extend({}, $.fn.bootstrapTreeTable.defaults, options || {});
|
||||
// 是否有radio或checkbox
|
||||
var hasSelectItem = false;
|
||||
target.data_list = null; //用于存放格式化后的数据-按父分组
|
||||
target.data_obj = null; //用于存放格式化后的数据-按id存对象
|
||||
// 在外层包装一下div,样式用的bootstrap-table的
|
||||
var _main_div = $("<div class='bootstrap-tree-table'></div>");
|
||||
target.before(_main_div);
|
||||
_main_div.append(target);
|
||||
target.addClass("table table-hover treetable-table");
|
||||
if (options.striped) {
|
||||
target.addClass('table-striped');
|
||||
}
|
||||
if (options.bordered) {
|
||||
target.addClass('table-bordered');
|
||||
}
|
||||
// 工具条在外层包装一下div,样式用的bootstrap-table的
|
||||
if (options.toolbar) {
|
||||
var _tool_div = $("<div class='treetable-bars pull-left'></div>");
|
||||
_tool_div.append($(options.toolbar));
|
||||
_main_div.before(_tool_div);
|
||||
}
|
||||
// 格式化数据,优化性能
|
||||
var formatData = function (data) {
|
||||
var _root = options.rootIdValue ? options.rootIdValue : null
|
||||
$.each(data, function (index, item) {
|
||||
// 添加一个默认属性,用来判断当前节点有没有被显示
|
||||
item.isShow = false;
|
||||
// 这里兼容几种常见Root节点写法
|
||||
// 默认的几种判断
|
||||
var _defaultRootFlag = item[options.parentId] == '0' ||
|
||||
item[options.parentId] == 0 ||
|
||||
item[options.parentId] == null ||
|
||||
item[options.parentId] == '';
|
||||
if (!item[options.parentId] || (_root ? (item[options.parentId] == options.rootIdValue) : _defaultRootFlag)) {
|
||||
if (!target.data_list["_root_"]) {
|
||||
target.data_list["_root_"] = [];
|
||||
}
|
||||
if (!target.data_obj["id_" + item[options.id]]) {
|
||||
target.data_list["_root_"].push(item);
|
||||
}
|
||||
} else {
|
||||
if (!target.data_list["_n_" + item[options.parentId]]) {
|
||||
target.data_list["_n_" + item[options.parentId]] = [];
|
||||
}
|
||||
if (!target.data_obj["id_" + item[options.id]]) {
|
||||
target.data_list["_n_" + item[options.parentId]].push(item);
|
||||
}
|
||||
}
|
||||
target.data_obj["id_" + item[options.id]] = item;
|
||||
});
|
||||
}
|
||||
// 得到根节点
|
||||
var getRootNodes = function () {
|
||||
return target.data_list["_root_"];
|
||||
};
|
||||
// 递归获取子节点并且设置子节点
|
||||
var handleNode = function (parentNode, lv, row_id, p_id, tbody) {
|
||||
var _ls = target.data_list["_n_" + parentNode[options.id]];
|
||||
var tr = renderRow(parentNode, _ls ? true : false, lv, row_id, p_id);
|
||||
tbody.append(tr);
|
||||
if (_ls) {
|
||||
$.each(_ls, function (i, item) {
|
||||
var _row_id = row_id + "_" + i
|
||||
handleNode(item, (lv + 1), _row_id, row_id, tbody)
|
||||
});
|
||||
}
|
||||
};
|
||||
// 绘制行
|
||||
var renderRow = function (item, isP, lv, row_id, p_id) {
|
||||
// 标记已显示
|
||||
item.isShow = true;
|
||||
item.row_id = row_id;
|
||||
item.p_id = p_id;
|
||||
item.lv = lv;
|
||||
var tr = $('<tr id="' + row_id + '" pid="' + p_id + '"></tr>');
|
||||
var _icon = options.expanderCollapsedClass;
|
||||
if (options.expandAll) {
|
||||
tr.css("display", "table");
|
||||
_icon = options.expanderExpandedClass;
|
||||
} else if (lv == 1) {
|
||||
tr.css("display", "table");
|
||||
_icon = (options.expandFirst) ? options.expanderExpandedClass : options.expanderCollapsedClass;
|
||||
} else if (lv == 2) {
|
||||
if (options.expandFirst) {
|
||||
tr.css("display", "table");
|
||||
} else {
|
||||
tr.css("display", "none");
|
||||
}
|
||||
_icon = options.expanderCollapsedClass;
|
||||
} else {
|
||||
tr.css("display", "none");
|
||||
_icon = options.expanderCollapsedClass;
|
||||
}
|
||||
$.each(options.columns, function (index, column) {
|
||||
// 判断有没有选择列
|
||||
if (column.field == 'selectItem') {
|
||||
hasSelectItem = true;
|
||||
var td = $('<td style="text-align:center;width:36px"></td>');
|
||||
if (column.radio) {
|
||||
var _ipt = $('<input name="select_item" type="radio" value="' + item[options.id] + '"></input>');
|
||||
td.append(_ipt);
|
||||
}
|
||||
if (column.checkbox) {
|
||||
var _ipt = $('<input name="select_item" type="checkbox" value="' + item[options.id] + '"></input>');
|
||||
td.append(_ipt);
|
||||
}
|
||||
tr.append(td);
|
||||
} else {
|
||||
// 过滤了特殊字符
|
||||
var value = getItemField(item, column.field, true);
|
||||
var td = $('<td title="' + value + '" name="' + column.field + '" style="' + ((column.width) ? ('width:' + column.width) : '') + ((column.align) ? (';text-align:' + column.align) : '') + '"></td>');
|
||||
// 增加formatter渲染
|
||||
if (column.formatter) {
|
||||
td.html(column.formatter.call(this, value, item, index));
|
||||
} else {
|
||||
td.text(value);
|
||||
}
|
||||
if (options.expandColumn == index) {
|
||||
if (!isP) {
|
||||
td.prepend('<span class="treetable-expander"></span>')
|
||||
} else {
|
||||
td.prepend('<span class="treetable-expander ' + _icon + '"></span>')
|
||||
}
|
||||
for (var int = 0; int < (lv - 1); int++) {
|
||||
td.prepend('<span class="treetable-indent"></span>')
|
||||
}
|
||||
}
|
||||
tr.append(td);
|
||||
}
|
||||
});
|
||||
return tr;
|
||||
}
|
||||
// 加载完数据后渲染表格
|
||||
var renderTable = function (data) {
|
||||
|
||||
var tbody = target.find("tbody");
|
||||
var thead = target.find("thead");
|
||||
// 加载完数据先清空
|
||||
tbody.html("");
|
||||
if (!data || data.total <= 0 || !data.rows || data.rows.length <= 0) {
|
||||
var _empty = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">没有找到匹配的记录</div></td></tr>'
|
||||
tbody.html(_empty);
|
||||
return;
|
||||
}
|
||||
data = data.rows;
|
||||
// 格式化数据
|
||||
formatData(data);
|
||||
// 开始绘制
|
||||
var rootNode = getRootNodes();
|
||||
if (rootNode) {
|
||||
$.each(rootNode, function (i, item) {
|
||||
var _row_id = "row_id_" + i;
|
||||
handleNode(item, 1, _row_id, "row_root", tbody);
|
||||
});
|
||||
}
|
||||
// 下边的操作主要是为了查询时让一些没有根节点的节点显示
|
||||
$.each(data, function (i, item) {
|
||||
if (!item.isShow) {
|
||||
var tr = renderRow(item, false, 1, "", "");
|
||||
tbody.append(tr);
|
||||
}
|
||||
});
|
||||
target.append(tbody);
|
||||
//动态设置表头宽度
|
||||
thead.css("width", tbody.children(":first").css("width"));
|
||||
registerExpanderEvent();
|
||||
registerRowClickEvent();
|
||||
};
|
||||
// 注册行点击选中事件
|
||||
var registerRowClickEvent = function () {
|
||||
if(!options.clickToSelect) {
|
||||
return false;
|
||||
}
|
||||
target.find("tbody").find("tr").unbind();
|
||||
target.find("tbody").find("tr").click(function () {
|
||||
if (hasSelectItem) {
|
||||
var _ipt = $(this).find("input[name='select_item']");
|
||||
if (_ipt.attr("type") == "radio" || _ipt.attr("type") == "checkbox") {
|
||||
_ipt.prop('checked', true);
|
||||
target.find("tbody").find("tr").removeClass("treetable-selected");
|
||||
$(this).addClass("treetable-selected");
|
||||
} else {
|
||||
if (_ipt.prop('checked')) {
|
||||
_ipt.prop('checked', false);
|
||||
$(this).removeClass("treetable-selected");
|
||||
} else {
|
||||
_ipt.prop('checked', true);
|
||||
$(this).addClass("treetable-selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 注册小图标点击事件--展开缩起
|
||||
var registerExpanderEvent = function () {
|
||||
target.find("tbody").find("tr").find(".treetable-expander").unbind();
|
||||
target.find("tbody").find("tr").find(".treetable-expander").click(function () {
|
||||
var _isExpanded = $(this).hasClass(options.expanderExpandedClass);
|
||||
var _isCollapsed = $(this).hasClass(options.expanderCollapsedClass);
|
||||
if (_isExpanded || _isCollapsed) {
|
||||
var tr = $(this).parent().parent();
|
||||
var row_id = tr.attr("id");
|
||||
var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']"); //下所有
|
||||
if (_isExpanded) {
|
||||
$(this).removeClass(options.expanderExpandedClass);
|
||||
$(this).addClass(options.expanderCollapsedClass);
|
||||
if (_ls && _ls.length > 0) {
|
||||
$.each(_ls, function (index, item) {
|
||||
$(item).css("display", "none");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass(options.expanderCollapsedClass);
|
||||
$(this).addClass(options.expanderExpandedClass);
|
||||
if (_ls && _ls.length > 0) {
|
||||
$.each(_ls, function (index, item) {
|
||||
// 父icon
|
||||
var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander");
|
||||
if (_p_icon.hasClass(options.expanderExpandedClass)) {
|
||||
$(item).css("display", "table");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 根据column的field获取实际的值
|
||||
var getItemField = function (item, field, escape) {
|
||||
var value = item;
|
||||
|
||||
if (typeof field !== 'string' || item.hasOwnProperty(field)) {
|
||||
return escape ? escapeHtml(item[field]) : item[field]
|
||||
}
|
||||
|
||||
var props = field.split('.');
|
||||
for (var p in props) {
|
||||
value = value && value[props[p]]
|
||||
}
|
||||
return escape ? escapeHtml(value) : value
|
||||
};
|
||||
var escapeHtml = function(text){
|
||||
if (typeof text === 'string') {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/`/g, '`')
|
||||
}
|
||||
return text
|
||||
};
|
||||
// 加载数据前
|
||||
target.load = function (parms) {
|
||||
// 加载数据前先清空
|
||||
target.data_list = {};
|
||||
target.data_obj = {};
|
||||
// 加载数据前先清空
|
||||
target.html("");
|
||||
// 构造表头
|
||||
var thr = $('<tr></tr>');
|
||||
$.each(options.columns, function (i, item) {
|
||||
var th = null;
|
||||
// 判断有没有选择列
|
||||
if (i == 0 && item.field == 'selectItem') {
|
||||
hasSelectItem = true;
|
||||
th = $('<th style="width:36px"><input name="btSelectAll" id="btSelectAll" type="checkbox" ></th>');
|
||||
} else {
|
||||
th = $('<th style="' + ((item.width) ? ('width:' + item.width) : '') + '"></th>');
|
||||
}
|
||||
th.text(item.title);
|
||||
thr.append(th);
|
||||
});
|
||||
var thead = $('<thead class="treetable-thead"></thead>');
|
||||
thead.append(thr);
|
||||
target.append(thead);
|
||||
// 构造表体
|
||||
var tbody = $('<tbody class="treetable-tbody"></tbody>');
|
||||
target.append(tbody);
|
||||
// 添加加载loading
|
||||
var _loading = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">正在努力地加载数据中,请稍候……</div></td></tr>'
|
||||
tbody.html(_loading);
|
||||
// 默认高度
|
||||
if (options.height) {
|
||||
tbody.css("height", options.height);
|
||||
}
|
||||
if (options.url) {
|
||||
$.ajax({
|
||||
type: options.type,
|
||||
url: options.url,
|
||||
data: parms ? parms : options.ajaxParams,
|
||||
dataType: "JSON",
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
renderTable(data);
|
||||
|
||||
initPagination(options, data);
|
||||
},
|
||||
error: function (xhr, textStatus) {
|
||||
var _errorMsg = '<tr><td colspan="' + options.columns.length + '"><div style="display: block;text-align: center;">' + xhr.responseText + '</div></td></tr>'
|
||||
tbody.html(_errorMsg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
renderTable(options.data);
|
||||
}
|
||||
|
||||
/* 全选 */
|
||||
var $selectAll = target.find('[name="btSelectAll"]')
|
||||
$selectAll.off('click').on('click', function (e) {
|
||||
var checked = $(e.currentTarget).prop('checked')
|
||||
checked ? checkAll() : uncheckAll();
|
||||
});
|
||||
function checkAll() {
|
||||
checkAll_(true);
|
||||
}
|
||||
|
||||
function uncheckAll() {
|
||||
checkAll_(false);
|
||||
}
|
||||
function checkAll_(checked) {
|
||||
var rows;
|
||||
if (!checked) {
|
||||
rows = target.bootstrapTreeTable('getSelections');
|
||||
}
|
||||
target.find("input[name=select_item]").filter(':enabled').prop('checked', checked);
|
||||
if (checked) {
|
||||
rows = target.bootstrapTreeTable('getSelections');
|
||||
}
|
||||
target.trigger(checked ? 'checkAll' : 'uncheckAll', rows)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 初始化分页
|
||||
*
|
||||
* @param options
|
||||
* @param data
|
||||
* @param curPage
|
||||
*/
|
||||
var initPagination = function (options, data, curPage) {
|
||||
if(!options.pagination) {
|
||||
return false;
|
||||
}
|
||||
var pageSizeArr = options.pageSize, total = data.total, pageSize = pageSizeArr[0], currentPage = curPage ? curPage : options.curPage,
|
||||
totalPage = (total >= pageSize) ? ((total / pageSize) + ((total % pageSize) > 0 ? 1 : 0)) : 1, i;
|
||||
if (totalPage <= 1) {
|
||||
return false;
|
||||
}
|
||||
// 可选的pageSize
|
||||
var pageSizeOptions = '';
|
||||
for (i in pageSizeArr) {
|
||||
pageSizeOptions += '<option value="' + pageSizeArr[i] + '" ' + (pageSize === pageSizeArr[i] ? 'selected="selected"' : '') + '>' + pageSizeArr[i] + '</option>';
|
||||
}
|
||||
// page item
|
||||
var pageItems = '';
|
||||
if(currentPage > 1) {
|
||||
pageItems += '<li class="page-item"><a class="page-link" href="javascript:;" data-id="' + (currentPage - 1) + '">«</a></li>';
|
||||
}
|
||||
|
||||
for (i = 1; i < totalPage; i++) {
|
||||
pageItems += '<li class="page-item {active}"><a class="page-link" href="javascript:;" data-id="' + i + '">' + i + '</a></li>';
|
||||
pageItems = pageItems.replace("{active}", (currentPage === i) ? 'active' : '');
|
||||
}
|
||||
if(currentPage !== totalPage) {
|
||||
pageItems += '<li class="page-item"><a class="page-link" href="javascript:;" data-id="' + (currentPage + 1) + '">»</a></li>';
|
||||
}
|
||||
|
||||
// 模板 --> 替换
|
||||
var tpl = '<div class="row table-pagination-box"><div class="col-sm-5 col-md-5"><div class="table-pagination-info"role="status"aria-live="polite">共{totalPage}页{total}条数据,每页显示<select name="pageSize"aria-controls="sampleTable"class="form-control form-control-sm table-pagesize">{pageSizeOption}</select>条记录</div></div><div class="col-sm-7 col-md-7"style="display: {display}"><ul class="pagination">{pageItem}</ul></div></div>';
|
||||
tpl = tpl.replace("{total}", total);
|
||||
tpl = tpl.replace("{totalPage}", totalPage);
|
||||
tpl = tpl.replace("{pageSizeOption}", pageSizeOptions);
|
||||
tpl = tpl.replace("{display}", (totalPage > 1) ? 'block' : 'none');
|
||||
tpl = tpl.replace("{pageItem}", pageItems);
|
||||
target.after(tpl);
|
||||
};
|
||||
// 添加数据刷新表格
|
||||
target.appendData = function (data) {
|
||||
// 下边的操作主要是为了查询时让一些没有根节点的节点显示
|
||||
$.each(data, function (i, item) {
|
||||
var _data = target.data_obj["id_" + item[options.id]];
|
||||
var _p_data = target.data_obj["id_" + item[options.parentId]];
|
||||
var _c_list = target.data_list["_n_" + item[options.parentId]];
|
||||
var row_id = ""; //行id
|
||||
var p_id = ""; //父行id
|
||||
var _lv = 1; //如果没有父就是1默认显示
|
||||
var tr; //要添加行的对象
|
||||
if (_data && _data.row_id && _data.row_id != "") {
|
||||
row_id = _data.row_id; // 如果已经存在了,就直接引用原来的
|
||||
}
|
||||
if (_p_data) {
|
||||
p_id = _p_data.row_id;
|
||||
if (row_id == "") {
|
||||
var _tmp = 0
|
||||
if (_c_list && _c_list.length > 0) {
|
||||
_tmp = _c_list.length;
|
||||
}
|
||||
row_id = _p_data.row_id + "_" + _tmp;
|
||||
}
|
||||
_lv = _p_data.lv + 1; //如果有父
|
||||
// 绘制行
|
||||
tr = renderRow(item, false, _lv, row_id, p_id);
|
||||
|
||||
var _p_icon = $("#" + _p_data.row_id).children().eq(options.expandColumn).find(".treetable-expander");
|
||||
var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass);
|
||||
var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass);
|
||||
// 父节点有没有展开收缩按钮
|
||||
if (_isExpanded || _isCollapsed) {
|
||||
// 父节点展开状态显示新加行
|
||||
if (_isExpanded) {
|
||||
tr.css("display", "table");
|
||||
}
|
||||
} else {
|
||||
// 父节点没有展开收缩按钮则添加
|
||||
_p_icon.addClass(options.expanderCollapsedClass);
|
||||
}
|
||||
|
||||
if (_data) {
|
||||
$("#" + _data.row_id).before(tr);
|
||||
$("#" + _data.row_id).remove();
|
||||
} else {
|
||||
// 计算父的同级下一行
|
||||
var _tmp_ls = _p_data.row_id.split("_");
|
||||
var _p_next = _p_data.row_id.substring(0, _p_data.row_id.length - 1) + (parseInt(_tmp_ls[_tmp_ls.length - 1]) + 1);
|
||||
// 画上
|
||||
$("#" + _p_next).before(tr);
|
||||
}
|
||||
} else {
|
||||
tr = renderRow(item, false, _lv, row_id, p_id);
|
||||
if (_data) {
|
||||
$("#" + _data.row_id).before(tr);
|
||||
$("#" + _data.row_id).remove();
|
||||
} else {
|
||||
// 画上
|
||||
var tbody = target.find("tbody");
|
||||
tbody.append(tr);
|
||||
}
|
||||
}
|
||||
item.isShow = true;
|
||||
formatData([item]);
|
||||
});
|
||||
registerExpanderEvent();
|
||||
registerRowClickEvent();
|
||||
}
|
||||
// 加载数据
|
||||
target.load();
|
||||
target.data('bootstrap.tree.table', target);
|
||||
return target;
|
||||
};
|
||||
|
||||
// 组件方法封装........
|
||||
$.fn.bootstrapTreeTable.methods = {
|
||||
// 为了兼容bootstrap-table的写法,统一返回数组,这里返回了表格显示列的数据
|
||||
getSelections: function (target, data) {
|
||||
// 所有被选中的记录input
|
||||
var _ipts = target.find("tbody").find("tr").find("input[name='select_item']:checked");
|
||||
var chk_value = [];
|
||||
_ipts.each(function (i, v) {
|
||||
var _ipt = $(v);
|
||||
if (_ipt.attr("type") == "radio" || _ipt.attr("type") == "checkbox") {
|
||||
var _data = target.data_obj["id_" + _ipt.val()];
|
||||
chk_value.push(_data);
|
||||
} else {
|
||||
_ipt.each(function (_i, _item) {
|
||||
var _data = target.data_obj["id_" + $(_item).val()];
|
||||
chk_value.push(_data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return chk_value;
|
||||
},
|
||||
// 刷新记录
|
||||
refresh: function (target, parms) {
|
||||
if (parms) {
|
||||
target.load(parms);
|
||||
} else {
|
||||
target.load();
|
||||
}
|
||||
},
|
||||
// 添加数据到表格
|
||||
appendData: function (target, data) {
|
||||
if (data) {
|
||||
target.appendData(data);
|
||||
}
|
||||
}
|
||||
// 组件的其他方法也可以进行类似封装........
|
||||
};
|
||||
|
||||
$.fn.bootstrapTreeTable.defaults = {
|
||||
id: 'id', // 选取记录返回的值,用于设置父子关系
|
||||
parentId: 'parentId', // 用于设置父子关系
|
||||
rootIdValue: null, //设置根节点id值----可指定根节点,默认为null,"",0,"0"
|
||||
data: null, // 构造table的数据集合
|
||||
type: "POST", // 请求数据的ajax类型
|
||||
url: null, // 请求数据的ajax的url
|
||||
ajaxParams: {}, // 请求数据的ajax的data属性
|
||||
expandColumn: 1, // 在哪一列上面显示展开按钮
|
||||
expandAll: false, // 是否全部展开
|
||||
expandFirst: false, // 是否默认第一级展开--expandAll为false时生效
|
||||
striped: false, // 是否各行渐变色
|
||||
bordered: true, // 是否显示边框
|
||||
columns: [],
|
||||
toolbar: null, //顶部工具条
|
||||
height: 0,
|
||||
pagination: false, // 是否启用分页
|
||||
pageSize: [20, 30, 40, 50], // 可选的分页大小
|
||||
clickToSelect: false,
|
||||
expanderExpandedClass: 'fa fa-folder-open-o fa-fw',// 展开的按钮的图标
|
||||
expanderCollapsedClass: 'fa fa-folder-o fa-fw',// 缩起的按钮的图标
|
||||
curPage: 1
|
||||
};
|
||||
})(jQuery);
|
@ -0,0 +1,91 @@
|
||||
/*jslint newcap: true */
|
||||
/*global inlineAttachment: false */
|
||||
/**
|
||||
* CodeMirror version for inlineAttachment
|
||||
*
|
||||
* Call inlineAttachment.attach(editor) to attach to a codemirror instance
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var codeMirrorEditor = function(instance) {
|
||||
|
||||
if (!instance.getWrapperElement) {
|
||||
throw "Invalid CodeMirror object given";
|
||||
}
|
||||
|
||||
this.codeMirror = instance;
|
||||
};
|
||||
|
||||
codeMirrorEditor.prototype.getValue = function() {
|
||||
return this.codeMirror.getValue();
|
||||
};
|
||||
|
||||
codeMirrorEditor.prototype.insertValue = function(val) {
|
||||
this.codeMirror.replaceSelection(val);
|
||||
};
|
||||
|
||||
codeMirrorEditor.prototype.setValue = function(val) {
|
||||
var cursor = this.codeMirror.getCursor();
|
||||
this.codeMirror.setValue(val);
|
||||
this.codeMirror.setCursor(cursor);
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach InlineAttachment to CodeMirror
|
||||
*
|
||||
* @param {CodeMirror} codeMirror
|
||||
*/
|
||||
codeMirrorEditor.attach = function(codeMirror, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var editor = new codeMirrorEditor(codeMirror),
|
||||
inlineattach = new inlineAttachment(options, editor),
|
||||
el = codeMirror.getWrapperElement();
|
||||
|
||||
el.addEventListener('paste', function(e) {
|
||||
inlineattach.onPaste(e);
|
||||
}, false);
|
||||
|
||||
codeMirror.setOption('onDragEvent', function(data, e) {
|
||||
if (e.type === "drop") {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return inlineattach.onDrop(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
inlineAttachment.editors.codemirror3 = codeMirrorEditor;
|
||||
|
||||
var codeMirrorEditor4 = function(instance) {
|
||||
codeMirrorEditor.call(this, instance);
|
||||
};
|
||||
|
||||
codeMirrorEditor4.attach = function(codeMirror, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var editor = new codeMirrorEditor(codeMirror),
|
||||
inlineattach = new inlineAttachment(options, editor),
|
||||
el = codeMirror.getWrapperElement();
|
||||
|
||||
el.addEventListener('paste', function(e) {
|
||||
inlineattach.onPaste(e);
|
||||
}, false);
|
||||
|
||||
codeMirror.on('drop', function(data, e) {
|
||||
if (inlineattach.onDrop(e)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
inlineAttachment.editors.codemirror4 = codeMirrorEditor4;
|
||||
|
||||
})();
|
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* gentelella前端框架的核心js类 (https://colorlib.com/polygon/gentelella/index.html)
|
||||
* @date 2019-01-29
|
||||
*/
|
||||
var gentelella = window.gentelella || {
|
||||
initSidebar: function () {
|
||||
var a = function () {
|
||||
$RIGHT_COL.css("min-height", $(window).height());
|
||||
var a = $BODY.outerHeight(),
|
||||
b = $BODY.hasClass("footer_fixed") ? -10 : $FOOTER.height(),
|
||||
c = $LEFT_COL.eq(1).height() + $SIDEBAR_FOOTER.height(),
|
||||
d = a < c ? c : a;
|
||||
d -= $NAV_MENU.height() + b, $RIGHT_COL.css("min-height", d)
|
||||
};
|
||||
$SIDEBAR_MENU.find("a").on("click", function (b) {
|
||||
var c = $(this).parent();
|
||||
c.is(".active") ? (c.removeClass("active active-sm"), $("ul:first", c).slideUp(function () {
|
||||
a()
|
||||
})) : (c.parent().is(".child_menu") ? $BODY.is(".nav-sm") && ($SIDEBAR_MENU.find("li").removeClass("active active-sm"), $SIDEBAR_MENU.find("li ul").slideUp()) : ($SIDEBAR_MENU.find("li").removeClass("active active-sm"), $SIDEBAR_MENU.find("li ul").slideUp()), c.addClass("active"), $("ul:first", c).slideDown(function () {
|
||||
a()
|
||||
}))
|
||||
}), $MENU_TOGGLE.on("click", function () {
|
||||
$BODY.hasClass("nav-md") ? ($SIDEBAR_MENU.find("li.active ul").hide(), $SIDEBAR_MENU.find("li.active").addClass("active-sm").removeClass("active")) : ($SIDEBAR_MENU.find("li.active-sm ul").show(), $SIDEBAR_MENU.find("li.active-sm").addClass("active").removeClass("active-sm")), $BODY.toggleClass("nav-md nav-sm"), a()
|
||||
}), $SIDEBAR_MENU.find('a[href="' + CURRENT_URL + '"]').parent("li").addClass("current-page"), $SIDEBAR_MENU.find("a").filter(function () {
|
||||
return this.href == CURRENT_URL
|
||||
}).parent("li").addClass("current-page").parents("ul").slideDown(function () {
|
||||
a()
|
||||
}).parent().addClass("active"), $(window).smartresize(function () {
|
||||
a()
|
||||
}), a(), $.fn.mCustomScrollbar && $(".menu_fixed").mCustomScrollbar({
|
||||
autoHideScrollbar: !0,
|
||||
theme: "minimal",
|
||||
mouseWheel: {
|
||||
preventDefault: !0
|
||||
}
|
||||
})
|
||||
},
|
||||
initDaterangepicker: function () {
|
||||
$('.myDatepicker').datetimepicker({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
ignoreReadonly: true,
|
||||
allowInputToggle: true
|
||||
});
|
||||
},
|
||||
initValidator: function () {
|
||||
"undefined" != typeof validator && (validator.message.date = "not a real date", $("form").on("blur", "input[required], input.optional, select.required", validator.checkField).on("change", "select.required", validator.checkField).on("keypress", "input[required][pattern]", validator.keypress), $(".multi.required").on("keyup blur", "input", function () {
|
||||
validator.checkField.apply($(this).siblings().last()[0])
|
||||
}), $("form").submit(function (a) {
|
||||
a.preventDefault();
|
||||
var b = !0;
|
||||
return validator.checkAll($(this)) || (b = !1), b && this.submit(), !1
|
||||
}));
|
||||
},
|
||||
initHelloMsg: function () {
|
||||
var $helloMsg = $("#hello_msg");
|
||||
var now = new Date();
|
||||
var nowHours = now.getHours();
|
||||
$helloMsg.html((nowHours >= 0 && nowHours <= 5) ? "凌晨好" : (nowHours > 5 && nowHours <= 9) ? "早上好" : ((nowHours > 9 && nowHours <= 12) ? "上午好" : ((nowHours > 12 && nowHours <= 13) ? "中午好" : ((nowHours > 13 && nowHours <= 18) ? "下午好" : "晚上好"))));
|
||||
},
|
||||
initSwitchery: function (delay) {
|
||||
setTimeout(function () {
|
||||
var elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
|
||||
elems.forEach(function (html) {
|
||||
var switchery = new Switchery(html, {
|
||||
color: '#26B99A',
|
||||
size: 'small'
|
||||
});
|
||||
});
|
||||
}, delay || 0);
|
||||
},
|
||||
initiICheck: function () {
|
||||
$("input[type=checkbox], input[type=radio]").iCheck({
|
||||
checkboxClass: 'icheckbox_square-green',
|
||||
radioClass: 'iradio_square-green',
|
||||
increaseArea: '20%' // optional
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function gd(a, b, c) {
|
||||
return new Date(a, b - 1, c).getTime()
|
||||
}
|
||||
|
||||
!function (a, b) {
|
||||
var c = function (a, b, c) {
|
||||
var d;
|
||||
return function () {
|
||||
function h() {
|
||||
c || a.apply(f, g), d = null
|
||||
}
|
||||
|
||||
var f = this,
|
||||
g = arguments;
|
||||
d ? clearTimeout(d) : c && a.apply(f, g), d = setTimeout(h, b || 100)
|
||||
}
|
||||
};
|
||||
jQuery.fn[b] = function (a) {
|
||||
return a ? this.bind("resize", c(a)) : this.trigger(b)
|
||||
}
|
||||
}(jQuery, "smartresize");
|
||||
|
||||
var CURRENT_URL = window.location.href.split("#")[0].split("?")[0],
|
||||
$BODY = $("body"),
|
||||
$MENU_TOGGLE = $("#menu_toggle"),
|
||||
$SIDEBAR_MENU = $("#sidebar-menu"),
|
||||
$SIDEBAR_FOOTER = $(".sidebar-footer"),
|
||||
$LEFT_COL = $(".left_col"),
|
||||
$RIGHT_COL = $(".right_col"),
|
||||
$NAV_MENU = $(".nav_menu"),
|
||||
$FOOTER = $("footer"),
|
||||
randNum = function () {
|
||||
return Math.floor(21 * Math.random()) + 20
|
||||
};
|
||||
$(document).ready(function () {
|
||||
$(".collapse-link").on("click", function () {
|
||||
var a = $(this).closest(".x_panel"),
|
||||
b = $(this).find("i"),
|
||||
c = a.find(".x_content");
|
||||
a.attr("style") ? c.slideToggle(200, function () {
|
||||
a.removeAttr("style")
|
||||
}) : (c.slideToggle(200), a.css("height", "auto")), b.toggleClass("fa-chevron-up fa-chevron-down")
|
||||
}), $(".close-link").click(function () {
|
||||
var a = $(this).closest(".x_panel");
|
||||
a.remove()
|
||||
});
|
||||
}), $(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
container: "body"
|
||||
})
|
||||
}), $(".progress .progress-bar")[0] && $(".progress .progress-bar").progressbar(), $(document).ready(function () {
|
||||
if ($(".js-switch")[0]) {
|
||||
var a = Array.prototype.slice.call(document.querySelectorAll(".js-switch"));
|
||||
a.forEach(function (a) {
|
||||
new Switchery(a, {
|
||||
color: "#26B99A"
|
||||
})
|
||||
})
|
||||
}
|
||||
}), $(document).ready(function () {
|
||||
gentelella.initiICheck();
|
||||
});
|
||||
"undefined" != typeof NProgress && ($(document).ready(function () {
|
||||
NProgress.start()
|
||||
}), $(window).load(function () {
|
||||
NProgress.done()
|
||||
}));
|
||||
$(document).ready(function () {
|
||||
// 工具提示
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
// 图片预览
|
||||
$(".showImage").fancybox();
|
||||
/* 自定义下拉 div */
|
||||
$(".custom-dropdown").on("click", function () {
|
||||
var a = $(this).closest(".custom-panel"),
|
||||
b = $(this).find("i"),
|
||||
c = a.find(".custom-container");
|
||||
a.attr("style") ? c.slideToggle(200, function () {
|
||||
a.removeAttr("style")
|
||||
}) : (c.slideToggle(200), a.css("height", "auto")), b.toggleClass("fa-angle-double-up fa-angle-double-down")
|
||||
});
|
||||
|
||||
$(".showContent").click(function () {
|
||||
$(this).toggleClass('fa-plus-square fa-minus-square');
|
||||
$(".disable-content").slideToggle(400);
|
||||
});
|
||||
gentelella.initSidebar();
|
||||
gentelella.initDaterangepicker();
|
||||
gentelella.initValidator();
|
||||
gentelella.initHelloMsg();
|
||||
});
|
@ -0,0 +1,399 @@
|
||||
/*jslint newcap: true */
|
||||
/*global XMLHttpRequest: false, FormData: false */
|
||||
/*
|
||||
* Inline Text Attachment
|
||||
*
|
||||
* Author: Roy van Kaathoven
|
||||
* Contact: ik@royvankaathoven.nl
|
||||
*/
|
||||
(function(document, window) {
|
||||
'use strict';
|
||||
|
||||
var inlineAttachment = function(options, instance) {
|
||||
this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults);
|
||||
this.editor = instance;
|
||||
this.filenameTag = '{filename}';
|
||||
this.lastValue = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Will holds the available editors
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
inlineAttachment.editors = {};
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
inlineAttachment.util = {
|
||||
|
||||
/**
|
||||
* Simple function to merge the given objects
|
||||
*
|
||||
* @param {Object[]} object Multiple object parameters
|
||||
* @returns {Object}
|
||||
*/
|
||||
merge: function() {
|
||||
var result = {};
|
||||
for (var i = arguments.length - 1; i >= 0; i--) {
|
||||
var obj = arguments[i];
|
||||
for (var k in obj) {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
result[k] = obj[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a line of text at the bottom, ensuring there aren't unnecessary newlines
|
||||
*
|
||||
* @param {String} appended Current content
|
||||
* @param {String} previous Value which should be appended after the current content
|
||||
*/
|
||||
appendInItsOwnLine: function(previous, appended) {
|
||||
return (previous + "\n\n[[D]]" + appended)
|
||||
.replace(/(\n{2,})\[\[D\]\]/, "\n\n")
|
||||
.replace(/^(\n*)/, "");
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts the given value at the current cursor position of the textarea element
|
||||
*
|
||||
* @param {HtmlElement} el
|
||||
* @param {String} value Text which will be inserted at the cursor position
|
||||
*/
|
||||
insertTextAtCursor: function(el, text) {
|
||||
var scrollPos = el.scrollTop,
|
||||
strPos = 0,
|
||||
browser = false,
|
||||
range;
|
||||
|
||||
if ((el.selectionStart || el.selectionStart === '0')) {
|
||||
browser = "ff";
|
||||
} else if (document.selection) {
|
||||
browser = "ie";
|
||||
}
|
||||
|
||||
if (browser === "ie") {
|
||||
el.focus();
|
||||
range = document.selection.createRange();
|
||||
range.moveStart('character', -el.value.length);
|
||||
strPos = range.text.length;
|
||||
} else if (browser === "ff") {
|
||||
strPos = el.selectionStart;
|
||||
}
|
||||
|
||||
var front = (el.value).substring(0, strPos);
|
||||
var back = (el.value).substring(strPos, el.value.length);
|
||||
el.value = front + text + back;
|
||||
strPos = strPos + text.length;
|
||||
if (browser === "ie") {
|
||||
el.focus();
|
||||
range = document.selection.createRange();
|
||||
range.moveStart('character', -el.value.length);
|
||||
range.moveStart('character', strPos);
|
||||
range.moveEnd('character', 0);
|
||||
range.select();
|
||||
} else if (browser === "ff") {
|
||||
el.selectionStart = strPos;
|
||||
el.selectionEnd = strPos;
|
||||
el.focus();
|
||||
}
|
||||
el.scrollTop = scrollPos;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Default configuration options
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
inlineAttachment.defaults = {
|
||||
/**
|
||||
* URL where the file will be send
|
||||
*/
|
||||
uploadUrl: 'upload_attachment.php',
|
||||
|
||||
/**
|
||||
* Which method will be used to send the file to the upload URL
|
||||
*/
|
||||
uploadMethod: 'POST',
|
||||
|
||||
/**
|
||||
* Name in which the file will be placed
|
||||
*/
|
||||
uploadFieldName: 'file',
|
||||
|
||||
/**
|
||||
* Extension which will be used when a file extension could not
|
||||
* be detected
|
||||
*/
|
||||
defaultExtension: 'png',
|
||||
|
||||
/**
|
||||
* JSON field which refers to the uploaded file URL
|
||||
*/
|
||||
jsonFieldName: 'filename',
|
||||
|
||||
/**
|
||||
* Allowed MIME types
|
||||
*/
|
||||
allowedTypes: [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/gif'
|
||||
],
|
||||
|
||||
/**
|
||||
* Text which will be inserted when dropping or pasting a file.
|
||||
* Acts as a placeholder which will be replaced when the file is done with uploading
|
||||
*/
|
||||
progressText: '![Uploading file...]()',
|
||||
|
||||
/**
|
||||
* When a file has successfully been uploaded the progressText
|
||||
* will be replaced by the urlText, the {filename} tag will be replaced
|
||||
* by the filename that has been returned by the server
|
||||
*/
|
||||
urlText: "",
|
||||
|
||||
/**
|
||||
* Text which will be used when uploading has failed
|
||||
*/
|
||||
errorText: "Error uploading file",
|
||||
|
||||
/**
|
||||
* Extra parameters which will be send when uploading a file
|
||||
*/
|
||||
extraParams: {},
|
||||
|
||||
/**
|
||||
* Extra headers which will be send when uploading a file
|
||||
*/
|
||||
extraHeaders: {},
|
||||
|
||||
/**
|
||||
* Before the file is send
|
||||
*/
|
||||
beforeFileUpload: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers when a file is dropped or pasted
|
||||
*/
|
||||
onFileReceived: function() {},
|
||||
|
||||
/**
|
||||
* Custom upload handler
|
||||
*
|
||||
* @return {Boolean} when false is returned it will prevent default upload behavior
|
||||
*/
|
||||
onFileUploadResponse: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Custom error handler. Runs after removing the placeholder text and before the alert().
|
||||
* Return false from this function to prevent the alert dialog.
|
||||
*
|
||||
* @return {Boolean} when false is returned it will prevent default error behavior
|
||||
*/
|
||||
onFileUploadError: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* When a file has succesfully been uploaded
|
||||
*/
|
||||
onFileUploaded: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads the blob
|
||||
*
|
||||
* @param {Blob} file blob data received from event.dataTransfer object
|
||||
* @return {XMLHttpRequest} request object which sends the file
|
||||
*/
|
||||
inlineAttachment.prototype.uploadFile = function(file) {
|
||||
var me = this,
|
||||
formData = new FormData(),
|
||||
xhr = new XMLHttpRequest(),
|
||||
settings = this.settings,
|
||||
extension = settings.defaultExtension || settings.defualtExtension;
|
||||
|
||||
if (typeof settings.setupFormData === 'function') {
|
||||
settings.setupFormData(formData, file);
|
||||
}
|
||||
|
||||
// Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
|
||||
// http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
|
||||
if (file.name) {
|
||||
var fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches) {
|
||||
extension = fileNameMatches[1];
|
||||
}
|
||||
}
|
||||
|
||||
var remoteFilename = "image-" + Date.now() + "." + extension;
|
||||
if (typeof settings.remoteFilename === 'function') {
|
||||
remoteFilename = settings.remoteFilename(file);
|
||||
}
|
||||
|
||||
formData.append(settings.uploadFieldName, file, remoteFilename);
|
||||
|
||||
// Append the extra parameters to the formdata
|
||||
if (typeof settings.extraParams === "object") {
|
||||
for (var key in settings.extraParams) {
|
||||
if (settings.extraParams.hasOwnProperty(key)) {
|
||||
formData.append(key, settings.extraParams[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xhr.open('POST', settings.uploadUrl);
|
||||
|
||||
// Add any available extra headers
|
||||
if (typeof settings.extraHeaders === "object") {
|
||||
for (var header in settings.extraHeaders) {
|
||||
if (settings.extraHeaders.hasOwnProperty(header)) {
|
||||
xhr.setRequestHeader(header, settings.extraHeaders[header]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onload = function() {
|
||||
// If HTTP status is OK or Created
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
me.onFileUploadResponse(xhr);
|
||||
} else {
|
||||
me.onFileUploadError(xhr);
|
||||
}
|
||||
};
|
||||
if (settings.beforeFileUpload(xhr) !== false) {
|
||||
xhr.send(formData);
|
||||
}
|
||||
return xhr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns if the given file is allowed to handle
|
||||
*
|
||||
* @param {File} clipboard data file
|
||||
*/
|
||||
inlineAttachment.prototype.isFileAllowed = function(file) {
|
||||
if (file.kind === 'string') { return false; }
|
||||
if (this.settings.allowedTypes.indexOf('*') === 0){
|
||||
return true;
|
||||
} else {
|
||||
return this.settings.allowedTypes.indexOf(file.type) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles upload response
|
||||
*
|
||||
* @param {XMLHttpRequest} xhr
|
||||
* @return {Void}
|
||||
*/
|
||||
inlineAttachment.prototype.onFileUploadResponse = function(xhr) {
|
||||
if (this.settings.onFileUploadResponse.call(this, xhr) !== false) {
|
||||
var result = JSON.parse(xhr.responseText),
|
||||
filename = result[this.settings.jsonFieldName];
|
||||
|
||||
if (result && filename) {
|
||||
var newValue;
|
||||
if (typeof this.settings.urlText === 'function') {
|
||||
newValue = this.settings.urlText.call(this, filename, result);
|
||||
} else {
|
||||
newValue = this.settings.urlText.replace(this.filenameTag, filename);
|
||||
}
|
||||
var text = this.editor.getValue().replace(this.lastValue, newValue);
|
||||
this.editor.setValue(text);
|
||||
this.settings.onFileUploaded.call(this, filename);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when a file has failed to upload
|
||||
*
|
||||
* @param {XMLHttpRequest} xhr
|
||||
* @return {Void}
|
||||
*/
|
||||
inlineAttachment.prototype.onFileUploadError = function(xhr) {
|
||||
if (this.settings.onFileUploadError.call(this, xhr) !== false) {
|
||||
var text = this.editor.getValue().replace(this.lastValue, "");
|
||||
this.editor.setValue(text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a file has been inserted, either by drop or paste
|
||||
*
|
||||
* @param {File} file
|
||||
* @return {Void}
|
||||
*/
|
||||
inlineAttachment.prototype.onFileInserted = function(file) {
|
||||
if (this.settings.onFileReceived.call(this, file) !== false) {
|
||||
this.lastValue = this.settings.progressText;
|
||||
this.editor.insertValue(this.lastValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when a paste event occured
|
||||
* @param {Event} e
|
||||
* @return {Boolean} if the event was handled
|
||||
*/
|
||||
inlineAttachment.prototype.onPaste = function(e) {
|
||||
var result = false,
|
||||
clipboardData = e.clipboardData,
|
||||
items;
|
||||
|
||||
if (typeof clipboardData === "object") {
|
||||
items = clipboardData.items || clipboardData.files || [];
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
if (this.isFileAllowed(item)) {
|
||||
result = true;
|
||||
this.onFileInserted(item.getAsFile());
|
||||
this.uploadFile(item.getAsFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) { e.preventDefault(); }
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a drop event occures
|
||||
* @param {Event} e
|
||||
* @return {Boolean} if the event was handled
|
||||
*/
|
||||
inlineAttachment.prototype.onDrop = function(e) {
|
||||
var result = false;
|
||||
for (var i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
var file = e.dataTransfer.files[i];
|
||||
if (this.isFileAllowed(file)) {
|
||||
result = true;
|
||||
this.onFileInserted(file);
|
||||
this.uploadFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
window.inlineAttachment = inlineAttachment;
|
||||
|
||||
})(document, window);
|
1
blog-admin/src/main/resources/static/assets/js/jquery-form.js
vendored
Normal file
427
blog-admin/src/main/resources/static/assets/js/validator.js
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
Validator v1.1.0
|
||||
(c) Yair Even Or
|
||||
https://github.com/yairEO/validator
|
||||
|
||||
MIT-style license.
|
||||
*/
|
||||
|
||||
var validator = (function($){
|
||||
var message, tests, checkField, validate, mark, unmark, field, minmax, defaults,
|
||||
validateWords, lengthRange, lengthLimit, pattern, alertTxt, data,
|
||||
email_illegalChars = /[\(\)\<\>\,\;\:\\\/\"\[\]]/,
|
||||
email_filter = /^.+@.+\..{2,6}$/; // exmaple email "steve@s-i.photo"
|
||||
|
||||
/* general text messages
|
||||
*/
|
||||
message = {
|
||||
invalid : '无效的输入',
|
||||
checked : 'must be checked',
|
||||
empty : '必填项',
|
||||
min : '输入内容太短',
|
||||
max : '输入内容太长',
|
||||
number_min : '数字太小',
|
||||
number_max : '数字太大',
|
||||
url : '无效的URL',
|
||||
number : '必须为数字',
|
||||
email : '无效的邮箱',
|
||||
email_repeat : '邮箱不一致',
|
||||
password_repeat : '密码不一致',
|
||||
repeat : '不一致',
|
||||
complete : 'input is not complete',
|
||||
select : '必选项'
|
||||
};
|
||||
// message = {
|
||||
// invalid : 'invalid input',
|
||||
// checked : 'must be checked',
|
||||
// empty : 'please put something here',
|
||||
// min : 'input is too short',
|
||||
// max : 'input is too long',
|
||||
// number_min : 'too low',
|
||||
// number_max : 'too high',
|
||||
// url : 'invalid URL',
|
||||
// number : 'not a number',
|
||||
// email : 'email address is invalid',
|
||||
// email_repeat : 'emails do not match',
|
||||
// password_repeat : 'passwords do not match',
|
||||
// repeat : 'no match',
|
||||
// complete : 'input is not complete',
|
||||
// select : 'Please select an option'
|
||||
// };
|
||||
if(!window.console){
|
||||
console={};
|
||||
console.log=console.warn=function(){ return; }
|
||||
}
|
||||
|
||||
// defaults
|
||||
defaults = {
|
||||
alerts : true,
|
||||
classes : {
|
||||
item : 'item',
|
||||
alert : 'alert',
|
||||
bad : 'bad'
|
||||
}
|
||||
};
|
||||
|
||||
/* Tests for each type of field (including Select element)
|
||||
*/
|
||||
tests = {
|
||||
sameAsPlaceholder : function(a){
|
||||
return $.fn.placeholder && a.attr('placeholder') !== undefined && data.val == a.prop('placeholder');
|
||||
},
|
||||
hasValue : function(a){
|
||||
if( !a ){
|
||||
alertTxt = message.empty;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 'linked' is a special test case for inputs which their values should be equal to each other (ex. confirm email or retype password)
|
||||
linked : function(a,b){
|
||||
if( b != a ){
|
||||
// choose a specific message or a general one
|
||||
alertTxt = message[data.type + '_repeat'] || message.no_match;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
email : function(a){
|
||||
if ( !email_filter.test( a ) || a.match( email_illegalChars ) ){
|
||||
alertTxt = a ? message.email : message.empty;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// a "skip" will skip some of the tests (needed for keydown validation)
|
||||
text : function(a, skip){
|
||||
// make sure there are at least X number of words, each at least 2 chars long.
|
||||
// for example 'john F kenedy' should be at least 2 words and will pass validation
|
||||
if( validateWords ){
|
||||
var words = a.split(' ');
|
||||
// iterrate on all the words
|
||||
var wordsLength = function(len){
|
||||
for( var w = words.length; w--; )
|
||||
if( words[w].length < len )
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
if( words.length < validateWords || !wordsLength(2) ){
|
||||
alertTxt = message.complete;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if( !skip && lengthRange && a.length < lengthRange[0] ){
|
||||
alertTxt = message.min;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if there is max length & field length is greater than the allowed
|
||||
if( lengthRange && lengthRange[1] && a.length > lengthRange[1] ){
|
||||
alertTxt = message.max;
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the field's value should obey any length limits, and if so, make sure the length of the value is as specified
|
||||
if( lengthLimit && lengthLimit.length ){
|
||||
while( lengthLimit.length ){
|
||||
if( lengthLimit.pop() == a.length ){
|
||||
alertTxt = message.complete;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( pattern ){
|
||||
var regex, jsRegex;
|
||||
switch( pattern ){
|
||||
case 'alphanumeric' :
|
||||
regex = /^[a-zA-Z0-9]+$/i;
|
||||
break;
|
||||
case 'numeric' :
|
||||
regex = /^[0-9]+$/i;
|
||||
break;
|
||||
case 'phone' :
|
||||
regex = /^\+?([0-9]|[-|' '])+$/i;
|
||||
break;
|
||||
default :
|
||||
regex = pattern;
|
||||
}
|
||||
try{
|
||||
jsRegex = new RegExp(regex).test(a);
|
||||
if( a && !jsRegex )
|
||||
return false;
|
||||
}
|
||||
catch(err){
|
||||
console.log(err, field, 'regex is invalid');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
number : function(a){
|
||||
// if not not a number
|
||||
if( isNaN(parseFloat(a)) && !isFinite(a) ){
|
||||
alertTxt = message.number;
|
||||
return false;
|
||||
}
|
||||
// not enough numbers
|
||||
else if( lengthRange && a.length < lengthRange[0] ){
|
||||
alertTxt = message.min;
|
||||
return false;
|
||||
}
|
||||
// check if there is max length & field length is greater than the allowed
|
||||
else if( lengthRange && lengthRange[1] && a.length > lengthRange[1] ){
|
||||
alertTxt = message.max;
|
||||
return false;
|
||||
}
|
||||
else if( minmax[0] && (a|0) < minmax[0] ){
|
||||
alertTxt = message.number_min;
|
||||
return false;
|
||||
}
|
||||
else if( minmax[1] && (a|0) > minmax[1] ){
|
||||
alertTxt = message.number_max;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Date is validated in European format (day,month,year)
|
||||
date : function(a){
|
||||
var day, A = a.split(/[-./]/g), i;
|
||||
// if there is native HTML5 support:
|
||||
if( field[0].valueAsNumber )
|
||||
return true;
|
||||
|
||||
for( i = A.length; i--; ){
|
||||
if( isNaN(parseFloat(a)) && !isFinite(a) )
|
||||
return false;
|
||||
}
|
||||
try{
|
||||
day = new Date(A[2], A[1]-1, A[0]);
|
||||
if( day.getMonth()+1 == A[1] && day.getDate() == A[0] )
|
||||
return day;
|
||||
return false;
|
||||
}
|
||||
catch(er){
|
||||
console.log('date test: ', err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
url : function(a){
|
||||
// minimalistic URL validation
|
||||
function testUrl(url){
|
||||
return /^(https?:\/\/)?([\w\d\-_]+\.+[A-Za-z]{2,})+\/?/.test( url );
|
||||
}
|
||||
if( !testUrl( a ) ){
|
||||
alertTxt = a ? message.url : message.empty;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
hidden : function(a){
|
||||
if( lengthRange && a.length < lengthRange[0] ){
|
||||
alertTxt = message.min;
|
||||
return false;
|
||||
}
|
||||
if( pattern ){
|
||||
var regex;
|
||||
if( pattern == 'alphanumeric' ){
|
||||
regex = /^[a-z0-9]+$/i;
|
||||
if( !regex.test(a) ){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
select : function(a){
|
||||
if( !tests.hasValue(a) ){
|
||||
alertTxt = message.select;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/* marks invalid fields
|
||||
*/
|
||||
mark = function( field, text ){
|
||||
if( !text || !field || !field.length )
|
||||
return false;
|
||||
|
||||
// check if not already marked as a 'bad' record and add the 'alert' object.
|
||||
// if already is marked as 'bad', then make sure the text is set again because i might change depending on validation
|
||||
var item = field.closest('.' + defaults.classes.item),
|
||||
warning;
|
||||
|
||||
if( item.hasClass(defaults.classes.bad) ){
|
||||
if( defaults.alerts )
|
||||
item.find('.'+defaults.classes.alert).html(text);
|
||||
}
|
||||
|
||||
|
||||
else if( defaults.alerts ){
|
||||
warning = $('<div class="'+ defaults.classes.alert +'">').html( text );
|
||||
item.append( warning );
|
||||
}
|
||||
|
||||
item.removeClass(defaults.classes.bad);
|
||||
// a delay so the "alert" could be transitioned via CSS
|
||||
setTimeout(function(){
|
||||
item.addClass(defaults.classes.bad);
|
||||
}, 0);
|
||||
};
|
||||
/* un-marks invalid fields
|
||||
*/
|
||||
unmark = function( field ){
|
||||
if( !field || !field.length ){
|
||||
console.warn('no "field" argument, null or DOM object not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
field.closest('.' + defaults.classes.item)
|
||||
.removeClass(defaults.classes.bad)
|
||||
.find('.'+ defaults.classes.alert).remove();
|
||||
};
|
||||
|
||||
function testByType(type, value){
|
||||
if( type == 'tel' )
|
||||
pattern = pattern || 'phone';
|
||||
|
||||
if( !type || type == 'password' || type == 'tel' || type == 'search' || type == 'file' )
|
||||
type = 'text';
|
||||
|
||||
|
||||
return tests[type] ? tests[type](value, true) : true;
|
||||
}
|
||||
|
||||
function prepareFieldData(el){
|
||||
field = $(el);
|
||||
|
||||
field.data( 'valid', true ); // initialize validity of field
|
||||
field.data( 'type', field.attr('type') ); // every field starts as 'valid=true' until proven otherwise
|
||||
pattern = field.attr('pattern');
|
||||
}
|
||||
|
||||
/* Validations per-character keypress
|
||||
*/
|
||||
function keypress(e){
|
||||
prepareFieldData(this);
|
||||
// String.fromCharCode(e.charCode)
|
||||
|
||||
if( e.charCode ){
|
||||
return testByType( this.type, this.value );
|
||||
}
|
||||
}
|
||||
|
||||
/* Checks a single form field by it's type and specific (custom) attributes
|
||||
*/
|
||||
function checkField(){
|
||||
// skip testing fields whom their type is not HIDDEN but they are HIDDEN via CSS.
|
||||
if( this.type !='hidden' && $(this).is(':hidden') )
|
||||
return true;
|
||||
|
||||
prepareFieldData(this);
|
||||
|
||||
field.data( 'val', field[0].value.replace(/^\s+|\s+$/g, "") ); // cache the value of the field and trim it
|
||||
data = field.data();
|
||||
|
||||
// Check if there is a specific error message for that field, if not, use the default 'invalid' message
|
||||
alertTxt = message[field.prop('name')] || message.invalid;
|
||||
|
||||
// Special treatment
|
||||
if( field[0].nodeName.toLowerCase() === "select" ){
|
||||
data.type = 'select';
|
||||
}
|
||||
else if( field[0].nodeName.toLowerCase() === "textarea" ){
|
||||
data.type = 'text';
|
||||
}
|
||||
/* Gather Custom data attributes for specific validation:
|
||||
*/
|
||||
validateWords = data['validateWords'] || 0;
|
||||
lengthRange = data['validateLengthRange'] ? (data['validateLengthRange']+'').split(',') : [1];
|
||||
lengthLimit = data['validateLength'] ? (data['validateLength']+'').split(',') : false;
|
||||
minmax = data['validateMinmax'] ? (data['validateMinmax']+'').split(',') : ''; // for type 'number', defines the minimum and/or maximum for the value as a number.
|
||||
|
||||
data.valid = tests.hasValue(data.val);
|
||||
|
||||
if( field.hasClass('optional') && !data.valid )
|
||||
data.valid = true;
|
||||
|
||||
|
||||
// for checkboxes
|
||||
if( field[0].type === "checkbox" ){
|
||||
data.valid = field[0].checked;
|
||||
alertTxt = message.checked;
|
||||
}
|
||||
|
||||
// check if field has any value
|
||||
else if( data.valid ){
|
||||
/* Validate the field's value is different than the placeholder attribute (and attribute exists)
|
||||
* this is needed when fixing the placeholders for older browsers which does not support them.
|
||||
* in this case, make sure the "placeholder" jQuery plugin was even used before proceeding
|
||||
*/
|
||||
if( tests.sameAsPlaceholder(field) ){
|
||||
alertTxt = message.empty;
|
||||
data.valid = false;
|
||||
}
|
||||
|
||||
// if this field is linked to another field (their values should be the same)
|
||||
if( data.validateLinked ){
|
||||
var linkedTo = data['validateLinked'].indexOf('#') == 0 ? $(data['validateLinked']) : $(':input[name=' + data['validateLinked'] + ']');
|
||||
data.valid = tests.linked( data.val, linkedTo.val() );
|
||||
}
|
||||
/* validate by type of field. use 'attr()' is proffered to get the actual value and not what the browsers sees for unsupported types.
|
||||
*/
|
||||
else if( data.valid || data.type == 'select' )
|
||||
data.valid = testByType(data.type, data.val);
|
||||
|
||||
}
|
||||
|
||||
// mark / unmark the field, and set the general 'submit' flag accordingly
|
||||
if( data.valid )
|
||||
unmark( field );
|
||||
else{
|
||||
mark( field, alertTxt );
|
||||
submit = false;
|
||||
}
|
||||
|
||||
return data.valid;
|
||||
}
|
||||
|
||||
/* vaildates all the REQUIRED fields prior to submiting the form
|
||||
*/
|
||||
function checkAll( $form ){
|
||||
$form = $($form);
|
||||
|
||||
if( $form.length == 0 ){
|
||||
console.warn('element not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
var that = this,
|
||||
submit = true, // bindSaveInfoEvent the scope
|
||||
// get all the input/textareas/select fields which are required or optional (meaning, they need validation only if they were filled)
|
||||
fieldsToCheck = $form.find(':input').filter('[required=required], .required, .optional').not('[disabled=disabled]');
|
||||
|
||||
fieldsToCheck.each(function(){
|
||||
// use an AND operation, so if any of the fields returns 'false' then the submitted result will be also FALSE
|
||||
submit = submit * checkField.apply(this);
|
||||
});
|
||||
|
||||
return !!submit; // casting the variable to make sure it's a boolean
|
||||
}
|
||||
|
||||
return {
|
||||
defaults : defaults,
|
||||
checkField : checkField,
|
||||
keypress : keypress,
|
||||
checkAll : checkAll,
|
||||
mark : mark,
|
||||
unmark : unmark,
|
||||
message : message,
|
||||
tests : tests
|
||||
}
|
||||
})(jQuery);
|
604
blog-admin/src/main/resources/static/assets/js/zhyd.core.js
Normal file
@ -0,0 +1,604 @@
|
||||
/**
|
||||
*
|
||||
* 项目核心Js类,负责项目前端模板方面的初始化等操作
|
||||
*
|
||||
*/
|
||||
var editor = null, simplemde = null;
|
||||
|
||||
var zhyd = window.zhyd || {
|
||||
combox: {
|
||||
init: function () {
|
||||
$('select[target=combox]').each(function (e) {
|
||||
var $this = $(this);
|
||||
var url = $this.data("url");
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
var method = $this.data("method") || "get";
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
success: function (json) {
|
||||
if (json && json.status == 200) {
|
||||
var optionTpl = '<option value="">请选择</option>{{#data}}<option value="{{id}}">{{name}}</option>{{#nodes}}<option value="{{id}}"> > {{name}}</option>{{/nodes}}{{/data}}';
|
||||
var html = Mustache.render(optionTpl, json);
|
||||
$this.html(html);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('ul[target=combox], ol[target=combox]').each(function (e) {
|
||||
var $this = $(this);
|
||||
var url = $this.data("url");
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
var method = $this.data("method") || "get";
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
success: function (json) {
|
||||
if (json && json.status == 200) {
|
||||
var liTpl = '{{#data}}<li data-value="{{id}}">{{name}}</li>{{/data}}';
|
||||
var html = Mustache.render(liTpl, json);
|
||||
$this.html(html);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
tagsInput: {
|
||||
init: function () {
|
||||
setTimeout(function () {
|
||||
$('select[target="tagsinput"], input[target="tagsinput"]').each(function () {
|
||||
var $this = $(this);
|
||||
var $bindBox = $this.data("bind-box");
|
||||
if (!$bindBox || $bindBox.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this.tagsinput({
|
||||
itemValue: 'id',
|
||||
itemText: 'name',
|
||||
maxTags: 3,
|
||||
maxChars: 20,
|
||||
trimValue: true,
|
||||
focusClass: 'focus'
|
||||
});
|
||||
|
||||
function add() {
|
||||
var thisId = $(this).data("value");
|
||||
var thisText = $(this).text().trim();
|
||||
$this.tagsinput('add', {"id": thisId, "name": thisText}, {add: false});
|
||||
}
|
||||
|
||||
$($bindBox).find("li").each(function () {
|
||||
var $li = $(this);
|
||||
$li.bind('click', add);
|
||||
});
|
||||
$(".bootstrap-tagsinput input").bind('keydown', function (event) {
|
||||
var thisVal = $(this).val();
|
||||
if (event.key == 'Enter' || event.keyCode == '13') {
|
||||
$.post('/tag/add', {name: thisVal, description: thisVal}, function (response) {
|
||||
if (response.status !== 200) {
|
||||
$.alert.error(response.message);
|
||||
} else {
|
||||
var data = response.data;
|
||||
$this.tagsinput('add', {"id": data.id, "name": data.name}, {addNew: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$this.on('itemAdded', function (event) {
|
||||
var tag = event.item;
|
||||
if (event.options && event.options.addNew) {
|
||||
$(".bootstrap-tagsinput input").val('');
|
||||
$('<li data-value="' + tag.id + '">' + tag.name + '</li>').bind('click', add).appendTo($($bindBox));
|
||||
}
|
||||
});
|
||||
})
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
initTextSlider: function () {
|
||||
$.fn.textSlider = function (settings) {
|
||||
settings = jQuery.extend({
|
||||
speed: "normal",
|
||||
line: 2,
|
||||
timer: 3000
|
||||
}, settings);
|
||||
return this.each(function () {
|
||||
$.fn.textSlider.scllor($(this), settings);
|
||||
});
|
||||
};
|
||||
$.fn.textSlider.scllor = function ($this, settings) {
|
||||
var ul = $("ul:eq(0)", $this);
|
||||
var timerID;
|
||||
var li = ul.children();
|
||||
var liHight = $(li[0]).height();
|
||||
var upHeight = 0 - settings.line * liHight;//滚动的高度;
|
||||
var scrollUp = function () {
|
||||
ul.animate({marginTop: upHeight}, settings.speed, function () {
|
||||
for (i = 0; i < settings.line; i++) {
|
||||
ul.find("li:first", $this).appendTo(ul);
|
||||
}
|
||||
ul.css({marginTop: 0});
|
||||
});
|
||||
};
|
||||
var autoPlay = function () {
|
||||
timerID = window.setInterval(scrollUp, settings.timer);
|
||||
};
|
||||
var autoStop = function () {
|
||||
window.clearInterval(timerID);
|
||||
};
|
||||
//事件绑定
|
||||
ul.hover(autoStop, autoPlay).mouseout();
|
||||
};
|
||||
|
||||
if ($("#scrolldiv")) {
|
||||
$("#scrolldiv").textSlider({line: 1, speed: 300, timer: 10000});
|
||||
}
|
||||
},
|
||||
wangEditor: {
|
||||
_instance: window.wangEditor,
|
||||
defaultConfig: {
|
||||
container: "#editor",
|
||||
textareaName: "content",
|
||||
uploadUrl: "",
|
||||
uploadFileName: "file",
|
||||
uploadType: "",
|
||||
customCss: {}
|
||||
|
||||
},
|
||||
init: function (options) {
|
||||
var config = $.extend(zhyd.wangEditor.defaultConfig, options);
|
||||
var E = window.wangEditor;
|
||||
editor = new E(config.container);
|
||||
// 配置编辑器 start
|
||||
// 关闭粘贴样式的过滤
|
||||
editor.customConfig.pasteFilterStyle = false;
|
||||
editor.customConfig.zIndex = 100;
|
||||
if (config.textareaName) {
|
||||
$('<textarea class="wangeditor-textarea" id="' + config.textareaName + '" name="' + config.textareaName + '" style="display: none" required="required"></textarea>').insertAfter($(config.container));
|
||||
}
|
||||
var $contentBox = $('textarea[name=' + config.textareaName + ']');
|
||||
editor.customConfig.onchange = function (html) {
|
||||
// 监控变化,同步更新到 textarea
|
||||
$contentBox.val(html);
|
||||
};
|
||||
// 注册上传文件插件
|
||||
zhyd.wangEditor.plugins.registerUpload(editor, config.uploadUrl, config.uploadFileName, config.uploadType, function (result, curEditor) {
|
||||
// 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
|
||||
// insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
|
||||
if (result.status == 200) {
|
||||
var imgFullPath = result.data;
|
||||
curEditor.txt.append('<img src="' + imgFullPath + '" alt="" style="max-width: 100%;height: auto;border-radius: 6px;"/>');
|
||||
// 解决上传完图片如果未进行其他操作,则不会触发编辑器的“change”事件,导致实际文章内容中缺少最后上传的图片文件 2018-07-13
|
||||
$contentBox.val(editor.txt.html());
|
||||
} else {
|
||||
$.alert.error(result.message);
|
||||
}
|
||||
});
|
||||
// 配置编辑器 end
|
||||
editor.create();
|
||||
// 注册全屏插件
|
||||
zhyd.wangEditor.plugins.registerFullscreen(config.container);
|
||||
// 注册图片资源库
|
||||
zhyd.wangEditor.plugins.registerMaterial(editor, $contentBox);
|
||||
|
||||
if (config.customCss) {
|
||||
// 自定义编辑器的样式
|
||||
for (var key in config.customCss) {
|
||||
var value = config.customCss[key];
|
||||
editor.$textContainerElem.css(key, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
registerFullscreen: function () {
|
||||
var E = zhyd.wangEditor._instance;
|
||||
// 全屏插件
|
||||
E.fullscreen = {
|
||||
init: function (editorBox) {
|
||||
$(editorBox + " .w-e-toolbar").append('<div class="w-e-menu"><a class="_wangEditor_btn_fullscreen" href="###" onclick="window.wangEditor.fullscreen.toggleFullscreen(\'' + editorBox + '\')" data-toggle="tooltip" data-placement="bottom" title data-original-title="全屏编辑"><i class="fa fa-expand"></i></a></div>')
|
||||
},
|
||||
toggleFullscreen: function (editorSelector) {
|
||||
$(editorSelector).toggleClass('fullscreen-editor');
|
||||
var $a = $(editorSelector + ' ._wangEditor_btn_fullscreen');
|
||||
var $i = $a.find("i:first-child");
|
||||
if ($i.hasClass("fa-expand")) {
|
||||
$a.attr("data-original-title", "退出全屏");
|
||||
$i.removeClass("fa-expand").addClass("fa-compress")
|
||||
} else {
|
||||
$a.attr("data-original-title", "全屏编辑");
|
||||
$i.removeClass("fa-compress").addClass("fa-expand")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化全屏插件
|
||||
var n = arguments.length;
|
||||
for (var i = 0; i < n; i++) {
|
||||
E.fullscreen.init(arguments[i]);
|
||||
}
|
||||
},
|
||||
registerUpload: function (editor, uploadUrl, uploadFileName, uploadType, callback) {
|
||||
if (uploadUrl) {
|
||||
// 上传图片到服务器
|
||||
editor.customConfig.uploadImgServer = uploadUrl;
|
||||
editor.customConfig.uploadFileName = uploadFileName;
|
||||
// 将图片大小限制为 50M
|
||||
editor.customConfig.uploadImgMaxSize = 50 * 1024 * 1024;
|
||||
// 超时时间
|
||||
editor.customConfig.uploadImgTimeout = 10000;
|
||||
// 自定义上传参数
|
||||
editor.customConfig.uploadImgParams = {
|
||||
// 如果版本 <=v3.1.0 ,属性值会自动进行 encode ,此处无需 encode
|
||||
// 如果版本 >=v3.1.1 ,属性值不会自动 encode ,如有需要自己手动 encode
|
||||
uploadType: uploadType
|
||||
};
|
||||
editor.customConfig.customAlert = function (msg) {
|
||||
$.alert.error(msg);
|
||||
};
|
||||
editor.customConfig.uploadImgHooks = {
|
||||
error: function (xhr, editor) {
|
||||
$.alert.error("图片上传出错");
|
||||
},
|
||||
timeout: function (xhr, editor) {
|
||||
$.alert.error("请求超时");
|
||||
},
|
||||
customInsert: function (insertImg, result, editor) {
|
||||
if (callback) {
|
||||
callback(result, editor);
|
||||
} else {
|
||||
console.log('upload callback:' + insertImg, result, editor);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
registerMaterial: function (editor, $contentBox) {
|
||||
$("div[id^='w-e-img']").unbind("click").click(function () {
|
||||
setTimeout(function () {
|
||||
// 删掉原来的input#file,防止触发原选中文件的函数
|
||||
$(".w-e-up-img-container").find("input").remove();
|
||||
// 重新绑定选中图片按钮的事件
|
||||
$("div[id^='up-trigger'], div.w-e-up-btn").unbind("click").click(function () {
|
||||
$.modal.material.open({multiSelect: true, selectable: 10}, function (selectedImageUrls) {
|
||||
if(!selectedImageUrls) {
|
||||
return false;
|
||||
}
|
||||
for(var i = 0; i < selectedImageUrls.length; i ++){
|
||||
editor.txt.append('<img src="' + selectedImageUrls[i] + '" alt="" style="max-width: 100%;height: auto;border-radius: 6px;"/>');
|
||||
$contentBox.val(editor.txt.html());
|
||||
}
|
||||
})
|
||||
})
|
||||
}, 50);
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
simpleMDE: {
|
||||
defaultConfig: {
|
||||
id: "mdEditor",
|
||||
uploadUrl: "",
|
||||
uploadType: "",
|
||||
uniqueId: "mdEditor_1"
|
||||
},
|
||||
init: function (options) {
|
||||
var $op = $.extend(zhyd.wangEditor.defaultConfig, options);
|
||||
zhyd.simpleMDE.plugins.registerAutosave();
|
||||
// Powered by https://github.com/sparksuite/simplemde-markdown-editor
|
||||
simplemde = new SimpleMDE({
|
||||
// textarea的DOM对象
|
||||
element: document.getElementById($op.id),
|
||||
// 自动下载FontAwesome,设为false为不下载(如果设为false则必须手动引入)
|
||||
autoDownloadFontAwesome: false,
|
||||
// 自动聚焦输入框
|
||||
autofocus: true,
|
||||
// 是否自动保存正在编写的文本
|
||||
autosave: {
|
||||
// 启用自动保存功能
|
||||
enabled: true,
|
||||
// 自动保存的间隔,以毫秒为单位。默认为15000(15s)
|
||||
delay: 15000,
|
||||
// 唯一的字符串标识符(保证每个SimpleMDE编辑器的uniqueId唯一)
|
||||
uniqueId: $op.uniqueId,
|
||||
msg: "自动保存成功了"
|
||||
},
|
||||
placeholder: "请输入文本内容",
|
||||
// 如果设置为true,则会出现JS警报窗口,询问链接或图像URL(插入图片或链接弹窗)。默认为false
|
||||
promptURLs: false,
|
||||
renderingConfig: {
|
||||
// 如果设置为true,将使用highlight.js高亮显示。默认为false
|
||||
codeSyntaxHighlighting: true
|
||||
},
|
||||
showIcons: ["code", "table", "clean-block", "horizontal-rule"],
|
||||
tabSize: 4,
|
||||
status: false
|
||||
});
|
||||
zhyd.simpleMDE.plugins.registerFullscreen();
|
||||
zhyd.simpleMDE.plugins.registerUpload($op.uploadUrl, simplemde);
|
||||
zhyd.simpleMDE.plugins.registerMaterial();
|
||||
|
||||
$(".editor-preview-side").addClass("markdown-body");
|
||||
},
|
||||
plugins: {
|
||||
registerAutosave: function () {
|
||||
// js实现aop切面编程,实时保存文章内容
|
||||
Function.prototype.after = function (afterfn) {
|
||||
var __self = this;
|
||||
//保存原函数的引用
|
||||
return function () {
|
||||
//返回包含了原函数和新函数的"代理"函数
|
||||
afterfn.apply(this, arguments);//(1)
|
||||
//执行新函数,且保证this不被劫持,新函数接受的参数
|
||||
//也会被原封不动地传入原函数,新函数在原函数之前执行
|
||||
return __self.apply(this, arguments);//(2)
|
||||
//执行原函数并返回原函数的执行结果
|
||||
//并且保证this不被劫持
|
||||
}
|
||||
};
|
||||
var showMsg = function () {
|
||||
var $div = $('<div></div>');
|
||||
$div.css({
|
||||
'position': 'absolute',
|
||||
'right': '10px',
|
||||
'top': 0,
|
||||
'padding': '5px',
|
||||
'font-size': '12px',
|
||||
'color': '#ccc',
|
||||
'opacity': 0
|
||||
});
|
||||
$div.html("自动保存完成");
|
||||
$div.appendTo($(".CodeMirror"));
|
||||
$div.animate({opacity: 1}, 1000, function () {
|
||||
$div.animate({opacity: 0}, 1000, function () {
|
||||
$(this).remove();
|
||||
})
|
||||
})
|
||||
};
|
||||
SimpleMDE.prototype.autosave = SimpleMDE.prototype.autosave.after(showMsg);
|
||||
},
|
||||
registerFullscreen: function () {
|
||||
var $fullscreen = $(".editor-toolbar a.fa-arrows-alt, .editor-toolbar a.fa-columns");
|
||||
$fullscreen.click(function () {
|
||||
var $this = $(this);
|
||||
if ($fullscreen.hasClass("active")) {
|
||||
$(".CodeMirror, .CodeMirror-scroll").css('max-height', 'none');
|
||||
} else {
|
||||
$(".CodeMirror, .CodeMirror-scroll").css('max-height', '200px');
|
||||
}
|
||||
});
|
||||
},
|
||||
registerUpload: function (uploadUrl, simplemde) {
|
||||
if (uploadUrl) {
|
||||
inlineAttachment.editors.codemirror4.attach(simplemde.codemirror, {
|
||||
uploadUrl: uploadUrl
|
||||
});
|
||||
}
|
||||
},
|
||||
registerMaterial: function () {
|
||||
function getState(cm, pos) {
|
||||
pos = pos || cm.getCursor("start");
|
||||
var stat = cm.getTokenAt(pos);
|
||||
if(!stat.type) return {};
|
||||
var types = stat.type.split(" ");
|
||||
var ret = {},
|
||||
data, text;
|
||||
for(var i = 0; i < types.length; i++) {
|
||||
data = types[i];
|
||||
if(data === "strong") {
|
||||
ret.bold = true;
|
||||
} else if(data === "variable-2") {
|
||||
text = cm.getLine(pos.line);
|
||||
if(/^\s*\d+\.\s/.test(text)) {
|
||||
ret["ordered-list"] = true;
|
||||
} else {
|
||||
ret["unordered-list"] = true;
|
||||
}
|
||||
} else if(data === "atom") {
|
||||
ret.quote = true;
|
||||
} else if(data === "em") {
|
||||
ret.italic = true;
|
||||
} else if(data === "quote") {
|
||||
ret.quote = true;
|
||||
} else if(data === "strikethrough") {
|
||||
ret.strikethrough = true;
|
||||
} else if(data === "comment") {
|
||||
ret.code = true;
|
||||
} else if(data === "link") {
|
||||
ret.link = true;
|
||||
} else if(data === "tag") {
|
||||
ret.image = true;
|
||||
} else if(data.match(/^header(\-[1-6])?$/)) {
|
||||
ret[data.replace("header", "heading")] = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function insertHtml(imgUrl){
|
||||
var cm = simplemde.codemirror;
|
||||
var state = getState(cm);
|
||||
var options = simplemde.options;
|
||||
|
||||
if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
|
||||
return;
|
||||
var text;
|
||||
var start = options.insertTexts.image[0];
|
||||
var end = options.insertTexts.image[1];
|
||||
var startPoint = cm.getCursor("start");
|
||||
var endPoint = cm.getCursor("end");
|
||||
if(imgUrl) {
|
||||
end = end.replace("#url#", imgUrl);
|
||||
}
|
||||
if(state.image) {
|
||||
text = cm.getLine(startPoint.line);
|
||||
start = text.slice(0, startPoint.ch);
|
||||
end = text.slice(startPoint.ch);
|
||||
cm.replaceRange(start + end, {
|
||||
line: startPoint.line,
|
||||
ch: 0
|
||||
});
|
||||
} else {
|
||||
text = cm.getSelection();
|
||||
var img = start + text + end;
|
||||
cm.replaceSelection(img);
|
||||
startPoint.ch += img.length;
|
||||
if(startPoint !== endPoint) {
|
||||
endPoint.ch += img.length;
|
||||
}
|
||||
}
|
||||
cm.setSelection(startPoint, endPoint);
|
||||
cm.focus();
|
||||
}
|
||||
try {
|
||||
var insertImage = document.getElementsByClassName("editor-toolbar")[0].getElementsByTagName("a")[9];
|
||||
insertImage.onclick = function (ev) {
|
||||
$.modal.material.open({multiSelect: true, selectable: 10}, function (selectedImageUrls) {
|
||||
if(!selectedImageUrls) {
|
||||
return false;
|
||||
}
|
||||
for(var i = 0; i < selectedImageUrls.length; i ++){
|
||||
insertHtml(selectedImageUrls[i]);
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mask: {
|
||||
_box: '<div class="mask {{maskType}}"><div class="masker"><i class="{{icon}}"></i></div><h3 class="text">{{text}}</h3></div>',
|
||||
_icon: {
|
||||
load: "fa fa-spinner fa-spin",
|
||||
lock: "fa fa-lock"
|
||||
},
|
||||
_open: function (container, msg, type) {
|
||||
var html = Mustache.render(this._box, {icon: this._icon[type], text: msg, maskType: type});
|
||||
$(container).append(html);
|
||||
},
|
||||
closeAll: function (container) {
|
||||
$(container).find("div.mask.load, div.mask.lock").remove();
|
||||
},
|
||||
init: function () {
|
||||
console.log("init mask...");
|
||||
$(".loading").each(function () {
|
||||
var $this = $(this);
|
||||
if (!$this.hasClass("locking")) {
|
||||
zhyd.mask.loading($this, $this.data("mask"));
|
||||
}
|
||||
});
|
||||
$(".locking").each(function () {
|
||||
var $this = $(this);
|
||||
zhyd.mask.locking($this, $this.data("mask"));
|
||||
});
|
||||
},
|
||||
loading: function (container, msg) {
|
||||
this._open(container, msg || "Loading", "load");
|
||||
},
|
||||
locking: function (container, msg) {
|
||||
this._open(container, msg || "Locking", "lock");
|
||||
},
|
||||
closeLoading: function (container) {
|
||||
$(container).find("div.mask.load").remove();
|
||||
},
|
||||
closeLocking: function (container) {
|
||||
$(container).find("div.mask.lock").remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
zhyd.initTextSlider();
|
||||
|
||||
$("img.lazy-img").lazyload({
|
||||
placeholder: appConfig.staticPath + "/img/loading.gif",
|
||||
effect: "fadeIn",
|
||||
threshold: 100
|
||||
});
|
||||
$(window).bind("load", function () {
|
||||
var timeout = setTimeout(function () {
|
||||
$("img.lazy-img").trigger("sporty");
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
/**
|
||||
* 图片预览
|
||||
* 必须指定预览图片的容器,格式:data-preview-container = "#containerID"
|
||||
*/
|
||||
$(".uploadPreview").each(function () {
|
||||
var $this = $(this);
|
||||
$this.uploadPreview({imgContainer: $this.data("preview-container")});
|
||||
});
|
||||
|
||||
$("#updPassBtn").click(function () {
|
||||
var $form = $("#updPassForm");
|
||||
if (validator.checkAll($form)) {
|
||||
$form.ajaxSubmit({
|
||||
type: "POST",
|
||||
url: '/passport/updatePwd',
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if (json.status == 200) {
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
});
|
||||
zhyd.combox.init();
|
||||
zhyd.tagsInput.init();
|
||||
zhyd.mask.init();
|
||||
|
||||
/**
|
||||
* 针对shiro框架中, ajax请求时session过期后的页面跳转
|
||||
*/
|
||||
$.ajaxSetup({
|
||||
complete: function (XMLHttpRequest, textStatus) {
|
||||
if (textStatus === "parsererror") {
|
||||
$.alert.error("会话已失效!请重新登录!", function () {
|
||||
window.location.reload();
|
||||
});
|
||||
} else if (textStatus === "error") {
|
||||
$.alert.error("请求超时,请稍后再试!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var notice = [
|
||||
'<strong>Hi Boy!</strong> 前台首页的 “轮播”只会显示“推荐文章”哦',
|
||||
'要想百度搜索引擎快速收录文章,可以试试“推送”功能哦',
|
||||
'批量推送文章到百度可以一次提交多篇文章哦',
|
||||
'碰到页面显示和数据库内容不一致的情况,可以先考虑清下redis缓存哦',
|
||||
'不可以随便用“文章搬运工”去爬取别人未授权的文章哈',
|
||||
'使用过程中如果有不能解决的问题,请去提issue哈,在群里消息太多,有时候会看不到消息记录',
|
||||
'可以通过右上角“系统配置”-“文章编辑器”选择默认的文章发布编辑器'
|
||||
];
|
||||
var $noticeBox = $("#notice-box");
|
||||
var tpl = '{{#data}}<li class="scrolltext-title">'
|
||||
+ '<a href="javascript:void(0)" rel="bookmark">{{&.}}</a>'
|
||||
+ '</li>{{/data}}';
|
||||
var html = Mustache.render(tpl, {"data": $.tool.shuffle(notice)});
|
||||
$noticeBox.html(html);
|
||||
|
||||
/**
|
||||
* 切换编辑器
|
||||
*/
|
||||
$("#changeEditor").click(function () {
|
||||
var $this = $(this);
|
||||
$.alert.confirm("确定要切换编辑器吗?切换后本页内容将可能会丢失?", function () {
|
||||
window.location.href = $this.data("href");
|
||||
})
|
||||
})
|
||||
});
|
315
blog-admin/src/main/resources/static/assets/js/zhyd.echarts.js
Normal file
@ -0,0 +1,315 @@
|
||||
/**
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018/5/21 11:05
|
||||
* @since 1.0
|
||||
*/
|
||||
var zhyd = window.zhyd || {};
|
||||
zhyd.chartConfig = {
|
||||
color: ["#26B99A", "#34495E", "#BDC3C7", "#3498DB", "#9B59B6", "#8abb6f", "#759c6a", "#bfd3b7"],
|
||||
title: {
|
||||
itemGap: 8,
|
||||
textStyle: {
|
||||
fontWeight: "normal",
|
||||
color: "#408829"
|
||||
}
|
||||
},
|
||||
dataRange: {
|
||||
color: ["#1f610a", "#97b58d"]
|
||||
},
|
||||
toolbox: {
|
||||
color: ["#408829", "#408829", "#408829", "#408829"]
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
axisPointer: {
|
||||
type: "line",
|
||||
lineStyle: {
|
||||
color: "#408829",
|
||||
type: "dashed"
|
||||
},
|
||||
crossStyle: {
|
||||
color: "#408829"
|
||||
},
|
||||
shadowStyle: {
|
||||
color: "rgba(200,200,200,0.3)"
|
||||
}
|
||||
}
|
||||
},
|
||||
dataZoom: {
|
||||
dataBackgroundColor: "#eee",
|
||||
fillerColor: "rgba(64,136,41,0.2)",
|
||||
handleColor: "#408829"
|
||||
},
|
||||
grid: {
|
||||
borderWidth: 0
|
||||
},
|
||||
categoryAxis: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#408829"
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ["#eee"]
|
||||
}
|
||||
}
|
||||
},
|
||||
valueAxis: {
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#408829"
|
||||
}
|
||||
},
|
||||
splitArea: {
|
||||
show: !0,
|
||||
areaStyle: {
|
||||
color: ["rgba(250,250,250,0.1)", "rgba(200,200,200,0.1)"]
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ["#eee"]
|
||||
}
|
||||
}
|
||||
},
|
||||
timeline: {
|
||||
lineStyle: {
|
||||
color: "#408829"
|
||||
},
|
||||
controlStyle: {
|
||||
normal: {
|
||||
color: "#408829"
|
||||
},
|
||||
emphasis: {
|
||||
color: "#408829"
|
||||
}
|
||||
}
|
||||
},
|
||||
k: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#68a54a",
|
||||
color0: "#a9cba2",
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "#408829",
|
||||
color0: "#86b379"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
map: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
color: "#ddd"
|
||||
},
|
||||
label: {
|
||||
textStyle: {
|
||||
color: "#c12e34"
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
areaStyle: {
|
||||
color: "#99d2dd"
|
||||
},
|
||||
label: {
|
||||
textStyle: {
|
||||
color: "#c12e34"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
force: {
|
||||
itemStyle: {
|
||||
normal: {
|
||||
linkStyle: {
|
||||
strokeColor: "#408829"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
chord: {
|
||||
padding: 4,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "rgba(128, 128, 128, 0.5)"
|
||||
},
|
||||
chordStyle: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "rgba(128, 128, 128, 0.5)"
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "rgba(128, 128, 128, 0.5)"
|
||||
},
|
||||
chordStyle: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: "rgba(128, 128, 128, 0.5)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
gauge: {
|
||||
startAngle: 225,
|
||||
endAngle: -45,
|
||||
axisLine: {
|
||||
show: !0,
|
||||
lineStyle: {
|
||||
color: [[.2, "#86b379"], [.8, "#68a54a"], [1, "#408829"]],
|
||||
width: 8
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
splitNumber: 10,
|
||||
length: 12,
|
||||
lineStyle: {
|
||||
color: "auto"
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: "auto"
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
length: 18,
|
||||
lineStyle: {
|
||||
color: "auto"
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
length: "90%",
|
||||
color: "auto"
|
||||
},
|
||||
title: {
|
||||
textStyle: {
|
||||
color: "#333"
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
textStyle: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
textStyle: {
|
||||
fontFamily: "Arial, Verdana, sans-serif"
|
||||
}
|
||||
};
|
||||
zhyd.createChart = function (options) {
|
||||
var op = $.extend({
|
||||
id: '',
|
||||
theme: zhyd.chartConfig,
|
||||
title: null,
|
||||
subtext: '',
|
||||
legendData: [],
|
||||
series: {
|
||||
name: '数值',
|
||||
type: 'line',
|
||||
seriesData: []
|
||||
}
|
||||
}, options);
|
||||
|
||||
var myChart = echarts.init(document.getElementById(op.id), op.theme);
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
x: "center",
|
||||
y: "bottom",
|
||||
data: op.legendData
|
||||
},
|
||||
calculable: !0,
|
||||
series: [{
|
||||
name: op.series.name,
|
||||
type: op.series.type,
|
||||
radius: "55%",
|
||||
center: ["50%", "35%"],
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
data: op.series.seriesData
|
||||
}]
|
||||
};
|
||||
// 使用刚指定的配置项和数据显示图表。
|
||||
myChart.setOption(option);
|
||||
};
|
||||
|
||||
function init_echarts() {
|
||||
if ("undefined" != typeof echarts) {
|
||||
if ($("#echart_line").length) {
|
||||
var f = echarts.init(document.getElementById("echart_line"), zhyd.chartConfig);
|
||||
f.setOption({
|
||||
tooltip: {
|
||||
trigger: "axis"
|
||||
},
|
||||
legend: {
|
||||
x: 220,
|
||||
y: 40,
|
||||
data: ["Intent", "Pre-order", "Deal"]
|
||||
},
|
||||
calculable: !0,
|
||||
xAxis: [{
|
||||
type: "category",
|
||||
boundaryGap: !1,
|
||||
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
}],
|
||||
yAxis: [{
|
||||
type: "value"
|
||||
}],
|
||||
series: [{
|
||||
name: "Deal",
|
||||
type: "line",
|
||||
smooth: !0,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
type: "default"
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [10, 12, 21, 54, 260, 830, 710]
|
||||
}, {
|
||||
name: "Pre-order",
|
||||
type: "line",
|
||||
smooth: !0,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
type: "default"
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [30, 182, 434, 791, 390, 30, 10]
|
||||
}, {
|
||||
name: "Intent",
|
||||
type: "line",
|
||||
smooth: !0,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaStyle: {
|
||||
type: "default"
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [1320, 1132, 601, 234, 120, 90, 20]
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018/6/7 10:48
|
||||
* @since 1.0
|
||||
*/
|
||||
var $publishForm = $("#publishForm");
|
||||
|
||||
if(articleId){
|
||||
setTimeout(function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/article/get/" + articleId,
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
var info = json.data;
|
||||
// 标签, 因为标签初始化是有延迟的,所以这而赋值的时候为了防止赋值失败,亦采用延迟处理
|
||||
setTimeout(function () {
|
||||
var tags = info.tags;
|
||||
for(var i = 0, len = tags.length; i < len ; i ++){
|
||||
var tag = tags[i];
|
||||
$('input[target="tagsinput"]').tagsinput('add', {"id": tag.id, "name": tag.name}, {add: false});
|
||||
}
|
||||
}, 1000);
|
||||
if($('input[name=original]')){
|
||||
$('input[name=original]').iCheck(info.original ? 'check' : 'uncheck');
|
||||
}
|
||||
if($('#comment')){
|
||||
$('#comment').iCheck(info.comment ? 'check' : 'uncheck');
|
||||
}
|
||||
if(info['coverImage']){
|
||||
$(".coverImage").attr('src', info['coverImage']);
|
||||
}
|
||||
var contentMd = info['contentMd'];
|
||||
if(contentMd){
|
||||
$("#contentMd").val(contentMd);
|
||||
if(simplemde){
|
||||
simplemde.value(contentMd);
|
||||
}
|
||||
}
|
||||
var contentHtml = info['content'];
|
||||
if(contentHtml){
|
||||
$("#content").val(contentHtml);
|
||||
if(editor){
|
||||
editor.txt.html(contentHtml);
|
||||
}
|
||||
}
|
||||
$publishForm.find("input[type!=checkbox], select, textarea").each(function () {
|
||||
new Table().clearText($(this), this.type, info);
|
||||
});
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
$(".to-choose-info").click(function () {
|
||||
if(validator.checkAll($publishForm)) {
|
||||
$("#publishModal").modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
// 点击保存
|
||||
$(".publishBtn").click(function () {
|
||||
if(validator.checkAll($publishForm)) {
|
||||
if(!$publishForm.find("input[name='tags']").val()) {
|
||||
$.alert.error("请至少选择一个标签");
|
||||
return;
|
||||
}
|
||||
if(!$("#description").val() || !$("#keywords").val()) {
|
||||
$.alert.error("请填写SEO相关的内容,填写后更容易被收录哦");
|
||||
return;
|
||||
}
|
||||
var isMarkdown = $("input[name=isMarkdown]").val();
|
||||
if(isMarkdown == 1 || isMarkdown == 'true'){
|
||||
$("#contentMd").val(simplemde.value());
|
||||
$("#content").val(simplemde.markdown(simplemde.value()));
|
||||
}
|
||||
|
||||
$publishForm.ajaxSubmit({
|
||||
type: "post",
|
||||
url: "/article/save",
|
||||
success: function (json) {
|
||||
if(isMarkdown == 1) {
|
||||
$.tool.delCache("smde_" + op.uniqueId);
|
||||
}
|
||||
$.alert.ajaxSuccess(json, function () {
|
||||
window.location.href = '/articles';
|
||||
});
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#file-upload-btn").click(function () {
|
||||
$.modal.material.open({multiSelect: false}, function (selectedImageUrl) {
|
||||
$("#cover-img-input").val(selectedImageUrl);
|
||||
$(".preview img.coverImage").attr("src", selectedImageUrl);
|
||||
})
|
||||
});
|
||||
|
||||
// 选择图片
|
||||
$("#file-btn").click(function () {
|
||||
var $this = $(this);
|
||||
$("#cover-img-file").click();
|
||||
});
|
||||
$("input[name=file]").uploadPreview({ imgContainer: ".preview", width: 190, height: 200 });
|
339
blog-admin/src/main/resources/static/assets/js/zhyd.table.js
Normal file
@ -0,0 +1,339 @@
|
||||
/**
|
||||
*
|
||||
* bootstrap-table工具类
|
||||
*
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018-04-22
|
||||
* @since 1.0
|
||||
*/
|
||||
function Table(options) {
|
||||
this.options = $.extend({
|
||||
tableBox: "#tablelist"
|
||||
}, options);
|
||||
}
|
||||
|
||||
Table.prototype = {
|
||||
init: function (callback) {
|
||||
this.createTable();
|
||||
this.bindBtnEvent(callback);
|
||||
return this;
|
||||
},
|
||||
createTable: function () {
|
||||
var $table = this;
|
||||
var options = this.options;
|
||||
var $tablelist = $(options.tableBox);
|
||||
$tablelist.bootstrapTable('destroy').bootstrapTable({
|
||||
url: options.url,
|
||||
method: 'post', //请求方式(*)
|
||||
toolbar: options.showToolbar !== false ? options.toolbar ? options.toolbar : '#toolbar' : '', //工具按钮用哪个容器
|
||||
striped: true, //是否显示行间隔色
|
||||
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
|
||||
contentType: "application/x-www-form-urlencoded", // 发送到服务器的数据编码类型, application/x-www-form-urlencoded为了实现post方式提交
|
||||
sortable: false, //是否启用排序
|
||||
sortOrder: "asc", //排序方式
|
||||
sortStable: true, // 设置为 true 将获得稳定的排序
|
||||
queryParams: $table.queryParams,//传递参数(*)
|
||||
queryParamsType: '',
|
||||
pagination: true, //是否显示分页(*)
|
||||
sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*)
|
||||
pageNumber: 1, //初始化加载第一页,默认第一页
|
||||
pageSize: 20, //每页的记录行数(*)
|
||||
pageList: [20, 40, 50, 100, 150], //可供选择的每页的行数(*)
|
||||
search: options.search !== false, //是否启用搜索框 根据sidePagination选择从前后台搜索
|
||||
strictSearch: true, //设置为 true启用 全匹配搜索,否则为模糊搜索
|
||||
searchOnEnterKey: true, // 设置为 true时,按回车触发搜索方法,否则自动触发搜索方法
|
||||
minimumCountColumns: 1, //最少允许的列数
|
||||
// showColumns: true, //是否显示 内容列下拉框
|
||||
showRefresh: true, //是否显示刷新按钮
|
||||
// showToggle: true, //是否显示详细视图和列表视图的切换按钮
|
||||
iconsPrefix: 'fa', // glyphicon of fa (font awesome)
|
||||
icons: {
|
||||
refresh: 'fa-refresh icon-refresh',
|
||||
toggle: 'fa-list-alt icon-list-alt',
|
||||
columns: 'fa-th icon-th',
|
||||
detailOpen: 'fa-plus icon-plus',
|
||||
detailClose: 'fa-minus icon-minus'
|
||||
},
|
||||
// detailView: true, //是否显示父子表
|
||||
// showExport: true, //是否显示导出
|
||||
// exportDataType: "basic", //basic', 'all', 'selected'.
|
||||
// clickToSelect: true, //是否启用点击选中行
|
||||
// singleSelect: true,
|
||||
height: 640, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度
|
||||
onEditableSave: function (field, row, oldValue, $el) {
|
||||
if (options.updateUrl) {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: options.updateUrl,
|
||||
data: {strJson: JSON.stringify(row)},
|
||||
success: function (json) {
|
||||
if (json.status == 200) {
|
||||
$.alert.info(json.message);
|
||||
} else {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$.alert.error("网络超时!");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$.alert.error("无效的请求地址!");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onLoadSuccess: function () {
|
||||
console.log("table加载完成");
|
||||
setTimeout(function () {
|
||||
gentelella.initSwitchery();
|
||||
}, 0);
|
||||
},
|
||||
onExpandRow: options.onExpandRow,
|
||||
rowStyle: options.rowStyle || function (row, index) {
|
||||
return {};
|
||||
},
|
||||
columns: options.columns
|
||||
});
|
||||
},
|
||||
bindBtnEvent: function (callback) {
|
||||
var $table = this;
|
||||
var options = this.options;
|
||||
/* 添加 */
|
||||
$("#btn_add").click(function () {
|
||||
$table.resetForm();
|
||||
var $modal = $("#addOrUpdateModal");
|
||||
|
||||
$modal.modal('show');
|
||||
$modal.find(".modal-dialog .modal-content .modal-header .modal-title").html("添加" + options.modalName);
|
||||
|
||||
var $password = $("#password");
|
||||
if ($password && $password[0]) {
|
||||
$password.attr("required", "required");
|
||||
}
|
||||
var $username = $("#username");
|
||||
if ($username && $username[0]) {
|
||||
$username.removeAttr("readonly");
|
||||
}
|
||||
$table.bindSaveInfoEvent(options.createUrl, callback);
|
||||
});
|
||||
|
||||
/* 修改 */
|
||||
this.bindClickEvent('.btn-update', function () {
|
||||
var $this = $(this);
|
||||
var userId = $this.attr("data-id");
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: options.getInfoUrl.replace("{id}", userId),
|
||||
success: function (json) {
|
||||
if(json.status != 200) {
|
||||
$.alert.error(json.message);
|
||||
return;
|
||||
}
|
||||
var info = json.data;
|
||||
// console.log(info);
|
||||
$table.resetForm(info);
|
||||
var $modal = $("#addOrUpdateModal");
|
||||
$modal.modal('show');
|
||||
$modal.find(".modal-dialog .modal-content .modal-header .modal-title").html("修改" + options.modalName);
|
||||
var $password = $("#password");
|
||||
if ($password && $password[0]) {
|
||||
$password.removeAttr("required");
|
||||
}
|
||||
var $username = $("#username");
|
||||
if ($username && $username[0]) {
|
||||
$username.attr("readonly", "readonly");
|
||||
}
|
||||
|
||||
$table.bindSaveInfoEvent(options.updateUrl, callback);
|
||||
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
});
|
||||
|
||||
/* 删除 */
|
||||
function remove(ids) {
|
||||
var len = 1;
|
||||
if(typeof ids == "object") {
|
||||
len = ids.length;
|
||||
}
|
||||
$.alert.confirm("确定删除已选中的" + len + "条 [ " + options.modalName + " ] 信息?", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: options.removeUrl,
|
||||
traditional: true,
|
||||
data: {'ids': ids},
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
$table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/* 批量删除用户 */
|
||||
$("#btn_delete_ids").click(function () {
|
||||
var selectedId = $table.getSelectedIds();
|
||||
if (!selectedId || selectedId == '[]' || selectedId.length == 0) {
|
||||
$.alert.error("请至少选择一条记录");
|
||||
return;
|
||||
}
|
||||
remove(selectedId);
|
||||
});
|
||||
|
||||
this.bindClickEvent('.btn-remove', function () {
|
||||
var $this = $(this);
|
||||
var userId = $this.attr("data-id");
|
||||
remove(userId);
|
||||
});
|
||||
},
|
||||
bindEvent: function(eventName, selector, callback){
|
||||
var options = this.options;
|
||||
$(options.tableBox).on(eventName, selector, callback);
|
||||
},
|
||||
bindClickEvent: function(selector, callback) {
|
||||
this.bindEvent("click", selector, callback);
|
||||
},
|
||||
queryParams: function (params) {
|
||||
params = $.extend({}, params);
|
||||
params.keywords = params.searchText;
|
||||
return params;
|
||||
},
|
||||
refresh: function () {
|
||||
var options = this.options;
|
||||
$(options.tableBox).bootstrapTable('refresh', {url: options.url});
|
||||
},
|
||||
getSelectedIds: function () {
|
||||
var selectedJson = this.getSelections();
|
||||
var ids = [];
|
||||
$.each(selectedJson, function (i) {
|
||||
ids.push(selectedJson[i].id);
|
||||
});
|
||||
return ids;
|
||||
},
|
||||
getSelections: function () {
|
||||
var options = this.options;
|
||||
return $(options.tableBox).bootstrapTable('getAllSelections')
|
||||
},
|
||||
isMultipartForm: function (form) {
|
||||
var $form = $(form),
|
||||
enctype = $form.attr("enctype");
|
||||
return enctype && enctype === "multipart/form-data";
|
||||
},
|
||||
bindSaveInfoEvent: function (url, callback) {
|
||||
var $table = this;
|
||||
var $form = $("#addOrUpdateForm"),
|
||||
$submitBtn = $(".addOrUpdateBtn"),
|
||||
$modal = $("#addOrUpdateModal");
|
||||
$submitBtn.unbind('click');
|
||||
callback && callback();
|
||||
$submitBtn.click(function () {
|
||||
if (validator.checkAll($form)) {
|
||||
if ($table.isMultipartForm($form)) {
|
||||
$form.ajaxSubmit({
|
||||
type: 'post',
|
||||
url: url,
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if(json.status == 200) {
|
||||
$modal.modal('hide');
|
||||
}
|
||||
$table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: url,
|
||||
data: $form.serialize(),
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if(json.status == 200) {
|
||||
$modal.modal('hide');
|
||||
}
|
||||
$table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm: function (info) {
|
||||
var $table = this;
|
||||
$("#addOrUpdateModal form input,#addOrUpdateModal form select,#addOrUpdateModal form textarea").each(function () {
|
||||
var $this = $(this);
|
||||
$table.clearText($this, this.type, info);
|
||||
});
|
||||
},
|
||||
clearText: function ($this, type, info) {
|
||||
if($this.hasClass("final") || $this.data("final")) {
|
||||
return;
|
||||
}
|
||||
var $div = $this.parents(".item");
|
||||
if ($div && $div.hasClass("bad")) {
|
||||
$div.removeClass("bad");
|
||||
$div.find("div.alert").remove();
|
||||
}
|
||||
if (info) {
|
||||
var thisName = $this.attr("name");
|
||||
if (("undefined" == typeof thisName)) {
|
||||
return;
|
||||
}
|
||||
var thisValue;
|
||||
if (thisName.indexOf(".") !== -1) {
|
||||
var nodeObj = info[thisName.split(".")[0]];
|
||||
if (nodeObj) {
|
||||
thisValue = nodeObj[thisName.split(".")[1]];
|
||||
}
|
||||
} else {
|
||||
thisValue = info[thisName];
|
||||
}
|
||||
if (type == 'radio') {
|
||||
var _typeof = (typeof thisValue);
|
||||
if (_typeof == "boolean") {
|
||||
$this.iCheck(((thisValue && 1 == $this.val()) || (!thisValue && 0 == $this.val())) ? 'check' : 'uncheck')
|
||||
} else if (_typeof == "number") {
|
||||
$this.iCheck((thisValue == $this.val()) ? 'check' : 'uncheck')
|
||||
} else if (_typeof == "string") {
|
||||
if (thisValue == $this.val()) {
|
||||
$this.iCheck('check');
|
||||
} else {
|
||||
$this.iCheck('uncheck');
|
||||
}
|
||||
}
|
||||
} else if (type.startsWith('select')) {
|
||||
if (thisValue == 'true' || thisValue == true) {
|
||||
thisValue = 1;
|
||||
} else if (thisValue == 'false' || thisValue == false) {
|
||||
thisValue = 0;
|
||||
}
|
||||
$this.val(thisValue);
|
||||
} else {
|
||||
if(type == 'file') {
|
||||
var previewContainer = $this.data("preview-container");
|
||||
if(previewContainer) {
|
||||
$(previewContainer).html('<img src="' + thisValue + '" class="img-responsive img-rounded auto-shake" alt="">')
|
||||
}
|
||||
} else if (type == 'password') {
|
||||
$this.val('');
|
||||
} else {
|
||||
$this.val(thisValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type === 'radio' || type === 'checkbox') {
|
||||
$this.iCheck('uncheck');
|
||||
} else {
|
||||
$this.val('');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
536
blog-admin/src/main/resources/static/assets/js/zhyd.tool.js
Normal file
@ -0,0 +1,536 @@
|
||||
/**
|
||||
*
|
||||
* 项目工具类
|
||||
*
|
||||
*/
|
||||
(function ($) {
|
||||
// 覆盖jquery-confirm中的函数
|
||||
$.jqAlert = $.alert;
|
||||
$.jqConfirm = $.confirm;
|
||||
|
||||
$.extend({
|
||||
alert: {
|
||||
info: function (content, callback, delayTime) {
|
||||
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
|
||||
$.jqAlert({
|
||||
icon: 'fa fa-info-circle',
|
||||
title: '友情提示',
|
||||
content: content,
|
||||
type: 'green',
|
||||
typeAnimated: true,
|
||||
autoClose: delayTime,
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: "关闭",
|
||||
btnClass: 'btn-default',
|
||||
action: callback
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function (content, callback, delayTime) {
|
||||
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
|
||||
$.jqAlert({
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
title: '警告',
|
||||
content: content,
|
||||
autoClose: delayTime,
|
||||
type: 'orange',
|
||||
typeAnimated: true,
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: "关闭",
|
||||
btnClass: 'btn-default',
|
||||
action: callback
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
confirm: function (content, confirmCallback, cancelCallback, delayTime) {
|
||||
delayTime = delayTime ? "cancel|" + delayTime : "cancel|5000";
|
||||
$.jqConfirm({
|
||||
icon: 'fa fa-question-circle',
|
||||
title: '确认?',
|
||||
content: content,
|
||||
autoClose: delayTime,
|
||||
type: 'dark',
|
||||
typeAnimated: true,
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: '确定',
|
||||
btnClass: 'btn-green',
|
||||
action: confirmCallback
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
btnClass: 'btn-default',
|
||||
action: cancelCallback
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
ajaxSuccessConfirm: function (json, callback, cancelCallback) {
|
||||
if (json.status == 200) {
|
||||
if (json.message) {
|
||||
$.alert.confirm(json.message, callback, cancelCallback);
|
||||
}
|
||||
} else {
|
||||
if (json.message) {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
ajaxSuccess: function (json, callback) {
|
||||
if (json.status == 200) {
|
||||
if (json.message) {
|
||||
$.alert.info(json.message, callback);
|
||||
}
|
||||
} else {
|
||||
if (json.message) {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
ajaxError: function () {
|
||||
$.alert.error("网络超时!");
|
||||
}
|
||||
}
|
||||
});
|
||||
$.extend({
|
||||
modal: {
|
||||
material: {
|
||||
_loadData: function(config, callback){
|
||||
$("#selectable").html(config.selectable), $("#selected").html(0);
|
||||
zhyd.mask.loading($(".material-body"), "加载中...");
|
||||
$.ajax({
|
||||
url: "/file/list",
|
||||
data: {pageNumber: config && config.pageNumber ? config.pageNumber : 1},
|
||||
type: "POST",
|
||||
success: function (json) {
|
||||
zhyd.mask.closeAll($(".material-body"));
|
||||
var $box = $(".list-file");
|
||||
var tpl = '{{#list}}<li class="material-item" data-imgUrl="{{fullFilePath}}"><div style="position: relative;">' +
|
||||
'<div class="selected-mask mask-xs"><div class="inner"></div><div class="icon"></div></div>' +
|
||||
'<img class="lazy-img" data-original="{{fullFilePath}}" alt="image"></div></li>{{/list}}{{^list}}<li>素材库为空</li>{{/list}}';
|
||||
var html = Mustache.render(tpl, json);
|
||||
var pageTpl = '{{#data}}<li class="material-page">\n' +
|
||||
' <div class="material-page-body">\n' +
|
||||
' {{#hasPreviousPage}}<a class="btn btn-default btn-sm material-pagination" data-page="{{prePage}}">\n' +
|
||||
' <i class="fa fa-caret-left"></i>\n' +
|
||||
' </a>{{/hasPreviousPage}}<span style="margin-right: 5px;">{{pageNum}}/{{pages}}</span>{{#hasNextPage}}<a class="btn btn-default btn-sm material-pagination" data-page="{{nextPage}}">\n' +
|
||||
' <i class="fa fa-caret-right"></i>\n' +
|
||||
' </a>{{/hasNextPage}}<input class="form-control input-sm material-input">\n' +
|
||||
' <a class="btn btn-default btn-sm material-jump">\n' +
|
||||
' Go\n' +
|
||||
' </a>\n' +
|
||||
' \n' +
|
||||
' </div>\n' +
|
||||
'</li>{{/data}}';
|
||||
html += Mustache.render(pageTpl, {data: json});
|
||||
$box.html(html);
|
||||
|
||||
// 图片懒加载
|
||||
var $lazyImg = $("img.lazy-img");
|
||||
$lazyImg.lazyload({
|
||||
placeholder : appConfig.cmsPath + "/assets/images/loading.gif",
|
||||
effect: "fadeIn",
|
||||
threshold: 100
|
||||
});
|
||||
$lazyImg.trigger("sporty");
|
||||
|
||||
// 绑定分页点击事件
|
||||
$(".material-pagination").unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
var pageNumber = $this.data("page");
|
||||
config.pageNumber = !pageNumber || isNaN(pageNumber) ? 1 : parseInt(pageNumber);
|
||||
$.modal.material._loadData(config);
|
||||
});
|
||||
// 绑定分页-跳转页面点击事件
|
||||
$(".material-jump").unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
var jumpTarget = $(".material-input").val();
|
||||
config.pageNumber = !jumpTarget || isNaN(jumpTarget) ? 1 : parseInt(jumpTarget);
|
||||
$.modal.material._loadData(config);
|
||||
});
|
||||
|
||||
// 绑定图片点击事件
|
||||
var selectable = 0;
|
||||
var $li = $box.find("li.material-item");
|
||||
$li.unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
if(config.multiSelect) {
|
||||
if($this.hasClass("active") || $this.hasClass("selected")) {
|
||||
selectable --;
|
||||
$this.removeClass("active selected");
|
||||
} else {
|
||||
if(selectable >= config.selectable) {
|
||||
$.alert.error("最多只能选择" + config.selectable + "张图片!");
|
||||
return false;
|
||||
}
|
||||
selectable ++;
|
||||
$this.addClass("active selected");
|
||||
}
|
||||
|
||||
$("#selected").html(selectable);
|
||||
} else {
|
||||
$this.addClass("current");
|
||||
$li.each(function () {
|
||||
!$(this).hasClass("current") && $(this).removeClass("active selected");
|
||||
});
|
||||
$this.toggleClass("active selected").removeClass("current");
|
||||
if($this.hasClass("active") || $this.hasClass("selected")) {
|
||||
$("#selected").html(1);
|
||||
} else {
|
||||
$("#selected").html(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 执行回调
|
||||
callback && callback($box);
|
||||
},
|
||||
error: function () {
|
||||
zhyd.mask.closeAll($(".material-body"));
|
||||
}
|
||||
})
|
||||
},
|
||||
open: function (config, callback){
|
||||
config = $.extend({
|
||||
// 是否多选
|
||||
multiSelect: false,
|
||||
// 可选择的数量,当multiSelect为true时可用
|
||||
selectable: 1
|
||||
}, config);
|
||||
$("#chooseImgModal").modal('show');
|
||||
var $this = this;
|
||||
// modal show事件,默认会有300ms的延迟,所以需要加setTimeout,且延迟时间要比modal弹出的时间大
|
||||
// 主要为了解决首次弹出素材库时,图片懒加载无法转换成真实图片地址的问题
|
||||
setTimeout(function () {
|
||||
$this._loadData(config, function ($box) {
|
||||
|
||||
$(".btn-confirm").unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
var imgUrls = [];
|
||||
$box.find("li").each(function () {
|
||||
var $thisLi = $(this);
|
||||
if($thisLi.hasClass("active") || $thisLi.hasClass("selected") ){
|
||||
var imgUrl = $thisLi.attr("data-imgUrl");
|
||||
imgUrls.push(imgUrl);
|
||||
}
|
||||
});
|
||||
if(config.multiSelect) {
|
||||
callback(imgUrls);
|
||||
} else {
|
||||
callback(imgUrls[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$("#btn-material-upload").unbind("click").click(function () {
|
||||
var $input = $("#input-material-upload");
|
||||
$input.click().unbind("change").change(function () {
|
||||
var selectedFiles = document.getElementById("input-material-upload").files;
|
||||
if(!selectedFiles || selectedFiles.length <= 0) {
|
||||
return false;
|
||||
}
|
||||
var $form = $("#materialForm");
|
||||
if (validator.checkAll($form)) {
|
||||
$form.ajaxSubmit({
|
||||
type: "post",
|
||||
url: "/file/add",
|
||||
success: function (json) {
|
||||
if (json.status == 200) {
|
||||
$.modal.material._loadData(config)
|
||||
} else {
|
||||
if (json.message) {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
}, 301);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$.extend({
|
||||
toastr: {
|
||||
initToastr: function () {
|
||||
if (toastr) {
|
||||
toastr.options = {
|
||||
closeButton: true,
|
||||
debug: false,
|
||||
progressBar: true,
|
||||
positionClass: "toast-top-right",
|
||||
onclick: null,
|
||||
showDuration: "300",//显示动作时间
|
||||
hideDuration: "1000",//隐藏动作时间
|
||||
timeOut: "3000",//自动关闭超时时间
|
||||
extendedTimeOut: "1000",
|
||||
showEasing: "swing",
|
||||
hideEasing: "linear",
|
||||
showMethod: "fadeIn",
|
||||
hideMethod: "fadeOut"
|
||||
};
|
||||
}
|
||||
},
|
||||
info: function (message) {
|
||||
this.initToastr();
|
||||
toastr.info(message);
|
||||
},
|
||||
success: function (message) {
|
||||
this.initToastr();
|
||||
toastr.success(message);
|
||||
},
|
||||
warning: function (message) {
|
||||
this.initToastr();
|
||||
toastr.warning(message);
|
||||
},
|
||||
error: function (message) {
|
||||
this.initToastr();
|
||||
toastr.error(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
$.extend({
|
||||
notify: {
|
||||
_open: function (type, title, text) {
|
||||
"undefined" != typeof PNotify && new PNotify({
|
||||
title: title,
|
||||
text: text,
|
||||
type: type,// Type of the notice. 'notice', 'info', 'success', or 'error'.
|
||||
addclass: "dark",// blue purple green aero red dark
|
||||
styling: "bootstrap3", // Can be 'brighttheme', 'bootstrap3', 'bootstrap4'
|
||||
icons: "fontawesome4", // Can be 'brighttheme', 'bootstrap3', 'fontawesome4', 'fontawesome5'
|
||||
shadow: true,
|
||||
delay: 3000
|
||||
// hide: !1
|
||||
});
|
||||
},
|
||||
info: function (message) {
|
||||
this._open("info", "通知", message);
|
||||
},
|
||||
success: function (message) {
|
||||
this._open("success", "成功", message);
|
||||
},
|
||||
notice: function (message) {
|
||||
this._open("notice", "注意", message);
|
||||
},
|
||||
error: function (message) {
|
||||
this._open("error", "警告", message);
|
||||
}
|
||||
}
|
||||
});
|
||||
$.extend({
|
||||
tool: {
|
||||
cache: function (key, value) {
|
||||
if (!value) {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
localStorage.setItem(key, value);
|
||||
return false;
|
||||
},
|
||||
delCache: function (key) {
|
||||
if (this.cache(key)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
},
|
||||
isEmpty: function (value) {
|
||||
if (value == null || this.trim(value) == "") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isInteger: function () {
|
||||
return (new RegExp(/^\d+$/).test(this));
|
||||
},
|
||||
isNumber: function (value, element) {
|
||||
return (new RegExp(/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/).test(this));
|
||||
},
|
||||
trim: function (value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace(/(^\s*)|(\s*$)|\r|\n/g, "");
|
||||
},
|
||||
html2Txt: function (value) {
|
||||
value = this.trim(value);
|
||||
value = value.replace(/(\n)/g, "");
|
||||
value = value.replace(/(\t)/g, "");
|
||||
value = value.replace(/(\r)/g, "");
|
||||
value = value.replace(/<\/?[^>]*>/g, "");
|
||||
value = value.replace(/\s*/g, "");
|
||||
return value;
|
||||
},
|
||||
currentPath: function () {
|
||||
/* // 域
|
||||
var domain = document.domain;
|
||||
// 当前页
|
||||
var nowurl = document.URL;
|
||||
// 来源页
|
||||
var fromurl = document.referrer;
|
||||
|
||||
console.log(domain);
|
||||
console.log(fromurl);
|
||||
console.log(nowurl);*/
|
||||
return window.location.pathname;
|
||||
},
|
||||
getMeta: function (name) {
|
||||
var meta = document.getElementsByTagName('meta');
|
||||
var share_desc = '';
|
||||
for (i in meta) {
|
||||
if (typeof meta[i].name != "undefined" && meta[i].name.toLowerCase() == name.toLowerCase()) {
|
||||
share_desc = meta[i].content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return share_desc;
|
||||
},
|
||||
random: function (min, max) {
|
||||
return Math.floor((Math.random() * max) + min);
|
||||
},
|
||||
shuffle: function (arr) {
|
||||
if (!arr) {
|
||||
return arr;
|
||||
}
|
||||
var len = arr.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var end = len - 1;
|
||||
var index = (Math.random() * (end + 1)) >> 0;
|
||||
var temp = arr[end];
|
||||
arr[end] = arr[index];
|
||||
arr[index] = temp;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
/**
|
||||
* 扩展String方法
|
||||
*/
|
||||
$.extend(String.prototype, {
|
||||
/*trim: function () {
|
||||
return this.replace(/(^\s*)|(\s*$)|\r|\n/g, "");
|
||||
},*/
|
||||
startsWith: function (pattern) {
|
||||
return this.indexOf(pattern) === 0;
|
||||
},
|
||||
endsWith: function (pattern) {
|
||||
var d = this.length - pattern.length;
|
||||
return d >= 0 && this.lastIndexOf(pattern) === d;
|
||||
},
|
||||
replaceSuffix: function (index) {
|
||||
return this.replace(/\[[0-9]+\]/, '[' + index + ']').replace('#index#', index);
|
||||
},
|
||||
getRequestURI: function () {
|
||||
var indexOf = this.indexOf("?");
|
||||
return (indexOf == -1) ? this : this.substr(0, indexOf);
|
||||
},
|
||||
getParams: function (encode) {
|
||||
var params = {},
|
||||
indexOf = this.indexOf("?");
|
||||
if (indexOf != -1) {
|
||||
var str = this.substr(indexOf + 1),
|
||||
strs = str.split("&");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
var item = strs[i].split("=");
|
||||
var val = encode ? item[1].encodeParam() : item[1];
|
||||
params[item[0]] = item.length > 1 ? val : '';
|
||||
}
|
||||
}
|
||||
return params;
|
||||
},
|
||||
encodeParam: function () {
|
||||
return encodeURIComponent(this);
|
||||
},
|
||||
replaceAll: function (os, ns) {
|
||||
return this.replace(new RegExp(os, "gm"), ns);
|
||||
},
|
||||
skipChar: function (ch) {
|
||||
if (!this || this.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (this.charAt(0) === ch) {
|
||||
return this.substring(1).skipChar(ch);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
isPositiveInteger: function () {
|
||||
return (new RegExp(/^[1-9]\d*$/).test(this));
|
||||
},
|
||||
isInteger: function () {
|
||||
return (new RegExp(/^\d+$/).test(this));
|
||||
},
|
||||
isNumber: function (value, element) {
|
||||
return (new RegExp(/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/).test(this));
|
||||
},
|
||||
isValidPwd: function () {
|
||||
return (new RegExp(/^([_]|[a-zA-Z0-9]){6,32}$/).test(this));
|
||||
},
|
||||
isValidMail: function () {
|
||||
return (new RegExp(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(this.trim()));
|
||||
},
|
||||
isSpaces: function () {
|
||||
for (var i = 0; i < this.length; i += 1) {
|
||||
var ch = this.charAt(i);
|
||||
if (ch != ' ' && ch != "\n" && ch != "\t" && ch != "\r") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isMobile: function () {
|
||||
return (new RegExp(/(^[0-9]{11,11}$)/).test(this));
|
||||
},
|
||||
isUrl: function () {
|
||||
return (new RegExp(/^[a-zA-z]+:\/\/([a-zA-Z0-9\-\.]+)([-\w .\/?%&=:]*)$/).test(this));
|
||||
},
|
||||
isExternalUrl: function () {
|
||||
return this.isUrl() && this.indexOf("://" + document.domain) == -1;
|
||||
},
|
||||
parseCurrency: function (num) {
|
||||
var numberValue = parseFloat(this);
|
||||
return parseFloat(numberValue.toFixed(num || 2));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Created by yadong.zhang on 2017-03-19.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 时间对象的格式化;
|
||||
*/
|
||||
Date.prototype.format = function (format) {
|
||||
/*
|
||||
* eg:format="YYYY-MM-dd hh:mm:ss";
|
||||
*/
|
||||
var o = {
|
||||
"M+": this.getMonth() + 1, // month
|
||||
"d+": this.getDate(), // day
|
||||
"h+": this.getHours(), // hour
|
||||
"m+": this.getMinutes(), // minute
|
||||
"s+": this.getSeconds(), // second
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), // quarter
|
||||
"S": this.getMilliseconds()
|
||||
// millisecond
|
||||
};
|
||||
|
||||
if (/(y+)/.test(format)) {
|
||||
format = format.replace(RegExp.$1, (this.getFullYear() + "")
|
||||
.substr(4 - RegExp.$1.length));
|
||||
}
|
||||
|
||||
for (var k in o) {
|
||||
if (new RegExp("(" + k + ")").test(format)) {
|
||||
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k]
|
||||
: ("00" + o[k]).substr(("" + o[k]).length));
|
||||
}
|
||||
}
|
||||
return format;
|
||||
};
|
171
blog-admin/src/main/resources/static/assets/js/zhyd.treetable.js
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 多级菜单
|
||||
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||
* @version 1.0
|
||||
* @date 2018/9/3 12:30
|
||||
*/
|
||||
|
||||
$.extend({
|
||||
/**
|
||||
* 表格插件
|
||||
*/
|
||||
table: {
|
||||
_default_config: {
|
||||
containerBox: '#tree-table-box',
|
||||
modalName: '',
|
||||
columns: [],
|
||||
data: [],
|
||||
toobarTemplate: '<div id="tree-table-toolbar" class="btn-group" role="group" aria-label="..."><button id="add-btn" type="button" class="btn btn-info" title="新增"><i class="fa fa-plus fa-fw"> </i> </button><button id="batch-delete-btn" type="button" class="btn btn-danger" title="批量删除"><i class="fa fa-trash-o fa-fw"> </i> </button></div>',
|
||||
oprater: {
|
||||
title: '操作',
|
||||
width: '100px',
|
||||
align: "center",
|
||||
formatter: function (value, row, index) {
|
||||
var curId = row.id;
|
||||
var actions = [];
|
||||
actions.push('<a class="btn btn-success btn-sm edit-btn" href="javascript:;" data-id="' + curId + '"><i class="fa fa-edit fa-fw"></i></a> ');
|
||||
// actions.push('<a class="btn btn-info btn-sm add-node-btn" href="javascript:;" data-id="' + curId + '"><i class="fa fa-plus fa-fw"></i></a> ');
|
||||
actions.push('<a class="btn btn-danger btn-sm del-node-btn" href="javascript:;" data-id="' + curId + '"><i class="fa fa-trash-o fa-fw"></i></a>');
|
||||
return actions.join('');
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param tableOptions 表格组件的参数
|
||||
* @param urlOptions 与后台交互的url参数
|
||||
*/
|
||||
init: function (tableOptions, urlOptions) {
|
||||
var initOptions = $.extend({}, this._default_config, tableOptions);
|
||||
if (initOptions.toobarTemplate) {
|
||||
$(initOptions.containerBox).before(initOptions.toobarTemplate);
|
||||
}
|
||||
initOptions.columns.push(initOptions.oprater);
|
||||
$(initOptions.containerBox).bootstrapTreeTable({
|
||||
url: urlOptions.listUrl,
|
||||
toolbar: "#tree-table-toolbar", //顶部工具条
|
||||
columns: initOptions.columns,
|
||||
data: initOptions.data
|
||||
});
|
||||
this.initBtnEvent(tableOptions, urlOptions);
|
||||
return initOptions;
|
||||
},
|
||||
/**
|
||||
* 刷新列表
|
||||
* @param tableOptions
|
||||
*/
|
||||
refresh: function (tableOptions) {
|
||||
var initOptions = $.extend({}, this._default_config, tableOptions);
|
||||
// $(initOptions.containerBox).data('bootstrap.tree.table').load();
|
||||
$(initOptions.containerBox).bootstrapTreeTable('refresh');
|
||||
},
|
||||
/**
|
||||
* 初始化按钮事件
|
||||
* @param tableOptions
|
||||
* @param urlOptions
|
||||
*/
|
||||
initBtnEvent: function (tableOptions, urlOptions) {
|
||||
var initOptions = $.extend({}, this._default_config, tableOptions);
|
||||
|
||||
// 获取已选择的id
|
||||
function getSelectedIds() {
|
||||
var selecteds = $(initOptions.containerBox).bootstrapTreeTable('getSelections');
|
||||
var selectedIds = [];
|
||||
$.each(selecteds, function (index, item) {
|
||||
selectedIds.push(item.id);
|
||||
});
|
||||
return (!selectedIds || selectedIds === '[]' || selectedIds.length === 0) ? null : selectedIds;
|
||||
}
|
||||
|
||||
function bindSaveInfoEvent(url) {
|
||||
var $addOrUpdateBtn = $(".addOrUpdateBtn");
|
||||
$addOrUpdateBtn.unbind('click');
|
||||
$addOrUpdateBtn.click(function () {
|
||||
var $addOrUpdateForm = $("#addOrUpdateForm");
|
||||
if (validator.checkAll($addOrUpdateForm)) {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: url,
|
||||
data: $addOrUpdateForm.serialize(),
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
$("#addOrUpdateModal").modal('hide');
|
||||
$.table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加
|
||||
$("#add-btn").click(function () {
|
||||
new Table().resetForm();
|
||||
var $addOrUpdateModal = $("#addOrUpdateModal");
|
||||
$addOrUpdateModal.find(".modal-dialog .modal-content .modal-header h5.modal-title").html("添加" + initOptions.modalName);
|
||||
$addOrUpdateModal.modal('show');
|
||||
|
||||
bindSaveInfoEvent(urlOptions.createUrl);
|
||||
});
|
||||
// 编辑
|
||||
$(initOptions.containerBox).on('click', '.edit-btn', function () {
|
||||
var $this = $(this);
|
||||
var userId = $this.attr("data-id");
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: urlOptions.getInfoUrl.replace("{id}", userId),
|
||||
success: function (json) {
|
||||
var info = json.data;
|
||||
// console.log(info);
|
||||
new Table().resetForm(info);
|
||||
var $addOrUpdateModal = $("#addOrUpdateModal");
|
||||
$addOrUpdateModal.modal('show');
|
||||
$addOrUpdateModal.find(".modal-dialog .modal-content .modal-header h5.modal-title").html("修改" + initOptions.modalName);
|
||||
bindSaveInfoEvent(urlOptions.updateUrl);
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
});
|
||||
// 添加子项
|
||||
$(initOptions.containerBox).on('click', '.add-node-btn', function () {
|
||||
getSelectedIds();
|
||||
});
|
||||
|
||||
function remove(ids) {
|
||||
$.alert.confirm("确定删除已选中的" + ids.length + "条 [ " + initOptions.modalName + " ] 信息?", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: urlOptions.removeUrl,
|
||||
traditional: true,
|
||||
data: {'ids': ids},
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
$.table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
// 删除子项
|
||||
$(initOptions.containerBox).on('click', '.del-node-btn', function () {
|
||||
var $this = $(this);
|
||||
var selectedId = $this.attr("data-id");
|
||||
remove([selectedId]);
|
||||
});
|
||||
|
||||
// 批量删除
|
||||
$("#batch-delete-btn").click(function () {
|
||||
var selectedId = getSelectedIds();
|
||||
if (!selectedId) {
|
||||
$.alert.error("请至少选择一条记录");
|
||||
return false;
|
||||
}
|
||||
remove(selectedId);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
/**
|
||||
*
|
||||
* 图片预览
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* jq 1.9以后已不再支持$.browser 和 $.browser.version,此处自己实现
|
||||
* Use of jQuery.browser is deprecated.
|
||||
* It's included for backwards compatibility and plugins,
|
||||
* although they should work to migrate away.
|
||||
*/
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
// Figure out what browser is being used
|
||||
jQuery.browser = {
|
||||
version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
|
||||
safari: /webkit/.test( userAgent ),
|
||||
opera: /opera/.test( userAgent ),
|
||||
msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
|
||||
mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
|
||||
};
|
||||
|
||||
jQuery.fn.extend({
|
||||
uploadPreview: function (opts) {
|
||||
var _self = this,
|
||||
_this = $(this);
|
||||
opts = jQuery.extend({
|
||||
imgContainer: "",
|
||||
width: 100,
|
||||
height: 100,
|
||||
suffix: ["gif", "jpeg", "jpg", "bmp", "png"],
|
||||
callback: function () {
|
||||
}
|
||||
}, opts || {});
|
||||
_self.getObjectURL = function (file) {
|
||||
var url = null;
|
||||
if (window.createObjectURL != undefined) {
|
||||
url = window.createObjectURL(file)
|
||||
} else if (window.URL != undefined) {
|
||||
url = window.URL.createObjectURL(file)
|
||||
} else if (window.webkitURL != undefined) {
|
||||
url = window.webkitURL.createObjectURL(file)
|
||||
}
|
||||
return url
|
||||
};
|
||||
_this.change(function () {
|
||||
var $this = this;
|
||||
if(!opts.imgContainer){
|
||||
console.error("未指定imgContainer");
|
||||
return;
|
||||
}
|
||||
var $container = $(opts.imgContainer);
|
||||
$container.html('<i class="fa fa-spinner fa-pulse"></i>');
|
||||
if ($this.value) {
|
||||
if (!RegExp("\.(" + opts.suffix.join("|") + ")$", "i").test($this.value.toLowerCase())) {
|
||||
$.alert.error("只支持以下几种文件格式:[" + opts.suffix.join(",") + "]");
|
||||
$this.value = "";
|
||||
$container.html('');
|
||||
return false
|
||||
}
|
||||
var $img = $("<img>");
|
||||
try {
|
||||
setTimeout(function () {
|
||||
$container.html('');
|
||||
$img.attr('src', _self.getObjectURL($this.files[0]));
|
||||
$img.addClass("img-responsive img-rounded auto-shake");
|
||||
$img.appendTo($container);
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
$.alert.error("当前浏览器不支持图片预览!");
|
||||
}
|
||||
opts.callback()
|
||||
} else {
|
||||
$container.html('');
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
276
blog-admin/src/main/resources/templates/article/list.ftl
Normal file
@ -0,0 +1,276 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="">
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">文章管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<div class="btn-group hidden-xs" id="toolbar">
|
||||
<@shiro.hasPermission name="article:publish">
|
||||
<a class="btn btn-success" title="发表文章" href="${(config.articleEditor! == 'md')?string('/article/publishMd', '/article/publish')}"> <i class="fa fa-pencil fa-fw"></i> </a>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="article:batchDelete">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="删除选中">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<#-- 由草稿状态批量修改为已发布状态 -->
|
||||
<@shiro.hasPermission name="article:publish">
|
||||
<button id="btn_update_status" type="button" class="btn btn-default" title="批量发布">
|
||||
<i class="fa fa-bullhorn fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="article:batchPush">
|
||||
<button id="btn_push_ids" type="button" class="btn btn-info" title="批量推送到百度">
|
||||
<i class="fa fa-send-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<table id="tablelist">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var trId = row.id;
|
||||
// var recommended = row.recommended ? '<i class="fa fa-thumbs-o-down"></i>取消推荐' : '<i class="fa fa-thumbs-o-up"></i>推荐';
|
||||
// var top = row.top ? '<i class="fa fa-arrow-circle-down"></i>取消置顶' : '<i class="fa fa-arrow-circle-up"></i>置顶';
|
||||
var operateBtn = [
|
||||
'<@shiro.hasPermission name="article:push"><a class="btn btn-sm btn-info btn-push" title="推送" data-id="' + trId + '"><i class="fa fa-send-o"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="article:edit"><a class="btn btn-sm btn-success" href="/article/update/' + trId + '"><i class="fa fa-edit fa-fw"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="article:delete"><a class="btn btn-sm btn-danger btn-remove" data-id="' + trId + '"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>',
|
||||
<#--'<@shiro.hasPermission name="article:top"><a class="btn btn-sm btn-success btn-top" data-id="' + trId + '">' + top + '</a></@shiro.hasPermission>',-->
|
||||
<#--'<@shiro.hasPermission name="article:recommend"><a class="btn btn-sm btn-success btn-recommend" data-id="' + trId + '">' + recommended + '</a></@shiro.hasPermission>'-->
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var options = {
|
||||
modalName: "文章",
|
||||
url: "/article/list",
|
||||
getInfoUrl: "/article/get/{id}",
|
||||
removeUrl: "/article/remove",
|
||||
columns: [
|
||||
{
|
||||
checkbox: true
|
||||
}, {
|
||||
field: 'title',
|
||||
title: '标题',
|
||||
width: '270px',
|
||||
formatter: function (code, row, index) {
|
||||
var title = code;
|
||||
if(!title) {
|
||||
return '-';
|
||||
}
|
||||
title = title.length > 30 ? (title.substr(0, 30) + '...') : title;
|
||||
var id = row.id;
|
||||
var status = row.status ? '<span class="label label-success" style="margin-right: 5px;">已发布</span>' : '<span class="label label-danger" style="margin-right: 5px;">草稿</span>';
|
||||
return status + '<a href="' + appConfig.wwwPath + '/article/' + id + '" target="_blank" title="' + code + '">' + title + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'coverImage',
|
||||
title: '封面图',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
editable: false,
|
||||
formatter: function (code, row, index) {
|
||||
return code ? '<a href="' + code + '" class="showImage" title="' + row.title + '" rel="external nofollow"><img src="' + code + '" alt="' + row.title + '" class="img-rounded" style="width: 30px;height: auto;"></a>' : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'comment',
|
||||
title: '评论',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
var checked = code ? 'checked' : '';
|
||||
return '<input type="checkbox" name="comment" class="js-switch btn-comment" data-id="' + row.id + '" data-type="comment" ' + checked + '>';
|
||||
}
|
||||
}, {
|
||||
field: 'recommended',
|
||||
title: '推荐 <i class="fa fa-question-circle-o" title="推荐的文章会在首页滚动显示"></i>',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
var checked = code ? 'checked' : '';
|
||||
return '<input type="checkbox" name="recommended" class="js-switch btn-recommended" data-id="' + row.id + '" data-type="recommend" ' + checked + '>';
|
||||
}
|
||||
}, {
|
||||
field: 'top',
|
||||
title: '置顶',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
var checked = code ? 'checked' : '';
|
||||
return '<input type="checkbox" name="top" class="js-switch btn-top" data-id="' + row.id + '" data-type="top" ' + checked + '>';
|
||||
}
|
||||
}, {
|
||||
field: 'lookCount',
|
||||
title: '浏览',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'commentCount',
|
||||
title: '评论',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'loveCount',
|
||||
title: '喜欢',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'createTime',
|
||||
title: '发布时间',
|
||||
width: '130px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return new Date(code).format("yyyy-MM-dd hh:mm:ss")
|
||||
}
|
||||
}, {
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
align: "center",
|
||||
width: '100px',
|
||||
formatter: operateFormatter //自定义方法,添加操作按钮
|
||||
}
|
||||
]
|
||||
};
|
||||
// 初始化table组件
|
||||
var table = new Table(options);
|
||||
table.init();
|
||||
|
||||
table.bindClickEvent('.switchery', function () {
|
||||
var $input = $(this).prev();
|
||||
|
||||
var id = $input.data("id");
|
||||
var type = $input.data("type");
|
||||
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/article/update/" + type,
|
||||
traditional: true,
|
||||
data: {'id': id},
|
||||
success: function (json) {
|
||||
if (json.status !== 200) {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 推送到百度
|
||||
*/
|
||||
table.bindClickEvent('.btn-push', function () {
|
||||
var $this = $(this);
|
||||
var userId = $this.attr("data-id");
|
||||
push(userId);
|
||||
});
|
||||
|
||||
/**
|
||||
* 批量推送到百度
|
||||
*/
|
||||
$("#btn_push_ids").click(function () {
|
||||
var selectedId = table.getSelectedIds();
|
||||
if (!selectedId || selectedId == '[]' || selectedId.length == 0) {
|
||||
$.alert.error("请至少选择一条记录");
|
||||
return;
|
||||
}
|
||||
push(selectedId);
|
||||
});
|
||||
|
||||
/**
|
||||
* 批量修改状态
|
||||
*/
|
||||
$("#btn_update_status").click(function () {
|
||||
var selectedId = table.getSelectedIds();
|
||||
if (!selectedId || selectedId == '[]' || selectedId.length == 0) {
|
||||
$.alert.error("请至少选择一条记录");
|
||||
return;
|
||||
}
|
||||
$.alert.confirm("确定批量发布?发布完成后用户可见", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/article/batchPublish",
|
||||
traditional: true,
|
||||
data: {'ids': selectedId},
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
function push(ids) {
|
||||
$.alert.confirm("确定推送到百度站长平台?", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/article/pushToBaidu/urls",
|
||||
traditional: true,
|
||||
data: {'ids': ids},
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if (json.status == 200) {
|
||||
var dataJson = JSON.parse(json.data);
|
||||
/**
|
||||
* success int 成功推送的url条数
|
||||
* remain int 当天剩余的可推送url条数
|
||||
* not_same_site array 由于不是本站url而未处理的url列表
|
||||
* not_valid array 不合法的url列表
|
||||
*/
|
||||
var successNum = dataJson.success;
|
||||
var remain = dataJson.remain;
|
||||
var notSameSite = dataJson.not_same_site;
|
||||
var notValid = dataJson.not_valid;
|
||||
var message = '成功推送' + successNum + '条url\n';
|
||||
if (notValid) {
|
||||
message += '不合法的url:' + notValid + '\n';
|
||||
}
|
||||
message += '今日剩余' + remain + '条可推送的url。';
|
||||
$.alert.info(message, null, 5000);
|
||||
}
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
@ -0,0 +1,89 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<#setting number_format="#">
|
||||
<@header>
|
||||
<link href="https://cdn.jsdelivr.net/npm/simplemde@1.11.2/dist/simplemde.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/github-markdown-css@2.10.0/github-markdown.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/highlight.js@9.12.0/styles/github.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.CodeMirror, .CodeMirror-scroll {
|
||||
min-height: 130px;
|
||||
max-height: 200px;
|
||||
}
|
||||
.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word) {
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
</@header>
|
||||
<div class="clearfix"></div>
|
||||
<form id="publishForm" class="form-horizontal form-label-left" novalidate>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li><a href="/articles">文章列表</a></li>
|
||||
<li class="active">发布文章-Markdown编辑器</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>发布文章 <small>可以通过右上角“系统配置”-“文章编辑器”选择默认的文章发布编辑器</small></h2>
|
||||
<#if !id??>
|
||||
<div class="pull-right"><small>切换到 <a class="pointer" id="changeEditor" data-href="/article/publish">wangEditor编辑器</a></small></div>
|
||||
</#if>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<input type="hidden" name="id">
|
||||
<input type="hidden" name="isMarkdown" value="1">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-1" for="title">标题 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-8">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="title" id="title" required="required" placeholder="请输入标题"/>
|
||||
</div>
|
||||
<div class="col-md-1 col-sm-1 col-xs-1">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="square" checked name="original"> 原创
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="password">内容 <span class="required">*</span></label>
|
||||
<div class="col-md-10 col-sm-10 col-xs-10">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="content" name="content" style="display: none" required="required"></textarea>
|
||||
<textarea class="form-control col-md-7 col-xs-12 valid" id="contentMd" name="contentMd" style="display: none" required="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-12"></label>
|
||||
<div class="col-md-10 col-sm-10 col-xs-12">
|
||||
<button type="button" class="btn btn-success to-choose-info"><i class="fa fa-pencil"> 发布文章</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@publishmodal></@publishmodal>
|
||||
</form>
|
||||
</div>
|
||||
<@chooseImgModal></@chooseImgModal>
|
||||
<@footer>
|
||||
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/highlight.js@9.12.0/lib/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/simplemde@1.11.2/dist/simplemde.min.js"></script>
|
||||
<script type="text/javascript" src="/assets/js/inline-attachment.js"></script>
|
||||
<script type="text/javascript" src="/assets/js/codemirror.inline-attachment.js"></script>
|
||||
<script>
|
||||
var op = {
|
||||
id: "contentMd",
|
||||
uniqueId: "mdEditor_1",
|
||||
uploadUrl: "/api/uploadFileForMd"
|
||||
};
|
||||
zhyd.simpleMDE.init(op);
|
||||
articleId = '${id}';
|
||||
</script>
|
||||
<script src="/assets/js/zhyd.publish-article.js"></script>
|
||||
</@footer>
|
77
blog-admin/src/main/resources/templates/article/publish.ftl
Normal file
@ -0,0 +1,77 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<#setting number_format="#">
|
||||
<@header>
|
||||
|
||||
</@header>
|
||||
<form id="publishForm" class="form-horizontal form-label-left" novalidate>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li><a href="/articles">文章列表</a></li>
|
||||
<li class="active">发布文章-wangEditor编辑器</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>发布文章 <small>可以通过右上角“系统配置”-“文章编辑器”选择默认的文章发布编辑器</small></h2>
|
||||
<#if !id??>
|
||||
<div class="pull-right"><small>切换到 <a class="pointer" id="changeEditor" data-href="/article/publishMd">Markdown编辑器</a></small></div>
|
||||
</#if>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<input type="hidden" name="isMarkdown" value="0">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="title">标题 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="title" id="title" required="required" placeholder="请输入标题"/>
|
||||
</div>
|
||||
<div class="col-md-1 col-sm-1 col-xs-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="square" checked name="original"> 原创
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="password">内容 <span class="required">*</span></label>
|
||||
<div class="col-md-11 col-sm-11 col-xs-12">
|
||||
<div id="editor" style="width: 100%;height: 150px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-1 col-sm-1 col-xs-12"></label>
|
||||
<div class="col-md-10 col-sm-10 col-xs-12">
|
||||
<button type="button" class="btn btn-success to-choose-info"><i class="fa fa-pencil"> 发布文章</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@publishmodal></@publishmodal>
|
||||
</form>
|
||||
<@chooseImgModal></@chooseImgModal>
|
||||
<@footer>
|
||||
<script>
|
||||
articleId = '${id}';
|
||||
$(function () {
|
||||
zhyd.wangEditor.init({
|
||||
container: "#editor",
|
||||
textareaName: "content",
|
||||
uploadUrl: "/api/uploadFile",
|
||||
uploadFileName: "file",
|
||||
uploadType: "article",
|
||||
customCss: {
|
||||
"height": "100%",
|
||||
"max-height": "115px"
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<script src="/assets/js/zhyd.publish-article.js"></script>
|
||||
</@footer>
|
116
blog-admin/src/main/resources/templates/article/tags.ftl
Normal file
@ -0,0 +1,116 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="">
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">文章标签管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<div class="btn-group hidden-xs" id="toolbar">
|
||||
<@shiro.hasPermission name="tag:add">
|
||||
<button id="btn_add" type="button" class="btn btn-info" title="新增标签">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="tag:batchDelete">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="删除选中">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<table id="tablelist">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="添加标签">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="name">名称 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="name" id="name" required="required" placeholder="请输入标签名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="description">标签描述 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="description" name="description" placeholder="请输入标签描述" maxlength="100"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var trId = row.id;
|
||||
var operateBtn = [
|
||||
'<@shiro.hasPermission name="tag:edit"><a class="btn btn-sm btn-success btn-update" data-id="' + trId + '"title="编辑"><i class="fa fa-edit fa-fw"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="tag:delete"><a class="btn btn-sm btn-danger btn-remove" data-id="' + trId + '"title="删除"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>'
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var options = {
|
||||
modalName: "标签",
|
||||
url: "/tag/list",
|
||||
getInfoUrl: "/tag/get/{id}",
|
||||
updateUrl: "/tag/edit",
|
||||
removeUrl: "/tag/remove",
|
||||
createUrl: "/tag/add",
|
||||
columns: [
|
||||
{
|
||||
checkbox: true
|
||||
}, {
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: '60px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'name',
|
||||
title: '名称',
|
||||
width: '150px',
|
||||
formatter: function (code, row, index) {
|
||||
var id = row.id;
|
||||
return '<a href="' + appConfig.wwwPath + '/tag/' + id + '" target="_blank">' + row.name + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'description',
|
||||
title: '描述',
|
||||
width: '450px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
align: "center",
|
||||
width: '100px',
|
||||
formatter: operateFormatter //自定义方法,添加操作按钮
|
||||
}
|
||||
]
|
||||
};
|
||||
// 初始化table组件
|
||||
var table = new Table(options);
|
||||
table.init();
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
191
blog-admin/src/main/resources/templates/article/types.ftl
Normal file
@ -0,0 +1,191 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="">
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">文章分类管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<table id="tree-table-box">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="添加分类">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="name">名称 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="name" id="name" required="required" placeholder="请输入分类名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="type">父级 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||
<select id="pid" name="pid" class="form-control col-md-5 col-xs-5" target="combox" data-url="/type/listParent" data-method="post"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="description">描述 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="description" name="description" placeholder="请输入分类描述" maxlength="100"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="sort">排序 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="sort" id="sort" placeholder="请输入排序"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="available">是否可用 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" class="flat" checked name="available" value="1"> 可用
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" class="flat" name="available" value="0"> 禁用
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="icon">图标 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="icon" id="icon" placeholder="请输入图标,比如:fa fa-qq"/>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script type="text/javascript" src="/assets/js/zhyd.treetable.js"></script>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var trId = row.id;
|
||||
var operateBtn = [
|
||||
'<@shiro.hasPermission name="type:edit"><a class="btn btn-sm btn-success btn-update" data-id="' + trId + '"title="编辑"><i class="fa fa-edit fa-fw"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="type:delete"><a class="btn btn-sm btn-danger btn-remove" data-id="' + trId + '"title="删除"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>'
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$.table.init({
|
||||
modalName: "分类",
|
||||
columns: [{
|
||||
field: 'selectItem',
|
||||
checkbox: true
|
||||
}, {
|
||||
field: '-',
|
||||
title: '层级',
|
||||
width: "60px",
|
||||
align: "center"
|
||||
}, {
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: '60px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'name',
|
||||
title: '名称',
|
||||
width: '180px',
|
||||
formatter: function (code, row, index) {
|
||||
var id = row.id;
|
||||
return '<a href="' + appConfig.wwwPath + '/type/' + id + '" target="_blank">' + row.name + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'parent.name',
|
||||
title: '父级分类',
|
||||
width: '180px',
|
||||
formatter: function (code, row, index) {
|
||||
var parent = row.parent;
|
||||
if(!parent) {
|
||||
return "-";
|
||||
}
|
||||
return '<a href="' + appConfig.wwwPath + '/type/' + parent.id + '" target="_blank">' + parent.name + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'description',
|
||||
title: '描述',
|
||||
width: '550px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'sort',
|
||||
title: '排序',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'available',
|
||||
title: '可用',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? '<span class="label label-success">可用</span>' : '<span class="label label-danger">不可用</span>';
|
||||
}
|
||||
}, {
|
||||
field: 'icon',
|
||||
title: '图标',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
return '<i class="' + row.icon + '"></i>';
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
listUrl: "/type/list",
|
||||
getInfoUrl: "/type/get/{id}",
|
||||
updateUrl: "/type/edit",
|
||||
removeUrl: "/type/remove",
|
||||
createUrl: "/type/add"
|
||||
});
|
||||
|
||||
/**
|
||||
* 当修改类型信息时,禁用父级列表中value和当前待修改的类型的id一致的option
|
||||
*/
|
||||
var editId;
|
||||
$("#addOrUpdateModal").unbind('show.bs.modal').on('show.bs.modal', function () {
|
||||
editId = $("#addOrUpdateModal").find("form>input[name=id]").val();
|
||||
if(editId) {
|
||||
$("select#pid option[value='" + editId + "']").attr("disabled","disabled");
|
||||
}
|
||||
}).unbind('hide.bs.modal').on('hide.bs.modal', function () {
|
||||
if(editId) {
|
||||
$("select#pid option[value='" + editId + "']").removeAttr("disabled");
|
||||
editId = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
299
blog-admin/src/main/resources/templates/comment/list.ftl
Normal file
@ -0,0 +1,299 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="">
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">评论管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<div class="btn-group hidden-xs" id="toolbar">
|
||||
<@shiro.hasPermission name="comment:batchDelete">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="删除选中">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<table id="tablelist">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--添加弹框-->
|
||||
<div class="modal fade" id="replyModal" tabindex="-1" role="dialog" aria-labelledby="addroleLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addroleLabel">回复评论</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="replyForm" class="form-horizontal form-label-left" novalidate>
|
||||
<input type="hidden" name="sid" id="sid">
|
||||
<input type="hidden" name="pid" id="pid">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="description">评论 </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea class="form-control col-md-12 col-xs-12" rows="10" cols="20" id="content" name="content" placeholder="请输入评论"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
|
||||
<button type="button" class="btn btn-success replyBtn"><i class="fa fa-mail-reply"> 回复</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--/添加弹框-->
|
||||
<!--添加弹框-->
|
||||
<div class="modal fade" id="auditModal" tabindex="-1" role="dialog" aria-labelledby="addroleLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addroleLabel">审核评论</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="auditForm" class="form-horizontal form-label-left" novalidate>
|
||||
<input type="hidden" name="id" id="audit_id">
|
||||
<input type="hidden" name="sid" id="audit_sid">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="description">状态 </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<select name="status" id="status" class="form-control" required="required">
|
||||
<option value="">请选择</option>
|
||||
<option value="APPROVED">审核通过</option>
|
||||
<option value="REJECT">审核失败</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group hide" id="status-remark">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="description">备注 </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="remark" name="remark" placeholder="请输入审核备注(审核失败/删除的原因)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="description">回复该评论 </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="contentText" name="contentText" rows="10" cols="20" placeholder="请输入评论"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="description">发送邮件 </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="square" name="sendEmail"> 勾选发送
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
|
||||
<button type="button" class="btn btn-success auditBtn"><i class="fa fa-hand-o-up"> 提交审核</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var id = row.id;
|
||||
var sid = row.sid;
|
||||
var operateBtn = [
|
||||
'<@shiro.hasPermission name="comment:reply"><a class="btn btn-primary btn-reply" data-id="' + id + '" data-sid="' + sid + '" title="回复"><i class="fa fa-reply"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="comment:audit"><a class="btn btn-warning btn-audit" data-id="' + id + '" data-sid="' + sid + '" title="审核"><i class="fa fa-shield"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="comment:delete"><a class="btn btn-danger btn-remove" data-id="' + id + '" data-sid="' + sid + '" title="删除"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>'
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var options = {
|
||||
modalName: "评论",
|
||||
url: "/comment/list",
|
||||
getInfoUrl: "/comment/get/{id}",
|
||||
updateUrl: "/comment/edit",
|
||||
removeUrl: "/comment/remove",
|
||||
createUrl: "/comment/add",
|
||||
columns: [
|
||||
{
|
||||
checkbox: true
|
||||
}, {
|
||||
field: 'avatar',
|
||||
title: '作者',
|
||||
width: '200px',
|
||||
formatter: function (code, row, index) {
|
||||
return '<ul class="list-unstyled">' +
|
||||
'<li>' +
|
||||
'<a href="' + row.url + '" target="_blank"><img src="' + filterXSS(row.avatar) + '" onerror="this.src=\'/assets/images/user.png\'" style="width: 20px;border-radius: 50%;position: relative;top: -2px;"/> ' + filterXSS(row.nickname) + '</a>' +
|
||||
'<a href="javascript:void(0);" onclick="window.open(\'tencent://message/?uin=' + row.qq + '&Menu=yes\')" rel="external nofollow" target="_blank"><i class="fa fa-qq fa-fw"></i></a>' +
|
||||
'<a href="mailto:' + filterXSS(row.email) + '" rel="external nofollow" target="_blank"><i class="fa fa-envelope fa-fw"></i></a>' +
|
||||
'</li>' +
|
||||
'<li><i class="fa fa-address-book-o fa-fw"></i> <span style="color: #a9a9a9;">' + row.ip + ' | ' + row.address + '</span></li>' +
|
||||
'<li><i class="fa fa-windows fa-fw"></i> <span style="color: #a9a9a9;">' + row.os + ' | ' + row.browser + '</span></li>' +
|
||||
'<li><i class="fa fa-clock-o fa-fw"></i> <span style="color: #a9a9a9;">' + row.createTimeString + '</span></li></ul>';
|
||||
}
|
||||
}, {
|
||||
field: 'content',
|
||||
title: '内容',
|
||||
width: '380px',
|
||||
formatter: function (code, row, index) {
|
||||
var content = filterXSS(row.content);
|
||||
var source = '<a href="' + appConfig.wwwPath + row.sourceUrl + '" target="_blank">' + row.articleTitle + '</a>';
|
||||
var $parent = row.parent;
|
||||
var parent = $parent ? '<div style="background-color: #f1f1f1;padding: 5px;margin: 5px;border-radius: 4px;"><div style="padding-left: 10px;"><i class="fa fa-quote-left fa-fw"></i><strong>原评:</strong>' + filterXSS($parent.content) + '</div></div>' : '';
|
||||
return '<div style="border-bottom: 1px solid #dcddde;margin-bottom: 10px;">评论自:'+source+'</div>' +
|
||||
'<div class="col-md-12">' + content + parent + '</div>';
|
||||
}
|
||||
}, {
|
||||
field: 'support',
|
||||
title: '赞/踩',
|
||||
width: '40px',
|
||||
align: "center",
|
||||
formatter: function (code, row, index) {
|
||||
return row.support + "/" + row.oppose;
|
||||
}
|
||||
}, {
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: '40px',
|
||||
align: "center",
|
||||
formatter: function (code, row, index) {
|
||||
var html = '';
|
||||
if(code == 'VERIFYING'){
|
||||
html = '<span class="label label-danger">' + row.statusDesc + '</span>';
|
||||
} else if (code == 'REJECT') {
|
||||
html = '<span class="label label-warning">' + row.statusDesc + '</span>';
|
||||
} else if (code == 'DELETED') {
|
||||
html = '<span class="label label-danger">' + row.statusDesc + '</span>';
|
||||
} else {
|
||||
html = '<span class="label label-success">' + row.statusDesc + '</span>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
}, {
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
align: "center",
|
||||
width: '100px',
|
||||
formatter: operateFormatter //自定义方法,添加操作按钮
|
||||
}
|
||||
],
|
||||
rowStyle: function (row, index) {
|
||||
//这里有5个取值代表5中颜色['active', 'success', 'info', 'warning', 'danger'];
|
||||
var strclass = "";
|
||||
if (row.status == 'REJECT'|| row.status == 'DELETED') {
|
||||
strclass = 'warning';
|
||||
} else if (row.status == 'VERIFYING') {
|
||||
strclass = 'danger';
|
||||
}
|
||||
return { 'classes': strclass }
|
||||
}
|
||||
};
|
||||
// 初始化table组件
|
||||
var table = new Table(options);
|
||||
table.init();
|
||||
|
||||
var $tablelist = $(table.options.tableBox);
|
||||
/**
|
||||
* 回复
|
||||
*/
|
||||
$tablelist.on('click', '.btn-reply', function () {
|
||||
var $this = $(this);
|
||||
var $replyForm = $("#replyForm");
|
||||
$replyForm.find("input,select,textarea").each(function () {
|
||||
var $this = $(this);
|
||||
new Table().clearText($this, this.type);
|
||||
});
|
||||
var pid = $this.attr("data-id");
|
||||
var sid = $this.attr("data-sid");
|
||||
$("#sid").val(sid);
|
||||
$("#pid").val(pid);
|
||||
$("#replyModal").modal('show');
|
||||
var $replyBtn = $(".replyBtn");
|
||||
$replyBtn.unbind("click");
|
||||
$replyBtn.click(function () {
|
||||
if (validator.checkAll($replyForm)) {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/comment/reply",
|
||||
data: $replyForm.serialize(),
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
$("#replyModal").modal('hide');
|
||||
table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
/**
|
||||
* audit
|
||||
*/
|
||||
$tablelist.on('click', '.btn-audit', function () {
|
||||
var $this = $(this);
|
||||
var userId = $this.attr("data-id");
|
||||
var $auditForm = $("#auditForm");
|
||||
$auditForm.find("input,select,textarea").each(function () {
|
||||
var $this = $(this);
|
||||
new Table().clearText($this, this.type);
|
||||
});
|
||||
$("#audit_id").val(userId);
|
||||
$("#audit_sid").val($this.attr("data-sid"));
|
||||
|
||||
$("#auditModal").modal('show');
|
||||
var $auditBtn = $(".auditBtn");
|
||||
$auditBtn.unbind("click");
|
||||
$auditBtn.click(function () {
|
||||
if (validator.checkAll($auditForm)) {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/comment/audit",
|
||||
data: $("#auditForm").serialize(),
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
$("#auditModal").modal('hide');
|
||||
table.refresh();
|
||||
zhyd.initCommentNotify();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$("#status").change(function () {
|
||||
var thisVal = $(this).val();
|
||||
if(thisVal === "REJECT" || thisVal === "DELETED") {
|
||||
$("#status-remark").removeClass("hide");
|
||||
} else {
|
||||
$("#status-remark").addClass("hide");
|
||||
$("#remark").val("");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
672
blog-admin/src/main/resources/templates/config.ftl
Normal file
@ -0,0 +1,672 @@
|
||||
<#include "include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">系统配置</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>系统配置 </h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12 profile_left">
|
||||
<div class="" role="tabpanel" data-example-id="togglable-tabs">
|
||||
<ul id="myTab" class="nav nav-tabs bar_tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#tab_basic" id="basic-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-info fa-fw"></i> 基本信息</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_seo" id="seo-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-sitemap fa-fw"></i> SEO</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_storage" id="storage-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-cube fa-fw"></i> 云存储</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_auth" id="auth-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-sign-in fa-fw"></i> 登录</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_comment" id="comment-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-commenting-o fa-fw"></i> 评论</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_article_editor" id="article-editor-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-edit fa-fw"></i> 文章编辑器</a>
|
||||
</li>
|
||||
<li role="presentation" class="">
|
||||
<a href="#tab_contact" role="tab" id="contact-tab" data-toggle="tab" aria-expanded="false"><i class="fa fa-id-card-o fa-fw"></i> 联系方式</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#tab_praise" id="praise-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-money fa-fw"></i> 赞赏码</a>
|
||||
</li>
|
||||
<li role="presentation" class="">
|
||||
<a href="#tab_setting" role="tab" id="setting-tab" data-toggle="tab" aria-expanded="false"><i class="fa fa-tasks fa-fw"></i> 其他</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade active in" id="tab_basic"
|
||||
aria-labelledby="basic-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="siteDesc">站点简介 <i class="fa fa-question-circle" title="一句话简介"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="siteDesc" id="siteDesc" required="required" placeholder="一句话简介"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="domain">主域名 <i class="fa fa-question-circle" title="例如:https://www.zhyd.me的主域名就是zhyd.me"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="domain" id="domain" required="required" placeholder="例如: zhyd.me"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="siteUrl">博客地址 <i class="fa fa-question-circle" title="博客前台地址,例如: http://localhost:8443"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="siteUrl" id="siteUrl" required="required" placeholder="例如: http://localhost:8443"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="siteFavicon">站点图标
|
||||
<i class="fa fa-question-circle" title="favicon,浏览器标签网站标题左侧的图标"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="siteFavicon" id="siteFavicon" required="required" placeholder="例如:http://localhost:8443/favicon.ico"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="staticWebSite">资源文件域名
|
||||
<i class="fa fa-question-circle" title="js、css、img等文件的域名地址,如果是在本项目内,则与“博客地址”设置一样即可"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="staticWebSite" id="staticWebSite" required="required" placeholder="例如:http://localhost:8443"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="staticWebSite">管理系统地址
|
||||
<i class="fa fa-question-circle" title="博客后台管理系统的地址,例如: http://localhost:8085"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="cmsUrl" id="cmsUrl" required="required" placeholder="例如: http://localhost:8085"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="copyright">版权信息
|
||||
<i class="fa fa-question-circle" title="网站版权信息"></i> </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="copyright" id="copyright" placeholder="例如:Copyright © ${.now?string("yyyy")} zhyd.me All Rights Reserved"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="copyright">网站备案号
|
||||
<i class="fa fa-question-circle" title="网站备案号"></i> </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="recordNumber" id="recordNumber" placeholder="例如:鲁ICP备17054970号-1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="lazyloadPath">懒加载图片
|
||||
<i class="fa fa-question-circle" title="用于前台网站中对图片进行懒加载显示"></i> </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="lazyloadPath" id="lazyloadPath" placeholder="例如:${config.staticWebSite}/img/loading.gif"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="installdate">博客安装日期
|
||||
<i class="fa fa-question-circle" title="用于前台计算系统运行天数"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class='input-group date myDatepicker'>
|
||||
<input type='text' class="form-control" readonly="readonly" id="installdate" name="installdate" required="required" placeholder="请选择系统安装日期"/>
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_seo" aria-labelledby="seo-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="siteName">首页标题 <i class="fa fa-question-circle" title="百度白皮书推荐的格式:关键词1_关键词2_关键词3_关键词4-品牌词"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="siteName" id="siteName" required="required" placeholder="请输入站点名"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="homeDesc">META描述 <i class="fa fa-question-circle" title="对keywords进行扩展描述,100~130字左右即可"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="homeDesc" name="homeDesc" required="required" placeholder="请输入首页描述" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="homeKeywords">META关键词 <i class="fa fa-question-circle" title="网站关键字,半角逗号分割,不建议多,贴合网站主题"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="homeKeywords" name="homeKeywords" required="required" placeholder="请输入首页关键字(半角逗号分隔)" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="baiduPushToken">百度推送Token <i class="fa fa-question-circle" title="方便百度引擎快速收录"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="baiduPushToken" id="baiduPushToken" required="required" placeholder="请输入百度推送Token,推送功能能加快百度搜索引擎对博文的索引速度"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<small>获取地址:<a href="https://ziyuan.baidu.com/linksubmit/index" target="_blank">点击获取百度推送Token</a></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="baiduPushCookie">百度推送Cookie <i class="fa fa-question-circle" title="请求API使用"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" name="baiduPushCookie" id="baiduPushCookie" required="required" placeholder="请输入百度推送Cookie" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<small>帮助文档:<a href="http://t.cn/AiCIWi0Q" target="_blank">OneBlog-第三方配置参考-百度站长平台配置</a></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_storage" aria-labelledby="storage-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="alert alert-info" role="alert" style="color: white">
|
||||
<a href="#" class="close" data-dismiss="alert">×</a>
|
||||
<i class="fa fa-info-circle fa-fw"></i>注意:系统<strong>暂不自持自动同步</strong>各个云存储空间中的文件,所以当切换云存储类型时可能会造成<strong>部分图片不可用</strong>的情况!请悉知!
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="title">存储类型 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-8">
|
||||
<div class="checkbox">
|
||||
<label for="storageType" style="margin-right: 10px"> <input type="radio" class="square" name="storageType" value="local" checked="checked"/> 本地 </label>
|
||||
<label for="storageType" style="margin-right: 10px"><input type="radio" class="square" name="storageType" value="qiniu"/> 七牛云 </label>
|
||||
<label for="storageType" style="margin-right: 10px"><input type="radio" class="square" name="storageType" value="aliyun"/> 阿里云OSS</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-box" id="local">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="localFileUrl">文件服务器域名 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="localFileUrl" id="localFileUrl" required="required" placeholder="请输入文件服务器域名,如:http://file.zhyd.me/"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="localFilePath">文件存储路径 <i class="fa fa-question-circle" title="Nginx服务中root后面对应的目录地址"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="localFilePath" id="localFilePath" required="required" placeholder="请输入文件存储路径,如:/var/www/oneblog/upload/"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<small>本地文件服务器,需要手动<strong class="red">搭建文件服务器</strong>。作者推荐Nginx,<a class="pointer" data-toggle="modal" data-target="#storageNginxServerModal">获取nginx文件服务器配置</a> </small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-box hide" id="qiniu">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="qiniuBucketName">Bucket 名称 <i class="fa fa-question-circle" title="存储空间名称"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="qiniuBucketName" id="qiniuBucketName" required="required" placeholder="请输入Bucket 名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="qiniuAccessKey">Access Key <i class="fa fa-question-circle" title="密钥获取地址:https://portal.qiniu.com/user/key"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="qiniuAccessKey" id="qiniuAccessKey" required="required" placeholder="请输入Access Key"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="qiniuSecretKey">Secret Key <i class="fa fa-question-circle" title="密钥获取地址:https://portal.qiniu.com/user/key"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="qiniuSecretKey" id="qiniuSecretKey" required="required" placeholder="请输入Secret Key"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="qiniuBasePath">七牛云cdn域名 <i class="fa fa-question-circle" title="如果未自定义域名,则填写临时域名,格式:http://***.**.**/"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="qiniuBasePath" id="qiniuBasePath" required="required" placeholder="请输入七牛域名,格式:http://***.**.**/"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-box hide" id="aliyun">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="aliyunBucketName">Bucket 名称 <i class="fa fa-question-circle" title="存储空间名称"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="aliyunBucketName" id="aliyunBucketName" required="required" placeholder="请输入Bucket 名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="aliyunEndpoint">地域节点(EndPoint) <i class="fa fa-question-circle" title="地域节点,注意必须填写外网地址,非内网"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="aliyunEndpoint" id="aliyunEndpoint" required="required" placeholder="请输入endpoint,如:http://oss-cn-hangzhou.aliyuncs.com"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="aliyunFileUrl">Bucket 域名 <i class="fa fa-question-circle" title="默认为bucketName + endpoint,若使用自定义的域名,请修改"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="aliyunFileUrl" id="aliyunFileUrl" required="required" placeholder="默认为bucketName + endpoint,若使用自定义的域名,请修改"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="aliyunAccessKey">Access Key <i class="fa fa-question-circle" title="阿里云API密钥,获取地址:https://ak-console.aliyun.com/#/"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="aliyunAccessKey" id="aliyunAccessKey" required="required" placeholder="请输入Access Key"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="aliyunAccessKeySecret">Access Key Secret <i class="fa fa-question-circle" title="阿里云API密钥,获取地址:https://ak-console.aliyun.com/#/"></i> <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="aliyunAccessKeySecret" id="aliyunAccessKeySecret" required="required" placeholder="请输入Access Key Secret"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_auth" aria-labelledby="auth-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="loginRetryNum">登录重试次数 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="loginRetryNum" id="loginRetryNum" required="required" placeholder="请输入登录重试次数,默认5"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="sessionTimeOut">Session有效期 <span class="required">*</span></label>
|
||||
<div class="col-md-3 col-sm-3 col-xs-3">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="sessionTimeOut" id="sessionTimeOut" required="required" placeholder="请输入Session有效期,默认1小时"/>
|
||||
</div>
|
||||
<div class="col-md-1 col-sm-1 col-xs-1">
|
||||
<select name="sessionTimeOutUnit" id="sessionTimeOutUnit" class="form-control" required="required" >
|
||||
<@zhydTag method="sessionTimeOutUnit">
|
||||
<option value="">请选择</option>
|
||||
<#list sessionTimeOutUnit as item>
|
||||
<option value="${item}">${item}</option>
|
||||
</#list>
|
||||
</@zhydTag>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_comment" aria-labelledby="comment-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12 aero" for="anonymous">允许匿名评论?
|
||||
<i class="fa fa-question-circle" title="【暂不可用】是否允许匿名评论,如果为否则必须需要登录。"></i>
|
||||
</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><label for="anonymous" class="pointer"> <input type="radio" class="square" checked name="anonymous" value="1"> 开启 </label></li>
|
||||
<li><label for="anonymous" class="pointer"> <input type="radio" class="square" name="anonymous" value="0"> 关闭 </label></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment">开启留言板评论 <i class="fa fa-question-circle" title="控制留言板页面的评论框显示情况"></i></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><label for="comment" class="pointer"> <input type="radio" class="square" checked name="comment" value="1"> 开启 </label></li>
|
||||
<li><label for="comment" class="pointer"> <input type="radio" class="square" name="comment" value="0"> 关闭 </label></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="editorPlaceholder">Placeholder <i class="fa fa-question-circle" title="占位符,当没输入内容时显示该值"></i>
|
||||
</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" id="editorPlaceholder" name="editorPlaceholder" placeholder="例如:说点什么吧">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="editorAlert">警示语 <i class="fa fa-question-circle" title="评论框右下角显示的内容"></i> </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" id="editorAlert" name="editorAlert" placeholder="例如:讲文明、要和谐">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_article_editor" aria-labelledby="article-editor-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="articleEditor">默认文章编辑器 <i class="fa fa-question-circle" title="文章编辑器"></i></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
|
||||
<select name="articleEditor" id="articleEditor" class="form-control">
|
||||
<option value="md">Markdown编辑器</option>
|
||||
<option value="we">WangEditor编辑器</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_contact" aria-labelledby="contact-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="authorName">站长名称</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="authorName" id="authorName" placeholder="请输入站长名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="authorEmail">站长邮箱</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="authorEmail" id="authorEmail" placeholder="请输入站长邮箱"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="wxCode">微信二维码</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="wxCode" id="wxCode" placeholder="请输入微信二维码"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="qq">QQ</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="qq" id="qq" placeholder="请输入QQ"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="weibo">微博</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="weibo" id="weibo" placeholder="请输入微博"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="github">GitHub</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="github" id="github" placeholder="请输入GitHub"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_praise" aria-labelledby="praise-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="homeDesc">微信赞赏码 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="file" class="form-control col-md-7 col-xs-12 uploadPreview" data-preview-container="#wxPraiseCodePreview" name="wxPraiseCode" id="wxPraiseCode"/>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div id="wxPraiseCodePreview" style="width: 200px;height: auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="homeKeywords">支付宝赞赏码 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="file" class="form-control col-md-7 col-xs-12 uploadPreview" data-preview-container="#zfbPraiseCodePreview" id="zfbPraiseCode" name="zfbPraiseCode"/>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div id="zfbPraiseCodePreview" style="width: 200px;height: auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="tab_setting" aria-labelledby="setting-tab">
|
||||
<form class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="authorName">百度Api的AK <i class="fa fa-question-circle" title="用于通过百度地址接口获取用户当前的位置"></i></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="baiduApiAk" id="baiduApiAk" placeholder="请输入百度Api的AK"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<small>获取地址:<a href="http://lbsyun.baidu.com/apiconsole/key" target="_blank">点击获取百度Api AK</a></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenance">维护通知
|
||||
<i class="fa fa-question-circle" title="网站在更新前, 可以通过开启该功能,通知用户"></i> </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><label for="maintenance" class="pointer"> <input type="radio" class="square" checked name="maintenance" value="1"> 显示 </label> </li>
|
||||
<li><label for="maintenance" class="pointer"> <input type="radio" class="square" name="maintenance" value="0"> 关闭 </label></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenanceDate">维护日期</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class='input-group date myDatepicker'>
|
||||
<input type='text' class="form-control" readonly="readonly" id="maintenanceDate" name="maintenanceDate" placeholder="请输入维护日期"/>
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenanceTime">维护用时</label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class='input-group'>
|
||||
<input type='text' class="form-control" id="maintenanceTime" name="maintenanceTime" placeholder="请输入维护大约需要的时间"/>
|
||||
<span class="input-group-addon">
|
||||
分
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="dynamicTitle">动态标题 <i class="fa fa-question-circle" title="当切换浏览器tab时,在原tab上的标题。比如https://www.zhyd.me上的“麻溜儿回来~~~”"></i></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type='text' class="form-control" id="dynamicTitle" name="dynamicTitle" placeholder="请输入切换窗口时想要显示的标题,如:麻溜儿回来~~~"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<button type="button" class="btn btn-primary saveBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="storageNginxServerModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="storageNginxServerModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="storageNginxServerModalLabel">Nginx文件服务器配置</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<@zhydTag method="template" key="TM_NGINX_FILE_SERVER">
|
||||
<#if template!>
|
||||
<textarea class="form-control" rows="15" cols="">${template.refValue}</textarea>
|
||||
<#else>
|
||||
<textarea class="form-control" placeholder="暂无可参考的配置" disabled readonly></textarea>
|
||||
</#if>
|
||||
</@zhydTag>
|
||||
|
||||
<div class="item form-group">
|
||||
<fieldset>
|
||||
<legend style="padding-bottom: 0;"><h4>使用帮助<i class="fa fa-question-circle fa-fw"></i></h4>
|
||||
</legend>
|
||||
<dl>
|
||||
<dt><i class="fa fa-info-circle fa-fw"></i>1. 替换配置文件中的指定内容</dt>
|
||||
<dd><code>serverName</code> 改为自己的域名</dd>
|
||||
<dd><code>serverPath</code> Nginx文件服务映射的服务器路径,同云存储中填写的“文件存储路径”</dd>
|
||||
<dd><code>serverReferers</code> 防盗链的Referers,多个用空格分隔,支持通配符,比如:<code>*.zhyd.me zhyd.me</code></dd>
|
||||
<dd><code>serverLogoPath</code> 触发防盗链后显示的默认图片,即当别人引用你网站中的图片时,会触发防盗链,对方网站中看到的就是 <code>serverLogoPath</code>对应的文件内容</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt><i class="fa fa-info-circle fa-fw"></i>2. 添加Nginx配置</dt>
|
||||
<dd>i. 将上方文本域修改后的内容保存为<code>**.conf</code>,放入到Nginx配置文件目录中</dd>
|
||||
<dd>ii. 重启Nginx</dd>
|
||||
<dd>iii. 尝试访问<code>serverName</code>检查Nginx是否配置成功</dd>
|
||||
</dl>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var oldStorageType, firstLoad = true;
|
||||
$.ajax({
|
||||
url: '/config/get',
|
||||
type: 'POST',
|
||||
success: function (json) {
|
||||
var data = json.data;
|
||||
oldStorageType = data.storageType;
|
||||
$("#myTabContent").find("input, select, textarea").each(function () {
|
||||
new Table().clearText($(this), this.type, data);
|
||||
});
|
||||
changeMaintenance(data.maintenance && data.maintenance == 1, data.maintenance);
|
||||
data.zfbPraiseCode && $("#zfbPraiseCodePreview").html('<img src="' + data.zfbPraiseCode + '" alt="支付宝赞赏码" class="img-responsive img-rounded auto-shake">');
|
||||
data.wxPraiseCode && $("#wxPraiseCodePreview").html('<img src="' + data.wxPraiseCode + '" alt="微信赞赏码" class="img-responsive img-rounded auto-shake">');
|
||||
}
|
||||
});
|
||||
|
||||
$(".saveBtn").click(function () {
|
||||
var $this = $(this);
|
||||
var $form = $this.parents("form");
|
||||
if (validator.checkAll($form)) {
|
||||
$form.ajaxSubmit({
|
||||
type: "POST",
|
||||
url: '/config/save',
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#tab_storage input[name=storageType]").on('ifChecked', function (event) {
|
||||
var $this = $(this);
|
||||
var thisValue = $this.val();
|
||||
if (!$("#" + thisValue).hasClass("hide")) {
|
||||
return;
|
||||
}
|
||||
function changeStorageBox() {
|
||||
$(".storage-box").each(function () {
|
||||
var $box = $(this);
|
||||
if ($box.attr("id") === thisValue) {
|
||||
$box.removeClass("hide").find("input").removeAttr("disabled").removeAttr("readonly");
|
||||
} else {
|
||||
$box.addClass("hide").find("input").attr("disabled", "disabled").attr("readonly", "readonly");
|
||||
}
|
||||
});
|
||||
}
|
||||
if(firstLoad) {
|
||||
changeStorageBox();
|
||||
firstLoad = false;
|
||||
oldStorageType = thisValue;
|
||||
} else {
|
||||
if(oldStorageType !== thisValue) {
|
||||
$.alert.confirm("您确定要切换云存储类型吗?切换后原文件将不可访问!", function () {
|
||||
oldStorageType = thisValue;
|
||||
changeStorageBox();
|
||||
}, function () {
|
||||
$("#tab_storage input[name=storageType]").each(function () {
|
||||
var $this = $(this);
|
||||
$this.iCheck((oldStorageType !== $this.val()) ? 'uncheck' : 'check');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#tab_setting input[name=maintenance]").on('ifChanged', function (event) {
|
||||
changeMaintenance($(this).is(':checked'), $(this).val());
|
||||
});
|
||||
function changeMaintenance(checked, thisVal){
|
||||
if (checked && thisVal == 1) {
|
||||
$("#maintenanceDate, #maintenanceTime").each(function () {
|
||||
var $this = $(this);
|
||||
var $label = $this.parents("div.form-group").find("label");
|
||||
$this.attr("required", "required");
|
||||
$label.append('<span class="required">*</span>');
|
||||
})
|
||||
} else {
|
||||
$("#maintenanceDate, #maintenanceTime").each(function () {
|
||||
var $this = $(this);
|
||||
var $span = $this.parents("div.form-group").find("label span");
|
||||
$this.removeAttr("required");
|
||||
$span.remove();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$("#aliyunBucketName, #aliyunEndpoint").change(function () {
|
||||
var $fileUrl = $("#aliyunFileUrl");
|
||||
var aliyunBucketName = $("#aliyunBucketName").val();
|
||||
var aliyunEndpoint = $("#aliyunEndpoint").val();
|
||||
if(aliyunBucketName && aliyunEndpoint) {
|
||||
$fileUrl.val("https://" + aliyunBucketName + "." + aliyunEndpoint + "/");
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
39
blog-admin/src/main/resources/templates/error/401.ftl
Normal file
@ -0,0 +1,39 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb></@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="main_container">
|
||||
<div class="col-md-12">
|
||||
<div class="col-middle">
|
||||
<div class="text-center text-center">
|
||||
<h1 class="error-number">401</h1>
|
||||
<h2>无权操作</h2>
|
||||
<p>您当前无权操作,请联系管理员。</p>
|
||||
<div class="mid_center">
|
||||
<form>
|
||||
<div class="col-xs-12 form-group pull-right top_search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for..."> <span
|
||||
class="input-group-btn">
|
||||
<a class="btn btn-default" href="javascript:history.go(-1);">Go!</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
||||
|
||||
|
||||
|
36
blog-admin/src/main/resources/templates/error/403.ftl
Normal file
@ -0,0 +1,36 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb></@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="main_container">
|
||||
<div class="col-md-12">
|
||||
<div class="col-middle">
|
||||
<div class="text-center text-center">
|
||||
<h1 class="error-number">403</h1>
|
||||
<h2>拒绝访问</h2>
|
||||
<p>访问此资源需要身份验证。您当前没有权限</p>
|
||||
<div class="mid_center">
|
||||
<form>
|
||||
<div class="col-xs-12 form-group pull-right top_search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for..."> <span
|
||||
class="input-group-btn">
|
||||
<a class="btn btn-default" href="javascript:history.go(-1);">Go!</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
36
blog-admin/src/main/resources/templates/error/404.ftl
Normal file
@ -0,0 +1,36 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb></@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="main_container">
|
||||
<div class="col-md-12">
|
||||
<div class="col-middle">
|
||||
<div class="text-center text-center">
|
||||
<h1 class="error-number">404</h1>
|
||||
<h2>抱歉,我们找不到这个页面</h2>
|
||||
<p>This page you are looking for does not exist。</p>
|
||||
<div class="mid_center">
|
||||
<form>
|
||||
<div class="col-xs-12 form-group pull-right top_search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for..."> <span
|
||||
class="input-group-btn">
|
||||
<a class="btn btn-default" href="javascript:history.go(-1);">Go!</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
36
blog-admin/src/main/resources/templates/error/500.ftl
Normal file
@ -0,0 +1,36 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb></@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="main_container">
|
||||
<div class="col-md-12">
|
||||
<div class="col-middle">
|
||||
<div class="text-center text-center">
|
||||
<h1 class="error-number">500</h1>
|
||||
<h2>内部服务器错误</h2>
|
||||
<p>我们会自动跟踪这些错误,但如果问题仍然存在,请随时联系我们。同时,您可以尝试重试。</p>
|
||||
<div class="mid_center">
|
||||
<form>
|
||||
<div class="col-xs-12 form-group pull-right top_search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for..."> <span
|
||||
class="input-group-btn">
|
||||
<a class="btn btn-default" href="javascript:history.go(-1);">Go!</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
206
blog-admin/src/main/resources/templates/file/list.ftl
Normal file
@ -0,0 +1,206 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">图片库管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="btn-group hidden-xs" id="toolbar" style="padding: 10px 0;">
|
||||
<@shiro.hasPermission name="files">
|
||||
<button id="btn_add" type="button" class="btn btn-info" title="新增图片">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="files">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="删除选中">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<div class="x_panel">
|
||||
<form id="file-form">
|
||||
<div class="x_content file-container" id="file-container">
|
||||
<div class="col-md-55">
|
||||
<div class="thumbnail">
|
||||
<div class="image view view-first">
|
||||
<img style="display: block;margin: 0 auto;margin-top: 10px;" src="/assets/images/loading.gif" alt="image" />
|
||||
</div>
|
||||
<div class="caption">
|
||||
<p>暂无可用的图片... </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="添加图片">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="url">选择图片 <span class="required">*</span></label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="file" class="form-control" name="file" id="file" accept="image/bmp,image/png,image/jpeg,image/jpg,image/gif" required="required"/>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script>
|
||||
var curSstorageType = '${config.storageType}';
|
||||
$(function () {
|
||||
loadData(1);
|
||||
|
||||
function loadData(pageNumber){
|
||||
$.ajax({
|
||||
url: "/file/list",
|
||||
data: {pageNumber: pageNumber},
|
||||
type: "POST",
|
||||
success: function (json) {
|
||||
var tpl = '{{#list}}<div class="col-md-55">\n' +
|
||||
' <div class="thumbnail">\n' +
|
||||
' <div class="image view view-first pointer file-item">\n' +
|
||||
' <img style="width: 100%; display: block;" src="{{fullFilePath}}" onerror="this.alt=\'图片加载失败\'" alt="{{originalFileName}}" title="{{originalFileName}}" />\n' +
|
||||
' <div class="vmask">\n' +
|
||||
' <p>点击选中</p>\n' +
|
||||
' <div class="tools tools-bottom">\n' +
|
||||
' <a href="{{fullFilePath}}" class="file-icon showImage" title="{{filePath}}"><i class="fa fa-eye"></i></a>\n' +
|
||||
' <a href="{{fullFilePath}}" target="_blank" class="file-icon" title="复制地址(打开标签后复制)"><i class="fa fa-link"></i></a>\n' +
|
||||
' <a class="pointer file-icon" data-event="del" data-value="{{id}}" data-storage-type="{{storageType}}" title="删除文件"><i class="fa fa-times"></i></a>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="selected-mask">\n' +
|
||||
' <input type="checkbox" class="square mask-checkbox" name="ids" value="{{id}}" data-storage-type="{{storageType}}" />' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' <div class="caption">\n' +
|
||||
' <p><span title="{{originalFileName}}">{{originalFileName}}</span><img src="/assets/images/icons/{{storageType}}.svg" alt="{{storageType}}" title="{{storageType}}"></p>\n' +
|
||||
' </div>\n' +
|
||||
' </div>\n' +
|
||||
' </div>{{/list}}';
|
||||
var html = Mustache.render(tpl, json);
|
||||
var pageTpl = '<ul class="list-unstyled">{{#data}}<li class="file-page">\n' +
|
||||
' <div class="file-page-body">\n' +
|
||||
' {{#hasPreviousPage}}<a class="btn btn-default btn-sm file-pagination" data-page="{{prePage}}">\n' +
|
||||
' <i class="fa fa-caret-left"></i>\n' +
|
||||
' </a>{{/hasPreviousPage}}<span style="margin-right: 5px;">{{pageNum}}/{{pages}}</span>{{#hasNextPage}}<a class="btn btn-default btn-sm file-pagination" data-page="{{nextPage}}">\n' +
|
||||
' <i class="fa fa-caret-right"></i>\n' +
|
||||
' </a>{{/hasNextPage}}<input class="form-control input-sm file-input">\n' +
|
||||
' <a class="btn btn-default btn-sm file-jump">\n' +
|
||||
' Go\n' +
|
||||
' </a>\n' +
|
||||
' \n' +
|
||||
' </div>\n' +
|
||||
'</li>{{/data}}</ul>';
|
||||
html += Mustache.render(pageTpl, {data: json});
|
||||
$("#file-container").html(html);
|
||||
|
||||
// 绑定分页点击事件
|
||||
$(".file-pagination").unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
var pageNumber = $this.data("page");
|
||||
loadData(!pageNumber || isNaN(pageNumber) ? 1 : parseInt(pageNumber));
|
||||
});
|
||||
// 绑定分页-跳转页面点击事件
|
||||
$(".file-jump").unbind("click").click(function () {
|
||||
var $this = $(this);
|
||||
var jumpTarget = $(".file-input").val();
|
||||
loadData(!jumpTarget || isNaN(jumpTarget) ? 1 : parseInt(jumpTarget));
|
||||
});
|
||||
|
||||
gentelella.initiICheck();
|
||||
$('.mask-checkbox').on('ifChanged', function (event) {
|
||||
var $this = $(this),
|
||||
$thumbnail = $this.parents("div.thumbnail");
|
||||
if ($this.is(':checked')) {
|
||||
$thumbnail.addClass("selected");
|
||||
} else {
|
||||
$thumbnail.removeClass("selected");
|
||||
}
|
||||
});
|
||||
bindFileItemEvent();
|
||||
function bindFileItemEvent() {
|
||||
$(".file-item").click(function () {
|
||||
var $checkbox = $(this).find('.mask-checkbox');
|
||||
$checkbox.iCheck($checkbox.is(':checked') ? "uncheck" : "check");
|
||||
});
|
||||
}
|
||||
$("#btn_delete_ids").click(function () {
|
||||
var canBeDeleted = true;
|
||||
$('.mask-checkbox').each(function () {
|
||||
var $this = $(this);
|
||||
var storageType = $this.data("storage-type");
|
||||
if($this.is(':checked') && storageType != curSstorageType) {
|
||||
canBeDeleted = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if(!canBeDeleted) {
|
||||
$.alert.error("【不可删除】当前选择的文件存储于不同的云存储平台!");
|
||||
return false;
|
||||
}
|
||||
del($("#file-form").serialize());
|
||||
});
|
||||
$(".file-icon").click(function () {
|
||||
$(".file-item").unbind("click");
|
||||
var event = $(this).data("event");
|
||||
var id = $(this).data("value");
|
||||
var storageType = $(this).data("storage-type");
|
||||
if(event) {
|
||||
del({'ids': id}, storageType);
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
bindFileItemEvent();
|
||||
})
|
||||
}
|
||||
});
|
||||
function del(data, storageType){
|
||||
if(storageType && storageType != curSstorageType) {
|
||||
$.alert.error("【不可删除】该文件存储于[" + storageType + "],当前系统的云存储类型为[" + curSstorageType + "]");
|
||||
return false;
|
||||
}
|
||||
$.alert.confirm("确定删除该选中的文件?不可恢复,请确认!", function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/file/remove",
|
||||
traditional: true,
|
||||
data: data,
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json, function () {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
$("#btn_add").click(function () {
|
||||
$("#addOrUpdateModal").modal('show');
|
||||
$(".addOrUpdateBtn").unbind('click').click(function () {
|
||||
var $form = $("#addOrUpdateForm");
|
||||
if (validator.checkAll($form)) {
|
||||
$form.ajaxSubmit({
|
||||
type: "post",
|
||||
url: "/file/add",
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json, function () {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
345
blog-admin/src/main/resources/templates/include/macros.ftl
Normal file
@ -0,0 +1,345 @@
|
||||
<#-- 公共顶部 -->
|
||||
<#macro header sidebar=true setting=true>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${config.siteName}后台管理系统</title>
|
||||
<link href="/assets/images/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/jquery-confirm@3.3.2/dist/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/jquery.fancybox@2.1.5/source/jquery.fancybox.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/toastr@2.0.3/nuget/content/content/toastr.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/icheck@1.0.2/skins/square/green.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.11.1/dist/bootstrap-table.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/daterangepicker@2.1.25/daterangepicker.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-datetimepicker-npm@4.17.37-npm/build/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@ztree/ztree_v3@3.5.37/css/metroStyle/metroStyle.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/switchery@0.0.2/switchery.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@adactive/bootstrap-tagsinput@0.8.2/dist/bootstrap-tagsinput-typeahead.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@adactive/bootstrap-tagsinput@0.8.2/dist/bootstrap-tagsinput.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/switchery-npm@0.8.2/index.min.css" rel="stylesheet">
|
||||
<#--
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.buttons.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.nonblock.css" rel="stylesheet">-->
|
||||
|
||||
<link href="/assets/css/bootstrap-treetable.css" rel="stylesheet" type="text/css" />
|
||||
<link href="/assets/css/zhyd.core.css" rel="stylesheet">
|
||||
<#nested>
|
||||
</head>
|
||||
<body class="nav-md">
|
||||
<div class="container body">
|
||||
<div class="main_container">
|
||||
<#if sidebar>
|
||||
<div class="col-md-3 left_col">
|
||||
<div class="left_col scroll-view">
|
||||
<div class="navbar nav_title" style="border: 0;">
|
||||
<a href="/" class="site_title"><i class="fa fa-coffee"></i> <span>${config.siteName}</span></a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<@shiro.user>
|
||||
<div class="profile clearfix">
|
||||
<div class="profile_pic">
|
||||
<img src="/assets/images/loading.gif" alt="..." class="img-circle profile_img">
|
||||
</div>
|
||||
<div class="profile_info">
|
||||
<span id="hello_msg"> </span>
|
||||
<h2>尊敬的管理员</h2>
|
||||
</div>
|
||||
</div>
|
||||
</@shiro.user>
|
||||
<br />
|
||||
<#include "/layout/sidebar.ftl"/>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<#if setting>
|
||||
<#include "/layout/setting.ftl"/>
|
||||
</#if>
|
||||
|
||||
<div class="right_col" role="main" style="${sidebar?string('','margin-left: 0;')}">
|
||||
</#macro>
|
||||
|
||||
<#-- 公共底部 -->
|
||||
<#macro footer footerHtml=true>
|
||||
<#if footerHtml>
|
||||
<footer>
|
||||
<div class="pull-right">
|
||||
Gentelella - Bootstrap Admin Template by <a href="https://colorlib.com">Colorlib</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</footer>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<#include "/layout/footer.ftl"/>
|
||||
|
||||
<#nested>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</#macro>
|
||||
|
||||
<#-- 面包屑导航内容 + 系统通知 -->
|
||||
<#macro breadcrumb>
|
||||
<div class="row">
|
||||
<div class="col col-md-8">
|
||||
<nav class="breadcrumb">
|
||||
<div class="notify"><i class="fa fa-bullhorn fa-fw"></i></div>
|
||||
<div id="scrolldiv">
|
||||
<div class="scrolltext">
|
||||
<ul class="list-unstyled" id="notice-box">
|
||||
<li class="scrolltext-title">
|
||||
<a href="javascript:void(0)" rel="bookmark">其实我们可以将所有的问题归结为两种:一种是没饭吃饿出来的;一种是吃饱了撑出来的。</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="col col-md-4 text-right">
|
||||
<#nested>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#-- 发布文章填写文章详情的弹窗模板 -->
|
||||
<#macro publishmodal>
|
||||
<div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishLabel" data-backdrop="static">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="" role="tabpanel" data-example-id="togglable-tabs">
|
||||
<ul id="myTab" class="nav nav-tabs bar_tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#article" id="article-tab" role="tab" data-toggle="tab" aria-expanded="true">文章属性</a>
|
||||
</li>
|
||||
<li role="presentation" class="">
|
||||
<a href="#seo" role="tab" id="seo-tab" data-toggle="tab" aria-expanded="false">SEO</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="" class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade active in" id="article" aria-labelledby="article-tab">
|
||||
<div class="row">
|
||||
<div class="col col-md-3">
|
||||
<div class="item form-group">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="choose-local-img" style="text-align: center;">
|
||||
<input id="cover-img-file" type="file" name="file" style="display: none">
|
||||
<input id="cover-img-input" type="hidden" name="coverImage">
|
||||
<div class="preview fa-2x" style="width: 100%;height: 186.98px;background: #f8fafc;border-radius: 5px;text-align: center;">
|
||||
<img class="coverImage" src="" alt="">
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-round btn-info" id="file-upload-btn" style="margin-top: 10px;">上传封面图片</button>
|
||||
<div class="tip" style="margin-top: 10px;color: #c3c3c3;">
|
||||
<div class="clearfix"></div>
|
||||
<small style="font-size: 12px;">图片不宜过大</small>
|
||||
<div class="clearfix"></div>
|
||||
<small style="font-size: 12px;">建议尺寸 1190*300</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-md-9">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="nickname">分类 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<div class="input-group">
|
||||
<select class="form-control" name="typeId" id="typeId" target="combox" data-url="/type/listAll" data-method="post" required="required"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="mobile">标签(*3) <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<input type="text" name="tags" target="tagsinput" data-bind-box="#tags-list">
|
||||
<ul class="list-unstyled list-inline tags-list" id="tags-list" target="combox" data-url="/tag/listAll" data-method="post" style="max-height: 150px;overflow-y: scroll;"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="mobile">状态 <span class="required">*</span></label>
|
||||
<div class="col-md-10 col-sm-10 col-xs-12 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><input type="radio" class="square" checked name="status" value="1"> 发布</li>
|
||||
<li><input type="radio" class="square" name="status" value="0"> 草稿</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="comment">开启评论 </label>
|
||||
<div class="col-md-10 col-sm-10 col-xs-12 fixed-radio-checkbox">
|
||||
<input type="checkbox" class="square" name="comment" id="comment">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="seo" aria-labelledby="seo-tab">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="password">摘要 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="description" name="description" required="required" maxlength="200"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="password">关键词 <span class="required">*</span></label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="keywords" name="keywords" required="required" maxlength="50"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-angle-left"> 返回修改</i></button>
|
||||
<button type="button" class="btn btn-success publishBtn"><i class="fa fa-paper-plane-o"> 确定发布</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#-- 发布文章时选择图片的弹窗模板 -->
|
||||
<#macro chooseImgModal>
|
||||
<div class="modal fade chooseImgModal" id="chooseImgModal" tabindex="-1" role="dialog" aria-labelledby="chooseImgLabelledby" aria-hidden="true" data-backdrop="static"
|
||||
data-keyboard="false">
|
||||
<div class="modal-dialog <#--modal-lg-->" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="chooseImgLabelledby"><i class="fa fa-image fa-fw"></i>素材库</h4>
|
||||
</div>
|
||||
<div class="modal-body material-body">
|
||||
<div class="btn-group" style="width: 100%;margin: 0 5px 5px 5px;padding: 0 0 10px 0;border-bottom: 1px solid #e7e7eb;">
|
||||
<form action="" id="materialForm">
|
||||
<input id="input-material-upload" type="file" name="file" multiple="multiple" accept="image/bmp,image/png,image/jpeg,image/jpg,image/gif" style="display: none;">
|
||||
<button id="btn-material-upload" type="button" class="btn btn-success btn-md" title="本地上传">
|
||||
<i class="fa fa-cloud-upload fa-fw"></i> 本地上传
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="fade active in material-box">
|
||||
<ul class="list-unstyled list-file">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="material-status pull-left">已选<span id="selected">0</span>个,可选<span id="selectable">1</span>个</span>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
|
||||
<button type="button" class="btn btn-success btn-confirm" data-dismiss="modal"><i class="fa fa-hand-o-up"> 确定</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#-- 添加或者修改列表记录时的弹窗模板 -->
|
||||
<#macro addOrUpdateMOdal defaultTitle="">
|
||||
<div class="modal fade" id="addOrUpdateModal" tabindex="-1" role="dialog" aria-labelledby="addroleLabel" data-backdrop="static">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="addroleLabel">${defaultTitle}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addOrUpdateForm" class="form-horizontal form-label-left" novalidate>
|
||||
<#nested>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
|
||||
<button type="button" class="btn btn-success addOrUpdateBtn"><i class="fa fa-save"> 保存</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#-- 网站首页的项目介绍内容 -->
|
||||
<#macro aboutOneBlog>
|
||||
<div class="modal fade" id="noticeModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
|
||||
<div class="modal-dialog modal-lg" role="document" style="width: 70%;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel2">关于OneBlog - 一个简洁美观、功能强大并且自适应的Java博客</h4>
|
||||
</div>
|
||||
<div class="modal-body notice-box">
|
||||
<div class="row">
|
||||
<div class="col col-lg-4 col-sm-4 col-md-4 col-xs-4">
|
||||
<fieldset>
|
||||
<legend>关注公众号</legend>
|
||||
<a href="https://gitee.com/yadong.zhang/static/raw/master/wx/wechat_account_500x500.jpg" class="showImage" title="关注公众号:码一码" rel="external nofollow" style="display: block;text-align: center">
|
||||
<img src="https://gitee.com/yadong.zhang/static/raw/master/wx/wechat_account_500x500.jpg" class="img-rounded" alt="关注公众号:码一码" width="300">
|
||||
</a>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col col-lg-4 col-sm-4 col-md-4 col-xs-4">
|
||||
<fieldset>
|
||||
<legend>关于OneBlog</legend>
|
||||
|
||||
<h2>项目相关</h2>
|
||||
<ul>
|
||||
<li>开源项目地址①:<a href="https://gitee.com/yadong.zhang/DBlog" target="_blank">https://gitee.com/yadong.zhang/DBlog</a></li>
|
||||
<li>开源项目地址②:<a href="https://github.com/zhangyd-c/OneBlog" target="_blank">https://github.com/zhangyd-c/OneBlog</a></li>
|
||||
<li>博主网站首页:<a href="https://www.zhyd.me" target="_blank">https://www.zhyd.me</a></li>
|
||||
</ul>
|
||||
<h2>Demo演示</h2>
|
||||
<ul>
|
||||
<li><a href="http://dblog-admin.zhyd.me" target="_blank">后台</a>:用户名:root,密码:123456</li>
|
||||
<li><a href="http://dblog-web.zhyd.me" target="_blank">前台</a></li>
|
||||
</ul>
|
||||
<h2>获取帮助</h2>
|
||||
<ul>
|
||||
<li>相关Wiki:<a href="https://gitee.com/yadong.zhang/DBlog/wikis" target="_blank">https://gitee.com/yadong.zhang/DBlog/wikis</a></li>
|
||||
<li>提issue:<a href="https://gitee.com/yadong.zhang/DBlog/issues" target="_blank">https://gitee.com/yadong.zhang/DBlog/issues</a></li>
|
||||
<li>留言:<a href="https://www.zhyd.me/guestbook" target="_blank">https://www.zhyd.me/guestbook</a></li>
|
||||
<li>加QQ群:<a href="http://shang.qq.com/wpa/qunwpa?idkey=9f986e9b33b1de953e1ef9a96cdeec990affd0ac7855e00ff103514de2027b60" target="_blank">190886500</a></li>
|
||||
</ul>
|
||||
<h2>其他开源作品</h2>
|
||||
<ul>
|
||||
<li><a href="https://gitee.com/yadong.zhang/JustAuth" target="_blank">JustAuth</a>:史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。 Login, so easy!</li>
|
||||
<li><a href="https://gitee.com/yadong.zhang/blog-hunter" target="_blank">blog-hunter</a>:博客猎手,基于webMagic的博客爬取工具,支持慕课、csdn、iteye、cnblogs、掘金和V2EX等各大主流博客平台。博客千万篇,版权第一条。狩猎不规范,亲人两行泪。</li>
|
||||
<li><a href="https://gitee.com/yadong.zhang/shiro" target="_blank">springboot-shiro</a>:Springboot + shiro权限管理。这或许是流程最详细、代码最干净、配置最简单的shiro上手项目了。</li>
|
||||
<li><a href="https://gitee.com/yadong.zhang" target="_blank">查看更多...</a></li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col col-lg-4 col-sm-4 col-md-4 col-xs-4">
|
||||
<fieldset>
|
||||
<legend>支持的功能</legend>
|
||||
<ul>
|
||||
<li><span><span><strong>多种编辑器</strong>:支持wangEditor和Markdown两种富文本编辑器,可以自行选择</span></span></li>
|
||||
<li><span><span><strong>自动申请友情链接</strong>:在线申请友情链接,无需站长手动配置,只需申请方添加完站长的连接后自行申请即可</span></span></li>
|
||||
<li><span><span><strong>百度推送</strong>:支持百度推送功能,加速百度搜索引擎收录博文</span></span></li>
|
||||
<li><span><span><strong>评论系统</strong>:自研的评论系统,支持显示用户地址、浏览器和os信息,后台可审核评论、开启匿名评论、回复和邮件通知评论</span></span></li>
|
||||
<li><span><span><strong>权限管理</strong>:后台配备完善的权限管理</span></span></li>
|
||||
<li><span><span><strong>SEO</strong>:自带robots、sitemap等seo模板,实现自动生成robots和sitemap</span></span></li>
|
||||
<li><span><span><strong>实时通讯</strong>:管理员可向在线的用户发送实时消息(需用户授权 - 基于websocket实现,具体参考<a href="https://www.zhyd.me/article/111">DBlog建站之Websocket的使用</a>)</span></span></li>
|
||||
<li><span><span><strong>系统配置支持快速配置</strong>:可通过后台手动修改诸如域名信息、SEO优化、赞赏码、七牛云以及更新维护通知等</span></span></li>
|
||||
<li><span><span><strong><i class="fa fa-fire fa-fw red"></i>多种文件存储</strong>:集成七牛云、阿里云OSS,实现文件云存储,同时支持本地文件存储</span></span></li>
|
||||
<li><span><span><strong><i class="fa fa-fire fa-fw red"></i>文件搬运工</strong>:集成<a href="https://gitee.com/yadong.zhang/blog-hunter">blog-hunter</a>实现“文章搬运工”功能,支持一键同步imooc、csdn、iteye或者cnblogs上的文章,可抓取列表和单个文章</span></span></li>
|
||||
<li><span><span><strong><i class="fa fa-fire fa-fw red"></i>第三方授权登录</strong>:集成<a href="https://gitee.com/yadong.zhang/JustAuth">JustAuth</a>实现第三方授权登录</span></span></li>
|
||||
</ul>
|
||||
</fieldset></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="pull-left">tips: 如不想显示该弹窗,可在 <code>index.ftl</code> 中搜索 <code>aboutOneBlog</code> 后删掉相关代码</span>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
305
blog-admin/src/main/resources/templates/index.ftl
Normal file
@ -0,0 +1,305 @@
|
||||
<#include "include/macros.ftl">
|
||||
<@header>
|
||||
<style>
|
||||
.notice-box ul {
|
||||
-webkit-padding-start: 40px!important;
|
||||
}
|
||||
.statistics-panel {
|
||||
color: #73879C;
|
||||
}
|
||||
.statistics-box {
|
||||
z-index: 1;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.statistics-box:hover {
|
||||
transform: translateY(-2px);
|
||||
-webkit-transform: translateY(-2px);
|
||||
-moz-transform: translateY(-2px);
|
||||
box-shadow: -10px 10px 30px -15px #9e9c9c;
|
||||
-webkit-box-shadow: -10px 10px 30px -15px #9e9c9c;
|
||||
-moz-box-shadow: -10px 10px 30px -15px #9e9c9c;
|
||||
transition: all .3s ease;
|
||||
}
|
||||
.statistics-box .icon i {
|
||||
margin: 0;
|
||||
font-size: 45px;
|
||||
line-height: 0;
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panel_toolbox {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
#statistics-article-list li.title {
|
||||
padding: 2px;
|
||||
width: 85%;
|
||||
float: left
|
||||
}
|
||||
|
||||
.recentArticles th.title div{
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.recentComments .content{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.recentComments .source div{
|
||||
width: 80px;
|
||||
}
|
||||
.word-prase {
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#statistics-article-list li.count {
|
||||
padding: 2px;
|
||||
width: 15%;
|
||||
float: left;
|
||||
text-align: right;
|
||||
}
|
||||
.notice-box li{
|
||||
line-height: 25px;
|
||||
}
|
||||
</style>
|
||||
</@header>
|
||||
<#-- 网站首页的项目介绍内容 -->
|
||||
<#--<@aboutOneBlog></@aboutOneBlog>-->
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12 top_tiles">
|
||||
<#-- 文章 -->
|
||||
<a href="/articles" class="statistics-panel">
|
||||
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 statistics-article" data-key="articleCount">
|
||||
<div class="tile-stats statistics-box">
|
||||
<div class="icon"><i class="fa fa-envira"></i></div>
|
||||
<div class="count"></div>
|
||||
<h4>文章</h4>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<#-- 标签 -->
|
||||
<a href="/article/tags" class="statistics-panel">
|
||||
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 statistics-tag" data-key="tagCount">
|
||||
<div class="tile-stats statistics-box">
|
||||
<div class="icon"><i class="fa fa-tags"></i></div>
|
||||
<div class="count"></div>
|
||||
<h4>标签</h4>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<#-- 分类 -->
|
||||
<a href="/article/types" class="statistics-panel">
|
||||
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 statistics-type" data-key="typeCount">
|
||||
<div class="tile-stats statistics-box">
|
||||
<div class="icon"><i class="fa fa-th"></i></div>
|
||||
<div class="count"></div>
|
||||
<h4>分类</h4>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<#-- 评论 -->
|
||||
<a href="/comments" class="statistics-panel">
|
||||
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 statistics-comment" data-key="commentCount">
|
||||
<div class="tile-stats statistics-box">
|
||||
<div class="icon"><i class="fa fa-comments-o"></i></div>
|
||||
<div class="count"></div>
|
||||
<h4>留言数</h4>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<#-- 分类文章数统计 -->
|
||||
<div class="col-md-4 col-sm-4 col-xs-12">
|
||||
<div class="x_panel fixed_height_320 statistics-box">
|
||||
<div class="x_title">
|
||||
<h2>分类文章数统计</h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div id="echart_type" style="height: 250px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#-- 文章访问TOP.10 -->
|
||||
<div class="col-md-4 col-sm-4 col-xs-12">
|
||||
<div class="x_panel fixed_height_320 statistics-box">
|
||||
<div class="x_title">
|
||||
<h2>文章访问TOP.10</h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content" id="statistics-article-list">
|
||||
<ul class="list-unstyled">
|
||||
<@articleTag method="hotList" pageSize="10">
|
||||
<#if hotList?? && (hotList?size > 0)>
|
||||
<#list hotList as item>
|
||||
<li class="title word-prase"><a href="${config.siteUrl}/article/${item.id?c}" title="${item.title}">${item.title}</a></li>
|
||||
<li class="count"><span title="浏览人次:${item.lookCount?c}">${item.lookCount?c}</span></li>
|
||||
</#list>
|
||||
</#if>
|
||||
</@articleTag>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#-- 爬虫访问统计TOP.10 -->
|
||||
<div class="col-md-4 col-sm-4 col-xs-12">
|
||||
<div class="x_panel fixed_height_320 statistics-box">
|
||||
<div class="x_title">
|
||||
<h2>爬虫访问统计TOP.10</h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div id="echart_spider" style="height: 250px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#-- 近期文章 -->
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class="x_panel statistics-box">
|
||||
<div class="x_title">
|
||||
<h2>近期文章 <small> </small></h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a href="/articles" target="_blank" title="查看更多"><i class="fa fa-ellipsis-h"></i></a></li>
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<table class="table table-bordered recentArticles">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title"><div class="word-prase">标题</div></th>
|
||||
<th>分类</th>
|
||||
<th>浏览数</th>
|
||||
<th>发布时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<@articleTag method="recentArticles" pageSize="5">
|
||||
<#if recentArticles?? && (recentArticles?size > 0)>
|
||||
<#list recentArticles as item>
|
||||
<tr>
|
||||
<th class="title"><div class="word-prase"><a href="${config.siteUrl}/article/${item.id?c}" title="${item.title}">${item.title}</a></div></th>
|
||||
<td><a href="${config.siteUrl}/type/${item.type.id?c}" target="_blank">${item.type.name}</a></td>
|
||||
<td>${item.lookCount?c}</td>
|
||||
<td>${item.createTime?string('yyyy-MM-dd')}</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</#if>
|
||||
</@articleTag>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#-- 近期评论 -->
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class="x_panel statistics-box">
|
||||
<div class="x_title">
|
||||
<h2>近期评论 <small> </small></h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a href="/comments" target="_blank" title="查看更多"><i class="fa fa-ellipsis-h"></i></a></li>
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<table class="table table-bordered recentComments">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><div>发起人</div></th>
|
||||
<th class="content"><div class="word-prase">评论内容</div></th>
|
||||
<th class="source"><div class="word-prase">出处</div></th>
|
||||
<th>评论时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<@zhydTag method="recentComments" pageSize="5">
|
||||
<#if recentComments?? && (recentComments?size > 0)>
|
||||
<#list recentComments as item>
|
||||
<tr>
|
||||
<th class="title word-prase"><div><a href="${item.url}" target="_blank" rel="external nofollow">${item.nickname!}</a></div></th>
|
||||
<td class="content"><div class="word-prase">${item.briefContent!}</div></td>
|
||||
<td class="source"><div class="word-prase"><a href="${config.siteUrl}${item.sourceUrl}#comment-${item.id?c}" target="_blank" rel="external nofollow">${item.articleTitle!}</a></div></td>
|
||||
<td>${item.createTime?string('yyyy-MM-dd')}</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</#if>
|
||||
</@zhydTag>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@4.1.0/dist/echarts.min.js"></script>
|
||||
<script src="/assets/js/zhyd.echarts.js"></script>
|
||||
<script>
|
||||
/* 顶部卡片统计 */
|
||||
$.post("/statistics/siteInfo", function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if(json.status == 200){
|
||||
var jsonData = json.data;
|
||||
|
||||
function setValue(dom, value) {
|
||||
var $dom = dom;
|
||||
$dom.find("div.tile-stats .count").text(value);
|
||||
}
|
||||
|
||||
$(".statistics-tag, .statistics-type, .statistics-comment, .statistics-article").each(function () {
|
||||
var $this = $(this);
|
||||
var jsonKey = $this.data("key");
|
||||
setValue($this, jsonData[jsonKey]);
|
||||
});
|
||||
}
|
||||
});
|
||||
/* 分类文章数统计 */
|
||||
$.post("/statistics/listType", function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if(json.status == 200){
|
||||
var jsonData = json.data;
|
||||
zhyd.createChart({id:'echart_type', legendData: getNames(jsonData, 'name'), series:{name:'分类文章数统计', type: 'pie', seriesData: jsonData}});
|
||||
}
|
||||
});
|
||||
|
||||
/* 爬虫访问统计 */
|
||||
$.post("/statistics/listSpider", function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
if(json.status == 200){
|
||||
var jsonData = json.data || [{name: '暂无', value: 0}];
|
||||
zhyd.createChart({id:'echart_spider', legendData: getNames(jsonData, 'name'), series:{name:'爬虫访问统计', type: 'pie', seriesData: jsonData}});
|
||||
}
|
||||
});
|
||||
function getNames(arr, key) {
|
||||
if(!arr){
|
||||
return [];
|
||||
}
|
||||
var resultArr = [];
|
||||
$.each(arr, function (i, v) {
|
||||
resultArr.push(v[key]);
|
||||
});
|
||||
return resultArr;
|
||||
}
|
||||
init_echarts();
|
||||
$("#noticeModal").modal('show');
|
||||
</script>
|
||||
</@footer>
|
@ -0,0 +1,200 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header>
|
||||
<style>
|
||||
.messanger {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.messanger .messages {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
margin: 10px 0;
|
||||
padding: 0 10px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.messanger .messages .message, .messanger .messages .ready {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
-webkit-box-align: start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.messanger .messages .message.me {
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: reverse;
|
||||
-ms-flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.messanger .messages .message.me img {
|
||||
margin-right: 0;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.messanger .messages .message.me .info {
|
||||
background-color: #009688;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.messanger .messages .message.me .info:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.messanger .messages .message.me .info:after {
|
||||
position: absolute;
|
||||
right: -13px;
|
||||
top: 0;
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 16px 16px 0;
|
||||
border-color: transparent #009688 transparent transparent;
|
||||
-webkit-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.messanger .messages .message img {
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.messanger .messages .message .info, .messanger .messages .ready .info {
|
||||
margin: 0;
|
||||
background-color: #ddd;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.messanger .messages .message .info:before {
|
||||
position: absolute;
|
||||
left: -14px;
|
||||
top: 0;
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 16px 16px 0;
|
||||
border-color: transparent #ddd transparent transparent;
|
||||
}
|
||||
|
||||
.messanger .sender {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.messanger .sender input[type="text"] {
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
border: 1px solid #009688;
|
||||
outline: none;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.messanger .sender button {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
</@header>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">socket通知</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>向web端发送消息通知 <small>需要web端用户授权</small></h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content" style="display: flex;justify-content: center;">
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2><i class="fa fa-whatsapp"></i> 聊天 <small>socket通信,当前在线: ${online!("0")}</small></h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li></li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="messanger">
|
||||
<div class="messages">
|
||||
<div class="ready">
|
||||
<p class="info">......</p>
|
||||
</div>
|
||||
<#--<div class="message">
|
||||
<img src="/assets/images/user.png">
|
||||
<p class="info">Hello there!<br>Good Morning</p>
|
||||
</div>
|
||||
<div class="message me">
|
||||
<img src="/assets/images/loading.gif">
|
||||
<p class="info">Hi<br>Good Morning</p>
|
||||
</div>
|
||||
<div class="message"><img src="/assets/images/user.png">
|
||||
<p class="info">How are you?</p>
|
||||
</div>
|
||||
<div class="message me"><img src="/assets/images/loading.gif">
|
||||
<p class="info">I'm Fine.</p>
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="sender">
|
||||
<input type="text" name="msg" id="msg" placeholder="Send Message">
|
||||
<button class="btn btn-primary" id="send-btn" type="button" style="margin: 0;"><i class="fa fa-lg fa-fw fa-paper-plane"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script type="text/javascript">
|
||||
$("#send-btn").click(function () {
|
||||
var $messages = $(".messages");
|
||||
var msg = $("#msg").val();
|
||||
$.post("/api/notice", {msg : msg}, function (json) {
|
||||
if (json.status == 200) {
|
||||
$messages.append('<div class="message me"><img src="/assets/images/loading.gif"><p class="info">' + msg + '</p></div>');
|
||||
} else {
|
||||
if (json.message) {
|
||||
$.alert.error(json.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
</@footer>
|
532
blog-admin/src/main/resources/templates/laboratory/remover.ftl
Normal file
@ -0,0 +1,532 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header>
|
||||
<style>
|
||||
.prod_title {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#removerForm .item .alert {
|
||||
margin: 5px 0 0 15px;
|
||||
}
|
||||
#removerForm .item.bad .alert {
|
||||
float: right;
|
||||
margin-top: -30px;
|
||||
left: -15px!important;
|
||||
opacity: 1;
|
||||
}
|
||||
.x_title h2 {
|
||||
font-weight: 700;
|
||||
}
|
||||
.tips {
|
||||
font-size: 12px;
|
||||
color: #bbbbbb;
|
||||
}
|
||||
</style>
|
||||
</@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">文章搬运工</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="row">
|
||||
<div class="" role="tabpanel" data-example-id="togglable-tabs">
|
||||
<ul id="myTab" class="nav nav-tabs bar_tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#multiple" id="multiple-tab" role="tab" data-toggle="tab" aria-expanded="true">抓取列表</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#single" id="single-tab" role="tab" data-toggle="tab" aria-expanded="true"><i class="fa fa-free-code-camp fa-fw red"></i>抓取单个文章</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane fade active in" id="multiple" aria-labelledby="multiple-tab">
|
||||
<form id="removerForm" action="/remover/run" target="spiderFrame" method="post" class="form-horizontal form-label-left" novalidate>
|
||||
<#-- 左侧 -->
|
||||
<div class="col-md-6">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2><i class="fa fa-user-secret"></i> 基本配置</h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="platform">博文平台 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||
<select name="platform" id="platform" class="form-control" required="required">
|
||||
<option value="">请选择</option>
|
||||
<#list platforms as item>
|
||||
<option value="${item.platform}">${item.platform} (${item.host})</option>
|
||||
</#list>
|
||||
<option value="">待续...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-3 col-xs-3">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="square" name="convertImg"> 转图存片
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
<i class="fa fa-exclamation-circle"></i> 勾选时默认将文章中的图片转存到自有云存储服务器中(需提前配置云存储服务器)
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="typeId">文章分类 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<select name="typeId" class="form-control typeId" required="required"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="uid">用户ID <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="uid" id="uid" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
<i class="fa fa-exclamation-circle"></i> 获取方式:
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#helpModal" data-img="/assets/images/spider/uid/imooc.png" data-title="慕课网“用户ID”获取方式">慕课网</a> |
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#helpModal" data-img="/assets/images/spider/uid/csdn.png" data-title="CSDN“用户ID”获取方式">CSDN</a> |
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#helpModal" data-img="/assets/images/spider/uid/iteye.png" data-title="ITeye“用户ID”获取方式">ITeye</a> |
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#helpModal" data-img="/assets/images/spider/uid/cnblogs.png" data-title="博客园“用户ID”获取方式">博客园</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="exitWay">停止方式 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<select name="exitWay" id="exitWay" class="form-control" required="required" style="width: 50%;display: inline-block">
|
||||
<#list exitWayList as exitWay>
|
||||
<option value="${exitWay}" data-def-count="${exitWay.defaultCount}" <#if exitWay_index == 2>selected="selected"</#if>>${exitWay.desc}</option>
|
||||
</#list>
|
||||
</select>
|
||||
<input class="form-control" style="width: 30%;display: inline-block" type="text" name="count" value="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
<i class="fa fa-exclamation-circle"></i>
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>默认:</strong>不做限制,抓取所有匹配到的文章,<strong class="red">慎用</strong></li>
|
||||
<li><strong>持续时间:</strong>按照爬虫运行的时间,理想状态时1s抓取一条,受实际网速影响</li>
|
||||
<li><strong>链接条数:</strong>按照指定的条数抓取,满足条数后程序自动停止</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="domain">网站根域名 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="domain" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="cookie">Cookie </label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea name="cookie" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
<i class="fa fa-exclamation-circle"></i> 需要登录时设置。Cookie获取方式: <a href="javascript:HandlerInterceptor;" data-toggle="modal" data-target="#helpModal" data-img="/assets/images/spider/cookie/cookie.png" data-title="“Cookie”获取方式(通用)">以CSDN为例</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="header">Header <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea name="header" class="form-control" required="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
<i class="fa fa-exclamation-circle"></i> Header主要是为了防止某些网站验证referer等信息防爬虫
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="entryUrls">程序入口 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<textarea name="entryUrls" class="form-control" required="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#-- 右侧 -->
|
||||
<div class="col-md-6">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2><i class="fa fa-tasks"></i> 爬虫Xpath抓取规则</h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="titleRegex">标题 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="titleRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="authorRegex">作者 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="authorRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="releaseDateRegex">发布日期 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="releaseDateRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="contentRegex">内容 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="contentRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="tagRegex">标签 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="tagRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="targetLinksRegex">待抓取的url <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="targetLinksRegex" class="form-control" required="required">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2><i class="fa fa-pagelines"></i> 爬虫其他配置项</h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="charset">网站编码 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="text" name="charset" class="form-control" value="utf8" readonly required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="sleepTime">延迟 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="number" name="sleepTime" class="form-control" value="1000" readonly required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9 tips">
|
||||
延迟和爬取速度以及被封的概率成正比!请慎用
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="retryTimes">重试次数 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="number" name="retryTimes" class="form-control" value="2" readonly required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="threadCount">线程个数 <span class="required">*</span></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<input type="number" name="threadCount" class="form-control" value="1" readonly required="required">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#declareModal"><i class="fa fa-truck"> GO!</i></button>
|
||||
<button type="reset" class="btn btn-default" id="resetBtn"><i class="fa fa-refresh"> 重置</i></button>
|
||||
<a type="button" class="btn btn-danger stopBtn hide"><i class="fa fa-stop-circle-o"> 停止</i></a>
|
||||
<a type="button" class="btn btn-info" id="showResultModal" style="display: none;"><i class="fa fa-eye"> 显示日志</i></a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane fade" id="single" aria-labelledby="single-tab">
|
||||
<form id="removerSingleForm" action="/remover/single" target="spiderFrame" method="post" class="form-horizontal form-label-left" novalidate>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="url">转图存片</label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="square" name="convertImg" checked="checked">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="url">请输入文章链接 <span class="required">*</span></label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<div class="input-group">
|
||||
<input type="text" name="url" class="form-control" value="" placeholder="例如:https://www.baidu.com/article/1234567.html" required="required">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" id="plus-btn" title="添加一条"><i class="fa fa-plus fa-fw"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="url-list"></div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="typeId">选择文章分类 <span class="required">*</span></label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<select name="typeId" class="form-control typeId" required="required"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="url"></label>
|
||||
<div class="col-md-9 col-sm-9 col-xs-9">
|
||||
<button type="button" class="btn btn-success" id="crawlSingle"><i class="fa fa-signing"> 就是它了!</i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="helpModalTitle"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<a href="" class="showImage" title="" rel="external nofollow">
|
||||
<img src="" alt="" id="helpModalImg" class="img-responsive img-rounded">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="declareModal" tabindex="-1" role="dialog" aria-labelledby="declareModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="declareModalTitle">声明</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul class="list-unstyled">
|
||||
<li>1.本工具开发初衷只是用来迁移 <strong>自己的文章</strong> 所用,因此不可用该工具 <strong>恶意窃取</strong> 他人劳动成果!</li>
|
||||
<li>2.因不听劝阻,使用该工具恶意窃取他们劳动成果而造成的一切不良后果,本人表示:坚决不背锅!</li>
|
||||
<li>3.如果该工具不好用,你们绝对不能打我!</li>
|
||||
<li>4.有问题、建议,请 <a href="https://gitee.com/yadong.zhang/DBlog/issues" target="_blank">提Issue</a>!</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a type="button" class="btn btn-success" id="submitBtn"><i class="fa fa-hand-grab-o"> 知道了!</i></a>
|
||||
<a href="javascript:;" data-dismiss="modal"><i class="fa fa-close"> 算了吧</i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="resultModal" tabindex="-1" role="dialog" aria-labelledby="resultModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<a type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></a>
|
||||
<h4 class="modal-title" id="resultModalLabel">程序正在执行中...</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="pageFormContent" id="pageFormContent" style="max-height: 300px;height: 300px;overflow-y: auto;">
|
||||
<div id="spider-message" class="profile_title"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a type="button" class="btn btn-danger stopBtn hide"><i class="fa fa-stop-circle-o"> 停止</i></a>
|
||||
<a type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<iframe src="" id="spiderFrame" name="spiderFrame" style="display: none"></iframe>
|
||||
<@footer>
|
||||
<script>
|
||||
var $urlList = $("#url-list"),
|
||||
$plusBtn = $("#plus-btn");
|
||||
|
||||
$plusBtn.on('click', function () {
|
||||
$urlList.append('<div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-3" for="url">请输入文章链接 <span class="required">*</span></label><div class="col-md-7 col-sm-7 col-xs-7"><div class="input-group"><input type="text" name="url" class="form-control" value="" placeholder="例如:https://www.baidu.com/article/1234567.html" required> <span class="input-group-btn"><button type="button" class="btn btn-danger minus-btn" title="删除该条"><i class="fa fa-minus fa-fw"></i></button></span></div></div></div>');
|
||||
|
||||
function minus(){
|
||||
var $this = $(this);
|
||||
$this.parents(".form-group").remove();
|
||||
}
|
||||
$(".minus-btn").unbind("click", minus).on('click', minus);
|
||||
});
|
||||
|
||||
var spiderConfig = ${spiderConfig};
|
||||
// 博文平台
|
||||
var $platform = $("#platform");
|
||||
// 各平台用户id
|
||||
var $uid = $("#uid");
|
||||
// 文章总页数
|
||||
var $totalPage = $("#totalPage");
|
||||
// 分割字符串的正则
|
||||
var reg = new RegExp('{\\w+}'), br = "\r\n";
|
||||
|
||||
$("#platform, #uid, #totalPage").change(function () {
|
||||
autoCompleForm();
|
||||
});
|
||||
function autoCompleForm() {
|
||||
var platform = $platform.val();
|
||||
if(!platform) {
|
||||
return;
|
||||
}
|
||||
var uid = $uid.val(),
|
||||
totalPage = $totalPage.val() | 1,
|
||||
curConfig = spiderConfig[platform];
|
||||
if(!curConfig) {
|
||||
$.toastr.warning("系统暂未内置[" + platform + "]平台的爬虫规则,请手动提取!")
|
||||
return;
|
||||
}
|
||||
$("#removerForm").find("input,textarea").each(function () {
|
||||
var $this = $(this);
|
||||
var thisName = $this.attr("name");
|
||||
var realText;
|
||||
$this.val((realText = parseText(curConfig, thisName, uid, totalPage)) ? realText : $this.val());
|
||||
});
|
||||
}
|
||||
function parseText(curConfig, thisName, uid, totalPage){
|
||||
var text = curConfig[thisName];
|
||||
if (typeof(text) === "undefined") {
|
||||
return text;
|
||||
}
|
||||
console.log("exec >> " + reg.exec(text));
|
||||
if(thisName === "header") {
|
||||
var header = "";
|
||||
for(var i in text) {
|
||||
header += text[i].replaceAll("{uid}", uid) + br;
|
||||
}
|
||||
text = header.substr(0, header.length - br.length);
|
||||
} else if(thisName === "entryUrls") {
|
||||
var entryUrl = "";
|
||||
for(var j = 1; j <= totalPage; j ++) {
|
||||
entryUrl += text[0].replaceAll("{uid}", uid).replaceAll("{curPage}", j) + br;
|
||||
}
|
||||
text = entryUrl.substr(0, entryUrl.length - br.length);
|
||||
} else if(reg.exec(text)) {
|
||||
text = text.replaceAll("{uid}", uid);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
$("#submitBtn").click(function () {
|
||||
var $form = $("form#removerForm");
|
||||
if (validator.checkAll($form)) {
|
||||
$("#declareModal").modal('hide');
|
||||
changeBtnState(true)
|
||||
$("#resultModal").modal('show');
|
||||
$("#showResultModal").show();
|
||||
$form.submit();
|
||||
$("#spider-message").html("<p> 程序正在初始化...</p>");
|
||||
}
|
||||
});
|
||||
|
||||
$("#showResultModal").click(function () {
|
||||
$("#resultModal").modal('show');
|
||||
});
|
||||
|
||||
function changeBtnState(state) {
|
||||
if(state) {
|
||||
$("button[type=button], button[type=reset]").button('loading');
|
||||
$(".stopBtn").removeClass("hide");
|
||||
} else {
|
||||
$("button[type=button], button[type=reset]").button('reset');
|
||||
$(".stopBtn").addClass("hide");
|
||||
}
|
||||
}
|
||||
|
||||
function printMessage(message){
|
||||
if(message == 'shutdown') {
|
||||
changeBtnState(false)
|
||||
return;
|
||||
}
|
||||
$("#spider-message").append("<p>" + message + "</p>");
|
||||
var $dom = document.getElementById("pageFormContent");
|
||||
$dom.scrollTop = $dom.scrollHeight;
|
||||
}
|
||||
|
||||
$('#helpModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget);
|
||||
var img = button.data('img');
|
||||
var title = button.data('title');
|
||||
var modal = $(this);
|
||||
modal.find('#helpModalTitle').text(title);
|
||||
var $img = modal.find('#helpModalImg');
|
||||
$img.attr("src", img).attr("alt", title);
|
||||
$img.parent().attr("href", img).attr("title", title);
|
||||
})
|
||||
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/type/listAll",
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
var data = '';
|
||||
if(data = json.data){
|
||||
var tpl = '<option value="">选择分类</option>{{#data}}<option value="{{id}}">{{name}}</option>{{#nodes}}<option value="{{id}}"> -- {{name}}</option>{{/nodes}}{{/data}}';
|
||||
var html = Mustache.render(tpl, json);
|
||||
$("select.typeId").html(html);
|
||||
}
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
|
||||
$("#exitWay").on('change', function () {
|
||||
var $this = $(this);
|
||||
var $input = $this.next('input');
|
||||
if($this.val() !== 'DEFAULT') {
|
||||
$input.removeAttr('readonly');
|
||||
} else {
|
||||
$input.attr('readonly', 'readonly')
|
||||
}
|
||||
$input.val($this.find("option:selected").data("def-count"));
|
||||
});
|
||||
|
||||
$(".stopBtn").on('click', function () {
|
||||
$.alert.confirm("确定停止当前进程?", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/remover/stop",
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
changeBtnState(false)
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
$("#crawlSingle").click(function () {
|
||||
var $form = $("form#removerSingleForm");
|
||||
if (validator.checkAll($form)) {
|
||||
changeBtnState(true)
|
||||
$("#resultModal").modal('show');
|
||||
$("#showResultModal").show();
|
||||
$form.submit();
|
||||
$("#spider-message").html("<p> 程序正在初始化...</p>");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
43
blog-admin/src/main/resources/templates/layout/footer.ftl
Normal file
@ -0,0 +1,43 @@
|
||||
<script type="text/javascript">
|
||||
var appConfig = {
|
||||
fileStoragePath: '${config.fileStoragePath}',
|
||||
wwwPath: '${config.siteUrl}',
|
||||
cmsPath: '${config.cmsUrl}',
|
||||
staticPath: '${config.staticWebSite}'
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@1.11.1/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.0/dist/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery-lazyload@1.9.3/jquery.lazyload.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery-confirm@3.3.2/dist/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery.fancybox@2.1.5/source/jquery.fancybox.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mustache@2.3.0/mustache.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/xss@0.3.3/dist/xss.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.0.3/nuget/content/scripts/toastr.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/icheck@1.0.2/icheck.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.11.1/dist/bootstrap-table.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.11.1/dist/locale/bootstrap-table-zh-CN.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@2.1.25/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@2.1.25/daterangepicker.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-datetimepicker-npm@4.17.37-npm/build/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-progressbar@0.9.0/bootstrap-progressbar.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ztree/ztree_v3@3.5.37/js/jquery.ztree.core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ztree/ztree_v3@3.5.37/js/jquery.ztree.excheck.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/switchery-npm@0.8.2/index.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/wangeditor@3.1.1/release/wangEditor.min.js" type="text/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@adactive/bootstrap-tagsinput@0.8.2/dist/bootstrap-tagsinput.min.js"></script>
|
||||
<#--
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.buttons.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pnotify/3.2.1/pnotify.nonblock.js"></script>-->
|
||||
|
||||
<script src="/assets/js/bootstrap-treetable.js" type="text/javascript"></script>
|
||||
<script src="/assets/js/validator.js"></script>
|
||||
<script src="/assets/js/ajaxfileupload.js"></script>
|
||||
<script src="/assets/js/jquery-form.js"></script>
|
||||
<script src="/assets/js/zhyd.tool.js"></script>
|
||||
<script src="/assets/js/zhyd.upload-preview.js"></script>
|
||||
<script src="/assets/js/gentelella.core.js"></script>
|
||||
<script src="/assets/js/zhyd.core.js"></script>
|
||||
<script src="/assets/js/zhyd.table.js"></script>
|
148
blog-admin/src/main/resources/templates/layout/setting.ftl
Normal file
@ -0,0 +1,148 @@
|
||||
<@shiro.user>
|
||||
<div class="top_nav">
|
||||
<div class="nav_menu">
|
||||
<nav>
|
||||
<div class="nav toggle">
|
||||
<a id="menu_toggle"><i class="fa fa-bars"></i></a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="">
|
||||
<a href="javascript:;" class="user-profile dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<img src="/assets/images/loading.gif" alt=""><#if user??>${user.username!}<#else>管理员</#if>
|
||||
<span class=" fa fa-angle-down"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-usermenu pull-right">
|
||||
<li><a data-toggle="modal" title="修改密码" data-target="#updPasswordModal">修改密码</a></li>
|
||||
<@shiro.hasRole name="role:root">
|
||||
<li>
|
||||
<a href="/config">
|
||||
<span>系统配置</span>
|
||||
</a>
|
||||
</li>
|
||||
</@shiro.hasRole>
|
||||
<li><a href="/passport/logout"><i class="fa fa-sign-out pull-right"></i> 退出系统</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<@shiro.hasPermission name="comments">
|
||||
<li role="presentation" class="dropdown">
|
||||
<@zhydTag method="getNewCommentInfo" pageSize="50">
|
||||
<a href="javascript:;" class="dropdown-toggle info-number" data-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa fa-envelope-o"></i>
|
||||
<span class="badge bg-green noticeNum">${getNewCommentInfo['total']}</span>
|
||||
</a>
|
||||
<#if getNewCommentInfo['total'] gt 0>
|
||||
<ul id="menu1" class="dropdown-menu list-unstyled msg_list" role="menu">
|
||||
<#list getNewCommentInfo["comments"] as item>
|
||||
<li>
|
||||
<a href="/comments">
|
||||
<span class="image"><img src="${item.avatar}" onerror="this.src='/assets/images/user.png'" alt="user avatar"></span>
|
||||
<span>
|
||||
<span>${item.nickname}</span>
|
||||
<span class="time">${item.createTime?string('yyyy-MM-dd HH:mm:ss')}</span>
|
||||
</span>
|
||||
<span class="message">点击查看&审核</span>
|
||||
</a>
|
||||
</li>
|
||||
</#list>
|
||||
<li id="event-li">
|
||||
<div class="text-center">
|
||||
<a href="/comments">
|
||||
<strong>立即处理</strong>
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</#if>
|
||||
</@zhydTag>
|
||||
</li>
|
||||
</@shiro.hasPermission>
|
||||
<li>
|
||||
<a href="${config.siteUrl!}" target="_blank">
|
||||
<i class="fa fa-desktop"> 访问前台</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" data-toggle="modal" data-target="#reward">
|
||||
<i class="fa fa-cny"> 捐赠博主</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="reward" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">捐赠博主 <small>码代码很累的!天热了,请作者吃根冰棍儿吧!<i class="fa fa-smile-o"></i></small></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="col-sm-12 col-md-12" style="text-align: center;margin: 0 auto;float: initial">
|
||||
<a href="/assets/images/reward/zfb_code.png" class="showImage" title="支付宝收钱码" rel="external nofollow">
|
||||
<img src="/assets/images/reward/zfb_code.png" alt="支付宝收钱码" class="img-rounded" style="width: 250px;height: auto;">
|
||||
</a>
|
||||
<a href="/assets/images/reward/wx_code.png" class="showImage" title="微信收钱码" rel="external nofollow">
|
||||
<img src="/assets/images/reward/wx_code.png" alt="微信收钱码" class="img-rounded" style="width: 250px;height: auto">
|
||||
</a>
|
||||
</div>
|
||||
<div style="width: 100%;color: #a3a3a3;font-size: 16px;font-family: 'Microsoft YaHei';text-align: center;margin-top: 10px">
|
||||
转账时请备注“<strong>博客赞助</strong>”,并另附上您的称呼(方便博主统计)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="font-bold"></small>
|
||||
</div>
|
||||
<small class="font-bold"> </small>
|
||||
</div>
|
||||
|
||||
<#-- 修改密码Modal -->
|
||||
<div class="modal fade" id="updPasswordModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel2">修改密码</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="" class="form-horizontal form-label-left" role="form" id="updPassForm">
|
||||
<input type="hidden" name="id" value="<#if user??>${user.id?c}</#if>">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="password">旧密码 <span
|
||||
class="required">*</span></label>
|
||||
<div class="col-sm-7 col-md-7 col-xs-7">
|
||||
<input class="form-control" id="oldPassword" name="password" required="required"
|
||||
type="password" minlength="6" maxlength="15">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label for="newPassword" class="control-label col-md-3 col-sm-3 col-xs-3">新密码 <span
|
||||
class="required">*</span></label>
|
||||
<div class="col-sm-7 col-md-7 col-xs-7">
|
||||
<input id="newPassword" type="password" name="newPassword" data-validate-length="5,20"
|
||||
class="form-control" required="required" minlength="6" maxlength="15">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label for="newPasswordRepeat" class="control-label col-md-3 col-sm-3 col-xs-3">重复新密码 <span
|
||||
class="required">*</span></label>
|
||||
<div class="col-sm-7 col-md-7 col-xs-7">
|
||||
<input id="newPasswordRepeat" type="password" name="newPasswordRepeat"
|
||||
data-validate-linked="newPassword" class="form-control" required="required"
|
||||
minlength="6" maxlength="15">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="updPassBtn">修改密码</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</@shiro.user>
|
42
blog-admin/src/main/resources/templates/layout/sidebar.ftl
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- sidebar menu -->
|
||||
<div id="sidebar-menu" class="main_menu_side hidden-print main_menu">
|
||||
<div class="menu_section">
|
||||
<ul class="nav side-menu">
|
||||
<@shiro.user>
|
||||
<li><a href="/"><i class="fa fa-home"></i>首页</a></li>
|
||||
</@shiro.user>
|
||||
<@zhydTag method="menus" userId="${user.id}">
|
||||
<#if menus?? && menus?size gt 0>
|
||||
<#list menus as item>
|
||||
<#if item.nodes?? && item.nodes?size gt 0>
|
||||
<li>
|
||||
<a><i class="${item.icon!}"></i> ${item.name!}<span class="fa fa-chevron-down"></span></a>
|
||||
<ul class="nav child_menu">
|
||||
<#list item.nodes as node>
|
||||
<#if node.permission!>
|
||||
<@shiro.hasPermission name="${node.permission!}">
|
||||
<li><a href="${node.url!}" ${(item.external?? && item.external)?string('target="_blank"','')}><i class="${node.icon!}"></i>${node.name!}</a></li>
|
||||
</@shiro.hasPermission>
|
||||
<#else>
|
||||
<li><a href="${node.url!}" ${(item.external?? && item.external)?string('target="_blank"','')}><i class="${node.icon!}"></i>${node.name!}</a></li>
|
||||
</#if>
|
||||
</#list>
|
||||
</ul>
|
||||
</li>
|
||||
<#else>
|
||||
<li><a href="${item.url!}" ${(item.external?? && item.external)?string('target="_blank"','')}><i class="${item.icon!}"></i>${item.name!}</a></li>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
</@zhydTag>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-footer hidden-small">
|
||||
<a > </a>
|
||||
<a > </a>
|
||||
<a > </a>
|
||||
<a href="/passport/logout" data-toggle="tooltip" data-placement="top" title="" data-original-title="退出系统">
|
||||
<span class="glyphicon glyphicon-off" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
224
blog-admin/src/main/resources/templates/link/list.ftl
Normal file
@ -0,0 +1,224 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">友情链接管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<div class="btn-group hidden-xs" id="toolbar">
|
||||
<@shiro.hasPermission name="link:add">
|
||||
<button id="btn_add" type="button" class="btn btn-info" title="新增友链">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="link:batchDelete">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="批量删除">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<table id="tablelist">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="添加友情链接">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="url">URL <span class="required">*</span></label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" name="url" id="url" required="required" placeholder="请输入URL"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="name">名称 <span class="required">*</span></label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" name="name" id="name" required="required" placeholder="请输入名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="description">描述 </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" id="description" name="description" placeholder="请输入描述"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="favicon">Logo </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" id="favicon" name="favicon" placeholder="请输入Logo"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="status">状态 </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><input type="radio" class="square" name="status" value="1"> 启用</li>
|
||||
<li><input type="radio" class="square" name="status" value="0"> 禁用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="homePageDisplay">首页显示 </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><input type="radio" class="square" name="homePageDisplay" value="1"> 是</li>
|
||||
<li><input type="radio" class="square" name="homePageDisplay" value="0"> 否</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="email">e-mail </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" id="email" name="email" placeholder="请输入email"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="qq">qq </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<input type="text" class="form-control" id="qq" name="qq" placeholder="请输入qq"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-3" for="remark">备注 </label>
|
||||
<div class="col-md-7 col-sm-7 col-xs-7">
|
||||
<textarea class="form-control" id="remark" name="remark"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var trId = row.id;
|
||||
var operateBtn = [
|
||||
'<@shiro.hasPermission name="link:edit"><a class="btn btn-sm btn-success btn-update" data-id="' + trId + '"title="编辑"><i class="fa fa-edit fa-fw"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="link:delete"><a class="btn btn-sm btn-danger btn-remove" data-id="' + trId + '"title="删除"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>'
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var options = {
|
||||
modalName: "友情链接",
|
||||
url: "/link/list",
|
||||
getInfoUrl: "/link/get/{id}",
|
||||
updateUrl: "/link/edit",
|
||||
removeUrl: "/link/remove",
|
||||
createUrl: "/link/add",
|
||||
columns: [
|
||||
{
|
||||
checkbox: true
|
||||
}, {
|
||||
field: 'url',
|
||||
title: 'URL',
|
||||
width: '120px',
|
||||
formatter: function (code) {
|
||||
return '<a href="'+code+'" target="_blank" rel="nofollow ">' + code + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'name',
|
||||
title: '名称',
|
||||
width: '100px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'description',
|
||||
title: '描述',
|
||||
width: '200px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'favicon',
|
||||
title: 'Logo',
|
||||
width: '40px',
|
||||
formatter: function (code) {
|
||||
return !code ? '' : '<img src="'+code+'" width="20">';
|
||||
}
|
||||
}, {
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: '40px',
|
||||
formatter: function (code, row, index) {
|
||||
return code ? "启用" : "<strong style='color: red;' title='" + row.remark + "'>禁用</strong>";
|
||||
}
|
||||
}, {
|
||||
field: 'homePageDisplay',
|
||||
title: '首页',
|
||||
width: '40px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? '<span class="label label-success">是</span>' : '<span class="label label-default">否</span>';
|
||||
}
|
||||
}, {
|
||||
field: 'source',
|
||||
title: '来源 <i class="fa fa-question-circle-o" title="\'ADMIN\'表示管理员添加,\'AUTOMATIC\'表示用户自动添加"></i>',
|
||||
width: '60px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'email',
|
||||
title: '联系方式',
|
||||
width: '60px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
var html = '';
|
||||
if(row.email){
|
||||
html += '<a href="mailto:' + row.email + '" target="_blank" rel="external nofollow"><i class="fa fa fa-envelope fa-fw"></i></a>';
|
||||
}
|
||||
if(row.qq){
|
||||
html += '<a href="javascript:void(0);" target="_blank" onclick="window.open(\'tencent://message/?uin=' + row.qq + '&Site=www.zhyd.me&Menu=yes\')" rel="external nofollow"><i class="fa fa fa-qq fa-fw"></i></a>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
}, {
|
||||
field: 'updateTime',
|
||||
title: '更新时间',
|
||||
width: '120px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return new Date(code).format("yyyy-MM-dd hh:mm:ss")
|
||||
}
|
||||
}, {
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
align: "center",
|
||||
width: '80px',
|
||||
formatter: operateFormatter //自定义方法,添加操作按钮
|
||||
}
|
||||
],
|
||||
rowStyle: function (row, index) {
|
||||
//这里有5个取值代表5中颜色['active', 'success', 'info', 'warning', 'danger'];
|
||||
var strclass = "";
|
||||
if (row.status) {
|
||||
// strclass = 'success';//还有一个active
|
||||
} else {
|
||||
strclass = 'danger';
|
||||
}
|
||||
return { 'classes': strclass }
|
||||
}
|
||||
};
|
||||
// 初始化table组件
|
||||
var table = new Table(options);
|
||||
table.init();
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
109
blog-admin/src/main/resources/templates/login.ftl
Normal file
@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${config.siteName}后台管理系统</title>
|
||||
<link href="/assets/images/favicon.ico" rel="icon">
|
||||
<link href="https://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
|
||||
<link href="/assets/css/zhyd.core.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="login">
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static"
|
||||
data-keyboard="false">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="login_wrapper">
|
||||
<div class="animate form login_form" style="position: relative;">
|
||||
<section class="login_content">
|
||||
<form action="/passport/signin" method="POST" id="login-form">
|
||||
<h1>登录管理系统</h1>
|
||||
<#if message??>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
${message!}
|
||||
</div>
|
||||
</#if>
|
||||
<div>
|
||||
<input type="text" class="form-control" placeholder="请输入用户名" name="username" required=""/>
|
||||
</div>
|
||||
<div>
|
||||
<input type="password" class="form-control" placeholder="请输入密码" name="password" required=""/>
|
||||
</div>
|
||||
<#if enableKaptcha?? && enableKaptcha>
|
||||
<div class="form-group col-xs-6" style="padding-left: 0px;">
|
||||
<img alt="点击获取验证码" id="img-kaptcha" src="/getKaptcha" style="cursor:pointer;height: 34px;width: 180px;">
|
||||
</div>
|
||||
<div class="form-group col-xs-6">
|
||||
<span><input type="text" class="form-control" placeholder="验证码" id="kaptcha" name="kaptcha"></span>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="form-group" style="text-align : left">
|
||||
<label><input type="checkbox" id="rememberMe" name="rememberMe" style="width: 12px; height: 12px;margin-right: 5px;"> 记住我</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-success btn-login" style="width: 100%;">登录</button>
|
||||
</div>
|
||||
<div class="login-loading hide">
|
||||
<i class="fa fa-spinner fa-pulse"></i>正在登录中...
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="separator">
|
||||
<div class="clearfix"></div>
|
||||
<div>
|
||||
Gentelella - Bootstrap Admin Template by <a href="https://colorlib.com">Colorlib</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://cdn.bootcss.com/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="https://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.js" type="text/javascript"></script>
|
||||
<script src="/assets/js/zhyd.tool.js"></script>
|
||||
<script>
|
||||
$("#modal").modal('show');
|
||||
$(".btn-login").click(function () {
|
||||
$(".login-loading").removeClass("hide");
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/passport/signin",
|
||||
data: $("#login-form").serialize(),
|
||||
dataType: "json",
|
||||
success: function (json) {
|
||||
$(".login-loading").addClass("hide");
|
||||
if (json.status == 200) {
|
||||
var historyUrl = json.data || "/";
|
||||
window.location.href = historyUrl;
|
||||
}else{
|
||||
$.alert.error(json.message);
|
||||
$("#img-kaptcha").attr("src", '/getKaptcha?time=' + new Date().getTime());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#img-kaptcha").click(function () {
|
||||
$(this).attr("src", '/getKaptcha?time=' + new Date().getTime());
|
||||
});
|
||||
document.onkeydown = function (event) {
|
||||
var e = event || window.event || arguments.callee.caller.arguments[0];
|
||||
if (e && e.keyCode == 13) {
|
||||
$(".btn-login").click();
|
||||
}
|
||||
};
|
||||
</script>
|
184
blog-admin/src/main/resources/templates/notice/list.ftl
Normal file
@ -0,0 +1,184 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">网站通知管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<div class="btn-group hidden-xs" id="toolbar">
|
||||
<@shiro.hasPermission name="notice:add">
|
||||
<button id="btn_add" type="button" class="btn btn-info" title="添加公告">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
<@shiro.hasPermission name="notice:batchDelete">
|
||||
<button id="btn_delete_ids" type="button" class="btn btn-danger" title="删除选中">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</button>
|
||||
</@shiro.hasPermission>
|
||||
</div>
|
||||
<table id="tablelist">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="发布通知">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="title">标题 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="title" id="title" required="required"
|
||||
placeholder="请输入标题"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="content">内容 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<textarea class="form-control col-md-7 col-xs-12" id="content" name="content" required="required"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="status">状态 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li>
|
||||
<div class="radio">
|
||||
<label> <input type="radio" class="square" name="status" required="required" value="RELEASE"> 已发布 </label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="radio">
|
||||
<label> <input type="radio" class="square" name="status" required="required" value="NOT_RELEASE"> 未发布 </label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script>
|
||||
/**
|
||||
* 操作按钮
|
||||
* @param code
|
||||
* @param row
|
||||
* @param index
|
||||
* @returns {string}
|
||||
*/
|
||||
function operateFormatter(code, row, index) {
|
||||
var trId = row.id;
|
||||
var status = row.status;
|
||||
var html = '';
|
||||
if (status && status == 'NOT_RELEASE') {
|
||||
html = '<@shiro.hasPermission name="notice:release"><a class="btn btn-sm btn-success btn-release" data-id="' + trId + '" title="发布"><i class="fa fa-rocket fa-fw"></i></a></@shiro.hasPermission>';
|
||||
} else {
|
||||
html = '<@shiro.hasPermission name="notice:withdraw"><a class="btn btn-sm btn-warning btn-withdraw" data-id="' + trId + '" title="撤回"><i class="fa fa-rocket fa-rotate-180 fa-fw"></i></a></@shiro.hasPermission>';
|
||||
}
|
||||
var operateBtn = [
|
||||
html,
|
||||
'<@shiro.hasPermission name="notice:edit"><a class="btn btn-sm btn-success btn-update" data-id="' + trId + '"title="编辑"><i class="fa fa-edit fa-fw"></i></a></@shiro.hasPermission>',
|
||||
'<@shiro.hasPermission name="notice:delete"><a class="btn btn-sm btn-danger btn-remove" data-id="' + trId + '"title="删除"><i class="fa fa-trash-o fa-fw"></i></a></@shiro.hasPermission>'
|
||||
];
|
||||
return operateBtn.join('');
|
||||
}
|
||||
|
||||
$(function () {
|
||||
var options = {
|
||||
modalName: "网站通知",
|
||||
url: "/notice/list",
|
||||
getInfoUrl: "/notice/get/{id}",
|
||||
updateUrl: "/notice/edit",
|
||||
removeUrl: "/notice/remove",
|
||||
createUrl: "/notice/add",
|
||||
columns: [
|
||||
{
|
||||
checkbox: true
|
||||
}, {
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: '60px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'title',
|
||||
title: '标题',
|
||||
width: '150px',
|
||||
formatter: function (code) {
|
||||
return '<a href="' + code + '" target="_blank" rel="nofollow ">' + code + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'content',
|
||||
title: '内容',
|
||||
width: '300px',
|
||||
formatter: function (code) {
|
||||
return '<a href="' + code + '" target="_blank" rel="nofollow ">' + code + '</a>';
|
||||
}
|
||||
}, {
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: '60px',
|
||||
align: 'center',
|
||||
formatter: function (code, row, index) {
|
||||
return (code && code == 'RELEASE') ? '<span class="label label-success">已发布</span>' : '<span class="label label-default">未发布</span>';
|
||||
}
|
||||
}, {
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
align: "center",
|
||||
width: '80px',
|
||||
formatter: operateFormatter //自定义方法,添加操作按钮
|
||||
}
|
||||
]
|
||||
};
|
||||
// 初始化table组件
|
||||
var table = new Table(options);
|
||||
table.init();
|
||||
|
||||
// 发布
|
||||
table.bindClickEvent('.btn-release', function () {
|
||||
var $this = $(this);
|
||||
var id = $this.data("id");
|
||||
$.alert.confirm("确定发布该条通知?发布后将对用户可见!", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/notice/release/" + id,
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
// 撤回
|
||||
table.bindClickEvent('.btn-withdraw', function () {
|
||||
var $this = $(this);
|
||||
var id = $this.data("id");
|
||||
$.alert.confirm("确定撤回该条通知?撤回后将对用户不可见!", function () {
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "/notice/withdraw/" + id,
|
||||
success: function (json) {
|
||||
$.alert.ajaxSuccess(json);
|
||||
table.refresh();
|
||||
},
|
||||
error: $.alert.ajaxError
|
||||
});
|
||||
}, function () {
|
||||
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</@footer>
|
96
blog-admin/src/main/resources/templates/other/editor.ftl
Normal file
@ -0,0 +1,96 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header>
|
||||
<style type="text/css">
|
||||
.toolbar {
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.text {
|
||||
border: 1px solid #ccc;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
</@header>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>wangEditor富文本编辑器用例
|
||||
<small><a href="http://www.wangeditor.com/" target="_blank">http://www.wangeditor.com/</a></small>
|
||||
</h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="name">菜单和编辑器区域分开 </label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<div id="toolbar" class="toolbar"></div>
|
||||
<div style="padding: 5px 0; color: #ccc">中间隔离带</div>
|
||||
<div id="div1" class="text" style="height: 100px">
|
||||
<p>第一个 demo(菜单和编辑器区域分开)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="name">普通的编辑器 </label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<div id="div2">
|
||||
<p>第二个 demo(常规)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="form-group row">
|
||||
<label class="control-label col-md-2 col-sm-2 col-xs-12" for="name">oneBlog系统定制的编辑器 </label>
|
||||
<div class="col-md-8 col-sm-8 col-xs-12">
|
||||
<div id="editor">
|
||||
<p>第三个 demo(oneBlog系统单独定制,支持文件上传)</p>
|
||||
<p>
|
||||
使用方式:
|
||||
<pre><code># html<br><div id="editor"></div><br><br># js<br>$.wangEditor.init({<br> container: "#editor",<br> textareaName: "content",<br> uploadUrl: "/api/uploadFile",<br> uploadFileName: "file",<br> uploadType: "goods",<br> customCss: {<br> "overflow-y": "scroll",<br> "height": "100%",<br> "max-height": "125px"<br> }<br>})</code></pre>
|
||||
</p>
|
||||
<ul>
|
||||
<li>container: 编辑器的id,默认为editor</li>
|
||||
<li>textareaName: 自动生成的textarea组件的name,默认为content。可以自定义为表单中实际的参数name</li>
|
||||
<li>uploadUrl: 文件上传的api路径。如果不为空,则开启上传文件的功能</li>
|
||||
<li>uploadFileName: 文件上传时后台接收文件的参数名,默认为file</li>
|
||||
<li>uploadType: 当前上传文件的场景类型,<strong>最好根据实际业务取名</strong>,它会决定最终上传完成后的文件路径,比如在商品信息管理页中指定了uploadType = goods,那么最终上传完成后的文件路径就是:<code>oneblog/goods/{filename}.png</code>,默认为空</li>
|
||||
<li>customCss: 自定义的css,可以修改编辑器大小,默认为空。注:如果是修改高度,必须通过<code>max-height</code>参数修改,并且一定要加上:<code>"overflow-y": "scroll"</code>, <code>"height": "100%"</code>这两项配置,否则可能会使编辑器显示不正确</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer>
|
||||
<script type="text/javascript">
|
||||
var E = window.wangEditor
|
||||
var editor1 = new E('#toolbar', '#div1');
|
||||
editor1.customConfig.zIndex = 10;
|
||||
editor1.create();
|
||||
|
||||
var editor2 = new E('#div2');
|
||||
editor2.customConfig.zIndex = 10;
|
||||
editor2.create();
|
||||
$("#div2").find(".w-e-text-container").css("height","100px");
|
||||
|
||||
// oneblog定制版的wangEditor
|
||||
zhyd.wangEditor.init({
|
||||
container: "#editor",
|
||||
textareaName: "content",
|
||||
uploadUrl: "/api/uploadFile",
|
||||
uploadFileName: "file",
|
||||
uploadType: "goods",
|
||||
customCss: {
|
||||
"overflow-y": "scroll",
|
||||
"height": "100%",
|
||||
"max-height": "600px"
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</@footer>
|
21
blog-admin/src/main/resources/templates/other/icons.ftl
Normal file
@ -0,0 +1,21 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>Font Awesome Icons
|
||||
<small>different icon design elements</small>
|
||||
</h2>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<iframe src="http://fontawesome.dashgame.com/" frameborder="0" style="width: 100%;height: 100%;min-height: 400px;"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
54
blog-admin/src/main/resources/templates/other/shiro.ftl
Normal file
@ -0,0 +1,54 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="x_panel">
|
||||
<div class="x_title">
|
||||
<h2>Shiro标签测试</h2>
|
||||
<ul class="nav navbar-right panel_toolbox">
|
||||
<li><a class="collapse-link"><i class="fa fa-chevron-up"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="x_content">
|
||||
<h2>1.guest(游客)</h2>
|
||||
<pre><@shiro.guest>
|
||||
您当前是游客,<a href="javascript:void(0);" class="dropdown-toggle qqlogin" >登录</a>
|
||||
</@shiro.guest> </pre>
|
||||
<h2>2.principal标签</h2>
|
||||
principal标签,取值取的是你登录的时候。在Realm实现类中的如下代码:
|
||||
<pre>....
|
||||
return new SimpleAuthenticationInfo(user,user.getPswd(), getName()); </pre>
|
||||
在new SimpleAuthenticationInfo(第一个参数,....)的第一个参数放的如果是一个username,那么就可以直接用。
|
||||
<pre><@shiro.principal/></pre>
|
||||
如果第一个参数放的是对象,要取username字段。
|
||||
<pre><@shiro.principal property="username"/></pre>
|
||||
<h2>3.hasRole标签(判断是否拥有这个角色)</h2>
|
||||
<pre><@shiro.hasRole name="admin">
|
||||
拥有角色admin
|
||||
</@shiro.hasRole> </pre>
|
||||
<h2>4.hasAnyRoles标签(判断是否拥有这些角色的其中一个)</h2>
|
||||
<pre><@shiro.hasAnyRoles name="admin,user,member">
|
||||
用户[<@shiro.principal/>]拥有角色admin或user或member
|
||||
</@shiro.hasAnyRoles> </pre>
|
||||
<h2>5.lacksRole标签(判断是否不拥有这个角色)</h2>
|
||||
<pre><@shiro.lacksRole name="admin">
|
||||
用户[<@shiro.principal/>]不拥有admin角色
|
||||
</@shiro.lacksRole> </pre>
|
||||
<h2>6.hasPermission标签(判断是否有拥有这个权限)</h2>
|
||||
<pre><@shiro.hasPermission name="user:add">
|
||||
用户[<@shiro.principal/>]拥有user:add权限
|
||||
</@shiro.hasPermission> </pre>
|
||||
<h2>7.lacksPermission标签(判断是否没有这个权限)</h2>
|
||||
<pre><@shiro.lacksPermission name="user:add">
|
||||
用户[<@shiro.principal/>]不拥有user:add权限
|
||||
</@shiro.lacksPermission> </pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@footer></@footer>
|
199
blog-admin/src/main/resources/templates/resources/list.ftl
Normal file
@ -0,0 +1,199 @@
|
||||
<#include "/include/macros.ftl">
|
||||
<@header></@header>
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-sm-12 col-xs-12">
|
||||
<@breadcrumb>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li class="active">资源管理</li>
|
||||
</ol>
|
||||
</@breadcrumb>
|
||||
<div class="x_panel">
|
||||
<div class="x_content">
|
||||
<div class="<#--table-responsive-->">
|
||||
<table id="tree-table-box">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<@addOrUpdateMOdal defaultTitle="添加资源链接">
|
||||
<input type="hidden" name="id">
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="name">资源名称 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="name" id="name" required="required" placeholder="请输入资源名称"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="type">资源类型 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<select name="type" id="type" required="required" class="form-control col-md-7 col-xs-12">
|
||||
<option value="">请选择</option>
|
||||
<option value="menu">菜单</option>
|
||||
<option value="button">按钮</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="type">父级资源 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||
<select id="parentId" name="parentId" class="form-control col-md-5 col-xs-5">
|
||||
<option value="">请选择</option>
|
||||
<@zhydTag method="availableMenus">
|
||||
<#if availableMenus?? && availableMenus?size gt 0>
|
||||
<#list availableMenus as item>
|
||||
<option value="${item.id?c}">${item.name}</option>
|
||||
<#if item.nodes?? && item.nodes?size gt 0>
|
||||
<#list item.nodes as node>
|
||||
<option value="${node.id?c}"> |-${node.name}</option>
|
||||
</#list>
|
||||
</#if>
|
||||
</#list>
|
||||
<#else>
|
||||
<option value="">无</option>
|
||||
</#if>
|
||||
</@zhydTag>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="url">资源链接 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="url" id="url" placeholder="请输入资源链接"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="permission">资源权限 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="permission" id="permission" placeholder="请输入资源权限"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="sort">资源排序 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="sort" id="sort" placeholder="请输入资源排序"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="icon">资源图标 </label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<input type="text" class="form-control col-md-7 col-xs-12" name="icon" id="icon" placeholder="请输入资源图标"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="mobile">是否可用 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><input type="radio" class="flat" checked="checked" name="available" value="1"> 可用</li>
|
||||
<li><input type="radio" class="flat" name="available" value="0"> 禁用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item form-group">
|
||||
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="mobile">外部链接 <span class="required">*</span></label>
|
||||
<div class="col-md-6 col-sm-6 col-xs-12">
|
||||
<ul class="list-unstyled list-inline">
|
||||
<li><input type="radio" class="flat" checked name="external" value="0"> 否</li>
|
||||
<li><input type="radio" class="flat" name="external" value="1"> 是</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</@addOrUpdateMOdal>
|
||||
<@footer>
|
||||
<script type="text/javascript" src="/assets/js/zhyd.treetable.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
$.table.init({
|
||||
modalName: "资源",
|
||||
columns: [{
|
||||
field: 'selectItem',
|
||||
checkbox: true
|
||||
}, {
|
||||
field: '-',
|
||||
title: '层级',
|
||||
width: "60px",
|
||||
align: "center"
|
||||
}, {
|
||||
field: 'name',
|
||||
title: '资源名',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'id',
|
||||
title: '资源ID',
|
||||
width: '60px',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'type',
|
||||
title: '资源类型',
|
||||
formatter: function (code) {
|
||||
return code == 'menu' ? '菜单' : '按钮';
|
||||
}
|
||||
}, {
|
||||
field: 'url',
|
||||
title: '资源地址',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'permission',
|
||||
title: '资源权限',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'parent.name',
|
||||
title: '父级资源',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'icon',
|
||||
title: '资源图标',
|
||||
width: '120px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? '<i class="' + code + ' fa-fw"></i>'+ code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'sort',
|
||||
title: '排序',
|
||||
width: '70px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? code : '-';
|
||||
}
|
||||
}, {
|
||||
field: 'external',
|
||||
title: '外部资源',
|
||||
width: '100px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? '<span class="label label-success">是</span>' : '<span class="label label-danger">否</span>';
|
||||
}
|
||||
}, {
|
||||
field: 'available',
|
||||
title: '可用',
|
||||
width: '80px',
|
||||
align: 'center',
|
||||
formatter: function (code) {
|
||||
return code ? '<span class="label label-success">可用</span>' : '<span class="label label-danger">不可用</span>';
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
listUrl: "/resources/list",
|
||||
getInfoUrl: "/resources/get/{id}",
|
||||
updateUrl: "/resources/edit",
|
||||
removeUrl: "/resources/remove",
|
||||
createUrl: "/resources/add",
|
||||
saveRolesUrl: "/resources/saveRoleResources"
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</@footer>
|