This commit is contained in:
liushuang 2024-07-28 23:39:54 +08:00
commit 8013c7f5f6
597 changed files with 47940 additions and 0 deletions

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。支持移动端自适应配有完备的前台和后台管理功能。
![JDK](https://img.shields.io/badge/JDK-1.8-green.svg)
![Maven](https://img.shields.io/badge/Maven-3.3.1-green.svg)
![MySQL](https://img.shields.io/badge/MySQL-5.6.4-green.svg)
![Redis](https://img.shields.io/badge/Redis-3.0.503-green.svg)
[![license](https://img.shields.io/badge/license-GPL%20v3-yellow.svg)](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/)
![admin端首页](https://images.gitee.com/uploads/images/2019/0129/191117_221c6064_784199.png "admin-index.png")
![admin端文章列表也](https://images.gitee.com/uploads/images/2019/0129/191135_21e4f61c_784199.png "admin-article.png")
![admin端发布文章页](https://images.gitee.com/uploads/images/2019/0129/191150_0d28d51a_784199.png "admin-publish-article.png")
![admin端系统配置页](https://images.gitee.com/uploads/images/2019/0129/191203_cc6941e4_784199.png "admin-config.png")
![admin端文章搬运工](https://images.gitee.com/uploads/images/2019/0129/191214_5e8f3c34_784199.png "admin-spider.png")
![admin端文章搬运工](https://images.gitee.com/uploads/images/2019/0129/191237_d015fcda_784199.png "admin-spider2.png")
![web端首页-pc](https://images.gitee.com/uploads/images/2019/0129/191409_d2604f7d_784199.png "web-index-pc.png")
![web端首页-mobile](https://images.gitee.com/uploads/images/2019/0129/191428_c76317e8_784199.png "web-index.png")
![web端文章详情页](https://images.gitee.com/uploads/images/2019/0129/191448_a2777443_784199.png "web-article-detail.png")
----
# 模块划分
| 模块 | 释义 | 备注 |
| :------------: | :------------: | :------------: |
| 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!
- 待续...
# 开源协议
[![license](https://img.shields.io/badge/license-GPL%20v3-yellow.svg)](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部分过滤器可指定参数如permsroles
*/
@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("/**");
}
}

@ -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);
}
}

@ -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);
}
}
}
}

@ -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}"
]
}
}

@ -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"
####################################自定义配置##########################################

@ -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"
####################################自定义配置##########################################

@ -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

@ -0,0 +1,17 @@
${AnsiColor.BLUE}
====================================================================================================================================
欢迎使用博客系统 - Powered By http://www.skying.cn
______ _
| ___ \ | |
| |_/ / | | ___ __ _
| ___ \ | | / _ \ / _` |
| |_/ / | | | (_) | | (_| |
\____/ |_| \___/ \__, |
__/ |
|___/
当前SpringBoot版本 :: ${spring-boot.version}
====================================================================================================================================

@ -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>

@ -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;}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

@ -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;
}
})

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/`/g, '&#x60;')
}
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: "![file]({filename})",
/**
* 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);

File diff suppressed because one or more lines are too long

@ -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);

@ -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,
// 自动保存的间隔以毫秒为单位。默认为1500015s
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");
})
})
});

@ -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 });

@ -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('');
}
}
}
};

@ -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;
};

@ -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('');
}
})
}
});

@ -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>

@ -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>

@ -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>

@ -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>

@ -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">&times;</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">&times;</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 + '&amp;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>

@ -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 &copy; ${.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">&times;</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="请输入endpointhttp://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">&times;</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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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">&nbsp;</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">&times;</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">&times;</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">&times;</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>

@ -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>

@ -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">&times;</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">&times;</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">&times;</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>

@ -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>

@ -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">&times;</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">&times;</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>

@ -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 >&nbsp;</a>
<a >&nbsp;</a>
<a >&nbsp;</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>

@ -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 + '&amp;Site=www.zhyd.me&amp;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>

@ -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>

@ -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>

@ -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>第三个 demooneBlog系统单独定制支持文件上传</p>
<p>
使用方式:
<pre><code># html<br>&lt;div id="editor"&gt;&lt;/div&gt;<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>

@ -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>

@ -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>&lt;@shiro.guest&gt;
您当前是游客,&lt;a href="javascript:void(0);" class="dropdown-toggle qqlogin" &gt;登录&lt;/a&gt;
&lt;/@shiro.guest&gt; </pre>
<h2>2.principal标签</h2>
principal标签取值取的是你登录的时候。在Realm实现类中的如下代码
<pre>....
return new SimpleAuthenticationInfo(user,user.getPswd(), getName()); </pre>
在new SimpleAuthenticationInfo(第一个参数,....)的第一个参数放的如果是一个username那么就可以直接用。
<pre>&lt;@shiro.principal/&gt;</pre>
如果第一个参数放的是对象要取username字段。
<pre>&lt;@shiro.principal property="username"/&gt;</pre>
<h2>3.hasRole标签判断是否拥有这个角色</h2>
<pre>&lt;@shiro.hasRole name="admin"&gt;
拥有角色admin
&lt;/@shiro.hasRole&gt; </pre>
<h2>4.hasAnyRoles标签判断是否拥有这些角色的其中一个</h2>
<pre>&lt;@shiro.hasAnyRoles name="admin,user,member"&gt;
用户[&lt;@shiro.principal/&gt;]拥有角色admin或user或member
&lt;/@shiro.hasAnyRoles&gt; </pre>
<h2>5.lacksRole标签判断是否不拥有这个角色</h2>
<pre>&lt;@shiro.lacksRole name="admin"&gt;
用户[&lt;@shiro.principal/&gt;]不拥有admin角色
&lt;/@shiro.lacksRole&gt; </pre>
<h2>6.hasPermission标签判断是否有拥有这个权限</h2>
<pre>&lt;@shiro.hasPermission name="user:add"&gt;
用户[&lt;@shiro.principal/&gt;]拥有user:add权限
&lt;/@shiro.hasPermission&gt; </pre>
<h2>7.lacksPermission标签判断是否没有这个权限</h2>
<pre>&lt;@shiro.lacksPermission name="user:add"&gt;
用户[&lt;@shiro.principal/&gt;]不拥有user:add权限
&lt;/@shiro.lacksPermission&gt; </pre>
</div>
</div>
</div>
</div>
</div>
<@footer></@footer>

@ -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}">&nbsp;&nbsp;|-${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>

Some files were not shown because too many files have changed in this diff Show More